• This repository has been archived on 01/May/2020
  • Stars
    star
    216
  • Rank 183,179 (Top 4 %)
  • Language
    Go
  • License
    MIT License
  • Created over 6 years ago
  • Updated almost 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A transport independent asynchronous duplex messaging library for Go


WebWire

WebWire for Go
An asynchronous duplex messaging library

Travis CI: build status Coveralls: Test Coverage GoReportCard CodeBeat: Status CodeClimate: Maintainability
Licence: MIT GoDoc

OpenCollective


WebWire is a high-performance transport independent asynchronous duplex messaging library and an open source binary message protocol with builtin authentication and support for UTF8 and UTF16 encoding. The webwire-go library provides a server implementation for the Go programming language.

Table of Contents

Installation

Choose any stable release from the available release tags and copy the source code into your project's vendor directory: $YOURPROJECT/vendor/github.com/qbeon/webwire-go. All necessary transitive dependencies are already embedded into the webwire-go repository.

Dep

If you're using dep, just use dep ensure to add a specific version of webwire-go including all its transitive dependencies to your project: dep ensure -add github.com/qbeon/[email protected]. This will remove all embedded transitive dependencies and move them to your projects vendor directory.

Go Get

You can also use go get: go get github.com/qbeon/webwire-go but beware that this will fetch the latest commit of the master branch which is currently not yet considered a stable release branch. It's therefore recommended to use dep instead.

Contribution

Contribution of any kind is always welcome and appreciated, check out our Contribution Guidelines for more information!

Maintainers

Maintainer Role Specialization
Roman Sharkov Core Maintainer Dev (Go, JavaScript)
Daniil Trishkin CI Maintainer DevOps

WebWire Binary Protocol

WebWire is built for speed and portability implementing an open source binary protocol. Protocol Subset Diagram

The first byte defines the type of the message. Requests and replies contain an incremental 8-byte identifier that must be unique in the context of the senders' session. A 0 to 255 bytes long 7-bit ASCII encoded name is contained in the header of a signal or request message. A header-padding byte is applied in case of UTF16 payload encoding to properly align the payload sequence. Fraudulent messages are recognized by analyzing the message length, out-of-range memory access attacks are therefore prevented.

Examples

Features

Request-Reply

Clients can initiate multiple simultaneous requests and receive replies asynchronously. Requests are multiplexed through the connection similar to HTTP2 pipelining. The below examples are using the webwire Go client.

// Send a request to the server,
// this will block the goroutine until either a reply is received
// or the default timeout triggers (if there is one)
reply, err := client.Request(
	context.Background(), // No cancelation, default timeout
	nil,                  // No name
	wwr.Payload{
		Data: []byte("sudo rm -rf /"), // Binary request payload
	},
)
defer reply.Close() // Close the reply
if err != nil {
	// Oh oh, the request failed for some reason!
}
reply.PayloadUtf8() // Here we go!

Requests will respect cancelable contexts and deadlines

cancelableCtx, cancel := context.WithCancel(context.Background())
defer cancel()
timedCtx, cancelTimed := context.WithTimeout(cancelableCtx, 1*time.Second)
defer cancelTimed()

// Send a cancelable request to the server with a 1 second deadline
// will block the goroutine for 1 second at max
reply, err := client.Request(timedCtx, nil, wwr.Payload{
	Encoding: wwr.EncodingUtf8,
	Data:     []byte("hurry up!"),
})
defer reply.Close()

// Investigate errors manually...
switch err.(type) {
case wwr.ErrCanceled:
	// Request was prematurely canceled by the sender
case wwr.ErrDeadlineExceeded:
	// Request timed out, server didn't manage to reply
	// within the user-specified context deadline
case wwr.TimeoutErr:
	// Request timed out, server didn't manage to reply
	// within the specified default request timeout duration
case nil:
	// Replied successfully
}

// ... or check for a timeout error the easier way:
if err != nil {
	if wwr.IsErrTimeout(err) {
		// Timed out due to deadline excess or default timeout
	} else {
		// Unexpected error
	}
}

reply // Just in time!

Client-side Signals

Individual clients can send signals to the server. Signals are one-way messages guaranteed to arrive, though they're not guaranteed to be processed like requests are. In cases such as when the server is being shut down, incoming signals are ignored by the server and dropped while requests will acknowledge the failure. The below examples are using the webwire Go client.

