socketio

package module
v0.0.0-...-eb1420b Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2020 License: BSD-3-Clause Imports: 18 Imported by: 0

README

socketio

socket.io/engine.io implementation in Go

Fork

Maintained fork of https://github.com/zyxar/socketio with bug fixes. Changes compared to the original:

  • Fixed client error handling
  • Fixed client disconnect event
  • Fixed client disconnect trigger (auto reconnect loop now doesn't get stuck)
  • Minor improvements
  • Merged room implementation
  • Added support for multiple handlers per event
  • TLS support

Install

go get -v -u github.com/Mindgamesnl/socketio

Features

  • compatible with official nodejs implementation;
  • socket.io server;
  • socket.io client (websocket only);
  • engine.io server;
  • engine.io client (websocket only);
  • binary data;
  • namespace support;
  • socket.io-msgpack-parser support;

Example

Server:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/Mindgamesnl/socketio"
)

func main() {
	server, _ := socketio.NewServer(time.Second*25, time.Second*5, socketio.DefaultParser)
	server.Namespace("/").
		OnConnect(func(so socketio.Socket) {
			log.Println("connected:", so.RemoteAddr(), so.Sid(), so.Namespace())
		}).
		OnDisconnect(func(so socketio.Socket) {
			log.Printf("%v %v %q disconnected", so.Sid(), so.RemoteAddr(), so.Namespace())
		}).
		OnError(func(so socketio.Socket, err ...interface{}) {
			log.Println("socket", so.Sid(), so.RemoteAddr(), so.Namespace(), "error:", err)
		}).
		OnEvent("message", func(so socketio.Socket, data string) {
			log.Println(data)
		})

	http.ListenAndServe(":8081", server)
}

Client:

const io = require('socket.io-client');
const socket = io('http://localhost:8081');
var id;

socket.on('connect', function() {
  console.log('connected');
  if (id === undefined) {
    id = setInterval(function() {
      socket.emit('message', 'hello there!')
    }, 2000);
  }
});
socket.on('event', console.log);
socket.on('disconnect', function() {
  console.log('disconnected');
  if (id) {
    clearInterval(id);
    id = undefined;
  }
});
With Acknowledgements
  • Server -> Client

Server:

	so.Emit("ack", "foo", func(msg string) {
		log.Println(msg)
	})

Client:

  socket.on('ack', function(name, fn) {
    console.log(name);
    fn('bar');
  })
  • Client -> Server

Server:

	server.Namespace("/").OnEvent("foobar", func(data string) (string, string) {
		log.Println("foobar:", data)
		return "foo", "bar"
	})

Client:

  socket.emit('foobar', '-wow-', function (foo, bar) {
    console.log('foobar:', foo, bar);
  });
With Binary Data

Server:

	server.Namespace("/").
		OnEvent("binary", func(data interface{}, b *socketio.Bytes) {
			log.Println(data)
			bb, _ := b.MarshalBinary()
			log.Printf("%x", bb)
		}).
		OnConnect(func(so socketio.Socket) {
			go func() {
				for {
					select {
					case <-time.After(time.Second * 2):
						if err := so.Emit("event", "check it out!", time.Now()); err != nil {
							log.Println(err)
							return
						}
					}
				}
			}()
		})

Client:

  var ab = new ArrayBuffer(4);
  var a = new Uint8Array(ab);
  a.set([1,2,3,4]);

  id = setInterval(function() {
    socket.emit('binary', 'buf:', ab);
  }, 2000);

  socket.on('event', console.log);
Binary Helper for protobuf
import (
	"github.com/golang/protobuf/proto"
)

type ProtoMessage struct {
	proto.Message
}

func (p ProtoMessage) MarshalBinary() ([]byte, error) {
	return proto.Marshal(p.Message)
}

func (p *ProtoMessage) UnmarshalBinary(b []byte) error {
	return proto.Unmarshal(b, p.Message)
}
Binary Helper for MessagePack
import (
	"github.com/tinylib/msgp/msgp"
)

type MessagePack struct {
	Message interface {
		msgp.MarshalSizer
		msgp.Unmarshaler
	}
}

func (m MessagePack) MarshalBinary() ([]byte, error) {
	return m.Message.MarshalMsg(nil)
}

func (m *MessagePack) UnmarshalBinary(b []byte) error {
	_, err := m.Message.UnmarshalMsg(b)
	return err
}
Customized Namespace

Server:

	server.Namespace("/ditto").OnEvent("disguise", func(msg interface{}, b socketio.Bytes) {
		bb, _ := b.MarshalBinary()
		log.Printf("%v: %x", msg, bb)
	})

