blob: a56c36be24970d3553914aa92a89ac4a27a7882b [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#if 1
#define SYS_LOG_DOMAIN "ws-echo-server"
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif
#include <zephyr.h>
#include <errno.h>
#include <stdio.h>
#include <net/net_pkt.h>
#include <net/net_core.h>
#include <net/net_context.h>
#include <net/websocket.h>
#include "common.h"
#include "config.h"
#define MAX_BUF_LEN 128
#define MAX_URL_LEN 128
#define SEND_TIMEOUT K_SECONDS(10)
#define ALLOC_TIMEOUT 100
static struct http_ctx http_ctx;
static struct http_server_urls http_urls;
static char buf[MAX_BUF_LEN];
static int msg_count;
/* Note that both tcp and udp can share the same pool but in this
* example the UDP context and TCP context have separate pools.
*/
#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
NET_PKT_TX_SLAB_DEFINE(echo_tx_tcp, 15);
NET_PKT_DATA_POOL_DEFINE(echo_data_tcp, 30);
static struct k_mem_slab *tx_tcp_slab(void)
{
return &echo_tx_tcp;
}
static struct net_buf_pool *data_tcp_pool(void)
{
return &echo_data_tcp;
}
#else
#define tx_tcp_slab NULL
#define data_tcp_pool NULL
#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */
/* The result buf size is set to large enough so that we can receive max size
* buf back. Note that mbedtls needs also be configured to have equal size
* value for its buffer size. See MBEDTLS_SSL_MAX_CONTENT_LEN option in TLS
* config file.
*/
#define RESULT_BUF_SIZE 1500
static u8_t result[RESULT_BUF_SIZE];
#if defined(CONFIG_HTTPS)
#if !defined(CONFIG_NET_APP_TLS_STACK_SIZE)
#define CONFIG_NET_APP_TLS_STACK_SIZE 8192
#endif /* CONFIG_NET_APP_TLS_STACK_SIZE */
#define APP_BANNER "Run TLS ws-server"
#define INSTANCE_INFO "Zephyr TLS ws-server #1"
/* Note that each net_app context needs its own stack as there will be
* a separate thread needed.
*/
NET_STACK_DEFINE(WS_ECHO_SERVER, ws_tls_stack,
CONFIG_NET_APP_TLS_STACK_SIZE, CONFIG_NET_APP_TLS_STACK_SIZE);
#define RX_FIFO_DEPTH 4
K_MEM_POOL_DEFINE(ssl_pool, 4, 64, RX_FIFO_DEPTH, 4);
#endif /* CONFIG_HTTPS */
#if defined(CONFIG_HTTPS)
/* Load the certificates and private RSA key. */
static const char echo_apps_cert_der[] = {
#include "echo-apps-cert.der.inc"
};
static const char echo_apps_key_der[] = {
#include "echo-apps-key.der.inc"
};
static int setup_cert(struct net_app_ctx *ctx,
mbedtls_x509_crt *cert,
mbedtls_pk_context *pkey)
{
int ret;
ret = mbedtls_x509_crt_parse(cert, echo_apps_cert_der,
sizeof(echo_apps_cert_der));
if (ret != 0) {
NET_ERR("mbedtls_x509_crt_parse returned %d", ret);
return ret;
}
ret = mbedtls_pk_parse_key(pkey, echo_apps_key_der,
sizeof(echo_apps_key_der), NULL, 0);
if (ret != 0) {
NET_ERR("mbedtls_pk_parse_key returned %d", ret);
return ret;
}
return 0;
}
#endif /* CONFIG_HTTPS */
#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Transfer-Encoding: chunked\r\n"
#define HTTP_STATUS_200_OK_GZ_CSS \
"HTTP/1.1 200 OK\r\n" \
"Content-Type: text/css\r\n" \
"Transfer-Encoding: chunked\r\n" \
"Content-Encoding: gzip\r\n"
#define HTML_HEADER "<html><head>" \
"<title>Zephyr HTTP Server</title>" \
"</head><body><h1>" \
"<center>Zephyr HTTP websocket server</center></h1>\r\n"
#define HTML_FOOTER "</body></html>\r\n"
static int http_response(struct http_ctx *ctx, const char *header,
const char *payload, size_t payload_len,
const struct sockaddr *dst)
{
char content_length[6];
int ret;
ret = http_add_header(ctx, header, dst, NULL);
if (ret < 0) {
NET_ERR("Cannot add HTTP header (%d)", ret);
return ret;
}
ret = snprintk(content_length, sizeof(content_length), "%zd",
payload_len);
if (ret <= 0 || ret >= sizeof(content_length)) {
ret = -ENOMEM;
return ret;
}
ret = http_add_header_field(ctx, "Content-Length", content_length,
dst, NULL);
if (ret < 0) {
NET_ERR("Cannot add Content-Length HTTP header (%d)", ret);
return ret;
}
ret = http_add_header(ctx, HTTP_CRLF, dst, NULL);
if (ret < 0) {
return ret;
}
ret = http_send_chunk(ctx, payload, payload_len, dst, NULL);
if (ret < 0) {
NET_ERR("Cannot send data to peer (%d)", ret);
return ret;
}
return http_send_flush(ctx, NULL);
}
static int http_serve_index_html(struct http_ctx *ctx,
const struct sockaddr *dst)
{
static const char index_html[] = {
#include "index.html.inc"
};
NET_DBG("Sending index.html (%zd bytes) to client",
sizeof(index_html));
return http_response(ctx, HTTP_STATUS_200_OK, index_html,
sizeof(index_html), dst);
}
static int http_serve_js(struct http_ctx *ctx,
const struct sockaddr *dst)
{
static const char js_gz[] = {
#include "ws.js.gz.inc"
};
NET_DBG("Sending ws.js (%zd bytes) to client", sizeof(js_gz));
return http_response(ctx, HTTP_STATUS_200_OK_GZ_CSS,
js_gz, sizeof(js_gz), dst);
}
static int http_response_soft_404(struct http_ctx *ctx,
const struct sockaddr *dst)
{
static const char *not_found =
HTML_HEADER
"<h2><center>404 Not Found</center></h2>"
HTML_FOOTER;
return http_response(ctx, HTTP_STATUS_200_OK, not_found,
strlen(not_found), dst);
}
static int append_and_send_data(struct http_ctx *http_ctx,
const struct sockaddr *dst,
bool final, const char *fmt, ...)
{
enum ws_opcode opcode = WS_OPCODE_CONTINUE;
va_list ap;
size_t len;
int ret;
va_start(ap, fmt);
vsnprintk(buf, MAX_BUF_LEN, fmt, ap);
va_end(ap);
len = strlen(buf);
if (final) {
if (msg_count == 0) {
opcode = WS_OPCODE_DATA_TEXT;
}
ret = ws_send_msg_to_client(http_ctx, buf, len,
opcode, final, dst, NULL);
if (ret < 0) {
NET_DBG("Could not send %zd bytes data to client",
len);
goto out;
} else {
NET_DBG("Sent %zd bytes to client", len);
}
msg_count = 0;
return ret;
}
if (msg_count == 0) {
opcode = WS_OPCODE_DATA_TEXT;
}
ret = ws_send_msg_to_client(http_ctx, buf, len,
opcode, final, dst, NULL);
if (ret < 0) {
NET_DBG("Could not send %zd bytes data to client", len);
goto out;
} else {
NET_DBG("Sent %zd bytes to client", len);
}
msg_count++;
out:
return ret;
}
static int ws_works(struct http_ctx *ctx,
const struct sockaddr *dst)
{
int ret = 0;
NET_INFO("WS url called");
append_and_send_data(ctx, dst, false, "connection");
append_and_send_data(ctx, dst, true, " established.");
return ret;
}
static void ws_connected(struct http_ctx *ctx,
enum http_connection_type type,
const struct sockaddr *dst,
void *user_data)
{
char url[32];
int len = min(sizeof(url), ctx->http.url_len);
memcpy(url, ctx->http.url, len);
url[len] = '\0';
NET_DBG("%s connect attempt URL %s",
type == HTTP_CONNECTION ? "HTTP" : "WS", url);
if (type == HTTP_CONNECTION) {
if (strncmp(ctx->http.url, "/index.html",
sizeof("/index.html") - 1) == 0) {
http_serve_index_html(ctx, dst);
http_close(ctx);
return;
}
if (strncmp(ctx->http.url, "/ws.js",
sizeof("/ws.js") - 1) == 0) {
http_serve_js(ctx, dst);
http_close(ctx);
return;
}
if (strncmp(ctx->http.url, "/",
ctx->http.url_len) == 0) {
http_serve_index_html(ctx, dst);
http_close(ctx);
return;
}
} else if (type == WS_CONNECTION) {
if (strncmp(ctx->http.url, "/ws",
sizeof("/ws") - 1) == 0) {
ws_works(ctx, dst);
return;
}
}
/* Give 404 error for all the other URLs we do not want to handle
* right now.
*/
http_response_soft_404(ctx, dst);
http_close(ctx);
}
static void ws_received(struct http_ctx *ctx,
struct net_pkt *pkt,
int status,
u32_t flags,
const struct sockaddr *dst,
void *user_data)
{
if (!status) {
struct net_buf *frag;
enum ws_opcode opcode;
int ret, hdr_len;
if (!pkt) {
return;
}
NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt));
if (flags & WS_FLAG_BINARY) {
opcode = WS_OPCODE_DATA_BINARY;
} else {
opcode = WS_OPCODE_DATA_TEXT;
}
hdr_len = net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt);
frag = pkt->frags;
while (frag) {
ret = ws_send_msg_to_client(ctx, frag->data + hdr_len,
frag->len - hdr_len,
opcode,
frag->frags ? true : false,
dst, user_data);
if (ret < 0) {
NET_DBG("Cannot send ws data (%d bytes) "
"back (%d)",
frag->len - hdr_len, ret);
} else {
NET_DBG("Sent %d bytes to client",
frag->len - hdr_len);
}
frag = frag->frags;
/* Websocket header is found in first fragment so
* reset the value here.
*/
hdr_len = 0;
}
http_send_flush(ctx, user_data);
net_pkt_unref(pkt);
} else {
NET_ERR("Receive error (%d)", status);
if (pkt) {
net_pkt_unref(pkt);
}
}
}
static void ws_sent(struct http_ctx *ctx,
int status,
void *user_data_send,
void *user_data)
{
NET_DBG("Data sent status %d", status);
}
static void ws_closed(struct http_ctx *ctx,
int status,
void *user_data)
{
NET_DBG("Connection %p closed", ctx);
}
static const char *get_string(int str_len, const char *str)
{
static char buf[64];
int len = min(str_len, sizeof(buf) - 1);
memcpy(buf, str, len);
buf[len] = '\0';
return buf;
}
static enum http_verdict default_handler(struct http_ctx *ctx,
enum http_connection_type type,
const struct sockaddr *dst)
{
NET_DBG("No handler for %s URL %s",
type == HTTP_CONNECTION ? "HTTP" : "WS",
get_string(ctx->http.url_len, ctx->http.url));
if (type == HTTP_CONNECTION) {
http_response_soft_404(ctx, dst);
}
return HTTP_VERDICT_DROP;
}
void start_server(void)
{
struct sockaddr addr, *server_addr;
int ret;
/*
* There are several options here for binding to local address.
* 1) The server address can be left empty in which case the
* library will bind to both IPv4 and IPv6 addresses and to
* default port 80 or 443 if TLS is enabled.
* 2) The server address can be partially filled, meaning that
* the address can be left to 0 and port can be set to desired
* value. If the protocol family in sockaddr is set to AF_UNSPEC,
* then both IPv4 and IPv6 socket is bound.
* 3) The address can be set to some real value.
*/
#define ADDR_OPTION 1
#if ADDR_OPTION == 1
server_addr = NULL;
ARG_UNUSED(addr);
#elif ADDR_OPTION == 2
/* Accept any local listening address */
memset(&addr, 0, sizeof(addr));
net_sin(&addr)->sin_port = htons(ZEPHYR_PORT);
/* In this example, listen both IPv4 and IPv6 */
addr.sa_family = AF_UNSPEC;
server_addr = &addr;
#elif ADDR_OPTION == 3
/* Set the bind address according to your configuration */
memset(&addr, 0, sizeof(addr));
/* In this example, listen only IPv6 */
addr.sa_family = AF_INET6;
net_sin6(&addr)->sin6_port = htons(ZEPHYR_PORT);
ret = net_ipaddr_parse(ZEPHYR_ADDR, &addr);
if (ret < 0) {
NET_ERR("Cannot set local address (%d)", ret);
panic(NULL);
}
server_addr = &addr;
#else
server_addr = NULL;
ARG_UNUSED(addr);
#endif
http_server_add_default(&http_urls, default_handler);
http_server_add_url(&http_urls, "/index.html", HTTP_URL_STANDARD);
http_server_add_url(&http_urls, "/ws.js", HTTP_URL_STANDARD);
http_server_add_url(&http_urls, "/", HTTP_URL_STANDARD);
http_server_add_url(&http_urls, "/ws", HTTP_URL_WEBSOCKET);
ret = http_server_init(&http_ctx, &http_urls, server_addr,
result, sizeof(result),
"Zephyr WS server", NULL);
if (ret < 0) {
NET_ERR("Cannot init web server (%d)", ret);
return;
}
http_set_cb(&http_ctx, ws_connected, ws_received, ws_sent, ws_closed);
#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
net_app_set_net_pkt_pool(&http_ctx.app_ctx, tx_tcp_slab, data_tcp_pool);
#endif
#if defined(CONFIG_NET_APP_TLS)
ret = http_server_set_tls(&http_ctx,
APP_BANNER,
INSTANCE_INFO,
strlen(INSTANCE_INFO),
setup_cert,
NULL,
&ssl_pool,
ws_tls_stack,
K_THREAD_STACK_SIZEOF(ws_tls_stack));
if (ret < 0) {
NET_ERR("Cannot enable TLS support (%d)", ret);
}
#endif
http_server_enable(&http_ctx);
}
void stop_server(void)
{
http_server_disable(&http_ctx);
http_release(&http_ctx);
}