github.com/ezoic/ws@v1.0.4-0.20220713205711-5c1d69e074c5/wsutil/handler.go (about) 1 package wsutil 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "strconv" 8 9 "github.com/ezoic/pool/pbytes" 10 "github.com/ezoic/ws" 11 ) 12 13 // ClosedError returned when peer has closed the connection with appropriate 14 // code and a textual reason. 15 type ClosedError struct { 16 Code ws.StatusCode 17 Reason string 18 } 19 20 // Error implements error interface. 21 func (err ClosedError) Error() string { 22 return "ws closed: " + strconv.FormatUint(uint64(err.Code), 10) + " " + err.Reason 23 } 24 25 // ControlHandler contains logic of handling control frames. 26 // 27 // The intentional way to use it is to read the next frame header from the 28 // connection, optionally check its validity via ws.CheckHeader() and if it is 29 // not a ws.OpText of ws.OpBinary (or ws.OpContinuation) – pass it to Handle() 30 // method. 31 // 32 // That is, passed header should be checked to get rid of unexpected errors. 33 // 34 // The Handle() method will read out all control frame payload (if any) and 35 // write necessary bytes as a rfc compatible response. 36 type ControlHandler struct { 37 Src io.Reader 38 Dst io.Writer 39 State ws.State 40 41 // DisableSrcCiphering disables unmasking payload data read from Src. 42 // It is useful when wsutil.Reader is used or when frame payload already 43 // pulled and ciphered out from the connection (and introduced by 44 // bytes.Reader, for example). 45 DisableSrcCiphering bool 46 } 47 48 // ErrNotControlFrame is returned by ControlHandler to indicate that given 49 // header could not be handled. 50 var ErrNotControlFrame = errors.New("not a control frame") 51 52 // Handle handles control frames regarding to the c.State and writes responses 53 // to the c.Dst when needed. 54 // 55 // It returns ErrNotControlFrame when given header is not of ws.OpClose, 56 // ws.OpPing or ws.OpPong operation code. 57 func (c ControlHandler) Handle(h ws.Header) error { 58 switch h.OpCode { 59 case ws.OpPing: 60 return c.HandlePing(h) 61 case ws.OpPong: 62 return c.HandlePong(h) 63 case ws.OpClose: 64 return c.HandleClose(h) 65 } 66 return ErrNotControlFrame 67 } 68 69 // HandlePing handles ping frame and writes specification compatible response 70 // to the c.Dst. 71 func (c ControlHandler) HandlePing(h ws.Header) error { 72 if h.Length == 0 { 73 // The most common case when ping is empty. 74 // Note that when sending masked frame the mask for empty payload is 75 // just four zero bytes. 76 return ws.WriteHeader(c.Dst, ws.Header{ 77 Fin: true, 78 OpCode: ws.OpPong, 79 Masked: c.State.ClientSide(), 80 }) 81 } 82 83 // In other way reply with Pong frame with copied payload. 84 p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{ 85 Length: h.Length, 86 Masked: c.State.ClientSide(), 87 })) 88 defer pbytes.Put(p) 89 90 // Deal with ciphering i/o: 91 // Masking key is used to mask the "Payload data" defined in the same 92 // section as frame-payload-data, which includes "Extension data" and 93 // "Application data". 94 // 95 // See https://tools.ietf.org/html/rfc6455#section-5.3 96 // 97 // NOTE: We prefer ControlWriter with preallocated buffer to 98 // ws.WriteHeader because it performs one syscall instead of two. 99 w := NewControlWriterBuffer(c.Dst, c.State, ws.OpPong, p) 100 r := c.Src 101 if c.State.ServerSide() && !c.DisableSrcCiphering { 102 r = NewCipherReader(r, h.Mask) 103 } 104 105 _, err := io.Copy(w, r) 106 if err == nil { 107 err = w.Flush() 108 } 109 110 return err 111 } 112 113 // HandlePong handles pong frame by discarding it. 114 func (c ControlHandler) HandlePong(h ws.Header) error { 115 if h.Length == 0 { 116 return nil 117 } 118 119 buf := pbytes.GetLen(int(h.Length)) 120 defer pbytes.Put(buf) 121 122 // Discard pong message according to the RFC6455: 123 // A Pong frame MAY be sent unsolicited. This serves as a 124 // unidirectional heartbeat. A response to an unsolicited Pong frame 125 // is not expected. 126 _, err := io.CopyBuffer(ioutil.Discard, c.Src, buf) 127 128 return err 129 } 130 131 // HandleClose handles close frame, makes protocol validity checks and writes 132 // specification compatible response to the c.Dst. 133 func (c ControlHandler) HandleClose(h ws.Header) error { 134 if h.Length == 0 { 135 err := ws.WriteHeader(c.Dst, ws.Header{ 136 Fin: true, 137 OpCode: ws.OpClose, 138 Masked: c.State.ClientSide(), 139 }) 140 if err != nil { 141 return err 142 } 143 144 // Due to RFC, we should interpret the code as no status code 145 // received: 146 // If this Close control frame contains no status code, _The WebSocket 147 // Connection Close Code_ is considered to be 1005. 148 // 149 // See https://tools.ietf.org/html/rfc6455#section-7.1.5 150 return ClosedError{ 151 Code: ws.StatusNoStatusRcvd, 152 } 153 } 154 155 // Prepare bytes both for reading reason and sending response. 156 p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{ 157 Length: h.Length, 158 Masked: c.State.ClientSide(), 159 })) 160 defer pbytes.Put(p) 161 162 // Get the subslice to read the frame payload out. 163 subp := p[:h.Length] 164 165 r := c.Src 166 if c.State.ServerSide() && !c.DisableSrcCiphering { 167 r = NewCipherReader(r, h.Mask) 168 } 169 if _, err := io.ReadFull(r, subp); err != nil { 170 return err 171 } 172 173 code, reason := ws.ParseCloseFrameData(subp) 174 if err := ws.CheckCloseFrameData(code, reason); err != nil { 175 // Here we could not use the prepared bytes because there is no 176 // guarantee that it may fit our protocol error closure code and a 177 // reason. 178 c.closeWithProtocolError(err) 179 return err 180 } 181 182 // Deal with ciphering i/o: 183 // Masking key is used to mask the "Payload data" defined in the same 184 // section as frame-payload-data, which includes "Extension data" and 185 // "Application data". 186 // 187 // See https://tools.ietf.org/html/rfc6455#section-5.3 188 // 189 // NOTE: We prefer ControlWriter with preallocated buffer to 190 // ws.WriteHeader because it performs one syscall instead of two. 191 w := NewControlWriterBuffer(c.Dst, c.State, ws.OpClose, p) 192 193 // RFC6455#5.5.1: 194 // If an endpoint receives a Close frame and did not previously 195 // send a Close frame, the endpoint MUST send a Close frame in 196 // response. (When sending a Close frame in response, the endpoint 197 // typically echoes the status code it received.) 198 _, err := w.Write(p[:2]) 199 if err != nil { 200 return err 201 } 202 if err = w.Flush(); err != nil { 203 return err 204 } 205 return ClosedError{ 206 Code: code, 207 Reason: reason, 208 } 209 } 210 211 func (c ControlHandler) closeWithProtocolError(reason error) error { 212 f := ws.NewCloseFrame(ws.NewCloseFrameBody( 213 ws.StatusProtocolError, reason.Error(), 214 )) 215 if c.State.ClientSide() { 216 ws.MaskFrameInPlace(f) 217 } 218 return ws.WriteFrame(c.Dst, f) 219 }