Client:

let ditto = io('http://localhost:8081/ditto');
ditto.emit('disguise', 'pidgey', new ArrayBuffer(8));

Parser

The encoder and decoder provided by socketio.DefaultParser is compatible with socket.io-parser, complying with revision 4 of socket.io-protocol.

An Event or Ack Packet with any data satisfying socketio.Binary interface (e.g. socketio.Bytes) would be encoded as BinaryEvent or BinaryAck Packet respectively.

socketio.MsgpackParser, compatible with socket.io-msgpack-parser, is an alternative custom parser.

TLS Terminator example using golang.org/x/crypto/acme/autocert

type WrappedServer struct {
	OriginalHandler http.Handler
}

func (wrapper WrappedServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, OPTIONS")
	res.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	wrapper.OriginalHandler.ServeHTTP(res, req)
}

func StartSocketServer() {
	server, _ := socketio.NewServer(time.Second*25, time.Second*5, socketio.DefaultParser)
	
	certManager := autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist("socket.example.com"), // your domain
		Cache:      autocert.DirCache("certs"),                   // cache folder
		Email:      "example@example.com",                        // owner email
	}

	actualServer := &http.Server{
		Addr: ":https",
		TLSConfig: &tls.Config{
			GetCertificate: certManager.GetCertificate,
			ServerName:     "socket.example.com",
		},
	}

	wrapper := WrappedServer{
		OriginalHandler: server,
	}
	actualServer.Handler = wrapper
	
	go http.ListenAndServe(":http", certManager.HTTPHandler(nil))
	log.Fatal(actualServer.ListenAndServeTLS("", ""))
}

Documentation

Index

Examples

Constants

View Source
const (
	// Revision is protocol version
	Revision = "4"
)

Variables

View Source
var (
	// ErrorNamespaceUnavaialble indicates error of client accessing to a non-existent namespace
	ErrorNamespaceUnavaialble = errors.New("namespace unavailable")
)
View Source
var (
	WebsocketTransport = engine.WebsocketTransport
)

Functions

This section is empty.

Types

type ArgsUnmarshaler

type ArgsUnmarshaler interface {
	UnmarshalArgs(args []reflect.Type, data []byte, bin [][]byte) ([]reflect.Value, error)
}

ArgsUnmarshaler unmarshals func arguments `args` from data and binary (bin, if exists). Decoder should implement ArgsUnmarshaler. For `DefaultParser`, data denotes the data in the 1st Packet (w/ type string), while bin denotes binary data in following packets if available; For `MsgpackParser`, bin is not used since all data are packed in a single Packet; args are acquired from reflection, usually by calling `newCallback(func)`

type Binary

Binary refers to binary data to be exchanged between socket.io server and client

type Bytes

type Bytes struct {
	Data []byte
}

Bytes is default implementation of Binary interface, a helper to transfer `[]byte`

func (Bytes) MarshalBinary

func (b Bytes) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler

func (*Bytes) MarshalBinaryTo

func (b *Bytes) MarshalBinaryTo(p []byte) error

MarshalBinaryTo copies data into 'p', implementing msgp.Extension.MarshalBinaryTo

func (*Bytes) UnmarshalBinary

func (b *Bytes) UnmarshalBinary(p []byte) error

UnmarshalBinary implements encoding.BinaryUnmarshaler

type Client

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

Client is socket.io client

func NewClient

func NewClient() (c *Client)

NewClient creates a Client instance; use Dial to initialize underlying network

func (Client) BroadcastToRoom

func (s Client) BroadcastToRoom(room string, event string, args ...interface{})

func (*Client) Close

func (c *Client) Close() error

Close closes underlying engine.io transport

func (*Client) Dial

func (c *Client) Dial(rawurl string, requestHeader http.Header, dialer engine.Dialer, parser Parser) (err error)

Dial connects to a socket.io server represented by `rawurl` and create Client instance on success.

func (*Client) Emit

func (c *Client) Emit(nsp string, event string, args ...interface{}) (err error)

Emit send event messages to namespace `nsp`

func (Client) EmitError

func (s Client) EmitError(arg interface{}) (err error)

EmitError implements Socket.EmitError

func (Client) GetHeader

func (s Client) GetHeader(key string) string

func (Client) GetQuery

func (s Client) GetQuery() url.Values

func (Client) HasBeenClosed

func (s Client) HasBeenClosed() bool

func (Client) Join

func (s Client) Join(room string)

