ReST over websocket, so you can serve swagger apis over websocket
A websocket server can be created using the NewWebSocketServer
function
func NewWebSocketServer(opts SocketServerOpts) *WebsocketServer
The above function creates a websocket server based on the options structure passed to the constructor function
type SocketServerOpts struct {
Addr string
KeepAlive bool
PingHdlr func(string) error
PongHdlr func(string) error
AppData []byte
Log Logger
}
Addr
the ip address and port the websocket server is listening to
KeepAlive
activates the heartbeat mechanism at the server side
PingHdlr
A custom function to websocket ping messages (default is to send back a pong message)
PongHdlr
a custom function to a websocket pong message (default is nothing)
AppData
optional application-level data passed with the heartbeat messages
Log
custom logger
After creating a websocket server, the user will probably need to listen the events happening on the websocket server.
To listen to the event stream of the websocket server
func (ss *WebsocketServer) EventStream() (<-chan ConnectionEvent, error)
The EventStream
method returs a channel that carries ConnectionEvent
instances
type ConnectionEvent struct {
EventType EvtType
ConnectionId string
}
There are three types of connection events:
- ConnectionReceived
- ConnectionClosed
- ConnectionFailure
The ConnectionEvent
also has the ConnectionId
field which is the ID of the connection associated with the event.
A websocket client can be created using the NewWebSocketServer
function
func NewWebSocketClient(opts SocketClientOpts) *WebsocketClient
This creates a websocket client based on the SocketClientOpts
type SocketClientOpts struct {
URL *url.URL
KeepAlive bool
PingHdlr func(string) error
PongHdlr func(string) error
AppData []byte
Logger Logger
}
The URL
is the url of the websocket server
A KeepAlive
set to true will activate the heartbeat mechanism from the client side and on connection failure, will attempt to reconnect to the websocket server with an exponential backoff.
This is a three-way handshake in the beginning of the connection. The purpose of this handshake is:
- agree on a unique id
ConnectionId
for the connection - allowing the websocket client to store some connection metadata at the websocket server side
In particular, this is the sequence of events:
- The websocket Client sends an initial message to the websocket server with the connection metadata (if any)
- The websocket server stores the metadata for the connection and responds with a unique connection id
- The client confirms the reception of the connection id
The Heartbeat is the mechanism by which connection failures can be detected. The Heartbeat simply utilizes the underlying websocket ping/pong messages to send periodic heartbeat messages to check the health of the connection. Heartbeat messages can be activated at the websocket client and/or server sides by setting the KeepAlive
field.
Websocket connections are bidirectional. This means the API server can be attached to either the websocket client or the websocket server.
If the api server is attached to the websocket client, the websocket server will act as the api client (issue requests to the websocket client and receive ap response)
After a connection has been established between the websocket server and the websocket client, the api server can be attached to any by hooking up the swagger api handler.
wsServer := NewWebSocketServer(opts)
ch, err := wsServer.EventStream()
// the following loop is safe to run in a separate go-routine
for {
select {
case event := <-ch:
if event.EventType == ConnectionReceived {
conn := wsServer.connectionFromConnID(event.ConnectionId)
conn.Serve(context.Background(), SwaggerAPIHandler)
}
case <-done:
return
}
}
// create a websocket client
wsClient := swaggersocket.NewWebSocketClient(clientOpts).WithMetaData("dummy connection metadata")
// connect to the websocket server
if err := wsClient.Connect(); err != nil {
panic(err)
}
///////////////////
// some code goes here
//////////////////
// serve the swagger api on the websocket client
wsClient.Connection().Serve(context.Background(), SwaggerAPIHandler)
SocketConnection
implements the RoundTripper interface. This means that the swagger client requires no modifications except for changing the RoundTripper of the runtime
For example, assuming that we are hooking the swagger client to the websocket client:
// create a websocket client
wsClient := swaggersocket.NewWebSocketClient(clientOpts).WithMetaData("dummy connection metadata")
// connect to the websocket server
if err := wsClient.Connect(); err != nil {
panic(err)
}
///////////////////
// some code goes here
//////////////////
// runtime is the go-openapi runtime
rt := runtime.New("localhost", "/", []string{"https"})
rt.Transport = wsClient.Connection()
// client is the swagger http client
cli := client.New(rt, nil)