Example: WebSocket Echo Server¶
A simple WebSocket echo server in three easy steps:
Fin!
Let’s have a peek:
1. WebSocket Protocol Callbacks¶
To start things off, we’ll define our callback functions for the following WebSockets events:
connect (analogous to
onopen
)receive (analogous to
onmessage
)close (analogous to
onclose
)
Connect Callback¶
The connection callback takes a single argument, a ymo_ws_session_t
(all
WebSockets event callbacks provide the session as the first argument — see
API docs for more info). On success, the callback should return YMO_OKAY
.
Otherwise, the connection is closed, and the return value is used to set
errno
.
NOTE: Yimmo sends all payload data (i.e. message bodies) using Apache-esque “bucket brigades” (
ymo_bucket_t
).
static ymo_status_t test_ws_connect_cb(ymo_ws_session_t* session)
{
ymo_log_info("New WebSocket session: %p!", (void*)session);
/* Encapsulate a string literal in a bucket: */
ymo_bucket_t* my_msg = YMO_BUCKET_FROM_REF("Hello!", 6);
/* Send a text-type message from with the FIN bit set: */
return ymo_ws_session_send(
session, YMO_WS_FLAG_FIN | YMO_WS_OP_TEXT, my_msg);
}
Receive Callback¶
This is invoked whenever a message is received from the client. In addition
to the session object, we get flags
(the first 8 bits of the RFC6455
message frame), data
(the raw data as const char*
), and len
the total
payload length. On success, the callback should return YMO_OKAY
. Otherwise,
the connection is closed, with the return value being used to set errno
.
static ymo_status_t test_ws_recv_cb(
ymo_ws_session_t* session,
void* user_data,
uint8_t flags,
const char* data,
size_t len)
{
if( data && len ) {
if( flags & YMO_WS_OP_TEXT ) {
ymo_log_info("Recv from %p: \"%.*s\"",
(void*)session, (int)len, data);
}
} else {
ymo_log_info(
"Got a message with a zero length payload "
"(allowed by RFC-6455!)");
}
return ymo_ws_session_send(
session, flags, YMO_BUCKET_FROM_CPY(data, len));
}
Heads up! — Message Framing:
Since we’re just echoing the payload back, we don’t bother checking or setting the
OP_TYPE
orFIN
bits — we just copy them back, as-is, to the client the way they were recieved.If you wanted to do something with the complete message — e.g. parse some JSON payload with a non-streaming parser, etc — you’d want to check the FIN bit and buffer the incoming frames until you got to the end, or use a buffered connection adapter.
Close Callback¶
The close callback is invoked when the WebSocket session has been terminated (by either the client or server).
WARNING: The
ymo_ws_session_t*
passed in as a parameter here cannot be meaningfully dereferenced after this function returns!
static void test_ws_close_cb(ymo_ws_session_t* session, void* user_data)
{
ymo_log_info("Session %p closed!", (void*)session);
return;
}
2. Add an HTTP callback for non-upgrade requests¶
For kicks, we add a simple HTTP callback to serve a plain-text 200 response
to any HTTP 1.0, 1.1 requests which do not attempt a connection upgrade or
HTTP2 upgrade requests (which are defaulted back to HTTP 1.1 via
ymo_http2_no_upgrade_handler
in main
):
For a deeper dive into what’s going on here, see the HTTP module docs.
static ymo_status_t test_http_callback(
ymo_http_session_t* session,
ymo_http_request_t* request,
ymo_http_response_t* response,
void* user_data)
{
ymo_http_response_insert_header(response, "content-type", "text/plain");
ymo_bucket_t* content = YMO_BUCKET_FROM_REF("OK", 2);
ymo_http_response_set_status_str(response, "200 OK");
ymo_http_response_body_append(response, content);
ymo_http_response_finish(response);
return YMO_OKAY;
}
3. Create and Start the Server!¶
What we do here is:
Get a handle to the default libev event loop using
ev_default_loop
.Invoke
ymo_proto_ws_create
to create a new instance of the libyimmo WebSockets protocol, registering our connect, receive, and close callbacks.Add some HTTP upgrade handlers:
ymo_ws_http_upgrade_handler
is the default HTTP->RFC6455 upgrade handler (you can roll your own, if you like; see theymo_http
module API docs for more info).ymo_http2_no_upgrade_handler
is a “fallback” handler which reverts HTTP2 upgrade attempts to HTTP/1.1
Instantiate a libyimmo HTTP server using
ymo_http_simple_init
, registering our test HTTP callback as the principle handler and our “upgrade handler” chain (which contains the WebSockets upgrade handlerprotocol).
Run the thing!
int main(int argc, char** argv)
{
ymo_log_init();
/* Say hello */
issue_startup_msg();
struct ev_loop* loop = ev_default_loop(0);
/* Create a websocket protocol object: */
ymo_proto_t* ws_proto = ymo_proto_ws_create(
YMO_WS_SERVER_DEFAULT,
&test_ws_connect_cb,
&test_ws_recv_cb,
&test_ws_close_cb);
/* Set up an array of HTTP "Upgrade" upgrade_handlers: */
ymo_http_upgrade_handler_t* upgrade_handlers[] = {
ymo_ws_http_upgrade_handler(ws_proto), /* WS upgrade handler */
ymo_http2_no_upgrade_handler(), /* HTTP2 --> HTTP/1.1. */
NULL, /* sentinel */
};
/* Create the HTTP server: */
ymo_server_t* http_srv = ymo_http_simple_init(
loop, HTTP_PORT, &test_http_callback, upgrade_handlers, NULL);
/* Run it! */
ymo_server_start(http_srv, loop);
if( http_srv ) {
ev_run(ev_default_loop(0), 0);
ymo_log(YMO_LOG_INFO, "Loop exited. Shutting down!");
ymo_server_free(http_srv);
} else {
ymo_log(YMO_LOG_ERROR, "Server failed to start!");
}
return 0;
}