From f42d4292cbbfa445ab8b89545832960da325fb2f Mon Sep 17 00:00:00 2001 From: Nick Withers Date: Mon, 15 Dec 2014 13:26:31 +1100 Subject: Enable WebSocket support in the Mongoose HTTP server --- cpukit/mghttpd/Makefile.am | 2 +- testsuites/libtests/mghttpd01/init.c | 54 ++++++++- testsuites/libtests/mghttpd01/test-http-client.c | 146 +++++++++++++++++++++-- testsuites/libtests/mghttpd01/test-http-client.h | 17 ++- 4 files changed, 205 insertions(+), 14 deletions(-) diff --git a/cpukit/mghttpd/Makefile.am b/cpukit/mghttpd/Makefile.am index 78af78d185..bb5f84ba16 100644 --- a/cpukit/mghttpd/Makefile.am +++ b/cpukit/mghttpd/Makefile.am @@ -7,7 +7,7 @@ include_mghttpddir = $(includedir)/mghttpd project_lib_LIBRARIES = libmghttpd.a libmghttpd_a_CPPFLAGS = $(AM_CPPFLAGS) # libmghttpd_a_CPPFLAGS += -DHAVE_MD5 -libmghttpd_a_CPPFLAGS += -DNO_SSL -DNO_POPEN -DNO_CGI +libmghttpd_a_CPPFLAGS += -DNO_SSL -DNO_POPEN -DNO_CGI -DUSE_WEBSOCKET libmghttpd_a_SOURCES = mongoose.c mongoose.h include_mghttpd_HEADERS = mongoose.h diff --git a/testsuites/libtests/mghttpd01/init.c b/testsuites/libtests/mghttpd01/init.c index a5eee8c865..9f285705b3 100644 --- a/testsuites/libtests/mghttpd01/init.c +++ b/testsuites/libtests/mghttpd01/init.c @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -43,6 +44,9 @@ const char rtems_test_name[] = "MGHTTPD 1"; "\r\n" \ "This is a message from the callback function.\r\n" +#define WSTEST_REQ "Test request" +#define WSTEST_RESP "This is a message from the WebSocket callback function." + #define INDEX_HTML "HTTP/1.1 200 OK\r\n" \ "Date: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n" \ "Last-Modified: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n" \ @@ -96,6 +100,22 @@ static int callback(struct mg_connection *conn) return 0; } +static int callback_websocket(struct mg_connection *connection, + int bits, + char *data, + size_t data_len) +{ + if (data_len == strlen(WSTEST_REQ) && strncmp(data, WSTEST_REQ, data_len) == 0) + { + mg_websocket_write(connection, WEBSOCKET_OPCODE_TEXT, WSTEST_RESP, strlen(WSTEST_RESP)); + + /* Don't close the WebSocket */ + return 1; + } + + return 0; +} + static void test_mg_index_html(void) { httpc_context httpc_ctx; @@ -164,10 +184,41 @@ static void test_mg_callback(void) free(buffer); } +static void test_mg_websocket(void) +{ + httpc_context httpc_ctx; + char *buffer = malloc(BUFFERSIZE); + bool brv = false; + int rv = 0; + + rtems_test_assert(buffer != NULL); + + puts("=== Get a WebSocket response generated from a callback function" \ + " from first Mongoose instance:"); + + httpc_init_context(&httpc_ctx); + brv = httpc_open_connection(&httpc_ctx, "127.0.0.1", 80); + rtems_test_assert(brv); + brv = httpc_ws_open_connection(&httpc_ctx); + rtems_test_assert(brv); + brv = httpc_ws_send_request(&httpc_ctx, WSTEST_REQ, buffer, BUFFERSIZE); + rtems_test_assert(brv); + brv = httpc_close_connection(&httpc_ctx); + rtems_test_assert(brv); + puts(buffer); + rv = strcmp(buffer, WSTEST_RESP); + rtems_test_assert(rv == 0); + + puts("=== OK"); + + free(buffer); +} + static void test_mongoose(void) { const struct mg_callbacks callbacks = { - .begin_request = callback + .begin_request = callback, + .websocket_data = callback_websocket }; const char *options[] = { "listening_ports", "80", @@ -192,6 +243,7 @@ static void test_mongoose(void) test_mg_index_html(); test_mg_callback(); + test_mg_websocket(); mg_stop(mg1); mg_stop(mg2); diff --git a/testsuites/libtests/mghttpd01/test-http-client.c b/testsuites/libtests/mghttpd01/test-http-client.c index 4902db46ab..806283b2d2 100644 --- a/testsuites/libtests/mghttpd01/test-http-client.c +++ b/testsuites/libtests/mghttpd01/test-http-client.c @@ -18,11 +18,34 @@ #include #include #include +#include #include +#include #include +#include + #include "test-http-client.h" +#define HTTPC_WS_CONN_REQ "GET / HTTP/1.1\r\n" \ + "Host: localhost\r\n" \ + "Upgrade: websocket\r\n" \ + "Connection: Upgrade\r\n" \ + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" \ + "Sec-WebSocket-Version: 13\r\n" \ + "\r\n" +#define HTTPC_WS_CONN_RESP "HTTP/1.1 101 Switching Protocols\r\n" \ + "Upgrade: websocket\r\n" \ + "Connection: Upgrade\r\n" \ + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" \ + "\r\n" + +static ssize_t httpc_read_full( + const httpc_context *ctx, + void *response, + size_t responsesize +); + void httpc_init_context( httpc_context *ctx ) @@ -33,7 +56,7 @@ void httpc_init_context( bool httpc_open_connection( httpc_context *ctx, - char *targethost, + const char *targethost, int targetport ) { @@ -76,21 +99,126 @@ bool httpc_close_connection( } bool httpc_send_request( - httpc_context *ctx, - char *request, + const httpc_context *ctx, + const char *request, char *response, int responsesize ) { - int size = strlen(request); - char lineend[] = " HTTP/1.1\r\n\r\n"; + rtems_test_assert(ctx != NULL); + rtems_test_assert(ctx->socket >= 0); + rtems_test_assert(request != NULL); + rtems_test_assert(response != NULL); + rtems_test_assert(responsesize > 1); + + static const char * const lineend = " HTTP/1.1\r\n\r\n"; + + write(ctx->socket, request, strlen(request)); + write(ctx->socket, lineend, strlen(lineend)); + + ssize_t size; + if((size = httpc_read_full(ctx, response, responsesize - 1)) == -1) + { + return false; + } + *(response + size) = '\0'; + + return true; +} + +bool httpc_ws_open_connection( + const httpc_context *ctx +) +{ + rtems_test_assert(ctx != NULL); + rtems_test_assert(ctx->socket >= 0); - write(ctx->socket, request, size); - write(ctx->socket, lineend, sizeof(lineend)); + write(ctx->socket, HTTPC_WS_CONN_REQ, strlen(HTTPC_WS_CONN_REQ)); - size = read(ctx->socket, response, responsesize-1); - response[size] = '\0'; + char response[strlen(HTTPC_WS_CONN_RESP)]; + if(httpc_read_full(ctx, response, sizeof(response)) != sizeof(response)) + { + return false; + } + if(strncmp(response, HTTPC_WS_CONN_RESP, sizeof(response)) != 0) + { + return false; + } return true; } +bool httpc_ws_send_request( + const httpc_context *ctx, + const char *request, + char *response, + int responsesize +) +{ + rtems_test_assert(ctx != NULL); + rtems_test_assert(ctx->socket >= 0); + rtems_test_assert(request != NULL); + rtems_test_assert(response != NULL); + rtems_test_assert(responsesize > 0); + + static const uint16_t ws_header_fin = 1U << 15; + static const uint16_t ws_header_text = 1U << 8; + static const uint16_t ws_header_size = 0x7FU; + + /* + * We don't support sending WebSocket messages which require multiple + * chunks + */ + if(strlen(request) > ws_header_size) { return false; } + + uint16_t header = htons(ws_header_fin | ws_header_text | strlen(request)); + + write(ctx->socket, &header, sizeof(header)); + write(ctx->socket, request, strlen(request)); + + if (httpc_read_full(ctx, &header, sizeof(header)) != sizeof(header)) + { + return false; + } + header = ntohs(header); + if (!(header & ws_header_fin)) { return false; } + if (!(header & ws_header_text)) { return false; } + if (responsesize < (header & ws_header_size) + 1) { return false; } + + responsesize = header & ws_header_size; + if (httpc_read_full(ctx, response, responsesize) != responsesize) + { + return false; + } + *(response + responsesize) = '\0'; + + return true; +} + + +static ssize_t httpc_read_full( + const httpc_context *ctx, + void *response, + size_t responsesize +) +{ + rtems_test_assert(ctx != NULL); + rtems_test_assert(ctx->socket >= 0); + rtems_test_assert(response != NULL); + rtems_test_assert(responsesize > 0); + + if (responsesize > SSIZE_MAX) { return -1; } + + unsigned char *pos = response; + + while(pos < (unsigned char *)response + responsesize) + { + ssize_t size = + read(ctx->socket, pos, (unsigned char *)response + responsesize - pos); + if (size == -1) { return -1; } + if (size == 0) { break; } + pos += size; + } + + return (pos - (unsigned char *)response); +} diff --git a/testsuites/libtests/mghttpd01/test-http-client.h b/testsuites/libtests/mghttpd01/test-http-client.h index d3e7cdb708..811c790110 100644 --- a/testsuites/libtests/mghttpd01/test-http-client.h +++ b/testsuites/libtests/mghttpd01/test-http-client.h @@ -36,7 +36,7 @@ void httpc_init_context( bool httpc_open_connection( httpc_context *ctx, - char *targethost, + const char *targethost, int targetport ); @@ -45,8 +45,19 @@ bool httpc_close_connection( ); bool httpc_send_request( - httpc_context *ctx, - char *request, + const httpc_context *ctx, + const char *request, + char *response, + int responsesize +); + +bool httpc_ws_open_connection( + const httpc_context *ctx +); + +bool httpc_ws_send_request( + const httpc_context *ctx, + const char *request, char *response, int responsesize ); -- cgit v1.2.3