123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- package wsutil
- import (
- "errors"
- "io"
- "io/ioutil"
- "strconv"
- "github.com/gobwas/pool/pbytes"
- "github.com/gobwas/ws"
- )
- // ClosedError returned when peer has closed the connection with appropriate
- // code and a textual reason.
- type ClosedError struct {
- Code ws.StatusCode
- Reason string
- }
- // Error implements error interface.
- func (err ClosedError) Error() string {
- return "ws closed: " + strconv.FormatUint(uint64(err.Code), 10) + " " + err.Reason
- }
- // ControlHandler contains logic of handling control frames.
- //
- // The intentional way to use it is to read the next frame header from the
- // connection, optionally check its validity via ws.CheckHeader() and if it is
- // not a ws.OpText of ws.OpBinary (or ws.OpContinuation) – pass it to Handle()
- // method.
- //
- // That is, passed header should be checked to get rid of unexpected errors.
- //
- // The Handle() method will read out all control frame payload (if any) and
- // write necessary bytes as a rfc compatible response.
- type ControlHandler struct {
- Src io.Reader
- Dst io.Writer
- State ws.State
- // DisableSrcCiphering disables unmasking payload data read from Src.
- // It is useful when wsutil.Reader is used or when frame payload already
- // pulled and ciphered out from the connection (and introduced by
- // bytes.Reader, for example).
- DisableSrcCiphering bool
- }
- // ErrNotControlFrame is returned by ControlHandler to indicate that given
- // header could not be handled.
- var ErrNotControlFrame = errors.New("not a control frame")
- // Handle handles control frames regarding to the c.State and writes responses
- // to the c.Dst when needed.
- //
- // It returns ErrNotControlFrame when given header is not of ws.OpClose,
- // ws.OpPing or ws.OpPong operation code.
- func (c ControlHandler) Handle(h ws.Header) error {
- switch h.OpCode {
- case ws.OpPing:
- return c.HandlePing(h)
- case ws.OpPong:
- return c.HandlePong(h)
- case ws.OpClose:
- return c.HandleClose(h)
- }
- return ErrNotControlFrame
- }
- // HandlePing handles ping frame and writes specification compatible response
- // to the c.Dst.
- func (c ControlHandler) HandlePing(h ws.Header) error {
- if h.Length == 0 {
- // The most common case when ping is empty.
- // Note that when sending masked frame the mask for empty payload is
- // just four zero bytes.
- return ws.WriteHeader(c.Dst, ws.Header{
- Fin: true,
- OpCode: ws.OpPong,
- Masked: c.State.ClientSide(),
- })
- }
- // In other way reply with Pong frame with copied payload.
- p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{
- Length: h.Length,
- Masked: c.State.ClientSide(),
- }))
- defer pbytes.Put(p)
- // Deal with ciphering i/o:
- // Masking key is used to mask the "Payload data" defined in the same
- // section as frame-payload-data, which includes "Extension data" and
- // "Application data".
- //
- // See https://tools.ietf.org/html/rfc6455#section-5.3
- //
- // NOTE: We prefer ControlWriter with preallocated buffer to
- // ws.WriteHeader because it performs one syscall instead of two.
- w := NewControlWriterBuffer(c.Dst, c.State, ws.OpPong, p)
- r := c.Src
- if c.State.ServerSide() && !c.DisableSrcCiphering {
- r = NewCipherReader(r, h.Mask)
- }
- _, err := io.Copy(w, r)
- if err == nil {
- err = w.Flush()
- }
- return err
- }
- // HandlePong handles pong frame by discarding it.
- func (c ControlHandler) HandlePong(h ws.Header) error {
- if h.Length == 0 {
- return nil
- }
- buf := pbytes.GetLen(int(h.Length))
- defer pbytes.Put(buf)
- // Discard pong message according to the RFC6455:
- // A Pong frame MAY be sent unsolicited. This serves as a
- // unidirectional heartbeat. A response to an unsolicited Pong frame
- // is not expected.
- _, err := io.CopyBuffer(ioutil.Discard, c.Src, buf)
- return err
- }
- // HandleClose handles close frame, makes protocol validity checks and writes
- // specification compatible response to the c.Dst.
- func (c ControlHandler) HandleClose(h ws.Header) error {
- if h.Length == 0 {
- err := ws.WriteHeader(c.Dst, ws.Header{
- Fin: true,
- OpCode: ws.OpClose,
- Masked: c.State.ClientSide(),
- })
- if err != nil {
- return err
- }
- // Due to RFC, we should interpret the code as no status code
- // received:
- // If this Close control frame contains no status code, _The WebSocket
- // Connection Close Code_ is considered to be 1005.
- //
- // See https://tools.ietf.org/html/rfc6455#section-7.1.5
- return ClosedError{
- Code: ws.StatusNoStatusRcvd,
- }
- }
- // Prepare bytes both for reading reason and sending response.
- p := pbytes.GetLen(int(h.Length) + ws.HeaderSize(ws.Header{
- Length: h.Length,
- Masked: c.State.ClientSide(),
- }))
- defer pbytes.Put(p)
- // Get the subslice to read the frame payload out.
- subp := p[:h.Length]
- r := c.Src
- if c.State.ServerSide() && !c.DisableSrcCiphering {
- r = NewCipherReader(r, h.Mask)
- }
- if _, err := io.ReadFull(r, subp); err != nil {
- return err
- }
- code, reason := ws.ParseCloseFrameData(subp)
- if err := ws.CheckCloseFrameData(code, reason); err != nil {
- // Here we could not use the prepared bytes because there is no
- // guarantee that it may fit our protocol error closure code and a
- // reason.
- c.closeWithProtocolError(err)
- return err
- }
- // Deal with ciphering i/o:
- // Masking key is used to mask the "Payload data" defined in the same
- // section as frame-payload-data, which includes "Extension data" and
- // "Application data".
- //
- // See https://tools.ietf.org/html/rfc6455#section-5.3
- //
- // NOTE: We prefer ControlWriter with preallocated buffer to
- // ws.WriteHeader because it performs one syscall instead of two.
- w := NewControlWriterBuffer(c.Dst, c.State, ws.OpClose, p)
- // RFC6455#5.5.1:
- // If an endpoint receives a Close frame and did not previously
- // send a Close frame, the endpoint MUST send a Close frame in
- // response. (When sending a Close frame in response, the endpoint
- // typically echoes the status code it received.)
- _, err := w.Write(p[:2])
- if err != nil {
- return err
- }
- if err = w.Flush(); err != nil {
- return err
- }
- return ClosedError{
- Code: code,
- Reason: reason,
- }
- }
- func (c ControlHandler) closeWithProtocolError(reason error) error {
- f := ws.NewCloseFrame(ws.NewCloseFrameBody(
- ws.StatusProtocolError, reason.Error(),
- ))
- if c.State.ClientSide() {
- ws.MaskFrameInPlace(f)
- }
- return ws.WriteFrame(c.Dst, f)
- }
|