Documentation
¶
Overview ¶
Gotalk is a complete muli-peer real-time messaging library. See https://github.com/rsms/gotalk#readme for a more in-depth explanation of what Gotalk is and what it can do for you.
Most commonly Gotalk is used for rich web app development, as an alternative to HTTP APIs, when the web app mainly runs client-side rather than uses a traditional "request a new page" style.
WebSocket example ¶
Here is an example of a minimal but fully functional web server with Gotalk over websocket:
package main import ( "github.com/rsms/gotalk" "net/http" ) type Message struct { Author string Body string } func main() { // This function handles requests for "test/message". gotalk.Handle("test/message", func(input string) (*Message, error) { // It can return any Go type. Here we return a structure and no error. return &Message{Author: "Bob", Body: input}, nil }) // mount Gotalk at "/gotalk/" http.Handle("/gotalk/", gotalk.NewWebSocketServer()) // mount a file server to handle all other requests http.Handle("/", http.FileServer(http.Dir("."))) panic(http.ListenAndServe("localhost:1234", nil)) }
Here is a matching HTML document; a very basic web app:
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"> <!-- load the built-in JS library --> <script type="text/javascript" src="/gotalk/gotalk.js"></script> </head> <body style="white-space:pre;font-family:monospace"><button>Send request</button> <script> // create a connection (automatically reconnects as needed) let c = gotalk.connection() .on('open', async() => log(`connection opened\n`)) .on('close', reason => log(`connection closed (reason: ${reason})\n`)) // make out button send a request document.body.firstChild.onclick = async () => { let res = await c.requestp('test/message', 'hello ' + new Date()) log(`reply: ${JSON.stringify(res, null, 2)}\n`) } function log(message) { document.body.appendChild(document.createTextNode(message)) } </script> </body> </html>
API layers ¶
Gotalk can be thought of as being composed by four layers:
- Request-response API with automatic Go data encoding/decoding
- Request management, connection management
- Transport (TCP, pipes, unix sockets, HTTP, WebSocket, etc)
- Framed & streaming byte-based message protocol
You can make use of only some parts. For example you could write and read structured data in files using the message protocol and basic file I/O, or use the high-level request-response API with some custom transport.
Index ¶
- Constants
- Variables
- func DefaultLoggerFunc(s *Sock, format string, args ...interface{})
- func FormatRequestID(n uint32) []byte
- func Handle(op string, fn interface{})
- func HandleBufferNotification(name string, fn BufferNoteHandler)
- func HandleBufferRequest(op string, fn BufferReqHandler)
- func HandleNotification(name string, fn interface{})
- func HandleStreamRequest(op string, fn StreamReqHandler)
- func MakeHeartbeatMsg(load uint16, b []byte) []byte
- func MakeMsg(t MsgType, id, name3 string, wait, size uint32) []byte
- func Pipe(handlers *Handlers, limits *Limits) (*Sock, *Sock, error)
- func ReadVersion(s io.Reader) (uint8, error)
- func Serve(how, addr string, acceptHandler SockHandler) error
- func TLSAddRootCerts(certFile string) error
- func TLSCertPool() *x509.CertPool
- func WriteVersion(s io.Writer) (int, error)
- type BufferNoteHandler
- type BufferReqHandler
- type Handlers
- func (h *Handlers) FindBufferRequestHandler(op string) BufferReqHandler
- func (h *Handlers) FindNotificationHandler(name string) BufferNoteHandler
- func (h *Handlers) FindStreamRequestHandler(op string) StreamReqHandler
- func (h *Handlers) Handle(op string, fn interface{})
- func (h *Handlers) HandleBufferNotification(name string, fn BufferNoteHandler)
- func (h *Handlers) HandleBufferRequest(op string, fn BufferReqHandler)
- func (h *Handlers) HandleNotification(name string, fn interface{})
- func (h *Handlers) HandleStreamRequest(op string, fn StreamReqHandler)
- func (h *Handlers) NewSubHandlers() *Handlers
- type Limits
- type LoggerFunc
- type MsgType
- type Request
- type Response
- type Server
- type Sock
- func (s *Sock) Addr() string
- func (s *Sock) Adopt(r io.ReadWriteCloser)
- func (s *Sock) BufferNotify(name string, buf []byte) error
- func (s *Sock) BufferRequest(op string, buf []byte) ([]byte, error)
- func (s *Sock) Close() error
- func (s *Sock) CloseError(protocolErrorCode int32) error
- func (s *Sock) Conn() io.ReadWriteCloser
- func (s *Sock) Connect(how, addr string, limits *Limits) error
- func (s *Sock) ConnectReader(r io.ReadWriteCloser, limits *Limits) error
- func (s *Sock) ConnectTLS(how, addr string, limits *Limits, config *tls.Config) error
- func (s *Sock) Handshake() error
- func (s *Sock) IsClosed() bool
- func (s *Sock) Notify(name string, v interface{}) error
- func (s *Sock) Read(limits *Limits) error
- func (s *Sock) Request(op string, in interface{}, out interface{}) error
- func (s *Sock) SendHeartbeat(load float32, buf []byte) error
- func (s *Sock) SendRequest(r *Request, reschan chan Response) error
- func (s *Sock) Shutdown(wg *sync.WaitGroup, timeout time.Duration) error
- func (s *Sock) StreamRequest(op string) (*StreamRequest, chan Response)
- func (s *Sock) String() string
- type SockHandler
- type StreamReqHandler
- type StreamRequest
- type WebSocket
- type WebSocketConnection
- type WebSocketServer
Constants ¶
const ( MsgTypeSingleReq = MsgType('r') MsgTypeStreamReq = MsgType('s') MsgTypeStreamReqPart = MsgType('p') MsgTypeSingleRes = MsgType('R') MsgTypeStreamRes = MsgType('S') MsgTypeErrorRes = MsgType('E') MsgTypeRetryRes = MsgType('e') MsgTypeNotification = MsgType('n') MsgTypeHeartbeat = MsgType('h') MsgTypeProtocolError = MsgType('f') )
Protocol message types
const ( ProtocolErrorAbnormal = 0 ProtocolErrorUnsupported = 1 ProtocolErrorInvalidMsg = 2 ProtocolErrorTimeout = 3 )
ProtocolError codes
const JSLibSHA1Base64 = "YLDgrGNN3M7gn9qZDeBmFusYWIQ="
const JSLibString = "" /* 18406-byte string literal not displayed */
const ProtocolVersion = uint8(1)
Version of this protocol
const Unlimited = uint32(0xFFFFFFFF)
Unlimited can be used with Limits.BufferRequests and Limits.StreamRequests
const Version = "1.3.7"
Current version of gotalk
Variables ¶
var ( ErrUnexpectedStreamingRes = errors.New("unexpected streaming response") ErrSockClosed = errors.New("socket closed") )
Returned by (Sock)BufferRequest when a streaming response is recieved
var ( ErrAbnormal = errors.New("abnormal condition") ErrUnsupported = errors.New("unsupported protocol") ErrInvalidMsg = errors.New("invalid protocol message") ErrTimeout = errors.New("timeout") )
var DefaultHandlers = &Handlers{}
Default handlers, manipulated by the package-level handle functions like HandleBufferRequest
var DefaultLimits = &Limits{ ReadTimeout: 30 * time.Second, BufferRequests: Unlimited, StreamRequests: 0, BufferMinWait: 500 * time.Millisecond, BufferMaxWait: 5000 * time.Millisecond, StreamMinWait: 500 * time.Millisecond, StreamMaxWait: 5000 * time.Millisecond, }
DefaultLimits does not limit buffer requests, and disables stream requests.
var HeartbeatMsgMaxLoad = 0xffff
Maximum value of a heartbeat's "load"
var NoLimits = &Limits{ BufferRequests: Unlimited, StreamRequests: Unlimited, }
NoLimits does not limit buffer requests or stream requests, nor does it have a read timeout.
Functions ¶
func DefaultLoggerFunc ¶ added in v1.3.4
DefaultLoggerFunc forwards the message to Go's "log" package; log.Printf(format, args...)
func FormatRequestID ¶
Returns a 4-byte representation of a 32-bit integer, suitable an integer-based request ID.
func Handle ¶
func Handle(op string, fn interface{})
Handle operation with automatic JSON encoding of values.
`fn` must conform to one of the following signatures:
func(*Sock, string, interface{}) (interface{}, error) -- takes socket, op and parameters func(*Sock, interface{}) (interface{}, error) -- takes socket and parameters func(*Sock) (interface{}, error) -- takes no parameters func(interface{}) (interface{}, error) -- takes parameters, but no socket func() (interface{},error) -- takes no socket or parameters
Where optionally the `interface{}` return value can be omitted, i.e:
func(*Sock, string, interface{}) error func(*Sock, interface{}) error func(*Sock) error func(interface{}) error func() error
If `op` is empty, handle all requests which doesn't have a specific handler registered.
func HandleBufferNotification ¶
func HandleBufferNotification(name string, fn BufferNoteHandler)
Handle notifications of a certain name with raw input buffers. If `name` is empty, handle all notifications which doesn't have a specific handler registered.
func HandleBufferRequest ¶
func HandleBufferRequest(op string, fn BufferReqHandler)
Handle operation with raw input and output buffers. If `op` is empty, handle all requests which doesn't have a specific handler registered.
func HandleNotification ¶
func HandleNotification(name string, fn interface{})
Handle notifications of a certain name with automatic JSON encoding of values.
`fn` must conform to one of the following signatures:
func(s *Sock, name string, v interface{}) -- takes socket, name and parameters func(name string, v interface{}) -- takes name and parameters, but no socket func(v interface{}) -- takes only parameters func() -- takes nothing
If `name` is empty, handle all notifications which doesn't have a specific handler registered.
func HandleStreamRequest ¶
func HandleStreamRequest(op string, fn StreamReqHandler)
Handle operation by reading and writing directly from/to the underlying stream. If `op` is empty, handle all requests which doesn't have a specific handler registered.
func MakeHeartbeatMsg ¶
Create a slice of bytes representing a heartbeat message
func Pipe ¶
Creates two sockets which are connected to eachother without any resource limits. If `handlers` is nil, DefaultHandlers are used. If `limits` is nil, DefaultLimits are used.
func ReadVersion ¶
Read the version the other end implements. Returns an error if this side's protocol is incompatible with the other side's version.
func Serve ¶
func Serve(how, addr string, acceptHandler SockHandler) error
Start a `how` server accepting connections at `addr`
func TLSAddRootCerts ¶
TLSAddRootCerts is a convenience for adding root (CA) certificates from a PEM file to the cert pool used by gotalk's TLS functions and returned by TLSCertPool()
func TLSCertPool ¶
TLSCertPool returns the root CA pool. This is normally the same as returned by crypto/x509.SystemCertPool and can be modified, i.e. by adding your own development CA certs. All gotalk TLS functions that creates tls.Config uses this.
Types ¶
type BufferNoteHandler ¶
type BufferReqHandler ¶
If a handler panics, it's assumed that the effect of the panic was isolated to the active request. Panic is recovered, a stack trace is logged, and connection is closed.
type Handlers ¶
type Handlers struct {
// contains filtered or unexported fields
}
The Handlers struct contains request and notifications handlers. Create a new set of handlers by simply creating a zero struct: `&Handlers{}`
func NewHandlers ¶
func NewHandlers() *Handlers
NewHandlers creates a new Handlers struct. DEPRECATED: use `&Handlers{}` instead
func (*Handlers) FindBufferRequestHandler ¶
func (h *Handlers) FindBufferRequestHandler(op string) BufferReqHandler
Look up a single-buffer handler for operation `op`. Returns `nil` if not found.
func (*Handlers) FindNotificationHandler ¶
func (h *Handlers) FindNotificationHandler(name string) BufferNoteHandler
Look up a handler for notification `name`. Returns `nil` if not found.
func (*Handlers) FindStreamRequestHandler ¶
func (h *Handlers) FindStreamRequestHandler(op string) StreamReqHandler
Look up a stream handler for operation `op`. Returns `nil` if not found.
func (*Handlers) Handle ¶
Handle operation with automatic JSON encoding of values.
`fn` must conform to one of the following signatures:
func(*Sock, string, interface{}) (interface{}, error) -- takes socket, op and parameters func(*Sock, interface{}) (interface{}, error) -- takes socket and parameters func(*Sock) (interface{}, error) -- takes no parameters func(interface{}) (interface{}, error) -- takes parameters, but no socket func() (interface{},error) -- takes no socket or parameters
Where optionally the `interface{}` return value can be omitted, i.e:
func(*Sock, string, interface{}) error func(*Sock, interface{}) error func(*Sock) error func(interface{}) error func() error
If `op` is empty, handle all requests which doesn't have a specific handler registered.
func (*Handlers) HandleBufferNotification ¶
func (h *Handlers) HandleBufferNotification(name string, fn BufferNoteHandler)
Handle notifications of a certain name with raw input buffers. If `name` is empty, handle all notifications which doesn't have a specific handler registered.
func (*Handlers) HandleBufferRequest ¶
func (h *Handlers) HandleBufferRequest(op string, fn BufferReqHandler)
Handle operation with raw input and output buffers. If `op` is empty, handle all requests which doesn't have a specific handler registered.
func (*Handlers) HandleNotification ¶
Handle notifications of a certain name with automatic JSON encoding of values.
`fn` must conform to one of the following signatures:
func(s *Sock, name string, v interface{}) -- takes socket, name and parameters func(name string, v interface{}) -- takes name and parameters, but no socket func(v interface{}) -- takes only parameters func() -- takes nothing
If `name` is empty, handle all notifications which doesn't have a specific handler registered.
func (*Handlers) HandleStreamRequest ¶
func (h *Handlers) HandleStreamRequest(op string, fn StreamReqHandler)
Handle operation by reading and writing directly from/to the underlying stream. If `op` is empty, handle all requests which doesn't have a specific handler registered.
func (*Handlers) NewSubHandlers ¶ added in v1.3.5
NewSubHandlers returns a new Handlers object which wraps the receiver. It can be used to override or extend h, without modifying h. For example, it could be used to expose an extra set of operations to certain sockets, like signed-in users.
type Limits ¶
type Limits struct { ReadTimeout time.Duration // timeout for reading messages from the network (0=no limit) BufferRequests uint32 // max number of concurrent buffer requests StreamRequests uint32 // max number of concurrent buffer requests BufferMinWait time.Duration // minimum time to wait when BufferRequests has been reached BufferMaxWait time.Duration // max time to wait when BufferRequests has been reached StreamMinWait time.Duration // minimum time to wait when StreamRequests has been reached StreamMaxWait time.Duration // max time to wait when StreamRequests has been reached }
type LoggerFunc ¶ added in v1.3.4
LoggerFunc is the signature of log functions. s is a related socket, or nil if none apply to the error.
var ( // ErrorLogger is called when an error occurs during writing or reading (rare) ErrorLogger LoggerFunc = DefaultLoggerFunc // HandlerErrorLogger is called when a handler function either panics or returns an error HandlerErrorLogger LoggerFunc = DefaultLoggerFunc )
type Response ¶
func (*Response) IsRetry ¶
True if response is a "server can't handle it right now, please retry" (RetryResult)
func (*Response) IsStreaming ¶
True if this is part of a streaming response (StreamResult)
type Server ¶
type Server struct { // Handlers associated with this server. Accepted sockets inherit the value. *Handlers // Limits. Accepted sockets are subject to the same limits. *Limits // Function to be invoked just after a new socket connection has been accepted and // protocol handshake has sucessfully completed. At this point the socket is ready // to be used. However the function will be called in the socket's "read" goroutine, // meaning no messages will be received on the socket until this function returns. AcceptHandler SockHandler // Template value for accepted sockets. Defaults to 0 (no automatic heartbeats) HeartbeatInterval time.Duration // Template value for accepted sockets. Defaults to nil OnHeartbeat func(load int, t time.Time) // Transport Listener net.Listener }
Accepts socket connections
func Listen ¶
Start a `how` server listening for connections at `addr`. You need to call Accept() on the returned socket to start accepting connections. `how` and `addr` are passed to `net.Listen()` and thus any values accepted by net.Listen are valid. The returned server has Handlers=DefaultHandlers and Limits=DefaultLimits set, which you can change if you want.
func ListenTLS ¶
Start a `how` server listening for connections at `addr` with TLS certificates. You need to call Accept() on the returned socket to start accepting connections. `how` and `addr` are passed to `net.Listen()` and thus any values accepted by net.Listen are valid. The returned server has Handlers=DefaultHandlers and Limits=DefaultLimits set, which you can change if you want.
func ListenTLSCustom ¶
Start a `how` server listening for connections at `addr` with custom TLS configuration. You need to call Accept() on the returned socket to start accepting connections. `how` and `addr` are passed to `net.Listen()` and thus any values accepted by net.Listen are valid. The returned server has Handlers=DefaultHandlers and Limits=DefaultLimits set, which you can change if you want.
func (*Server) EnableUnixSocketGC ¶
func (s *Server) EnableUnixSocketGC()
Unix sockets must be unlink()ed before being reused again. If you don't manage this yourself already, this function provides a limited but quick way to deal with cleanup by installing a signal handler.
type Sock ¶
type Sock struct { // Handlers associated with this socket Handlers *Handlers // Associate some application-specific data with this socket UserData interface{} // Enable streaming requests and set the limit for how many streaming requests this socket // can handle at the same time. Setting this to `0` disables streaming requests alltogether // (the default) while setting this to a large number might be cause for security concerns // as a malicious peer could send many "start stream" messages, but never sending // any "end stream" messages, slowly exhausting memory. StreamReqLimit int // A function to be called when the socket closes. // If the socket was closed because of a protocol error, `code` is >=0 and represents a // ProtocolError* constant. CloseHandler func(s *Sock, code int) // Automatically retry requests which can be retried AutoRetryRequests bool // HeartbeatInterval controls how much time a socket waits between sending its heartbeats. // If this is 0, automatic sending of heartbeats is disabled. // Defaults to 20 seconds when created with NewSock. HeartbeatInterval time.Duration // If not nil, this function is invoked when a heartbeat is recevied OnHeartbeat func(load int, t time.Time) // contains filtered or unexported fields }
func Connect ¶
Connect to a server via `how` at `addr`. Unless there's an error, the returned socket is already reading in a different goroutine and is ready to be used.
func ConnectTLS ¶
Connect to a server via `how` at `addr` over TLS. Unless there's an error, the returned socket is already reading in a different goroutine and is ready to be used.
func (*Sock) Adopt ¶
func (s *Sock) Adopt(r io.ReadWriteCloser)
Adopt an I/O stream, which should already be in a "connected" state. After adopting a new connection, you should call Handshake to perform the protocol handshake, followed by Read to read messages.
func (*Sock) BufferNotify ¶
Send a single-buffer notification
func (*Sock) BufferRequest ¶
Send a single-buffer request, wait for and return the response. Automatically retries the request if needed.
func (*Sock) Close ¶
Close this socket. It is safe for multiple goroutines to call this concurrently.
func (*Sock) CloseError ¶
Close this socket because of a protocol error (ProtocolErrorXXX)
func (*Sock) Conn ¶
func (s *Sock) Conn() io.ReadWriteCloser
Access the socket's underlying connection
func (*Sock) ConnectReader ¶
func (s *Sock) ConnectReader(r io.ReadWriteCloser, limits *Limits) error
Take control over reader r, perform initial handshake and begin communication on a background goroutine.
func (*Sock) ConnectTLS ¶
Connect to a server via `how` at `addr` over TLS. tls.Config is optional; passing nil is equivalent to &tls.Config{}
func (*Sock) Handshake ¶
Before reading any messages over a socket, handshake must happen. This function will block until the handshake either succeeds or fails.
func (*Sock) IsClosed ¶ added in v1.3.2
IsClosed returns true if Close() has been called. It is safe for multiple goroutines to call this concurrently.
func (*Sock) Read ¶
After completing a succesful handshake, call this function to read messages received to this socket. Does not return until the socket is closed. If HeartbeatInterval > 0 this method also sends automatic heartbeats.
func (*Sock) Request ¶
Send a single-value request where the input and output values are JSON-encoded
func (*Sock) SendRequest ¶
Send a single-buffer request. A response should be received from reschan.
func (*Sock) Shutdown ¶ added in v1.2.1
Shut down this socket, giving it timeout time to complete any ongoing work.
timeout should be a short duration as its used for I/O read and write timeout; any work in handlers does not account to the timeout (and is unlimited.) timeout is ignored if the underlying Conn() does not implement SetReadDeadline or SetWriteDeadline.
This method returns immediately. Once all work is complete, calls s.Close() and wg.Done().
This method should not be used with web socket connections. Instead, call Close() from your http.Server.RegisterOnShutdown handler.
func (*Sock) StreamRequest ¶
func (s *Sock) StreamRequest(op string) (*StreamRequest, chan Response)
Send a multi-buffer streaming request
type SockHandler ¶
type SockHandler func(*Sock)
type StreamReqHandler ¶
EOS when <-rch==nil
type StreamRequest ¶
type StreamRequest struct {
// contains filtered or unexported fields
}
func (*StreamRequest) End ¶
func (r *StreamRequest) End() error
func (*StreamRequest) Write ¶
func (r *StreamRequest) Write(b []byte) error
type WebSocket ¶ added in v1.2.0
type WebSocket struct { Sock // A function to be called when the socket closes. See Socket.CloseHandler for details. CloseHandler func(s *WebSocket, code int) }
WebSocket is a type of gotalk.Sock used for web socket connections, managed by a WebSocketServer.
func (*WebSocket) Conn ¶ added in v1.2.0
func (s *WebSocket) Conn() *WebSocketConnection
Conn returns the underlying web socket connection.
Accessing the web socket connection inside a handler function: Handler functions can opt in to receive a pointer to a Sock but not a WebSocket. This makes handlers more portable, testable and the implementation becomes simpler. However, sometimes you might need to access the web socket connection anyhow:
gotalk.Handle("foo", func(s *gotalk.Sock, m FooMessage) error { ws := s.Conn().(*gotalk.WebSocketConnection) // do something with ws return nil })
type WebSocketConnection ¶ added in v1.3.1
WebSocketConnection is an alias for the websocket connection type, to spare the use from having to import golang.org/x/net/websocket
type WebSocketServer ¶
type WebSocketServer struct { // Handlers describe what this server is capable of responding to. // Initially set to gotalk.DefaultHandlers by NewWebSocketServer(). // // Handler can be assigned a new set of handlers at any time. // Whenever a new socket is connected, it references the current value of Handlers, therefore // changes to Handlers has an effect for newly connected sockets only. *Handlers // Limits control resource limits. // Initially set to gotalk.DefaultLimits by NewWebSocketServer(). *Limits // OnConnect is an optional handler to be invoked when a new socket is connected. // This handler is only called for sockets which passed the protocol handshake. // If you want to deny a connection, simply call s.Close() on the socket in this handler. // // Gotalk checks if Origin header is a valid URL by default but does nothing else in terms of // origin validation. You might want to verify s.Conn().Config().Origin in OnConnect. OnConnect func(s *WebSocket) // HeartbeatInterval is not used directly by WebSocketServer but assigned to every new socket // that is connected. The default initial value (0) means "no automatic heartbeats" (disabled.) // // Note that automatic heartbeats are usually not a good idea for web sockets for two reasons: // // a) You usually want to keep as few connections open as possible; letting them time out is // often desired (heartbeats prevent connection timeout.) // // b) Automatic timeout uses more resources // HeartbeatInterval time.Duration // OnHeartbeat is an optional callback for heartbeat confirmation messages. // Not used directly by WebSocketServer but assigned to every new socket that is connected. OnHeartbeat func(load int, t time.Time) // Underlying websocket server (will become a function in gotalk 2) Server *websocket.Server // DEPRECATED use OnConnect instead OnAccept SockHandler // contains filtered or unexported fields }
WebSocketServer conforms to http.HandlerFunc and is used to serve Gotalk over HTTP or HTTPS
func NewWebSocketServer ¶ added in v1.2.0
func NewWebSocketServer() *WebSocketServer
NewWebSocketServer creates a web socket server which is a http.Handler
func (*WebSocketServer) ServeHTTP ¶
func (s *WebSocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
Source Files
¶
Directories
¶
Path | Synopsis |
---|---|
examples
|
|
limits
Demonstrates request limiting
|
Demonstrates request limiting |
pipe
A simple example demonstrating the use of gotalk.Pipe()
|
A simple example demonstrating the use of gotalk.Pipe() |
read-timeout
Demonstrates read timeout (or "idle" timeout)
|
Demonstrates read timeout (or "idle" timeout) |
stream
Demonstrates using streaming requests and results Demonstrates
|
Demonstrates using streaming requests and results Demonstrates |
tcp
Demonstrates how to use gotalk over direct TCP connections
|
Demonstrates how to use gotalk over direct TCP connections |
tls
demonstrates how to use gotalk over encrypted connections (TLS)
|
demonstrates how to use gotalk over encrypted connections (TLS) |
websocket-chat
Multi-room chat app implemented in gotalk
|
Multi-room chat app implemented in gotalk |
websocket-minimal
A minimal gotalk web app
|
A minimal gotalk web app |