websocket

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2020 License: MIT Imports: 20 Imported by: 0

README

WebSockt (Beta)

Go Report Card go.dev reference

this is an Go implementation of WebSocket protocol. just for study, DO NOT USING IN PRODUCTION !!!, so it will be easy to read and SIMPLE.

go version: 1.14.1, os: darwin-10.15.4

Get start

how to use this lib as WebSocket client, as the following code:

// examples/use-as-client/main.go
conn, err = websocket.Dial("ws://localhost:8080/echo")
if err != nil {
    panic(err)
}
go func() {
    for {
        if err = conn.SendMessage("hello"); err != nil {
            fmt.Printf("send failed, err=%v\n", err)
        }
        time.Sleep(3 * time.Second)
    }
}()

for {
    mt, msg, err := conn.ReadMessage()
    if err != nil {
        if ce, ok := err.(*websocket.CloseError); ok {
            fmt.Printf("close err=%d, %s", ce.Code, ce.Text)
            break
        }
        fmt.Printf("recv failed, err=%v\n", err)
        time.Sleep(1 * time.Second)
    }
    fmt.Printf("messageType=%d, msg=%s\n", mt, msg)
}
// example/use-as-server/main.go
package main

import (
    "net/http"

    "github.com/yeqown/log"
    "github.com/yeqown/websocket"
)

var upgrader websocket.Upgrader

func echo(w http.ResponseWriter, req *http.Request) {
    err := upgrader.Upgrade(w, req, func(conn *websocket.Conn) {
        for {
            mt, message, err := conn.ReadMessage()
            if err != nil {
                if closeErr, ok := err.(*websocket.CloseError); ok {
                    log.Warnf("conn closed, beacuse=%v", closeErr)
                    break
                }
                log.Errorf("read error, err=%v", err)
                continue
            }
            log.Infof("recv: mt=%d, msg=%s", mt, message)
            err = conn.SendMessage(string(message))
            if err != nil {
                log.Errorf("write error: err=%v", err)
                break
            }
        }

        log.Info("conn finished")
    })

    if err != nil {
        log.Errorf("upgrade error, err=%v", err)
        // if _, ok := err.(websocket.HandshakeError); ok {
        // 	log.Errorf(err)
        // }
        return
    }

    log.Infof("conn upgrade done")
}