// Send signal to server
err := client.Signal(
	[]byte("eventA"),
	wwr.Payload{
		Encoding: wwr.EncodingUtf8,
		Data:     []byte("something"),
	},
)

Server-side Signals

The server also can send signals to individual connected clients.

func OnRequest(
  _ context.Context,
  conn wwr.Connection,
  _ wwr.Message,
) (wwr.Payload, error) {
	// Send a signal to the client before replying to the request
	conn.Signal(
		nil, // No message name
		wwr.Payload{
			Encoding: wwr.EncodingUtf8,
			Data:     []byte("example")),
		},
	)

	// Reply nothing
	return wwr.Payload{}, nil
}

Namespaces

Different kinds of requests and signals can be differentiated using the builtin namespacing feature.

func OnRequest(
	_ context.Context,
	_ wwr.Connection,
	msg wwr.Message,
) (wwr.Payload, error) {
	switch msg.Name() {
	case "auth":
		// Authentication request
		return wwr.Payload{
			Encoding: wwr.EncodingUtf8,
      			Data:     []byte("this is an auth request"),
		}
	case "query":
		// Query request
		return wwr.Payload{
			Encoding: wwr.EncodingUtf8,
			Data:     []byte("this is a query request"),
		}
	}

	// Otherwise return nothing
	return wwr.Payload{}, nil
}
func OnSignal(
	_ context.Context,
	_ wwr.Connection,
	msg wwr.Message,
) {
	switch string(msg.Name()) {
	case "event A":
		// handle event A
	case "event B":
		// handle event B
	}
}

Sessions

Individual connections can get sessions assigned to identify them. The state of the session is automagically synchronized between the client and the server. WebWire doesn't enforce any kind of authentication technique though, it just provides a way to authenticate a connection. WebWire also doesn't enforce any kind of session storage, the user could implement a custom session manager implementing the WebWire SessionManager interface to use any kind of volatile or persistent session storage, be it a database or a simple in-memory map.

func OnRequest(
	_ context.Context,
	conn wwr.Connection,
	msg wwr.Message,
) (wwr.Payload, error) {
	// Verify credentials
	if string(msg.Payload()) != "secret:pass" {
		return wwr.Payload{}, wwr.ReqErr {
			Code:    "WRONG_CREDENTIALS",
			Message: "Incorrect username or password, try again",
		}
	}
	// Create session (will automatically synchronize to the client)
	err := conn.CreateSession(/*something that implements wwr.SessionInfo*/)
	if err != nil {
		return nil, fmt.Errorf("Couldn't create session for some reason")
	}

	// Complete request, reply nothing
	return wwr.Payload{}, nil
}

WebWire provides a basic file-based session manager implementation out of the box used by default when no custom session manager is defined. The default session manager creates a file with a .wwrsess extension for each opened session in the configured directory (which, by default, is the directory of the executable). During the restoration of a session the file is looked up by name using the session key, read and unmarshalled recreating the session object.

Concurrency

Messages are parsed and handled concurrently in a separate goroutine by default. The total number of concurrently executed handlers can be independently throttled down for each individual connection, which is unlimited by default.

All exported interfaces provided by both the server and the client are thread safe and can thus safely be used concurrently from within multiple goroutines, the library automatically synchronizes all concurrent operations.

Hooks

Various hooks provide the ability to asynchronously react to different kinds of events and control the behavior of both the client and the server.

Server-side Hooks

  • OnClientConnected
  • OnClientDisconnected
  • OnSignal
  • OnRequest

SessionManager Hooks

  • OnSessionCreated
  • OnSessionLookup
  • OnSessionClosed

SessionKeyGenerator Hooks

  • Generate

Graceful Shutdown

The server will finish processing all ongoing signals and requests before closing when asked to shut down.

// Will block until all handlers have finished
server.Shutdown()

While the server is shutting down new connections are refused with 503 Service Unavailable and incoming new requests from connected clients will be rejected with a special error: RegErrSrvShutdown. Any incoming signals from connected clients will be ignored during the shutdown.

Server-side client connections also support graceful shutdown, a connection will be closed when all work on it is done, while incoming requests and signals are handled similarly to shutting down the server.

// Will block until all work on this connection is done
connection.Close()

Multi-Language Support

The following libraries provide seamless support for various development environments providing fully compliant protocol implementations supporting the latest features.

Security

A webwire server can be hosted by a TLS protected server transport implementation to prevent man-in-the-middle attacks as well as to verify the identity of the server during connection establishment. Setting up a TLS protected websocket server for example is easy:

