wsutil

package
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 30, 2020 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package wsutil provides utilities for working with WebSocket protocol.

Overview:

  // Read masked text message from peer and check utf8 encoding.
  header, err := ws.ReadHeader(conn)
  if err != nil {
	  // handle err
  }

  // Prepare to read payload.
  r := io.LimitReader(conn, header.Length)
  r = wsutil.NewCipherReader(r, header.Mask)
  r = wsutil.NewUTF8Reader(r)

  payload, err := ioutil.ReadAll(r)
  if err != nil {
	  // handle err
  }

You could get the same behavior using just `wsutil.Reader`:

  r := wsutil.Reader{
	  Source:    conn,
	  CheckUTF8: true,
  }

  payload, err := ioutil.ReadAll(r)
  if err != nil {
	  // handle err
  }

Or even simplest:

  payload, err := wsutil.ReadClientText(conn)
  if err != nil {
	  // handle err
  }

Package is also exports tools for buffered writing:

  // Create buffered writer, that will buffer output bytes and send them as
  // 128-length fragments (with exception on large writes, see the doc).
  writer := wsutil.NewWriterSize(conn, ws.StateServerSide, ws.OpText, 128)

  _, err := io.CopyN(writer, rand.Reader, 100)
  if err == nil {
	  err = writer.Flush()
  }
  if err != nil {
	  // handle error
  }

For more utils and helpers see the documentation.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotEmpty is returned by Writer.WriteThrough() to indicate that buffer is
	// not empty and write through could not be done. That is, caller should call
	// Writer.FlushFragment() to make buffer empty.
	ErrNotEmpty = fmt.Errorf("writer not empty")

	// ErrControlOverflow is returned by ControlWriter.Write() to indicate that
	// no more data could be written to the underlying io.Writer because
	// MaxControlFramePayloadSize limit is reached.
	ErrControlOverflow = fmt.Errorf("control frame payload overflow")
)
View Source
var DefaultWriteBuffer = 4096

DefaultWriteBuffer contains size of Writer's default buffer. It used by Writer constructor functions.

View Source
var ErrInvalidUTF8 = fmt.Errorf("invalid utf8")

ErrInvalidUTF8 is returned by UTF8 reader on invalid utf8 sequence.

View Source
var ErrNoFrameAdvance = errors.New("no frame advance")

ErrNoFrameAdvance means that Reader's Read() method was called without preceding NextFrame() call.

View Source
var ErrNotControlFrame = errors.New("not a control frame")

ErrNotControlFrame is returned by ControlHandler to indicate that given header could not be handled.

Functions

func HandleClientControlMessage added in v1.0.0

func HandleClientControlMessage(conn io.Writer, msg Message) error

HandleClientControlMessage handles control frame from conn and writes response when needed.

It considers that caller represents server side.

func HandleControlMessage added in v1.0.0

func HandleControlMessage(conn io.Writer, state ws.State, msg Message) error

HandleControlMessage handles message which was read by ReadMessage() functions.

That is, it is expected, that payload is already unmasked and frame header were checked by ws.CheckHeader() call.

func HandleServerControlMessage added in v1.0.0

func HandleServerControlMessage(conn io.Writer, msg Message) error

HandleServerControlMessage handles control frame from conn and writes response when needed.

It considers that caller represents client side.

func NextReader

func NextReader(r io.Reader, s ws.State) (ws.Header, io.Reader, error)

NextReader prepares next message read from r. It returns header that describes the message and io.Reader to read message's payload. It returns non-nil error when it is not possible to read message's initial frame.

Note that next NextReader() on the same r should be done after reading all bytes from previously returned io.Reader. For more performant way to discard message use Reader and its Discard() method.

Note that it will not handle any "intermediate" frames, that possibly could be received between text/binary continuation frames. That is, if peer sent text/binary frame with fin flag "false", then it could send ping frame, and eventually remaining part of text/binary frame with fin "true" – with NextReader() the ping frame will be dropped without any notice. To handle this rare, but possible situation (and if you do not know exactly which frames peer could send), you could use Reader with OnIntermediate field set.

func PutWriter

func PutWriter(w *Writer)

PutWriter puts w for future reuse by GetWriter().

func ReadClientBinary

func ReadClientBinary(rw io.ReadWriter) ([]byte, error)

ReadClientBinary reads next binary message from rw, considering that caller represents server side. It is a shortcut for ReadData(rw, ws.StateServerSide). It discards received text messages.

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadClientData

func ReadClientData(rw io.ReadWriter) ([]byte, ws.OpCode, error)

ReadClientData reads next data message from rw, considering that caller represents server side. It is a shortcut for ReadData(rw, ws.StateServerSide).

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadClientText

func ReadClientText(rw io.ReadWriter) ([]byte, error)

ReadClientText reads next text message from rw, considering that caller represents server side. It is a shortcut for ReadData(rw, ws.StateServerSide). It discards received binary messages.

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadData

func ReadData(rw io.ReadWriter, s ws.State) ([]byte, ws.OpCode, error)

ReadData is a helper function that reads next data (non-control) message from rw. It takes care on handling all control frames. It will write response on control frames to the write part of rw. It blocks until some data frame will be received.

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadServerBinary

func ReadServerBinary(rw io.ReadWriter) ([]byte, error)

ReadServerBinary reads next binary message from rw, considering that caller represents client side. It is a shortcut for ReadData(rw, ws.StateClientSide). It discards received text messages.

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadServerData

func ReadServerData(rw io.ReadWriter) ([]byte, ws.OpCode, error)

ReadServerData reads next data message from rw, considering that caller represents client side. It is a shortcut for ReadData(rw, ws.StateClientSide).

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func ReadServerText

func ReadServerText(rw io.ReadWriter) ([]byte, error)

ReadServerText reads next text message from rw, considering that caller represents client side. It is a shortcut for ReadData(rw, ws.StateClientSide). It discards received binary messages.

Note this may handle and write control frames into the writer part of a given io.ReadWriter.

func WriteClientBinary

func WriteClientBinary(w io.Writer, p []byte) error

WriteClientBinary is the same as WriteClientMessage with ws.OpBinary.

func WriteClientMessage

func WriteClientMessage(w io.Writer, op ws.OpCode, p []byte) error

WriteClientMessage writes message to w, considering that caller represents client side.

func WriteClientText

func WriteClientText(w io.Writer, p []byte) error

WriteClientText is the same as WriteClientMessage with ws.OpText.

func WriteMessage

func WriteMessage(w io.Writer, s ws.State, op ws.OpCode, p []byte) error

WriteMessage is a helper function that writes message to the w. It constructs single frame with given operation code and payload. It uses given state to prepare side-dependent things, like cipher payload bytes from client to server. It will not mutate p bytes if cipher must be made.

If you want to write message in fragmented frames, use Writer instead.

func WriteServerBinary

func WriteServerBinary(w io.Writer, p []byte) error

WriteServerBinary is the same as WriteServerMessage with ws.OpBinary.

func WriteServerMessage

func WriteServerMessage(w io.Writer, op ws.OpCode, p []byte) error

WriteServerMessage writes message to w, considering that caller represents server side.

func WriteServerText

func WriteServerText(w io.Writer, p []byte) error

WriteServerText is the same as WriteServerMessage with ws.OpText.

Types

type CipherReader

type CipherReader struct {
	// contains filtered or unexported fields
}

CipherReader implements io.Reader that applies xor-cipher to the bytes read from source. It could help to unmask WebSocket frame payload on the fly.

func NewCipherReader

func NewCipherReader(r io.Reader, mask [4]byte) *CipherReader

NewCipherReader creates xor-cipher reader from r with given mask.

func (*CipherReader) Read

func (c *CipherReader) Read(p []byte) (n int, err error)

Read implements io.Reader interface. It applies mask given during initialization to every read byte.

func (*CipherReader) Reset

func (c *CipherReader) Reset(r io.Reader, mask [4]byte)

Reset resets CipherReader to read from r with given mask.

type CipherWriter

type CipherWriter struct {
	// contains filtered or unexported fields
}

CipherWriter implements io.Writer that applies xor-cipher to the bytes written to the destination writer. It does not modify the original bytes.

func NewCipherWriter

func NewCipherWriter(w io.Writer, mask [4]byte) *CipherWriter

NewCipherWriter creates xor-cipher writer to w with given mask.

func (*CipherWriter) Reset

func (c *CipherWriter) Reset(w io.Writer, mask [4]byte)

Reset reset CipherWriter to write to w with given mask.

func (*CipherWriter) Write

func (c *CipherWriter) Write(p []byte) (n int, err error)

Write implements io.Writer interface. It applies masking during initialization to every sent byte. It does not modify original slice.

type ClosedError

type ClosedError struct {
	Code   ws.StatusCode
	Reason string
}