func main() {
    http.HandleFunc("/echo", echo)

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Protocol

The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code. The security model used for this is the origin-based security model commonly used by web browsers. The protocol consists of an opening handshake followed by basic message framing, layered over TCP. The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections (e.g., using XMLHttpRequest or <\iframe>'s and long polling).

Frame (Core)

base frame is following:

READ MORE

How to work
  1. build connection from client
  2. accept connection in server side, start ping/pong
  3. send and recv message 3.0 assemble data frame, according to the protocol by RFC6455 3.1 handle exceptions (server panic; heartbeat loss)
  4. close connection

client and server sequence

References

Documentation

Overview

Application data: y bytes

Arbitrary "Application data", taking up the remainder of the frame after any "Extension data". The length of the "Application data" is equal to the payload length minus the length of the "Extension data".

Package websocket .

Index

Constants

View Source
const (
	// NoFrame .
	NoFrame MessageType = 0
	// TextMessage .
	TextMessage = MessageType(opCodeText)
	// BinaryMessage .
	BinaryMessage = MessageType(opCodeBinary)
	// CloseMessage .
	CloseMessage = MessageType(opCodeClose)
	// PingMessage .
	PingMessage = MessageType(opCodePing)
	// PongMessage .
	PongMessage = MessageType(opCodePong)
)
View Source
const (
	CloseNormalClosure           = 1000
	CloseGoingAway               = 1001
	CloseProtocolError           = 1002
	CloseUnsupportedData         = 1003
	CloseNoStatusReceived        = 1005
	CloseAbnormalClosure         = 1006
	CloseInvalidFramePayloadData = 1007
	ClosePolicyViolation         = 1008
	CloseMessageTooBig           = 1009
	CloseMandatoryExtension      = 1010
	CloseInternalServerErr       = 1011
	CloseServiceRestart          = 1012
	CloseTryAgainLater           = 1013
	CloseTLSHandshake            = 1015
)

Variables

View Source
var (
	ErrMaskNotSet = errors.New("mask is not set")
	ErrMaskSet    = errors.New("mask is set")
)
View Source
var (
	// ErrUnexpectedEOF .
	ErrUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
	// ErrInvalidFrame .
	ErrInvalidFrame = &CloseError{Code: CloseProtocolError, Text: "invalid frame: "}
)
View Source
var (
	// ErrInvalidData .
	ErrInvalidData = errors.New("invalid websocket data frame")
)
View Source
var (
	// ErrInvalidSchema .
	ErrInvalidSchema = errors.New("invalid schema")
)

Functions

func SetDebug

func SetDebug(debug bool)

SetDebug . open debug mode

Types

type CloseError

type CloseError struct {
	Code int
	Text string
}

CloseError .

func (*CloseError) Error

func (e *CloseError) Error() string

type Conn

type Conn struct {

	// State marks Conn current state, and it is basis of controlling the Conn.
	// unnecessary: maybe import an state machine to manage with
	State ConnState
	// contains filtered or unexported fields
}

Conn .

func Dial

func Dial(URL string, opts ...DialOption) (*Conn, error)

Dial .

func (*Conn) Close

func (c *Conn) Close()

Close .

func (*Conn) Connected

func (c *Conn) Connected() bool

Connected .

func (*Conn) Ping added in v1.1.0

func (c *Conn) Ping() (err error)

Ping conn send a ping packet to another side.

func (*Conn) ReadMessage

func (c *Conn) ReadMessage() (mt MessageType, msg []byte, err error)

ReadMessage . it will block to read message

func (*Conn) SendBinary

func (c *Conn) SendBinary(r io.Reader) (err error)

SendBinary . sending bianry data to other-side

func (*Conn) SendMessage

func (c *Conn) SendMessage(text string) (err error)

SendMessage . sending text data to other side

func (*Conn) SetPongHandler added in v1.1.0

func (c *Conn) SetPongHandler(handler func(s string))

SetPongHandler handler would be called while the Conn

type ConnState

type ConnState string

ConnState denotes the underlying connection's state.

const (
	// Connecting one state of ConnState
	Connecting ConnState = "connecting"
	// Connected one state of ConnState
	Connected ConnState = "connected"
	// Closing one state of ConnState
	Closing ConnState = "closing"
	// Closed one state of ConnState
	Closed ConnState = "closed"
)

type DialOption

type DialOption func(o *options)

func WithTLS

func WithTLS(cfg *tls.Config) DialOption

WithTLS generate DialOption with tls.Config.

cert, err := tls.LoadX509KeyPair(certFile, keyFile)
&tls.Config{
	Certificates: []tls.Certificate{cert},
}

type Frame

type Frame struct {
	Fin    uint16 // 1 bit
	RSV1   uint16 // 1 bit, 0
	RSV2   uint16 // 1 bit, 0
	RSV3   uint16 // 1 bit, 0
	OpCode OpCode // 4 bits
	Mask   uint16 // 1 bit

	// Payload length:  7 bits, 7+16 bits, or 7+64 bits
	//
	// if PayloadLen = 0 - 125, actual_payload_length = PayloadLen
	// if PayloadLen = 126, 	actual_payload_length = PayloadExtendLen[:16]
	// if PayloadLen = 127, 	actual_payload_length = PayloadExtendLen[:]
	PayloadLen       uint16 // 7 bits
	PayloadExtendLen uint64 // 64 bits

	MaskingKey uint32 // 32 bits
	Payload    []byte // no limit by RFC6455
}

Frame create an struct to contains WebSocket base frame data, and help to assemble and read data over TCP bytes stream

NOTICE: this definition wastes more space to help understand and each field should be unexported

!!!!CURRENTLY, THIS FRAME NOT CONSIDER ABOUT FRAGMENT!!!!

type HandshakeError

type HandshakeError struct {
	Text string
}

HandshakeError .

func (HandshakeError) Error

func (e HandshakeError) Error() string

type MessageType

type MessageType uint8

MessageType it is reference to frame.opcode

type OpCode

type OpCode uint16

OpCode (4bit) it decides how to parse payload data.

type Upgrader

type Upgrader struct {
	CheckOrigin func(req *http.Request) bool

	Timeout time.Duration
}

Upgrader std.HTTP / fasthttp / gin etc

func (Upgrader) Upgrade

func (ug Upgrader) Upgrade(w http.ResponseWriter, req *http.Request, fn func(conn *Conn)) error

Upgrade handle websocket upgrade request

NOTICE: why returnError and hackHandshakeResponse both exists: https://stackoverflow.com/questions/32657603/why-do-i-get-the-error-message-http-response-write-on-hijacked-connection

Directories

Path Synopsis
fragment command
use-as-client command
use-as-server command
with-tls command

Jump to

Keyboard shortcuts

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