diff --git a/hw11_telnet_client/go.mod b/hw11_telnet_client/go.mod index bbac73d..c2c3588 100644 --- a/hw11_telnet_client/go.mod +++ b/hw11_telnet_client/go.mod @@ -1,5 +1,8 @@ -module github.com/fixme_my_friend/hw11_telnet_client +module github.com/tiburon-777/HW_OTUS/hw11_telnet_client go 1.14 -require github.com/stretchr/testify v1.5.1 +require ( + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.5.1 +) diff --git a/hw11_telnet_client/go.sum b/hw11_telnet_client/go.sum index 331fa69..eb82f2c 100644 --- a/hw11_telnet_client/go.sum +++ b/hw11_telnet_client/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= diff --git a/hw11_telnet_client/main.go b/hw11_telnet_client/main.go index 9211b26..9cd7f9a 100644 --- a/hw11_telnet_client/main.go +++ b/hw11_telnet_client/main.go @@ -1,6 +1,53 @@ package main +import ( + "flag" + "log" + "net" + "os" + "sync" + "time" +) + func main() { - // Place your code here - // P.S. Do not rush to throw context down, think think if it is useful with blocking operation? + var timeout time.Duration + var wg sync.WaitGroup + flag.DurationVar(&timeout, "timeout", time.Second*10, "Set connection timeout. Default = 10s") + flag.Parse() + args := flag.Args() + if len(args) < 2 { + log.Fatal("incorrect host/port") + } + addr := net.JoinHostPort(args[0], args[1]) + client := NewTelnetClient(addr, timeout, os.Stdin, os.Stdout) + if err := client.Connect(); err != nil { + log.Fatal("Can't connect: ", err.Error()) + } + log.Println("...connected to", addr) + defer client.Close() + + wg.Add(2) + go readRoutine(&wg, client) + go writeRoutine(&wg, client) + wg.Wait() +} + +func readRoutine(wg *sync.WaitGroup, client TelnetClient) { + defer wg.Done() + for { + if err := client.Receive(); err != nil { + log.Fatal(err) + return + } + } +} + +func writeRoutine(wg *sync.WaitGroup, client TelnetClient) { + defer wg.Done() + for { + if err := client.Send(); err != nil { + log.Fatal(err) + return + } + } } diff --git a/hw11_telnet_client/telnet.go b/hw11_telnet_client/telnet.go index ef39c04..4c5ac1d 100644 --- a/hw11_telnet_client/telnet.go +++ b/hw11_telnet_client/telnet.go @@ -1,18 +1,58 @@ package main import ( + "bufio" + "errors" "io" + "net" "time" ) type TelnetClient interface { - // Place your code here + Connect() error + Send() error + Receive() error + Close() error +} + +type Client struct { + address string + timeout time.Duration + in io.ReadCloser + inScanner *bufio.Scanner + out io.Writer + conn net.Conn + connScanner *bufio.Scanner } func NewTelnetClient(address string, timeout time.Duration, in io.ReadCloser, out io.Writer) TelnetClient { - // Place your code here - return nil + return &Client{address: address, timeout: timeout, in: in, out: out} } -// Place your code here -// P.S. Author's solution takes no more than 50 lines +func (t *Client) Connect() error { + var err error + t.conn, err = net.DialTimeout("tcp", t.address, t.timeout) + t.inScanner = bufio.NewScanner(t.in) + t.connScanner = bufio.NewScanner(t.conn) + return err +} + +func (t *Client) Send() error { + if !t.inScanner.Scan() { + return errors.New("...EOF") + } + _, err := t.conn.Write(append(t.inScanner.Bytes(), '\n')) + return err +} + +func (t *Client) Receive() error { + if !t.connScanner.Scan() { + return errors.New("...connection closed by peer") + } + _, err := t.out.Write(append(t.connScanner.Bytes(), '\n')) + return err +} + +func (t *Client) Close() error { + return t.conn.Close() +} diff --git a/hw11_telnet_client/telnet_test.go b/hw11_telnet_client/telnet_test.go index 1f73022..cc60db4 100644 --- a/hw11_telnet_client/telnet_test.go +++ b/hw11_telnet_client/telnet_test.go @@ -62,4 +62,61 @@ func TestTelnetClient(t *testing.T) { wg.Wait() }) + + t.Run("bad host with timeout", func(t *testing.T) { + timeout := time.Second * 5 + client := NewTelnetClient("mail.ru:25", timeout, nil, nil) + start := time.Now() + err := client.Connect() + require.Error(t, err, "Can't connect: dial tcp 94.100.180.201:25: i/o timeout") + require.GreaterOrEqual(t, time.Since(start).Milliseconds(), timeout.Milliseconds()-time.Second.Milliseconds()) + require.LessOrEqual(t, time.Since(start).Milliseconds(), timeout.Milliseconds()+time.Second.Milliseconds()) + }) + + t.Run("close by peer", func(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:") + require.NoError(t, err) + defer func() { require.NoError(t, l.Close()) }() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + in := &bytes.Buffer{} + out := &bytes.Buffer{} + + timeout, err := time.ParseDuration("10s") + require.NoError(t, err) + + client := NewTelnetClient(l.Addr().String(), timeout, ioutil.NopCloser(in), out) + require.NoError(t, client.Connect()) + defer func() { require.NoError(t, client.Close()) }() + + in.WriteString("hello\n") + err = client.Send() + require.NoError(t, err) + err = client.Receive() + require.Error(t, err, "...connection closed by peer") + }() + + go func() { + defer wg.Done() + + conn, err := l.Accept() + require.NoError(t, err) + require.NotNil(t, conn) + + request := make([]byte, 1024) + n, err := conn.Read(request) + require.NoError(t, err) + require.Equal(t, "hello\n", string(request)[:n]) + require.NoError(t, conn.Close()) + + }() + + wg.Wait() + }) + }