Skip to content

elakito/swagsock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Websocket binding for REST

This libary can be used to add the Websocket protocol transparently to the exisitng REST services. As a result, these services can be invoked as usual using HTTP or using Websocket.

Default Protocol Binding

This default binding is originated from SwaggerSocket[1] and based on the protocol extension (org.atmosphere.interceptor.SimpleRestInterceptor) included in Atmosphere 2.4.4[2] to enable arbitrary REST services to be invoked using Websocket.

This binding has the following characteristics.

  • Each request contains the method, path, and optional type and data.

  • Each request may be sent with a unique-ID for the client to correlate responses to its requests.

  • The content-type header can be optionally supplied if the content is present.

  • The accept header can be optionally supplied if the response is expected.

  • Arbitrary Http headers are supported, but they may be omitted if thet are not part of the application semantics.

  • The content entity can be optionally supplied following the envelope.

  • A large message can be transported as a series of messages that can be reassembled at the receiving end.

  • The server can periodically send a heartbeat message to the connected clients to keep the connections alive.

The following message format is used for this binding. Each message is represented as a text or binary Websocket message. It consists of the headers part encoded in json and the optional content part which follows the headers part.

Format: General Syntax

{"id": "identifier", "code": status_code, "method": "method", "path": "path", "type": "type_value", "accept": "accept_value", "headers": headers_map, "continue": continue} content

where

  • identifier represents an identifier for the request message which is used in any response message to refer to the original request message,

  • status_code represents the status code of the response message.

  • method represents the request method,

  • path represents the request path,

  • type and accept represent the optional content-type and accept header values.

  • headers_map represents the optional headers other than content-type and accept headers.

  • content represents the optional content entity.

  • continue represents the optional boolean value which indicates the message continues, in other words, followed by another message.

Message Examples
Example 1. A GET request to path /foo

{"id": "123", "method": "GET", "path": "/foo"}

Example 2. A POST request to path /foo with text content "Hello World!"

{"id": "124", "method": "POST", "path": "/foo", "type": "text/plain"}Hello World!

Example 3. A POST request to path /foo with text content "Buenos Dias!" and header "X-Language" set to "es"

{"id": "125", "method": "POST", "path": "/foo", "type": "text/plain", "headers": {"X-Language": "es"}}Buenos Dias

Example 4. A response with content "Hello World!"

{"id": "123", "code": 200}
Hello World!

Example 5. A response with large content "From a …​…​ to z" split in two responses

{"id": "124", "code": 200, "continue": true}From a …​

{"id": "124", "code": 200}…​ to z

Protocol Establishment

To establish a connection, the client first sends an HTTP Websocket upgrade request to the service path. This request may contain the tracking ID query parameter. The name of this parameter can be either x-tracking-id or X-Atmosphere-tracking-id. The value set to this parameter is used to identify the client instance. If this is not set, the server will create one to track the client.

After the connection is established, the client sends the handshake request message to the server and the server responds with either the handshake response or error message. If the server responds with the error message, the connection will be closed by the server.

Example 6. Handshake request

{"version": "2.0"}

Example 7. Handshake response

{"version": "2.0", "trackingID": "b0cbb3b4-aaee-a63a-49ae-0d5a31af9c93"}

Example 8. Handshake error

{"version": "2.0", "error": "version_mismatch"}

After a successful handshake, the client can send arbitrary request messages described above to perform a series of operations.

The server will send the ping message to all the clients periodically while they are connected.

Integration

This websocket binding can be integrated to the server side code that is generated by go-swagger [3].

Server-Side

To enable this websocket binding, add the protocol handler in restapi/configure_…​.go or use the custom template templates/server to generate the server side code to have the the protocol handler automatically added in the generated file.

Adding the protocol handler at the server-side
import (
	...
	"github.com/elakito/swagsock/swagsock"
)

func setupGlobalMiddleware(handler http.Handler) http.Handler {
	return globalMiddleware(handler)
}

