# Examples The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage. ## Windows note To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function. ```cpp #include int main() { ix::initNetSystem(); ... ix::uninitNetSystem(); return 0; } ``` ## WebSocket client API ```cpp #include ... // Our websocket object ix::WebSocket webSocket; std::string url("ws://localhost:8080/"); webSocket.setUrl(url); // Optional heart beat, sent every 45 seconds when there is not any traffic // to make sure that load balancers do not kill an idle connection. webSocket.setPingInterval(45); // Per message deflate connection is enabled by default. You can tweak its parameters or disable it webSocket.disablePerMessageDeflate(); // Setup a callback to be fired when a message or an event (open, close, error) is received webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { if (msg->type == ix::WebSocketMessageType::Message) { std::cout << msg->str << std::endl; } } ); // Now that our callback is setup, we can start our background thread and receive messages webSocket.start(); // Send a message to the server (default to TEXT mode) webSocket.send("hello world"); // The message can be sent in BINARY mode (useful if you send MsgPack data for example) webSocket.sendBinary("some serialized binary data"); // ... finally ... // Stop the connection webSocket.stop() ``` ### Sending messages `WebSocketSendInfo result = websocket.send("foo")` will send a message. If the connection was closed, sending will fail, and the success field of the result object will be set to false. There could also be a compression error in which case the compressError field will be set to true. The payloadSize field and wireSize fields will tell you respectively how much bytes the message weight, and how many bytes were sent on the wire (potentially compressed + counting the message header (a few bytes). There is an optional progress callback that can be passed in as the second argument. If a message is large it will be fragmented into chunks which will be sent independantly. Everytime the we can write a fragment into the OS network cache, the callback will be invoked. If a user wants to cancel a slow send, false should be returned from within the callback. Here is an example code snippet copied from the ws send sub-command. Each fragment weights 32K, so the total integer is the wireSize divided by 32K. As an example if you are sending 32M of data, uncompressed, total will be 1000. current will be set to 0 for the first fragment, then 1, 2 etc... ``` auto result = _webSocket.sendBinary(serializedMsg, [this, throttle](int current, int total) -> bool { spdlog::info("ws_send: Step {} out of {}", current + 1, total); if (throttle) { std::chrono::duration duration(10); std::this_thread::sleep_for(duration); } return _connected; }); ``` ### ReadyState `getReadyState()` returns the state of the connection. There are 4 possible states. 1. ReadyState::Connecting - The connection is not yet open. 2. ReadyState::Open - The connection is open and ready to communicate. 3. ReadyState::Closing - The connection is in the process of closing. 4. ReadyState::Closed - The connection is closed or could not be opened. ### Open and Close notifications The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`. ```cpp webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { if (msg->type == ix::WebSocketMessageType::Open) { std::cout << "send greetings" << std::endl; // Headers can be inspected (pairs of string/string) std::cout << "Handshake Headers:" << std::endl; for (auto it : msg->headers) { std::cout << it.first << ": " << it.second << std::endl; } } else if (msg->type == ix::WebSocketMessageType::Close) { std::cout << "disconnected" << std::endl; // The server can send an explicit code and reason for closing. // This data can be accessed through the closeInfo object. std::cout << msg->closeInfo.code << std::endl; std::cout << msg->closeInfo.reason << std::endl; } } ); ``` ### Error notification A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error. ```cpp webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { if (msg->type == ix::WebSocketMessageType::Error) { std::stringstream ss; ss << "Error: " << msg->errorInfo.reason << std::endl; ss << "#retries: " << msg->eventInfo.retries << std::endl; ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl; ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl; std::cout << ss.str() << std::endl; } } ); ``` ### start, stop 1. `websocket.start()` connect to the remote server and starts the message receiving background thread. 2. `websocket.stop()` disconnect from the remote server and closes the background thread. ### Configuring the remote url The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url. ```cpp std::string url("wss://example.com"); websocket.configure(url); ``` ### Ping/Pong support Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec. ```cpp webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { if (msg->type == ix::WebSocketMessageType::Ping || msg->type == ix::WebSocketMessageType::Pong) { std::cout << "pong data: " << msg->str << std::endl; } } ); ``` A ping message can be sent to the server, with an optional data string. ```cpp websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long"); ``` ### Heartbeat. You can configure an optional heart beat / keep-alive, sent every 45 seconds when there is no any traffic to make sure that load balancers do not kill an idle connection. ```cpp webSocket.setPingInterval(45); ``` ### Supply extra HTTP headers. You can set extra HTTP headers to be sent during the WebSocket handshake. ```cpp WebSocketHttpHeaders headers; headers["foo"] = "bar"; webSocket.setExtraHeaders(headers); ``` ### Subprotocols You can specify subprotocols to be set during the WebSocket handshake. For more info you can refer to [this doc](https://hpbn.co/websocket/#subprotocol-negotiation). ```cpp webSocket.addSubprotocol("appProtocol-v1"); webSocket.addSubprotocol("appProtocol-v2"); ``` The protocol that the server did accept is available in the open info `protocol` field. ```cpp std::cout << "protocol: " << msg->openInfo.protocol << std::endl; ``` ### Automatic reconnection Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off. ```cpp webSocket.enableAutomaticReconnection(); // turn on webSocket.disableAutomaticReconnection(); // turn off bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state ``` The technique to calculate wait time is called [exponential backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`) ``` > Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently #retries: 1 Wait time(ms): 100 #retries: 2 Wait time(ms): 200 #retries: 3 Wait time(ms): 400 #retries: 4 Wait time(ms): 800 #retries: 5 Wait time(ms): 1600 #retries: 6 Wait time(ms): 3200 #retries: 7 Wait time(ms): 6400 #retries: 8 Wait time(ms): 10000 ``` The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried. ```cpp webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries(); ``` ## WebSocket server API ### Legacy api This api was actually changed to take a weak_ptr as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr which was creating cycles and then memory leaks problems. ```cpp #include ... // Run a server on localhost at a given port. // Bound host name, max connections and listen backlog can also be passed in as parameters. ix::WebSocketServer server(port); server.setOnConnectionCallback( [&server](std::weak_ptr webSocket, std::shared_ptr connectionState) { std::cout << "Remote ip: " << connectionState->remoteIp << std::endl; auto ws = webSocket.lock(); if (ws) { ws->setOnMessageCallback( [webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg) { if (msg->type == ix::WebSocketMessageType::Open) { std::cout << "New connection" << std::endl; // A connection state object is available, and has a default id // You can subclass ConnectionState and pass an alternate factory // to override it. It is useful if you want to store custom // attributes per connection (authenticated bool flag, attributes, etc...) std::cout << "id: " << connectionState->getId() << std::endl; // The uri the client did connect to. std::cout << "Uri: " << msg->openInfo.uri << std::endl; std::cout << "Headers:" << std::endl; for (auto it : msg->openInfo.headers) { std::cout << it.first << ": " << it.second << std::endl; } } else if (msg->type == ix::WebSocketMessageType::Message) { // For an echo server, we just send back to the client whatever was received by the server // All connected clients are available in an std::set. See the broadcast cpp example. // Second parameter tells whether we are sending the message in binary or text mode. // Here we send it in the same mode as it was received. auto ws = webSocket.lock(); if (ws) { ws->send(msg->str, msg->binary); } } } } ); } ); auto res = server.listen(); if (!res.first) { // Error handling return 1; } // Run the server in the background. Server can be stoped by calling server.stop() server.start(); // Block until server.stop() is called. server.wait(); ``` ### New api The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr down to the callbacks. The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object. ```cpp #include ... // Run a server on localhost at a given port. // Bound host name, max connections and listen backlog can also be passed in as parameters. ix::WebSocketServer server(port); server.setOnClientMessageCallback(std::shared_ptr connectionState, WebSocket& webSocket, const WebSocketMessagePtr& msg) { // The ConnectionState object contains information about the connection, // at this point only the client ip address and the port. std::cout << "Remote ip: " << connectionState->getRemoteIp(); if (msg->type == ix::WebSocketMessageType::Open) { std::cout << "New connection" << std::endl; // A connection state object is available, and has a default id // You can subclass ConnectionState and pass an alternate factory // to override it. It is useful if you want to store custom // attributes per connection (authenticated bool flag, attributes, etc...) std::cout << "id: " << connectionState->getId() << std::endl; // The uri the client did connect to. std::cout << "Uri: " << msg->openInfo.uri << std::endl; std::cout << "Headers:" << std::endl; for (auto it : msg->openInfo.headers) { std::cout << it.first << ": " << it.second << std::endl; } } else if (msg->type == ix::WebSocketMessageType::Message) { // For an echo server, we just send back to the client whatever was received by the server // All connected clients are available in an std::set. See the broadcast cpp example. // Second parameter tells whether we are sending the message in binary or text mode. // Here we send it in the same mode as it was received. webSocket.send(msg->str, msg->binary); } ); auto res = server.listen(); if (!res.first) { // Error handling return 1; } // Run the server in the background. Server can be stoped by calling server.stop() server.start(); // Block until server.stop() is called. server.wait(); ``` ## HTTP client API ```cpp #include ... // // Preparation // HttpClient httpClient; HttpRequestArgsPtr args = httpClient.createRequest(); // Custom headers can be set WebSocketHttpHeaders headers; headers["Foo"] = "bar"; args->extraHeaders = headers; // Timeout options args->connectTimeout = connectTimeout; args->transferTimeout = transferTimeout; // Redirect options args->followRedirects = followRedirects; args->maxRedirects = maxRedirects; // Misc args->compress = compress; // Enable gzip compression args->verbose = verbose; args->logger = [](const std::string& msg) { std::cout << msg; }; // // Synchronous Request // HttpResponsePtr out; std::string url = "https://www.google.com"; // HEAD request out = httpClient.head(url, args); // GET request out = httpClient.get(url, args); // POST request with parameters HttpParameters httpParameters; httpParameters["foo"] = "bar"; out = httpClient.post(url, httpParameters, args); // POST request with a body out = httpClient.post(url, std::string("foo=bar"), args); // // Result // auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc... auto errorCode = response->errorCode; // 200, 404, etc... auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string) auto body = response->body; // All the bytes from the response as an std::string auto errorMsg = response->errorMsg; // Descriptive error message in case of failure auto uploadSize = response->uploadSize; // Byte count of uploaded data auto downloadSize = response->downloadSize; // Byte count of downloaded data // // Asynchronous Request // bool async = true; HttpClient httpClient(async); auto args = httpClient.createRequest(url, HttpClient::kGet); // Push the request to a queue, bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response) { // This callback execute in a background thread. Make sure you uses appropriate protection such as mutex auto statusCode = response->statusCode; // acess results } ); // ok will be false if your httpClient is not async ``` See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart. ## HTTP server API ```cpp #include ix::HttpServer server(port, hostname); auto res = server.listen(); if (!res.first) { std::cerr << res.second << std::endl; return 1; } server.start(); server.wait(); ``` If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example. ```cpp setOnConnectionCallback( [this](HttpRequestPtr request, std::shared_ptr connectionState) -> HttpResponsePtr { // Build a string for the response std::stringstream ss; ss << connectionState->getRemoteIp(); << " " << request->method << " " << request->uri; std::string content = ss.str(); return std::make_shared(200, "OK", HttpErrorCode::Ok, WebSocketHttpHeaders(), content); } ``` ## TLS support and configuration To leverage TLS features, the library must be compiled with the option `USE_TLS=1`. If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x. Then, secure sockets are automatically used when connecting to a `wss://*` url. Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the `setTLSOptions` on `ix::WebSocket` (or `ix::WebSocketServer` or `ix::HttpServer`) ```cpp webSocket.setTLSOptions({ .certFile = "path/to/cert/file.pem", .keyFile = "path/to/key/file.pem", .caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format .tls = true // required in server mode }); ``` Specifying `certFile` and `keyFile` configures the certificate that will be used to communicate with TLS peers. On a client, this is only necessary for connecting to servers that require a client certificate. On a server, this is necessary for TLS support. Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates. - The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server. - The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity. - If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory. For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment. For a server, specifying `caFile` implies that: 1. You require clients to present a certificate 1. It must be signed by one of the trusted roots in the file