func (Client) Leave

func (s Client) Leave(room string)

func (Client) LeaveAll

func (s Client) LeaveAll()

func (Client) LocalAddr

func (s Client) LocalAddr() net.Addr

func (*Client) Namespace

func (c *Client) Namespace(nsp string) Namespace

Namespace ensures a Namespace instance exists in client

func (*Client) OnError

func (c *Client) OnError(fn func(interface{}))

OnError registers fn as error callback

func (Client) RemoteAddr

func (s Client) RemoteAddr() net.Addr

func (Client) SetHeader

func (s Client) SetHeader(key, value string)

func (*Client) Sid

func (c *Client) Sid() string

Sid returns session id assigned by socket.io server

type Decoder

type Decoder interface {
	Add(msgType MessageType, data []byte) error
	Decoded() <-chan *Packet
	ParseData(p *Packet) (string, []byte, [][]byte, error)
	ArgsUnmarshaler
}

Decoder decodes data into a Packet

type Encoder

type Encoder interface {
	Encode(p *Packet) ([]byte, [][]byte, error)
}

Encoder encodes a Packet into byte format

type MessageType

type MessageType = engine.MessageType

MessageType is alias of engine.MessageType

const (
	// MessageTypeString is alias of engine.MessageTypeString
	MessageTypeString MessageType = engine.MessageTypeString
	// MessageTypeBinary is alias of engine.MessageTypeBinary
	MessageTypeBinary MessageType = engine.MessageTypeBinary
)

type Namespace

type Namespace interface {
	// OnEvent registers event callback:
	// callback should be a valid function, 1st argument of which could be `socketio.Socket` or omitted;
	// the event callback would be called when a message received from a client with corresponding event;
	// upon invocation the corresponding `socketio.Socket` would be supplied if appropriate.
	OnEvent(event string, callback interface{}) Namespace // chainable
	// OnConnect registers fn as callback, which would be called when this Namespace is connected by a
	// client, i.e. upon receiving CONNECT packet (for non-root namespace) or connection establishment
	// ("/" namespace)
	OnConnect(fn func(so Socket)) Namespace // chainable
	// OnDisconnect registers fn as callback, which would be called when this Namespace is disconnected by a
	// client, i.e. upon receiving DISCONNECT packet or connection lost
	OnDisconnect(fn func(so Socket)) Namespace // chainable
	// OnError registers fn as callback, which would be called when error occurs in this Namespace
	OnError(fn func(so Socket, err ...interface{})) Namespace // chainable
}

Namespace is socket.io `namespace` abstraction

type Packet

type Packet struct {
	Type      PacketType  `msg:"type" json:"type"`
	Namespace string      `msg:"nsp" json:"nsp"`
	Data      interface{} `msg:"data" json:"data,omitempty"`
	ID        *uint64     `msg:"id" json:"id,omitempty"`
	// contains filtered or unexported fields
}

Packet is message abstraction, representing for data exchanged between socket.io server and client

func (*Packet) DecodeMsg

func (z *Packet) DecodeMsg(dc *msgp.Reader) (err error)

DecodeMsg implements msgp.Decodable

func (*Packet) EncodeMsg

func (z *Packet) EncodeMsg(en *msgp.Writer) (err error)

EncodeMsg implements msgp.Encodable

func (*Packet) MarshalMsg

func (z *Packet) MarshalMsg(b []byte) (o []byte, err error)

MarshalMsg implements msgp.Marshaler

func (*Packet) Msgsize

func (z *Packet) Msgsize() (s int)

Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message

func (*Packet) UnmarshalMsg

func (z *Packet) UnmarshalMsg(bts []byte) (o []byte, err error)

UnmarshalMsg implements msgp.Unmarshaler

type PacketType

type PacketType byte

PacketType indicates type of a Packet

const (
	PacketTypeConnect PacketType = iota
	PacketTypeDisconnect
	PacketTypeEvent
	PacketTypeAck
	PacketTypeError
	PacketTypeBinaryEvent
	PacketTypeBinaryAck
)

func (PacketType) String

func (p PacketType) String() string

type Parser

type Parser interface {
	Encoder() Encoder
	Decoder() Decoder
}

Parser provides Encoder and Decoder instance, like a factory

var (
	// ErrUnknownPacket indicates packet invalid or unknown when parser encoding/decoding data
	ErrUnknownPacket = errors.New("unknown packet")
	// DefaultParser is default parser implementation for socket.io, compatible with `socket.io-parser`.
	DefaultParser Parser = &defaultParser{}
	// MsgpackParser is msgpack parser implementation for socket.io, compatible with `socket.io-msgpack-parser`.
	MsgpackParser Parser = &msgpackParser{}
)