ClosedError returned when peer has closed the connection with appropriate code and a textual reason.

func (ClosedError) Error

func (err ClosedError) Error() string

Error implements error interface.

type ControlHandler

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
}

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.

func (ControlHandler) Handle added in v1.0.0

func (c ControlHandler) Handle(h ws.Header) error

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 (ControlHandler) HandleClose added in v1.0.0

func (c ControlHandler) HandleClose(h ws.Header) error

HandleClose handles close frame, makes protocol validity checks and writes specification compatible response to the c.Dst.

func (ControlHandler) HandlePing added in v1.0.0

func (c ControlHandler) HandlePing(h ws.Header) error

HandlePing handles ping frame and writes specification compatible response to the c.Dst.

func (ControlHandler) HandlePong added in v1.0.0

func (c ControlHandler) HandlePong(h ws.Header) error

HandlePong handles pong frame by discarding it.

type ControlWriter

type ControlWriter struct {
	// contains filtered or unexported fields
}

ControlWriter is a wrapper around Writer that contains some guards for buffered writes of control frames.

func NewControlWriter

func NewControlWriter(dest io.Writer, state ws.State, op ws.OpCode) *ControlWriter

NewControlWriter contains ControlWriter with Writer inside whose buffer size is at most ws.MaxControlFramePayloadSize + ws.MaxHeaderSize.

func NewControlWriterBuffer

func NewControlWriterBuffer(dest io.Writer, state ws.State, op ws.OpCode, buf []byte) *ControlWriter

NewControlWriterBuffer returns a new ControlWriter with buf as a buffer.

Note that it reserves x bytes of buf for header data, where x could be ws.MinHeaderSize or ws.MinHeaderSize+4 (depending on state). At most (ws.MaxControlFramePayloadSize + x) bytes of buf will be used.

It panics if len(buf) <= ws.MinHeaderSize + x.

func (*ControlWriter) Flush

func (c *ControlWriter) Flush() error

Flush flushes all buffered data to the underlying io.Writer.

func (*ControlWriter) Write

func (c *ControlWriter) Write(p []byte) (n int, err error)

Write implements io.Writer. It writes to the underlying Writer until it returns error or until ControlWriter write limit will be exceeded.

type DebugDialer

type DebugDialer struct {
	// Dialer contains WebSocket connection establishment options.
	Dialer ws.Dialer

	// OnRequest and OnResponse are the callbacks that will be called with the
	// HTTP request and response respectively.
	OnRequest, OnResponse func([]byte)
}

DebugDialer is a wrapper around ws.Dialer. It tracks i/o of WebSocket handshake. That is, it gives ability to receive copied HTTP request and response bytes that made inside Dialer.Dial().

Note that it must not be used in production applications that requires Dial() to be efficient.

func (*DebugDialer) Dial

func (d *DebugDialer) Dial(ctx context.Context, urlstr string) (conn net.Conn, br *bufio.Reader, hs ws.Handshake, err error)

Dial connects to the url host and upgrades connection to WebSocket. It makes it by calling d.Dialer.Dial().

type DebugUpgrader

type DebugUpgrader struct {
	// Upgrader contains upgrade to WebSocket options.
	Upgrader ws.Upgrader

	// OnRequest and OnResponse are the callbacks that will be called with the
	// HTTP request and response respectively.
	OnRequest, OnResponse func([]byte)
}

DebugUpgrader is a wrapper around ws.Upgrader. It tracks I/O of a WebSocket handshake.

Note that it must not be used in production applications that requires Upgrade() to be efficient.

func (*DebugUpgrader) Upgrade

func (d *DebugUpgrader) Upgrade(conn io.ReadWriter) (hs ws.Handshake, err error)

Upgrade calls Upgrade() on underlying ws.Upgrader and tracks I/O on conn.

type FrameHandlerFunc added in v1.0.0

type FrameHandlerFunc func(ws.Header, io.Reader) error

FrameHandlerFunc handles parsed frame header and its body represented by io.Reader.

Note that reader represents already unmasked body.

func ControlFrameHandler added in v1.0.0

func ControlFrameHandler(w io.Writer, state ws.State) FrameHandlerFunc

ControlFrameHandler returns FrameHandlerFunc for handling control frames. For more info see ControlHandler docs.

type Message

type Message struct {
	OpCode  ws.OpCode
	Payload []byte
}

Message represents a message from peer, that could be presented in one or more frames. That is, it contains payload of all message fragments and operation code of initial frame for this message.

