# Example: WebSocket Echo Server
A simple WebSocket echo server in three easy steps:
1. [Define some WebSocket event callbacks](#1-websocket-protocol-callbacks)
2. [Add a default HTTP handler](#2-add-an-http-callback-for-non-upgrade-requests)
3. [Create and start the server](#3-create-and-start-the-server)
*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`).
```C
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`.
```C
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` or `FIN` 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!_
```C
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.
```C
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:
1. Get a handle to the default libev event loop using `ev_default_loop`.
2. Invoke `ymo_proto_ws_create` to create a new instance of the libyimmo
WebSockets protocol, registering our connect, receive, and close
callbacks.
3. 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 the `ymo_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
4. 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 handler
+ protocol).
5. Run the thing!
```C
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;
}
```