type Server

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

Server is socket.io server implementation

Example
server, _ := NewServer(time.Second*5, time.Second*5, DefaultParser)
var onConnect = func(so Socket) {
	log.Println("connected:", so.RemoteAddr(), so.Sid(), so.Namespace())
	go func() {
		for {
			<-time.After(time.Second * 2)
			if err := so.Emit("event", "check it out!", time.Now()); err != nil {
				log.Println("emit:", err)
				return
			}
		}
	}()
	so.Emit("event", "hello world!")
}

var onDisconnect = func(so Socket) {
	log.Printf("%v %v %q disconnected", so.Sid(), so.RemoteAddr(), so.Namespace())
}

var onError = func(so Socket, err ...interface{}) {
	log.Println("socket", so.Sid(), so.RemoteAddr(), so.Namespace(), "error:", err)
}

server.Namespace("/").
	OnConnect(onConnect).
	OnDisconnect(onDisconnect).
	OnError(onError).
	OnEvent("message", func(so Socket, data string) {
		if err := so.Emit("ack", "woot", func(msg string, b *Bytes) {
			bb, _ := b.MarshalBinary()
			log.Printf("%s=> %x", msg, bb)
		}); err != nil {
			log.Println("emit:", err)
		}
	}).
	OnEvent("binary", func(data interface{}, b Bytes) {
		bb, _ := b.MarshalBinary()
		log.Printf("%s <- %x", data, bb)
	}).
	OnEvent("foobar", func(data string) (string, string) {
		log.Println("foobar:", data)
		return "foo", "bar"
	})

server.Namespace("/ditto").
	OnConnect(func(so Socket) {
		log.Println("connected:", so.RemoteAddr(), so.Sid(), so.Namespace())
	}).
	OnDisconnect(onDisconnect).
	OnError(onError).
	OnEvent("disguise", func(msg interface{}, b Bytes) {
		bb, _ := b.MarshalBinary()
		log.Printf("%v: %x", msg, bb)
	})

server.OnError(func(err error) {
	log.Printf("server error: %v", err)
})
defer server.Close()
log.Fatalln(http.ListenAndServe("localhost:8081", server))
Output:

Example (WithMsgpackParser)
server, _ := NewServer(time.Second*5, time.Second*5, MsgpackParser)
server.Namespace("/").
	OnConnect(func(so Socket) {
		log.Println("connected:", so.RemoteAddr(), so.Sid(), so.Namespace())
		so.Emit("event", "hello world!", time.Now())
	}).
	OnDisconnect(func(so Socket) {
		log.Printf("%v %v %q disconnected", so.Sid(), so.RemoteAddr(), so.Namespace())
	}).
	OnEvent("message", func(b msgp.Raw, data foobar) {
		log.Printf("%x %v", b, data)
	}).
	OnError(func(so Socket, err ...interface{}) {
		log.Println("socket", so.Sid(), so.RemoteAddr(), so.Namespace(), "error:", err)
	})
server.OnError(func(err error) {
	log.Println("server err:", err)
})
defer server.Close()
log.Fatalln(http.ListenAndServe("localhost:8081", server))
Output:

func NewServer

func NewServer(interval, timeout time.Duration, parser Parser, oc ...engine.OriginChecker) (server *Server, err error)

NewServer creates a socket.io server instance upon underlying engine.io transport

func (*Server) BroadcastToRoom

func (s *Server) BroadcastToRoom(room string, event string, args ...interface{})

func (*Server) Close

func (s *Server) Close() error

Close closes underlying engine.io transport

func (*Server) Namespace

func (s *Server) Namespace(nsp string) Namespace

Namespace ensures a Namespace instance exists in server

func (*Server) OnError

func (s *Server) OnError(fn func(err error))

OnError registers fn as callback for error handling

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler interface

type Socket

type Socket interface {
	GetQuery() url.Values
	Emit(event string, args ...interface{}) (err error)
	EmitError(arg interface{}) (err error)
	Namespace() string
	RemoteAddr() net.Addr
	LocalAddr() net.Addr
	GetHeader(key string) string
	SetHeader(key, value string)
	Sid() string
	HasBeenClosed() bool
	io.Closer
	Join(room string)
	Leave(room string)
	LeaveAll()
	BroadcastToRoom(room string, event string, args ...interface{})
}

Socket is abstraction of bidirectional socket.io connection

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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