func ReadClientMessage

func ReadClientMessage(r io.Reader, m []Message) ([]Message, error)

ReadClientMessage reads next message from r, considering that caller represents server side. It is a shortcut for ReadMessage(r, ws.StateServerSide, m)

func ReadMessage

func ReadMessage(r io.Reader, s ws.State, m []Message) ([]Message, error)

ReadMessage is a helper function that reads next message from r. It appends received message(s) to the third argument and returns the result of it and an error if some failure happened. That is, it probably could receive more than one message when peer sending fragmented message in multiple frames and want to send some control frame between fragments. Then returned slice will contain those control frames at first, and then result of gluing fragments.

TODO(gobwas): add DefaultReader with buffer size options.

func ReadServerMessage

func ReadServerMessage(r io.Reader, m []Message) ([]Message, error)

ReadServerMessage reads next message from r, considering that caller represents client side. It is a shortcut for ReadMessage(r, ws.StateClientSide, m)

type Reader

type Reader struct {
	Source io.Reader
	State  ws.State

	// SkipHeaderCheck disables checking header bits to be RFC6455 compliant.
	SkipHeaderCheck bool

	// CheckUTF8 enables UTF-8 checks for text frames payload. If incoming
	// bytes are not valid UTF-8 sequence, ErrInvalidUTF8 returned.
	CheckUTF8 bool

	OnContinuation FrameHandlerFunc
	OnIntermediate FrameHandlerFunc
	// contains filtered or unexported fields
}

Reader is a wrapper around source io.Reader which represents WebSocket connection. It contains options for reading messages from source.

Reader implements io.Reader, which Read() method reads payload of incoming WebSocket frames. It also takes care on fragmented frames and possibly intermediate control frames between them.

Note that Reader's methods are not goroutine safe.

func NewClientSideReader

func NewClientSideReader(r io.Reader) *Reader

NewClientSideReader is a helper function that calls NewReader with r and ws.StateClientSide.

func NewReader

func NewReader(r io.Reader, s ws.State) *Reader

NewReader creates new frame reader that reads from r keeping given state to make some protocol validity checks when it needed.

func NewServerSideReader

func NewServerSideReader(r io.Reader) *Reader

NewServerSideReader is a helper function that calls NewReader with r and ws.StateServerSide.

func (*Reader) Discard

func (r *Reader) Discard() (err error)

Discard discards current message unread bytes. It discards all frames of fragmented message.

func (*Reader) NextFrame

func (r *Reader) NextFrame() (hdr ws.Header, err error)

NextFrame prepares r to read next message. It returns received frame header and non-nil error on failure.

Note that next NextFrame() call must be done after receiving or discarding all current message bytes.

func (*Reader) Read

func (r *Reader) Read(p []byte) (n int, err error)

Read implements io.Reader. It reads the next message payload into p. It takes care on fragmented messages.

The error is io.EOF only if all of message bytes were read. If an io.EOF happens during reading some but not all the message bytes Read() returns io.ErrUnexpectedEOF.

The error is ErrNoFrameAdvance if no NextFrame() call was made before reading next message bytes.

type UTF8Reader

type UTF8Reader struct {
	Source io.Reader
	// contains filtered or unexported fields
}

UTF8Reader implements io.Reader that calculates utf8 validity state after every read byte from Source.

Note that in some cases client must call r.Valid() after all bytes are read to ensure that all of them are valid utf8 sequences. That is, some io helper functions such io.ReadAtLeast or io.ReadFull could discard the error information returned by the reader when they receive all of requested bytes. For example, the last read sequence is invalid and UTF8Reader returns number of bytes read and an error. But helper function decides to discard received error due to all requested bytes are completely read from the source.

Another possible case is when some valid sequence become split by the read bound. Then UTF8Reader can not make decision about validity of the last sequence cause it is not fully read yet. And if the read stops, Valid() will return false, even if Read() by itself dit not.

func NewUTF8Reader

func NewUTF8Reader(r io.Reader) *UTF8Reader

NewUTF8Reader creates utf8 reader that reads from r.

func (*UTF8Reader) Accepted added in v1.0.0

func (u *UTF8Reader) Accepted() int

Accepted returns number of valid bytes in last Read().

func (*UTF8Reader) Read

func (u *UTF8Reader) Read(p []byte) (n int, err error)

Read implements io.Reader.

func (*UTF8Reader) Reset

func (u *UTF8Reader) Reset(r io.Reader)