// The globalMiddleware uses the swaggersocket handler to handle websocket requests
func globalMiddleware(handler http.Handler) http.Handler {

    // instantiate the protocol handler
    conf := swagsock.NewConfig()
    conf.Heartbeat = 5
    conf.Log = log.New(os.Stdout, "[swagsocket] ", log.LstdFlags)
    responseMediator = conf.ResponseMediator

    protocolHandler := swagsock.CreateProtocolHandler(conf)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// use the protocol handler to handle websocket requests
		if swagsock.IsWebsocketUpgradeRequested(r) {
			protocolHandler.Serve(handler, w, r)
			return
		}

		// handle normal http requests
		handler.ServeHTTP(w, r)
	})
}

Client-Side

The code to enable the client side processing in the generated client code is available, however, this is still in work in progress.

  • The client code is using the custom template templates/client that adds the generatio of asynchronous API that can utilize Swaggersocket.

  • The operations can be invoked in two ways: the same way using Submit which synchronously waits for the response or another way using a new SubmitAsync which asynchronously waits for the response. In this second way, the websocket’s asynchronous behavior is utilized to deliver decoupled responses to the client.

Generated Echo and EchoAsync operation methods
func (a *Client) Echo(params *EchoParams) (*EchoOK, error) {
  ...
}

func (a *Client) EchoAsync(params *EchoParams, cb func(string, *EchoOK, error), sam swagsock.SubmitAsyncOption) (string, error) {
  ...
}

The first method corresponds to the standard method generated by the default template.

The second method corresponds to the method generated by the custom template. This asynchronous variant of the method takes two additional arguments and returns the request identifier and optional error. Those two arguments are the callback function and the submit option parameter.

First, the callback function will be invoked when the response is received and the first argument of this function is the request identifier. The rest of the arguments correspond to the successful response types and the error.

Second, the submit option determines how this callback is invoked and there are three possibilities. SubmitAsyncOptionNone denotes no special handling and the callback will be invoked once when the response is received. SubmitAsyncOptionSubscribe denotes that the response to be held in subscription and the callback will be invoked not only with the response to this invocation but also with additional responses pushed to this subscription. Finally, SubmitAsyncOptionUnsubscribe(string) denotes the subscription identified by the specified request identifier to be ended.

The asynchornous variant of the method uses SubmitAsync in contrast to Submit which is used by the synchronous variant.

ClientTransport
type ClientTransport interface {
	//Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error)
	Submit(*runtime.ClientOperation) (interface{}, error)
	//SubmitAsync(string, RequestWriter, ResponseReader, AuthInfoWriter, func(string, interface{})) (string, error)
	SubmitAsync(*runtime.ClientOperation, func(string, interface{}), SubmitAsyncOption) (string, error)
	//Close closes the socket
	Close()
}

Samples

  • examples/greeter

    • This server is generated from examples/greeter/swagger.yaml using go-swagger and it is enabled for websocket. This service has normal request and response operations for greeting service and in addition, the subscribe and unsubscribe operations to subsribe to the greet events and receive the greeting events asynchronously.

  • examples/greeter-client

    • This client is generated from examples/greeter/swagger.yaml using go-swagger with the custom template and it is enabled for websocket.

  • examples/node-greeter-clients/node-client

    • A node.js client using plain websocket library to call the greeter service.

  • examples/node-greeter-clients/atmosphere-node-client

    • A node.js client using atmosphere.js to call the greeter service.

  • examples/node-greeter-clients/swagsock-client

    • A node.js client using swagsock.js[4] to call the greeter service.

  • examples/chat

    • This server is generated from examples/chat/swagger.yaml using go-swagger and it is enabled for websocket. This is a chat service. This example includes a browser client that uses swagsock.js to connect to the chat service.

  • examples/chat-multirooms

    • This server is generated from examples/chat-multirooms/swagger.yaml using go-swagger and it is enabled for websocket. This is a chat service supporting multiple chat rooms. This example includes a browser client that uses swagsock.js to connect to the chat service.

The generated code included in the above examples are generated by go-swagger version 0.20.1, but an earlier version such as 0.17.0 should also work.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages