server_fasthttp.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package websocket
  5. import (
  6. "bytes"
  7. "fmt"
  8. "net"
  9. "net/url"
  10. "sync"
  11. "time"
  12. "github.com/savsgio/gotils/strconv"
  13. "github.com/valyala/fasthttp"
  14. )
  15. var strPermessageDeflate = []byte("permessage-deflate")
  16. var poolWriteBuffer = sync.Pool{
  17. New: func() interface{} {
  18. var buf []byte
  19. return buf
  20. },
  21. }
  22. // FastHTTPHandler receives a websocket connection after the handshake has been
  23. // completed. This must be provided.
  24. type FastHTTPHandler func(*Conn)
  25. // FastHTTPUpgrader specifies parameters for upgrading an HTTP connection to a
  26. // WebSocket connection.
  27. type FastHTTPUpgrader struct {
  28. // HandshakeTimeout specifies the duration for the handshake to complete.
  29. HandshakeTimeout time.Duration
  30. // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
  31. // size is zero, then buffers allocated by the HTTP server are used. The
  32. // I/O buffer sizes do not limit the size of the messages that can be sent
  33. // or received.
  34. ReadBufferSize, WriteBufferSize int
  35. // WriteBufferPool is a pool of buffers for write operations. If the value
  36. // is not set, then write buffers are allocated to the connection for the
  37. // lifetime of the connection.
  38. //
  39. // A pool is most useful when the application has a modest volume of writes
  40. // across a large number of connections.
  41. //
  42. // Applications should use a single pool for each unique value of
  43. // WriteBufferSize.
  44. WriteBufferPool BufferPool
  45. // Subprotocols specifies the server's supported protocols in order of
  46. // preference. If this field is not nil, then the Upgrade method negotiates a
  47. // subprotocol by selecting the first match in this list with a protocol
  48. // requested by the client. If there's no match, then no protocol is
  49. // negotiated (the Sec-Websocket-Protocol header is not included in the
  50. // handshake response).
  51. Subprotocols []string
  52. // Error specifies the function for generating HTTP error responses. If Error
  53. // is nil, then http.Error is used to generate the HTTP response.
  54. Error func(ctx *fasthttp.RequestCtx, status int, reason error)
  55. // CheckOrigin returns true if the request Origin header is acceptable. If
  56. // CheckOrigin is nil, then a safe default is used: return false if the
  57. // Origin request header is present and the origin host is not equal to
  58. // request Host header.
  59. //
  60. // A CheckOrigin function should carefully validate the request origin to
  61. // prevent cross-site request forgery.
  62. CheckOrigin func(ctx *fasthttp.RequestCtx) bool
  63. // EnableCompression specify if the server should attempt to negotiate per
  64. // message compression (RFC 7692). Setting this value to true does not
  65. // guarantee that compression will be supported. Currently only "no context
  66. // takeover" modes are supported.
  67. EnableCompression bool
  68. }
  69. func (u *FastHTTPUpgrader) responseError(ctx *fasthttp.RequestCtx, status int, reason string) error {
  70. err := HandshakeError{reason}
  71. if u.Error != nil {
  72. u.Error(ctx, status, err)
  73. } else {
  74. ctx.Response.Header.Set("Sec-Websocket-Version", "13")
  75. ctx.Error(fasthttp.StatusMessage(status), status)
  76. }
  77. return err
  78. }
  79. func (u *FastHTTPUpgrader) selectSubprotocol(ctx *fasthttp.RequestCtx) []byte {
  80. if u.Subprotocols != nil {
  81. clientProtocols := parseDataHeader(ctx.Request.Header.Peek("Sec-Websocket-Protocol"))
  82. for _, serverProtocol := range u.Subprotocols {
  83. for _, clientProtocol := range clientProtocols {
  84. if strconv.B2S(clientProtocol) == serverProtocol {
  85. return clientProtocol
  86. }
  87. }
  88. }
  89. } else if ctx.Response.Header.Len() > 0 {
  90. return ctx.Response.Header.Peek("Sec-Websocket-Protocol")
  91. }
  92. return nil
  93. }
  94. func (u *FastHTTPUpgrader) isCompressionEnable(ctx *fasthttp.RequestCtx) bool {
  95. extensions := parseDataHeader(ctx.Request.Header.Peek("Sec-WebSocket-Extensions"))
  96. // Negotiate PMCE
  97. if u.EnableCompression {
  98. for _, ext := range extensions {
  99. if bytes.HasPrefix(ext, strPermessageDeflate) {
  100. return true
  101. }
  102. }
  103. }
  104. return false
  105. }
  106. // Upgrade upgrades the HTTP server connection to the WebSocket protocol.
  107. //
  108. // The responseHeader is included in the response to the client's upgrade
  109. // request. Use the responseHeader to specify cookies (Set-Cookie) and the
  110. // application negotiated subprotocol (Sec-WebSocket-Protocol).
  111. //
  112. // If the upgrade fails, then Upgrade replies to the client with an HTTP error
  113. // response.
  114. func (u *FastHTTPUpgrader) Upgrade(ctx *fasthttp.RequestCtx, handler FastHTTPHandler) error {
  115. if !ctx.IsGet() {
  116. return u.responseError(ctx, fasthttp.StatusMethodNotAllowed, fmt.Sprintf("%s request method is not GET", badHandshake))
  117. }
  118. if !tokenContainsValue(strconv.B2S(ctx.Request.Header.Peek("Connection")), "Upgrade") {
  119. return u.responseError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("%s 'upgrade' token not found in 'Connection' header", badHandshake))
  120. }
  121. if !tokenContainsValue(strconv.B2S(ctx.Request.Header.Peek("Upgrade")), "Websocket") {
  122. return u.responseError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("%s 'websocket' token not found in 'Upgrade' header", badHandshake))
  123. }
  124. if !tokenContainsValue(strconv.B2S(ctx.Request.Header.Peek("Sec-Websocket-Version")), "13") {
  125. return u.responseError(ctx, fasthttp.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
  126. }
  127. if len(ctx.Response.Header.Peek("Sec-Websocket-Extensions")) > 0 {
  128. return u.responseError(ctx, fasthttp.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
  129. }
  130. checkOrigin := u.CheckOrigin
  131. if checkOrigin == nil {
  132. checkOrigin = fastHTTPcheckSameOrigin
  133. }
  134. if !checkOrigin(ctx) {
  135. return u.responseError(ctx, fasthttp.StatusForbidden, "websocket: request origin not allowed by FastHTTPUpgrader.CheckOrigin")
  136. }
  137. challengeKey := ctx.Request.Header.Peek("Sec-Websocket-Key")
  138. if len(challengeKey) == 0 {
  139. return u.responseError(ctx, fasthttp.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
  140. }
  141. subprotocol := u.selectSubprotocol(ctx)
  142. compress := u.isCompressionEnable(ctx)
  143. ctx.SetStatusCode(fasthttp.StatusSwitchingProtocols)
  144. ctx.Response.Header.Set("Upgrade", "websocket")
  145. ctx.Response.Header.Set("Connection", "Upgrade")
  146. ctx.Response.Header.Set("Sec-WebSocket-Accept", computeAcceptKeyBytes(challengeKey))
  147. if compress {
  148. ctx.Response.Header.Set("Sec-WebSocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
  149. }
  150. if subprotocol != nil {
  151. ctx.Response.Header.SetBytesV("Sec-WebSocket-Protocol", subprotocol)
  152. }
  153. ctx.Hijack(func(netConn net.Conn) {
  154. // var br *bufio.Reader // Always nil
  155. writeBuf := poolWriteBuffer.Get().([]byte)
  156. c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, nil, writeBuf)
  157. if subprotocol != nil {
  158. c.subprotocol = strconv.B2S(subprotocol)
  159. }
  160. if compress {
  161. c.newCompressionWriter = compressNoContextTakeover
  162. c.newDecompressionReader = decompressNoContextTakeover
  163. }
  164. // Clear deadlines set by HTTP server.
  165. netConn.SetDeadline(time.Time{})
  166. handler(c)
  167. writeBuf = writeBuf[0:0]
  168. poolWriteBuffer.Put(writeBuf)
  169. })
  170. return nil
  171. }
  172. // fastHTTPcheckSameOrigin returns true if the origin is not set or is equal to the request host.
  173. func fastHTTPcheckSameOrigin(ctx *fasthttp.RequestCtx) bool {
  174. origin := ctx.Request.Header.Peek("Origin")
  175. if len(origin) == 0 {
  176. return true
  177. }
  178. u, err := url.Parse(strconv.B2S(origin))
  179. if err != nil {
  180. return false
  181. }
  182. return equalASCIIFold(u.Host, strconv.B2S(ctx.Host()))
  183. }
  184. // FastHTTPIsWebSocketUpgrade returns true if the client requested upgrade to the
  185. // WebSocket protocol.
  186. func FastHTTPIsWebSocketUpgrade(ctx *fasthttp.RequestCtx) bool {
  187. return tokenContainsValue(strconv.B2S(ctx.Request.Header.Peek("Connection")), "Upgrade") &&
  188. tokenContainsValue(strconv.B2S(ctx.Request.Header.Peek("Upgrade")), "Websocket")
  189. }