Reset resets utf8 reader to read from r.

func (*UTF8Reader) Valid

func (u *UTF8Reader) Valid() bool

Valid checks current reader state. It returns true if all read bytes are valid UTF-8 sequences, and false if not.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer contains logic of buffering output data into a WebSocket fragments. It is much the same as bufio.Writer, except the thing that it works with WebSocket frames, not the raw data.

Writer writes frames with specified OpCode. It uses ws.State to decide whether the output frames must be masked.

Note that it does not check control frame size or other RFC rules. That is, it must be used with special care to write control frames without violation of RFC. You could use ControlWriter that wraps Writer and contains some guards for writing control frames.

If an error occurs writing to a Writer, no more data will be accepted and all subsequent writes will return the error. After all data has been written, the client should call the Flush() method to guarantee all data has been forwarded to the underlying io.Writer.

func GetWriter

func GetWriter(dest io.Writer, state ws.State, op ws.OpCode, n int) *Writer

GetWriter tries to reuse Writer getting it from the pool.

This function is intended for memory consumption optimizations, because NewWriter*() functions make allocations for inner buffer.

Note the it ceils n to the power of two.

If you have your own bytes buffer pool you could use NewWriterBuffer to use pooled bytes in writer.

func NewWriter

func NewWriter(dest io.Writer, state ws.State, op ws.OpCode) *Writer

NewWriter returns a new Writer whose buffer has the DefaultWriteBuffer size.

func NewWriterBuffer

func NewWriterBuffer(dest io.Writer, state ws.State, op ws.OpCode, buf []byte) *Writer

NewWriterBuffer returns a new Writer with buf as a buffer.

Note that it reserves x bytes of buf for header data, where x is in range [ws.MinHeaderSize,ws.MaxHeaderSize] (depending on state and buf size).

You could use ws.HeaderSize() to calculate number of bytes needed to store header data.

It panics if len(buf) is too small to fit header and payload data.

func NewWriterBufferSize

func NewWriterBufferSize(dest io.Writer, state ws.State, op ws.OpCode, n int) *Writer

NewWriterBufferSize returns a new Writer whose buffer size is equal to n. If n <= ws.MinHeaderSize then the default buffer size is used.

Note that Writer will reserve x bytes for header data, where x is in range [ws.MinHeaderSize,ws.MaxHeaderSize]. That is, frames flushed by Writer will not have payload length equal to n, except the case when Write() is called on empty Writer with len(p) > n.

func NewWriterSize

func NewWriterSize(dest io.Writer, state ws.State, op ws.OpCode, n int) *Writer

NewWriterSize returns a new Writer whose buffer size is at most n + ws.MaxHeaderSize. That is, output frames payload length could be up to n, except the case when Write() is called on empty Writer with len(p) > n.

If n <= 0 then the default buffer size is used as Writer's buffer size.

func (*Writer) Available

func (w *Writer) Available() int

Available returns how many bytes are unused in the buffer.

func (*Writer) Buffered

func (w *Writer) Buffered() int

Buffered returns the number of bytes that have been written into the current buffer.

func (*Writer) Flush

func (w *Writer) Flush() error

Flush writes any buffered data to the underlying io.Writer. It sends the frame with "fin" flag set to true.

If no Write() or ReadFrom() was made, then Flush() does nothing.

func (*Writer) FlushFragment

func (w *Writer) FlushFragment() error

FlushFragment writes any buffered data to the underlying io.Writer. It sends the frame with "fin" flag set to false.

func (*Writer) ReadFrom

func (w *Writer) ReadFrom(src io.Reader) (n int64, err error)

ReadFrom implements io.ReaderFrom.

func (*Writer) Reset

func (w *Writer) Reset(dest io.Writer, state ws.State, op ws.OpCode)

Reset discards any buffered data, clears error, and resets w to have given state and write frames with given OpCode to dest.

func (*Writer) Size

func (w *Writer) Size() int

Size returns the size of the underlying buffer in bytes.

func (*Writer) Write

func (w *Writer) Write(p []byte) (n int, err error)

Write implements io.Writer.

Note that even if the Writer was created to have N-sized buffer, Write() with payload of N bytes will not fit into that buffer. Writer reserves some space to fit WebSocket header data.

func (*Writer) WriteThrough

func (w *Writer) WriteThrough(p []byte) (n int, err error)

WriteThrough writes data bypassing the buffer. Note that Writer's buffer must be empty before calling WriteThrough().

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL