httpu.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package httpu
  2. import (
  3. "bufio"
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net"
  10. "net/http"
  11. "sync"
  12. "time"
  13. )
  14. // ClientInterface is the general interface provided to perform HTTP-over-UDP
  15. // requests.
  16. type ClientInterface interface {
  17. io.Closer
  18. // Do performs a request. The timeout is how long to wait for before returning
  19. // the responses that were received. An error is only returned for failing to
  20. // send the request. Failures in receipt simply do not add to the resulting
  21. // responses.
  22. Do(req *http.Request, numSends int) ([]*http.Response, error)
  23. }
  24. // HTTPUClient is a client for dealing with HTTPU (HTTP over UDP). Its typical
  25. // function is for HTTPMU, and particularly SSDP.
  26. type HTTPUClient struct {
  27. connLock sync.Mutex // Protects use of conn.
  28. conn net.PacketConn
  29. }
  30. var _ ClientInterface = &HTTPUClient{}
  31. // NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the
  32. // purpose.
  33. func NewHTTPUClient() (*HTTPUClient, error) {
  34. conn, err := net.ListenPacket("udp", ":0")
  35. if err != nil {
  36. return nil, err
  37. }
  38. return &HTTPUClient{conn: conn}, nil
  39. }
  40. // NewHTTPUClientAddr creates a new HTTPUClient which will broadcast packets
  41. // from the specified address, opening up a new UDP socket for the purpose
  42. func NewHTTPUClientAddr(addr string) (*HTTPUClient, error) {
  43. ip := net.ParseIP(addr)
  44. if ip == nil {
  45. return nil, errors.New("Invalid listening address")
  46. }
  47. conn, err := net.ListenPacket("udp", ip.String()+":0")
  48. if err != nil {
  49. return nil, err
  50. }
  51. return &HTTPUClient{conn: conn}, nil
  52. }
  53. // Close shuts down the client. The client will no longer be useful following
  54. // this.
  55. func (httpu *HTTPUClient) Close() error {
  56. httpu.connLock.Lock()
  57. defer httpu.connLock.Unlock()
  58. return httpu.conn.Close()
  59. }
  60. // Do implements ClientInterface.Do.
  61. //
  62. // Note that at present only one concurrent connection will happen per
  63. // HTTPUClient.
  64. func (httpu *HTTPUClient) Do(
  65. req *http.Request,
  66. numSends int,
  67. ) ([]*http.Response, error) {
  68. httpu.connLock.Lock()
  69. defer httpu.connLock.Unlock()
  70. // Create the request. This is a subset of what http.Request.Write does
  71. // deliberately to avoid creating extra fields which may confuse some
  72. // devices.
  73. var requestBuf bytes.Buffer
  74. method := req.Method
  75. if method == "" {
  76. method = "GET"
  77. }
  78. if _, err := fmt.Fprintf(&requestBuf, "%s %s HTTP/1.1\r\n", method, req.URL.RequestURI()); err != nil {
  79. return nil, err
  80. }
  81. if err := req.Header.Write(&requestBuf); err != nil {
  82. return nil, err
  83. }
  84. if _, err := requestBuf.Write([]byte{'\r', '\n'}); err != nil {
  85. return nil, err
  86. }
  87. destAddr, err := net.ResolveUDPAddr("udp", req.Host)
  88. if err != nil {
  89. return nil, err
  90. }
  91. ctx := req.Context()
  92. deadline, ok := ctx.Deadline()
  93. if !ok {
  94. deadline = time.Now().Add(2 * time.Second)
  95. }
  96. returned := make(chan struct{})
  97. defer close(returned)
  98. go func() {
  99. select {
  100. case <-ctx.Done():
  101. // if context is cancelled, stop any connections by setting time in the past.
  102. httpu.conn.SetDeadline(time.Now().Add(-time.Second))
  103. case <-returned:
  104. }
  105. }()
  106. if err = httpu.conn.SetDeadline(deadline); err != nil {
  107. return nil, err
  108. }
  109. // Send request.
  110. for i := 0; i < numSends; i++ {
  111. if n, err := httpu.conn.WriteTo(requestBuf.Bytes(), destAddr); err != nil {
  112. return nil, err
  113. } else if n < len(requestBuf.Bytes()) {
  114. return nil, fmt.Errorf("httpu: wrote %d bytes rather than full %d in request",
  115. n, len(requestBuf.Bytes()))
  116. }
  117. time.Sleep(5 * time.Millisecond)
  118. }
  119. // Await responses until timeout.
  120. var responses []*http.Response
  121. responseBytes := make([]byte, 2048)
  122. for {
  123. // 2048 bytes should be sufficient for most networks.
  124. n, _, err := httpu.conn.ReadFrom(responseBytes)
  125. if err != nil {
  126. if err, ok := err.(net.Error); ok {
  127. if err.Timeout() {
  128. break
  129. }
  130. if err.Temporary() {
  131. // Sleep in case this is a persistent error to avoid pegging CPU until deadline.
  132. time.Sleep(10 * time.Millisecond)
  133. continue
  134. }
  135. }
  136. return nil, err
  137. }
  138. // Parse response.
  139. response, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(responseBytes[:n])), req)
  140. if err != nil {
  141. log.Printf("httpu: error while parsing response: %v", err)
  142. continue
  143. }
  144. responses = append(responses, response)
  145. }
  146. // Timeout reached - return discovered responses.
  147. return responses, nil
  148. }