net: websocket: Initial support for server websocket
This commit creates a websocket library that can be used by applications. The websocket library implements currently only server role and it uses services provided by net-app API. The library supports TLS if enabled in configuration file. This also adds websocket calls to HTTP app server if websocket connection is established. Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
parent
0ecd77e435
commit
d70c7383de
11 changed files with 929 additions and 8 deletions
|
@ -33,9 +33,11 @@ config HTTP_HEADERS
|
|||
int "HTTP header field max number of items"
|
||||
depends on HTTP_SERVER
|
||||
default 8
|
||||
default 20 if WEBSOCKET
|
||||
help
|
||||
Number of HTTP header field items that an HTTP server
|
||||
application will handle
|
||||
application will handle. If websocket is enabled, then the
|
||||
default needs to be much bigger.
|
||||
|
||||
config HTTPS
|
||||
bool "HTTPS support"
|
||||
|
|
|
@ -66,6 +66,15 @@ int http_close(struct http_ctx *ctx)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_WEBSOCKET)
|
||||
if (ctx->websocket.pending) {
|
||||
net_pkt_unref(ctx->websocket.pending);
|
||||
ctx->websocket.pending = NULL;
|
||||
}
|
||||
|
||||
ctx->websocket.data_waiting = 0;
|
||||
#endif
|
||||
|
||||
return net_app_close(&ctx->app_ctx);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
#include <net/net_ip.h>
|
||||
#include <net/http.h>
|
||||
|
||||
#if defined(CONFIG_WEBSOCKET)
|
||||
#include <net/websocket.h>
|
||||
#include "../websocket/websocket_internal.h"
|
||||
#endif
|
||||
|
||||
#define BUF_ALLOC_TIMEOUT 100
|
||||
|
||||
#define HTTP_DEFAULT_PORT 80
|
||||
|
@ -314,6 +319,10 @@ static inline void new_client(struct http_ctx *ctx,
|
|||
struct net_context *net_ctx;
|
||||
const char *type_str = "HTTP";
|
||||
|
||||
if (type == WS_CONNECTION) {
|
||||
type_str = "WS";
|
||||
}
|
||||
|
||||
net_ctx = get_server_ctx(app_ctx, dst);
|
||||
if (net_ctx) {
|
||||
NET_INFO("[%p] %s connection from %s (%p)", ctx, type_str,
|
||||
|
@ -534,6 +543,17 @@ static void http_closed(struct net_app_ctx *app_ctx,
|
|||
if (ctx->cb.close) {
|
||||
ctx->cb.close(ctx, 0, ctx->user_data);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_WEBSOCKET)
|
||||
if (ctx->websocket.pending) {
|
||||
net_pkt_unref(ctx->websocket.pending);
|
||||
ctx->websocket.pending = NULL;
|
||||
}
|
||||
|
||||
ctx->websocket.data_waiting = 0;
|
||||
#endif
|
||||
|
||||
ctx->http.field_values_ctr = 0;
|
||||
}
|
||||
|
||||
static void http_received(struct net_app_ctx *app_ctx,
|
||||
|
@ -581,9 +601,9 @@ static void http_received(struct net_app_ctx *app_ctx,
|
|||
if (ctx->state == HTTP_STATE_OPEN) {
|
||||
/* We have active websocket session and there is no longer
|
||||
* any HTTP traffic in the connection. Give the data to
|
||||
* application to send.
|
||||
* application.
|
||||
*/
|
||||
goto http_only;
|
||||
goto ws_only;
|
||||
}
|
||||
|
||||
while (frag) {
|
||||
|
@ -595,7 +615,7 @@ static void http_received(struct net_app_ctx *app_ctx,
|
|||
ctx->http.request_buf_len) {
|
||||
|
||||
if (ctx->state == HTTP_STATE_HEADER_RECEIVED) {
|
||||
goto http_ready;
|
||||
goto ws_ready;
|
||||
}
|
||||
|
||||
/* If the caller has not supplied a callback, then
|
||||
|
@ -650,7 +670,7 @@ fail:
|
|||
http_send_error(ctx, 400, NULL, 0, dst);
|
||||
} else {
|
||||
if (ctx->state == HTTP_STATE_HEADER_RECEIVED) {
|
||||
goto http_ready;
|
||||
goto ws_ready;
|
||||
}
|
||||
|
||||
http_process_recv(ctx, dst);
|
||||
|
@ -664,17 +684,181 @@ quit:
|
|||
|
||||
return;
|
||||
|
||||
http_only:
|
||||
ws_only:
|
||||
if (ctx->cb.recv) {
|
||||
#if defined(CONFIG_WEBSOCKET)
|
||||
u32_t msg_len, header_len = 0;
|
||||
bool masked = true;
|
||||
int ret;
|
||||
|
||||
if (ctx->websocket.data_waiting == 0) {
|
||||
ctx->websocket.masking_value = 0;
|
||||
ctx->websocket.data_read = 0;
|
||||
ctx->websocket.msg_type_flag = 0;
|
||||
|
||||
if (ctx->websocket.pending) {
|
||||
/* Append the pending data to current buffer */
|
||||
int orig_len;
|
||||
|
||||
orig_len = net_pkt_appdatalen(
|
||||
ctx->websocket.pending);
|
||||
net_pkt_set_appdatalen(
|
||||
ctx->websocket.pending,
|
||||
orig_len + net_pkt_appdatalen(pkt));
|
||||
|
||||
net_pkt_frag_add(ctx->websocket.pending,
|
||||
pkt->frags);
|
||||
|
||||
pkt->frags = NULL;
|
||||
net_pkt_unref(pkt);
|
||||
|
||||
net_pkt_compact(ctx->websocket.pending);
|
||||
} else {
|
||||
ctx->websocket.pending = pkt;
|
||||
}
|
||||
|
||||
ret = ws_strip_header(ctx->websocket.pending, &masked,
|
||||
&ctx->websocket.masking_value,
|
||||
&msg_len,
|
||||
&ctx->websocket.msg_type_flag,
|
||||
&header_len);
|
||||
if (ret < 0) {
|
||||
/* Not enough bytes for a complete websocket
|
||||
* header, continue reading data.
|
||||
*/
|
||||
NET_DBG("[%p] pending %zd bytes, waiting more",
|
||||
ctx,
|
||||
net_pkt_get_len(ctx->websocket.pending));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->websocket.msg_type_flag & WS_FLAG_CLOSE) {
|
||||
NET_DBG("[%p] Close request from peer", ctx);
|
||||
http_close(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->websocket.msg_type_flag & WS_FLAG_PING) {
|
||||
NET_DBG("[%p] Ping request from peer", ctx);
|
||||
ws_send_msg(ctx, NULL, 0, WS_OPCODE_PONG,
|
||||
false, true, dst, NULL);
|
||||
ctx->websocket.data_waiting = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We have now received some data. It might not yet be
|
||||
* the full data that is told by msg_len but we can
|
||||
* already pass this data to caller.
|
||||
*/
|
||||
ctx->websocket.data_waiting = msg_len;
|
||||
|
||||
if (net_pkt_get_len(ctx->websocket.pending) ==
|
||||
header_len) {
|
||||
NET_DBG("[%p] waiting more data", ctx);
|
||||
|
||||
/* We do not need the websocket header
|
||||
* any more, so discard it.
|
||||
*/
|
||||
net_pkt_unref(ctx->websocket.pending);
|
||||
ctx->websocket.pending = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we have more data pending than the header len,
|
||||
* then discard the header as we do not need that.
|
||||
*/
|
||||
net_buf_pull(ctx->websocket.pending->frags,
|
||||
header_len);
|
||||
|
||||
pkt = ctx->websocket.pending;
|
||||
ctx->websocket.pending = NULL;
|
||||
|
||||
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt));
|
||||
net_pkt_set_appdata(pkt, pkt->frags->data);
|
||||
}
|
||||
|
||||
if (net_pkt_appdatalen(pkt) > ctx->websocket.data_waiting) {
|
||||
/* Now we received more data which in practice means
|
||||
* that we got the next websocket header.
|
||||
*/
|
||||
struct net_buf *hdr, *payload;
|
||||
struct net_pkt *cloned;
|
||||
|
||||
ret = net_pkt_split(pkt, pkt->frags,
|
||||
ctx->websocket.data_waiting,
|
||||
&payload, &hdr,
|
||||
ctx->timeout);
|
||||
if (ret < 0) {
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
net_pkt_frag_unref(pkt->frags);
|
||||
pkt->frags = NULL;
|
||||
|
||||
cloned = net_pkt_clone(pkt, ctx->timeout);
|
||||
if (!cloned) {
|
||||
net_pkt_unref(pkt);
|
||||
return;
|
||||
}
|
||||
|
||||
pkt->frags = payload;
|
||||
payload->frags = NULL;
|
||||
|
||||
net_pkt_compact(pkt);
|
||||
|
||||
cloned->frags = hdr;
|
||||
|
||||
ctx->websocket.pending = cloned;
|
||||
ctx->websocket.data_waiting = 0;
|
||||
|
||||
net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt));
|
||||
net_pkt_set_appdata(pkt, pkt->frags->data);
|
||||
|
||||
net_pkt_set_appdatalen(cloned, net_pkt_get_len(cloned));
|
||||
net_pkt_set_appdata(cloned, cloned->frags->data);
|
||||
|
||||
NET_DBG("More data (%d bytes) received, pending it",
|
||||
net_pkt_appdatalen(cloned));
|
||||
} else {
|
||||
ctx->websocket.data_waiting -= net_pkt_appdatalen(pkt);
|
||||
}
|
||||
|
||||
if (ctx->websocket.data_waiting) {
|
||||
NET_DBG("[%p] waiting still %u bytes", ctx,
|
||||
ctx->websocket.data_waiting);
|
||||
} else {
|
||||
NET_DBG("[%p] All bytes received", ctx);
|
||||
}
|
||||
|
||||
NET_DBG("[%p] Pass data (%d) to application for processing",
|
||||
ctx, net_pkt_appdatalen(pkt));
|
||||
|
||||
NET_DBG("[%p] Masked %s mask 0x%04x", ctx,
|
||||
masked ? "yes" : "no",
|
||||
ctx->websocket.masking_value);
|
||||
|
||||
if (masked) {
|
||||
/* Always deliver unmasked data to the application */
|
||||
ws_mask_pkt(pkt,
|
||||
ctx->websocket.masking_value,
|
||||
&ctx->websocket.data_read);
|
||||
}
|
||||
|
||||
ctx->cb.recv(ctx, pkt, 0, ctx->websocket.msg_type_flag,
|
||||
dst, ctx->user_data);
|
||||
#else
|
||||
ctx->cb.recv(ctx, pkt, 0, 0, dst, ctx->user_data);
|
||||
#endif
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
http_ready:
|
||||
ws_ready:
|
||||
http_change_state(ctx, HTTP_STATE_OPEN);
|
||||
url_connected(ctx, HTTP_CONNECT, dst);
|
||||
url_connected(ctx, WS_CONNECTION, dst);
|
||||
net_pkt_unref(pkt);
|
||||
ctx->http.field_values_ctr = 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_HTTPS)
|
||||
|
@ -786,7 +970,11 @@ static int on_headers_complete(struct http_parser *parser)
|
|||
{
|
||||
ARG_UNUSED(parser);
|
||||
|
||||
#if defined(CONFIG_WEBSOCKET)
|
||||
return ws_headers_complete(parser);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int init_http_parser(struct http_ctx *ctx)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue