WebSocket API#
Oasis supports WebSocket servers and clients.
A WebSocket server can also be used by attaching it to a specific URL of an HTTP server using the createHttpWsService API.
Header File#
OasisNet.h
WsEventDelegate Interface#
In the Oasis WebSocket service, users process WebSocket events through an object derived from the WsEventDelegate interface.
class WsEventDelegate : public std::enable_shared_from_this<WsEventDelegate>
{
public:
WsEventDelegate();
virtual ~WsEventDelegate();
public:
virtual void onOpen(const WsEventRef &evt);
virtual void onMessage(const WsEventRef &evt);
virtual void onError(const WsEventRef &evt);
virtual void onClose(const WsEventRef &evt);
};
Below is an example of onMessage processing for an Echo server.
void EchoWsEventDelegate::onMessage(const WsEventRef &evt)
{
if(wsEventMessageType(evt) == kWsMessageText) {
std::vector<uint8_t> data;
if(wsEventData(evt, data) > 0) {
std::string str = "Echo: ";
str += std::string(data.data(), data.data()+data.size());
wsSendText(evt, str.c_str());
}
}
}
WsEvent#
The message types are as follows:
| ws_message_t | Value | Description |
|---|---|---|
| kWsMessageUnknown | 0 | Unknown type. |
| kWsMessageText | 1 | Text type. |
| kWsMessageBinary | 2 | Binary type. |
| kWsMessagePing | 3 | Ping type. |
| kWsMessagePong | 4 | Pong (response to Ping) type. |
The status codes are as follows:
| kWsStatusCode_StatusCode | Value |
Description |
|---|---|---|
| NormalClosure | 1000 | Normal closure. |
| EndPointGoingAway | 1001 | Endpoint connection error. Occurs when the server is closed or the browser window is closed. |
| EndPointTerminatingConnectionDueToProtocolError | 1002 | Connection termination due to a protocol error. |
| EndPointTerminatingConnectionDueToUnknownOpcode | 1003 | Connection termination due to inability to process data due to an unknown Opcode. |
| NoStatusCodePresent | 1005 | No status code is present in the close frame. |
| ConnectionClosedAbnormally | 1006 | The connection was terminated abnormally without a close frame. |
| EndPointTerminatingConnectionDueToInconsistentData | 1007 | Connection termination due to receiving data that differs from the message type. For example, when non-UTF-8 data is received for a UTF-8 type message. |
| EndPointTerminatingConnectionDueToPolicy | 1008 | Connection termination due to general reasons such as security. |
| EndPointTerminatingConnectionDueToTooBigData | 1009 | Connection termination that occurs when data too large to process is received. |
| ClientTerminatingConnectionDueToLackOfExtensions | 1010 | The client terminates the connection because it cannot be processed during the extension verification process on the server side. |
| ServerTerminatingConnectionDueToUnexpectedCondition | 1011 | The server terminates the connection because it cannot process the request. |
| TlsHandshakeFailure | 1015 | Connection termination due to a TLS handshake error. |
onError event is called.
onError event is called.
- 0: Success
- -1: Failure
Server Functions#
The key-value map required to create a WebSocket server is as follows:
When creating a secure WebSocket server using TLS, the following key-value map is additionally required:
tls-certificate-request-authority-names key.- 0: Success
- -1: Failure
- 0: Success
- -1: Failure
- 0: Success
- -1: Failure
Client Functions#
ws://<ServerAddress>[:<PortNumber>] or wss://<ServerAddress>[:<PortNumber>]. wss is a connection that uses TLS. For example, it is formatted like wss://echo.websocket.org.
onOpen or onError event callback of the delegate is called. If the value is greater than 0, it waits for the specified time until connected to the server. Afterwards, it waits for the specified time until the WebSocket connection is completed. If time elapses, it returns a timeout (ETIMEDOUT) error to errno. Depending on the connection result, the onOpen or onError event callback of the delegate is called.
The key-value map required for wsOpenConnection is as follows:
- 0: Success
- -1: Failure
Example#
Below is an example of a WebSocket server.
#include "OasisAPI.h"
#include "OasisLog.h"
#include "OasisWeb.h"
#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>
#define DLOG_THIS 0x00080000
#undef DLOG_FLAGS
#define DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)
#undef DLOG_TAG
#define DLOG_TAG "WSS"
#define USE_RSA 1
#define USE_ECDSA 0
#define USE_DSS 0
using namespace oasis;
static bool continuing = true;
//for WebSocket upstream
class MyWsEventDelegate : public WsEventDelegate
{
public:
MyWsEventDelegate() {}
virtual ~MyWsEventDelegate() {}
public:
virtual void onOpen(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Open\n");
}
virtual void onMessage(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Message\n");
if(wsEventMessageType(evt) == kWsMessageText) {
std::vector<uint8_t> data;
if(wsEventData(evt, data) > 0) {
std::string str(data.data(), data.data()+data.size());
wsSendText(evt, str.c_str());
}
}
}
virtual void onError(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Error\n");
}
virtual void onClose(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Close\n");
}
};
void cancel_handler(int signum)
{
continuing = false;
}
int main(int argc, char* argv[])
{
int32_t c, err;
uint16_t port;
std::thread t3;
key_value_map_t parameters, fields;
if(argc < 2) {
printf("USAGE: %s <port number>\n", argv[0]);
return 255;
}
port = (uint16_t)atoi(argv[1]);
if(port == 0) {
printf("USAGE: %s <port number>\n", argv[0]);
return 255;
}
signal(SIGINT, cancel_handler);
parameters["offs-disable"] = "1";
if(oasis::initialize(parameters) < 0) {
DLOG(DLOG_ERROR, "Oasis init failed\n");
return -1;
}
parameters.clear();
parameters["port"] = std::to_string(port);
parameters["ca-certs-file-path"] = "/data/certs/RootCA.pem";
#if USE_RSA
parameters["cert-file-path"] = "/data/certs/fullchain.pem";
parameters["pkey-file-path"] = "/data/certs/privkey.pem";
#elif USE_ECDSA
parameters["cert-file-path"] = "/data/certs/ec_fullchain.pem";
parameters["pkey-file-path"] = "/data/certs/ec_pkey.pem";
#elif USE_DSS
parameters["cert-file-path"] = "/data/certs/dsa_fullchain.pem";
parameters["pkey-file-path"] = "/data/certs/dsa_pkey.pem";
#else
#error "No certficates!"
#endif
std::shared_ptr<MyWsEventDelegate> delegate = std::make_shared<MyWsEventDelegate>();
WsServerRef server = createWsServer(parameters, delegate);
if(!server) {
DLOG(DLOG_ERROR, "failed to create a websocket server\n");
return -1;
}
if(startWsServer(server) < 0) {
DLOG(DLOG_ERROR, "start failed\n");
goto done;
}
do {
usleep(10000);
} while (continuing);
stopWsServer(server);
done:
destroyWsServer(server);
oasis::finalize();
return 0;
}
Below is an example of a WebSocket client.
#include "OasisAPI.h"
#include "OasisLog.h"
#include "OasisWeb.h"
#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>
#define DLOG_THIS 0x00080000
#undef DLOG_FLAGS
#define DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)
#undef DLOG_TAG
#define DLOG_TAG "WSC"
using namespace oasis;
static TTYRaw tty_;
static bool continuing = true;
static bool ws_closed = false;
//for WebSocket upstream
class MyWsEventDelegate : public WsEventDelegate
{
public:
MyWsEventDelegate() {}
virtual ~MyWsEventDelegate() {}
public:
virtual void onOpen(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Open\n");
}
virtual void onMessage(const WsEventRef &evt) {
DLOG(DLOG_VERBOSE, "Message\n");
if(wsEventMessageType(evt) == kWsMessageText) {
std::string str;
wsEventText(evt, str);
DLOG0(DLOG_THIS, "==> %s\n", str.c_str());
}
}
virtual void onError(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Error: %d\n", wsEventStatusCode(evt));
}
virtual void onClose(const WsEventRef &evt) {
DLOG(DLOG_THIS, "Close: %d\n", wsEventStatusCode(evt));
ws_closed = true;
}
};
void cancel_handler(int signum)
{
continuing = false;
}
int main(int argc, char* argv[])
{
int32_t err;
int32_t status_code;
std::string reason_string;
std::string content_type;
int32_t wait_count = 0;
key_value_map_t parameters, fields;
std::string url = "wss://echo.websocket.org/";
if(argc >= 2) {
url = argv[1];
}
signal(SIGINT, cancel_handler);
parameters["offs-disable"] = "1";
if(oasis::initialize(parameters) < 0) {
DLOG(DLOG_ERROR, "Oasis init failed\n");
return -1;
}
parameters.clear();
parameters["ca-certs-file-path"] = "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem";
parameters["cert-file-path"] = "/data/certs/client_fullchain.pem";
parameters["pkey-file-path"] = "/data/certs/client_privkey.pem";
parameters["tls-disable-certificate-validation"] = "1";
parameters["tls-use-sni"] = "1";
//websocket-protocols
parameters["websocket-fragment-size-max"] = std::to_string(8192);
WsConnectionRef conn;
char buffer[512];
std::shared_ptr<MyWsEventDelegate> delegate = std::make_shared<MyWsEventDelegate>();
conn = wsOpenConnection(url.c_str(), parameters, delegate, 10000);
if(conn) {
DLOG(DLOG_THIS, "connected.\n");
strpcy(buffer, "hello, world!");
wsSendText(conn, (const char*)buffer);
DLOG(DLOG_THIS, "closing...\n");
wsCloseConnection(conn);
//need sometime to be closed.
int32_t max_wait = 20; //2 seconds
do {
usleep(10000);
} while(ws_closed == false && max_wait-- > 0);
}
conn = nullptr;
oasis::finalize();
return 0;
}