// Setup a secure webwire server instance
server, err := wwr.NewServer(
	serverImplementation,
	wwr.ServerOptions{
		Host: "localhost:443",
	},
	// Use a TLS protected transport layer
	&wwrgorilla.Transport{
		TLS: &wwrgorilla.TLS{
			// Provide key and certificate
			CertFilePath:       "path/to/certificate.crt",
			PrivateKeyFilePath: "path/to/private.key",
			// Specify TLS configs
			Config: &tls.Config{
				MinVersion:               tls.VersionTLS12,
				CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
				PreferServerCipherSuites: true,
				CipherSuites: []uint16{
					tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
					tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				},
			},
		},
	},
)
if err != nil {
	panic(fmt.Errorf("failed setting up wwr server: %s", err))
}
// Launch
if err := server.Run(); err != nil {
	panic(fmt.Errorf("wwr server failed: %s", err))
}

The above code example is using the webwire-go-gorilla transport implementation.


Β© 2018 Roman Sharkov [email protected]

More Repositories

1

Go-1-2-Proposal---Immutability

A a Go 1/2 language feature proposal to immutability
171
star
2

jscan

High performance JSON iterator & validator for Go
Go
87
star
3

dgraph_graphql_go

A GraphQL + Dgraph + Go + HTTP based backend service demo.
Go
50
star
4

QuickStreams

An asynchronous programming library for the QML programming language (Proof of Concept)
C++
46
star
5

llparser

A universal LL top-down parser written in Go
Go
45
star
6

sched

A job scheduler for Go with the ability to fast-forward time.
Go
28
star
7

templier

A Go Templ web frontend development environment that automatically rebuilds the server and reloads the tab.
Go
21
star
8

yamagiconf

The magic YAML configuration framework for Go
Go
15
star
9

goesgen

Go code generator for event-sourced systems
Go
12
star
10

webwire-js

A client implementation for the webwire-go asynchronous messaging library.
JavaScript
12
star
11

watchserve

An HTTP file server that's watching the served file for updates and automatically reloads the page
Go
9
star
12

taskhub

A GraphQL demo of a JIRA-like task tracking system demonstrating the use of persisted queries.
Go
7
star
13

eventlog

A persistent append-only event database
Go
7
star
14

go-battle-simulator

A concurrent battle simulator written in Go (#codingforfun)
Go
6
star
15

tmdwg-go

A timed analogue of sync.WaitGroup for Golang
Go
5
star
16

qbeon.com

The website that is hosted on qbeon.com serving as an introduction of Qbeon and the Qube network attache storage device with the intention to attract investment.
HTML
5
star
17

utf8

A fork of the encoding/utf8 Go standard library package with experimental performance optimizations.
Go
4
star
18

gapi

Graph API
Go
3
star
19

valfile

A CLI tool to statically check env vars, YAML, TOML, JSON, HCL, Jsonnet and .env files against a Go type.
Go
3
star
20

jscan-experimental-decoder

This is an experimental JSON decoder for Go based on jscan
Go
3
star
21

eventlog-example

This example showcases an event-sourced CQRS system based on github.com/romshark/eventlog
Go
3
star
22

demo-islands

An HTMX, Go, Templ, Templier, Svelte & WebComponents demo demonstrating the islands architecture.
CSS
2
star
23

rebuzzer

Rebuzzer is a fullscreen button in the browser which executes a command on the server by clicking on it
JavaScript
2
star
24

pres-jscan

Presentation of jscan at Go meetup in Zurich (October 3th 2023)
Go
2
star
25

jsonvalidate-go

High-performance JSON validation in Go
Go
1
star
26

htmx-demo-todoapp

An attempt at structuring an HTMX-driven Hypermedia application.
Go
1
star
27

shallyapassbot

Telegram anti-spam bot
Go
1
star
28

messenger-sim

A simulation of an event-sourced messenger system in Go
Go
1
star
29

contacts.qbeon.com

VCards for public appearance
HTML
1
star
30

jscan-benchmark

Benchmarks comparing github.com/romshark/jscan to other modules
Go
1
star
31

webwire-example-postboard

An advanced full-featured example of a webwire-go powered real-time application
Go
1
star
32

service-modelling-language

An experimental specification of an abstract service modelling language similar to GraphQL
Standard ML
1
star
33

strings

An attempt at improving the performance of Go standard library functions
Go
1
star