client.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. package main
  4. import (
  5. "bufio"
  6. "bytes"
  7. "crypto/tls"
  8. "io"
  9. "net"
  10. "os"
  11. "time"
  12. )
  13. // PayloadHandler defines a function which handles incoming
  14. // server messages.
  15. type PayloadHandler func([]byte)
  16. // ConnectionTimeout defines the deadline for a connection.
  17. const ConnectionTimeout = time.Minute * 10
  18. // Client defines an IRC client for a single network connection.
  19. type Client struct {
  20. handler PayloadHandler
  21. conn net.Conn
  22. reader *bufio.Reader
  23. }
  24. // NewClient creates a new client for the given handler.
  25. func NewClient(handler PayloadHandler) *Client {
  26. return &Client{
  27. handler: handler,
  28. }
  29. }
  30. // Open creates a new client connection to the given address with the format:
  31. // <host>:<port>.
  32. //
  33. // If the tls config is not nil, it will be used to upgrade the connection
  34. // to a TLS connection.
  35. func (c *Client) Open(address string, cfg *tls.Config) error {
  36. var err error
  37. c.conn, err = net.Dial("tcp", address)
  38. if err != nil {
  39. return err
  40. }
  41. if cfg != nil {
  42. c.reader = bufio.NewReader(tls.Client(c.conn, cfg))
  43. } else {
  44. c.reader = bufio.NewReader(c.conn)
  45. }
  46. return nil
  47. }
  48. // OpenFd opens a new client from the given file descriptor.
  49. // If the tls config is not nil, it will be used to upgrade the connection
  50. // to a TLS connection.
  51. func (c *Client) OpenFd(file *os.File, cfg *tls.Config) error {
  52. var err error
  53. c.conn, err = net.FileConn(file)
  54. if err != nil {
  55. return err
  56. }
  57. if cfg != nil {
  58. c.reader = bufio.NewReader(tls.Client(c.conn, cfg))
  59. } else {
  60. c.reader = bufio.NewReader(c.conn)
  61. }
  62. return nil
  63. }
  64. // Close closes the connection.
  65. func (c *Client) Close() error {
  66. return c.conn.Close()
  67. }
  68. // File returns the network's file descriptor.
  69. // This call is only valid as long as the connection is actually open.
  70. func (c *Client) File() (*os.File, error) {
  71. return c.conn.(*net.TCPConn).File()
  72. }
  73. // Run starts the message processing loop and does not return for as long
  74. // as there is an open connection.
  75. func (c *Client) Run() error {
  76. defer c.Close()
  77. for {
  78. line, err := c.read()
  79. if err != nil {
  80. return err
  81. }
  82. go c.handler(line)
  83. }
  84. return nil
  85. }
  86. // Write writes the given message to the underlying stream.
  87. func (c *Client) Write(p []byte) (int, error) {
  88. if c.conn == nil || len(p) == 0 {
  89. return 0, io.EOF
  90. }
  91. n, err := c.conn.Write(p)
  92. if err == nil {
  93. c.conn.SetDeadline(time.Now().Add(ConnectionTimeout))
  94. }
  95. return n, err
  96. }
  97. // Read reads the next message from the connection.
  98. // This call blocks until enough data is available or an error occurs.
  99. func (c *Client) read() ([]byte, error) {
  100. conn := c.conn
  101. rdr := c.reader
  102. if conn == nil {
  103. return nil, io.EOF
  104. }
  105. data, err := rdr.ReadBytes('\n')
  106. if err != nil {
  107. return nil, err
  108. }
  109. conn.SetDeadline(time.Now().Add(ConnectionTimeout))
  110. return bytes.TrimSpace(data), nil
  111. }