From 8bb9f3ca782c28f4e9218fff864ea752d142b28c Mon Sep 17 00:00:00 2001 From: kegilbert Date: Wed, 28 Nov 2018 15:44:58 -0600 Subject: [PATCH 1/8] Add HTTPServer example --- HTTPServer/LICENSE | 191 ++++++++++++++++++++++ HTTPServer/README.md | 20 +++ HTTPServer/http_response_builder.h | 250 +++++++++++++++++++++++++++++ HTTPServer/http_server.h | 235 +++++++++++++++++++++++++++ HTTPServer/main.cpp | 74 +++++++++ HTTPServer/mbed-http.lib | 1 + HTTPServer/mbed-os.lib | 1 + HTTPServer/mbed_app.json | 53 ++++++ HTTPServer/webpage.h | 16 ++ 9 files changed, 841 insertions(+) create mode 100644 HTTPServer/LICENSE create mode 100644 HTTPServer/README.md create mode 100644 HTTPServer/http_response_builder.h create mode 100644 HTTPServer/http_server.h create mode 100644 HTTPServer/main.cpp create mode 100644 HTTPServer/mbed-http.lib create mode 100644 HTTPServer/mbed-os.lib create mode 100644 HTTPServer/mbed_app.json create mode 100644 HTTPServer/webpage.h diff --git a/HTTPServer/LICENSE b/HTTPServer/LICENSE new file mode 100644 index 0000000..35ae8d2 --- /dev/null +++ b/HTTPServer/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +--- + +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/HTTPServer/README.md b/HTTPServer/README.md new file mode 100644 index 0000000..f7f752d --- /dev/null +++ b/HTTPServer/README.md @@ -0,0 +1,20 @@ +# mbed-os-example-http-server + +This application demonstrates how to run an HTTP server on an mbed OS 5 device. + +Request parsing is done through [nodejs/http-parser](https://github.com/nodejs/http-parser). + +## To build + +1. Open ``mbed_app.json`` and change the `network-interface` option to your connectivity method ([more info](https://github.com/ARMmbed/easy-connect)). +2. Build the project in the online compiler or using mbed CLI. +3. Flash the project to your development board. +4. Attach a serial monitor to your board to see the debug messages. + +## Tested on + +* K64F with Ethernet. +* NUCLEO_F411RE with ESP8266. + * For ESP8266, you need [this patch](https://github.com/ARMmbed/esp8266-driver/pull/41). + +But every networking stack that supports the [mbed OS 5 NetworkInterface API](https://docs.mbed.com/docs/mbed-os-api-reference/en/latest/APIs/communication/network_sockets/) should work. diff --git a/HTTPServer/http_response_builder.h b/HTTPServer/http_response_builder.h new file mode 100644 index 0000000..130fe49 --- /dev/null +++ b/HTTPServer/http_response_builder.h @@ -0,0 +1,250 @@ +/* + * PackageLicenseDeclared: Apache-2.0 + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MBED_HTTP_RESPONSE_BUILDER_ +#define _MBED_HTTP_RESPONSE_BUILDER_ + +#include +#include +#include "http_parser.h" +#include "http_parsed_url.h" + +static const char *get_http_status_string(uint16_t status_code) +{ + switch (status_code) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Entity"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + default : + return "Unknown"; + } +} + +class HttpResponseBuilder { +public: + HttpResponseBuilder(uint16_t a_status_code) + : status_code(a_status_code), status_message(get_http_status_string(a_status_code)) + { + } + + /** + * Set a header for the request + * If the key already exists, it will be overwritten... + */ + void set_header(string key, string value) + { + map::iterator it = headers.find(key); + + if (it != headers.end()) { + it->second = value; + } else { + headers.insert(headers.end(), pair(key, value)); + } + } + + char *build(const void *body, size_t body_size, size_t *size) + { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%d", body_size); + set_header("Content-Length", string(buffer)); + + char status_code_buffer[5]; + snprintf(status_code_buffer, sizeof(status_code_buffer), "%d", status_code /* max 5 digits */); + + *size = 0; + + // first line is HTTP/1.1 200 OK\r\n + *size += 8 + 1 + strlen(status_code_buffer) + 1 + strlen(status_message) + 2; + + // after that we'll do the headers + typedef map::iterator it_type; + for (it_type it = headers.begin(); it != headers.end(); it++) { + // line is KEY: VALUE\r\n + *size += it->first.length() + 1 + 1 + it->second.length() + 2; + } + + // then the body, first an extra newline + *size += 2; + + // body + *size += body_size; + + // Now let's print it + char *res = (char *)calloc(*size + 1, 1); + char *originalRes = res; + + res += sprintf(res, "HTTP/1.1 %s %s\r\n", status_code_buffer, status_message); + + typedef map::iterator it_type; + for (it_type it = headers.begin(); it != headers.end(); it++) { + // line is KEY: VALUE\r\n + res += sprintf(res, "%s: %s\r\n", it->first.c_str(), it->second.c_str()); + } + + res += sprintf(res, "\r\n"); + + if (body_size > 0) { + memcpy(res, body, body_size); + } + res += body_size; + + // Uncomment to debug... + // printf("----- BEGIN RESPONSE -----\n"); + // printf("%s", originalRes); + // printf("----- END RESPONSE -----\n"); + + return originalRes; + } + + nsapi_error_t send(TCPSocket *socket, const void *body, size_t body_size) + { + if (!socket) { + return NSAPI_ERROR_NO_SOCKET; + } + + size_t res_size; + char *response = build(body, body_size, &res_size); + + nsapi_error_t r = socket->send(response, res_size); + + free(response); + + return r; + } + +private: + uint16_t status_code; + const char *status_message; + map headers; +}; + +#endif // _MBED_HTTP_RESPONSE_BUILDER_ diff --git a/HTTPServer/http_server.h b/HTTPServer/http_server.h new file mode 100644 index 0000000..b562b98 --- /dev/null +++ b/HTTPServer/http_server.h @@ -0,0 +1,235 @@ +/* + * PackageLicenseDeclared: Apache-2.0 + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _HTTP_SERVER_ +#define _HTTP_SERVER_ + +#include "mbed.h" +#include "http_request_parser.h" +#include "http_response.h" +#include "http_response_builder.h" + +#ifndef HTTP_SERVER_MAX_CONCURRENT +#define HTTP_SERVER_MAX_CONCURRENT 5 +#endif + +typedef HttpResponse ParsedHttpRequest; + +/** + * \brief HttpServer implements the logic for setting up an HTTP server. + */ +class HttpServer { +public: + /** + * HttpRequest Constructor + * + * @param[in] network The network interface + */ + HttpServer(NetworkInterface *network) + { + _network = network; + _network->connect(); + } + + ~HttpServer() + { + for (size_t ix = 0; ix < HTTP_SERVER_MAX_CONCURRENT; ix++) { + if (socket_threads[ix]) { + delete socket_threads[ix]; + } + } + } + + /** + * Start running the server (it will run on it's own thread) + */ + nsapi_error_t start(uint16_t port, Callback a_handler) + { + server = new TCPSocket(); + + nsapi_error_t ret; + + ret = server->open(_network); + if (ret != NSAPI_ERROR_OK) { + return ret; + } + + ret = server->bind(port); + if (ret != NSAPI_ERROR_OK) { + return ret; + } + + server->listen(HTTP_SERVER_MAX_CONCURRENT); + + handler = a_handler; + + server_thread.start(callback(this, &HttpServer::server_loop)); + + return NSAPI_ERROR_OK; + } + +private: + + void receive_data() + { + TCPSocket *socket = sockets.back(); + + // needs to keep running until the socket gets closed + while (1) { + + ParsedHttpRequest *response = new ParsedHttpRequest(); + HttpParser *parser = new HttpParser(response, HTTP_REQUEST); + + // Set up a receive buffer (on the heap) + uint8_t *recv_buffer = (uint8_t *)malloc(HTTP_RECEIVE_BUFFER_SIZE); + + // TCPSocket::recv is called until we don't have any data anymore + nsapi_size_or_error_t recv_ret; + while ((recv_ret = socket->recv(recv_buffer, HTTP_RECEIVE_BUFFER_SIZE)) > 0) { + // Pass the chunk into the http_parser + int32_t nparsed = parser->execute((const char *)recv_buffer, recv_ret); + if (nparsed != recv_ret) { + printf("Parsing failed... parsed %ld bytes, received %d bytes\n", nparsed, recv_ret); + recv_ret = -2101; + break; + } + + if (response->is_message_complete()) { + break; + } + } + // error? + if (recv_ret <= 0) { + if (recv_ret < 0) { + printf("Error reading from socket %d\n", recv_ret); + } + + // error = recv_ret; + delete response; + delete parser; + free(recv_buffer); + + if (recv_ret < -3000 || recv_ret == 0) { + return; + } else { + continue; + } + } + + // When done, call parser.finish() + parser->finish(); + + // Free the receive buffer + free(recv_buffer); + + // Let user application handle the request, if user needs a handle to response they need to memcpy themselves + if (recv_ret > 0) { + handler(response, socket); + } + + // Free the response and parser + delete response; + delete parser; + } + } + + void server_loop() + { + while (1) { + TCPSocket *clt_sock = server->accept(); + if (clt_sock) { + log_server_state(); + + sockets.push_back(clt_sock); // so we can clear our disconnected sockets + + // and start listening for events there + Thread *t = new Thread(osPriorityNormal, 2048, NULL, NULL); + t->start(callback(this, &HttpServer::receive_data)); + + socket_thread_metadata_t *m = new socket_thread_metadata_t(); + m->socket = clt_sock; + m->thread = t; + socket_threads.push_back(m); + } else { + delete clt_sock; + } + + for (size_t ix = 0; ix < socket_threads.size(); ix++) { + if ((uint32_t)osThreadGetState(socket_threads[ix]->thread->get_id()) == osThreadTerminated) { + printf("Thread Deleted\r\n"); + socket_threads[ix]->thread->terminate(); + socket_threads[ix]->socket->close(); + delete socket_threads[ix]->thread; + socket_threads.erase(socket_threads.begin() + ix); + } + } + + } + } + + void log_server_state(void) + { + uint8_t count = SocketStats::mbed_stats_socket_get_each(&stats[0], MBED_CONF_NSAPI_SOCKET_STATS_MAX_COUNT); + printf("%-15s%-15s%-15s%-15s%-15s%-15s\n", "ID", "State", "Proto", "Sent", "Recv", "Time"); + + for (int i = 0; i < count; i++) { + printf("%-15p", stats[i].reference_id); + + switch (stats[i].state) { + case SOCK_CLOSED: + printf("%-15s", "Closed"); + break; + case SOCK_OPEN: + printf("%-15s", "Open"); + break; + case SOCK_CONNECTED: + printf("%-15s", "Connected"); + break; + case SOCK_LISTEN: + printf("%-15s", "Listen"); + break; + default: + printf("%-15s", "Error"); + break; + } + + if (NSAPI_TCP == stats[i].proto) { + printf("%-15s", "TCP"); + } else { + printf("%-15s", "UDP"); + } + printf("%-15d", stats[i].sent_bytes); + printf("%-15d", stats[i].recv_bytes); + printf("%-15lld\n", stats[i].last_change_tick); + } + } + + typedef struct { + TCPSocket *socket; + Thread *thread; + } socket_thread_metadata_t; + + TCPSocket *server; + NetworkInterface *_network; + Thread server_thread; + vector sockets; + vector socket_threads; + Callback handler; + mbed_stats_socket_t stats[MBED_CONF_NSAPI_SOCKET_STATS_MAX_COUNT]; +}; + +#endif // _HTTP_SERVER diff --git a/HTTPServer/main.cpp b/HTTPServer/main.cpp new file mode 100644 index 0000000..189888b --- /dev/null +++ b/HTTPServer/main.cpp @@ -0,0 +1,74 @@ +#include "mbed.h" +#include "http_server.h" +#include "http_response_builder.h" +#include "webpage.h" + +DigitalOut led(LED1); +bool stats_enabled = false; + +static uint8_t max_thread_count = 16; +mbed_stats_thread_t *thread_stats = new mbed_stats_thread_t[max_thread_count]; + +void report_thread_stats(void) +{ + printf("================ THREAD STATS ===============\r\n"); + // Collect and print running thread stats + int count = mbed_stats_thread_get_each(thread_stats, max_thread_count); + + for (int i = 0; i < count; i++) { + printf("Name: %s \r\n", thread_stats[i].name); + printf("State: %ld \r\n", thread_stats[i].state); + printf("Stack Space: %ld \r\n", thread_stats[i].stack_space); + } +} + +// Requests come in here +void request_handler(ParsedHttpRequest *request, TCPSocket *socket) +{ + + printf("Request came in: %s %s\n", http_method_str(request->get_method()), request->get_url().c_str()); + + if (request->get_method() == HTTP_GET && request->get_url() == "/") { + HttpResponseBuilder builder(200); + builder.set_header("Content-Type", "text/html; charset=utf-8"); + + builder.send(socket, response, sizeof(response) - 1); + } else if (request->get_method() == HTTP_POST && request->get_url() == "/toggle") { + printf("toggle LED called\n"); + led = !led; + + HttpResponseBuilder builder(200); + builder.send(socket, NULL, 0); + } else if (request->get_method() == HTTP_POST && request->get_url() == "/stats") { + report_thread_stats(); + + HttpResponseBuilder builder(200); + builder.send(socket, NULL, 0); + } else { + HttpResponseBuilder builder(404); + builder.send(socket, NULL, 0); + } +} + +int main() +{ + printf("Running...3.0\r\n"); + + // Connect to the network (see mbed_app.json for the connectivity method used) + NetworkInterface *network = NetworkInterface::get_default_instance(); + if (!network) { + printf("Cannot connect to the network, see serial output\n"); + return 1; + } + + HttpServer server(network); + nsapi_error_t res = server.start(8080, &request_handler); + + if (res == NSAPI_ERROR_OK) { + printf("Server is listening at http://%s:8080\n", network->get_ip_address()); + } else { + printf("Server could not be started... %d\n", res); + } + + wait(osWaitForever); +} diff --git a/HTTPServer/mbed-http.lib b/HTTPServer/mbed-http.lib new file mode 100644 index 0000000..ffd565d --- /dev/null +++ b/HTTPServer/mbed-http.lib @@ -0,0 +1 @@ +https://developer.mbed.org/teams/sandbox/code/mbed-http/#6daf67a96a91 diff --git a/HTTPServer/mbed-os.lib b/HTTPServer/mbed-os.lib new file mode 100644 index 0000000..c4df2fc --- /dev/null +++ b/HTTPServer/mbed-os.lib @@ -0,0 +1 @@ +https://github.com/ARMmbed/mbed-os/#e2ae84ec5922cb5c0a0f204ce84049e84113862e diff --git a/HTTPServer/mbed_app.json b/HTTPServer/mbed_app.json new file mode 100644 index 0000000..c383431 --- /dev/null +++ b/HTTPServer/mbed_app.json @@ -0,0 +1,53 @@ +{ + "config": { + "network-interface": { + "help": "options are ETHERNET, WIFI_ESP8266, WIFI_ODIN, WIFI_RTW, MESH_LOWPAN_ND, MESH_THREAD", + "value": "ETHERNET" + }, + "mesh_radio_type": { + "help": "options are ATMEL, MCR20, SPIRIT1, EFR32", + "value": "ATMEL" + }, + "esp8266-tx": { + "help": "Pin used as TX (connects to ESP8266 RX)", + "value": "D1" + }, + "esp8266-rx": { + "help": "Pin used as RX (connects to ESP8266 TX)", + "value": "D0" + }, + "esp8266-debug": { + "value": true + }, + "wifi-ssid": { + "value": "\"SSID\"" + }, + "wifi-password": { + "value": "\"Password\"" + } + }, + "target_overrides": { + "*": { + "target.features_add": ["NANOSTACK", "LOWPAN_ROUTER", "COMMON_PAL"], + "mbed-mesh-api.6lowpan-nd-channel-page": 0, + "mbed-mesh-api.6lowpan-nd-channel": 12, + "mbed-trace.enable": 0, + "mbed-http.http-buffer-size": 2048, + "platform.stdio-baud-rate": 115200, + "platform.thread-stats-enabled": 1, + "nsapi.socket-stats-enable": 1 + }, + "HEXIWEAR": { + "esp8266-tx": "PTD3", + "esp8266-rx": "PTD2" + }, + "NUCLEO_F401RE": { + "esp8266-tx": "D8", + "esp8266-rx": "D2" + }, + "NUCLEO_F411RE": { + "esp8266-tx": "D8", + "esp8266-rx": "D2" + } + } +} diff --git a/HTTPServer/webpage.h b/HTTPServer/webpage.h new file mode 100644 index 0000000..bce7183 --- /dev/null +++ b/HTTPServer/webpage.h @@ -0,0 +1,16 @@ +char response[] = + "" + "Hello from mbed" + "" + "

mbed webserver

" + "" + "" + "" + "" + + "" + ""; From 0c44933783720ab6d4e2659630a322b7390b609a Mon Sep 17 00:00:00 2001 From: kegilbert Date: Wed, 20 Feb 2019 19:16:55 -0600 Subject: [PATCH 2/8] Large scale refactor --- HTTPServer/LICENSE | 191 ---------------------- HTTPServer/README.md | 2 +- HTTPServer/http_response_builder.cpp | 231 +++++++++++++++++++++++++++ HTTPServer/http_response_builder.h | 214 +------------------------ HTTPServer/http_server.cpp | 188 ++++++++++++++++++++++ HTTPServer/http_server.h | 174 +------------------- HTTPServer/main.cpp | 9 +- HTTPServer/mbed_app.json | 39 +---- HTTPServer/webpage.h | 8 +- 9 files changed, 442 insertions(+), 614 deletions(-) delete mode 100644 HTTPServer/LICENSE create mode 100644 HTTPServer/http_response_builder.cpp create mode 100644 HTTPServer/http_server.cpp diff --git a/HTTPServer/LICENSE b/HTTPServer/LICENSE deleted file mode 100644 index 35ae8d2..0000000 --- a/HTTPServer/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - ---- - -http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright -Igor Sysoev. - -Additional changes are licensed under the same terms as NGINX and -copyright Joyent, Inc. and other Node contributors. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/HTTPServer/README.md b/HTTPServer/README.md index f7f752d..01995ee 100644 --- a/HTTPServer/README.md +++ b/HTTPServer/README.md @@ -6,7 +6,7 @@ Request parsing is done through [nodejs/http-parser](https://github.com/nodejs/h ## To build -1. Open ``mbed_app.json`` and change the `network-interface` option to your connectivity method ([more info](https://github.com/ARMmbed/easy-connect)). +1. Open ``mbed_app.json`` and change the `network-default-interface-type` option to your connectivity method ([more info](https://os.mbed.com/docs/development/apis/network-interfaces.html) 2. Build the project in the online compiler or using mbed CLI. 3. Flash the project to your development board. 4. Attach a serial monitor to your board to see the debug messages. diff --git a/HTTPServer/http_response_builder.cpp b/HTTPServer/http_response_builder.cpp new file mode 100644 index 0000000..e0b60e7 --- /dev/null +++ b/HTTPServer/http_response_builder.cpp @@ -0,0 +1,231 @@ +/* + * PackageLicenseDeclared: Apache-2.0 + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mbed.h" +#include "http_response_builder.h" + +static const char *get_http_status_string(uint16_t status_code) +{ + switch (status_code) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Entity"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + default : + return "Unknown"; + } +} + +HttpResponseBuilder::HttpResponseBuilder(uint16_t a_status_code) + : status_code(a_status_code), status_message(get_http_status_string(a_status_code)) +{ +} + +void HttpResponseBuilder::set_header(string key, string value) +{ + map::iterator it = headers.find(key); + + if (it != headers.end()) { + it->second = value; + } else { + headers.insert(headers.end(), pair(key, value)); + } +} + +char *HttpResponseBuilder::build(const void *body, size_t body_size, size_t *size) +{ + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%d", body_size); + set_header("Content-Length", string(buffer)); + + char status_code_buffer[5]; + snprintf(status_code_buffer, sizeof(status_code_buffer), "%d", status_code /* max 5 digits */); + + *size = 0; + + // first line is HTTP/1.1 200 OK\r\n + *size += 8 + 1 + strlen(status_code_buffer) + 1 + strlen(status_message) + 2; + + // after that we'll do the headers + typedef map::iterator it_type; + for (it_type it = headers.begin(); it != headers.end(); it++) { + // line is KEY: VALUE\r\n + *size += it->first.length() + 1 + 1 + it->second.length() + 2; + } + + // then the body, first an extra newline + *size += 2; + + // body + *size += body_size; + + // Now let's print it + char *res = (char *)calloc(*size + 1, 1); + char *originalRes = res; + + res += sprintf(res, "HTTP/1.1 %s %s\r\n", status_code_buffer, status_message); + + typedef map::iterator it_type; + for (it_type it = headers.begin(); it != headers.end(); it++) { + // line is KEY: VALUE\r\n + res += sprintf(res, "%s: %s\r\n", it->first.c_str(), it->second.c_str()); + } + + res += sprintf(res, "\r\n"); + + if (body_size > 0) { + memcpy(res, body, body_size); + } + res += body_size; + + // Uncomment to debug... + // printf("----- BEGIN RESPONSE -----\n"); + // printf("%s", originalRes); + // printf("----- END RESPONSE -----\n"); + + return originalRes; +} + +nsapi_error_t HttpResponseBuilder::send(TCPSocket *socket, const void *body, size_t body_size) +{ + if (!socket) { + return NSAPI_ERROR_NO_SOCKET; + } + + size_t res_size; + char *response = build(body, body_size, &res_size); + + nsapi_error_t r = socket->send(response, res_size); + + free(response); + + return r; +} diff --git a/HTTPServer/http_response_builder.h b/HTTPServer/http_response_builder.h index 130fe49..e3f0a07 100644 --- a/HTTPServer/http_response_builder.h +++ b/HTTPServer/http_response_builder.h @@ -23,223 +23,17 @@ #include "http_parser.h" #include "http_parsed_url.h" -static const char *get_http_status_string(uint16_t status_code) -{ - switch (status_code) { - case 100: - return "Continue"; - case 101: - return "Switching Protocols"; - case 102: - return "Processing"; - case 200: - return "OK"; - case 201: - return "Created"; - case 202: - return "Accepted"; - case 203: - return "Non-Authoritative Information"; - case 204: - return "No Content"; - case 205: - return "Reset Content"; - case 206: - return "Partial Content"; - case 207: - return "Multi-Status"; - case 208: - return "Already Reported"; - case 226: - return "IM Used"; - case 300: - return "Multiple Choices"; - case 301: - return "Moved Permanently"; - case 302: - return "Found"; - case 303: - return "See Other"; - case 304: - return "Not Modified"; - case 305: - return "Use Proxy"; - case 307: - return "Temporary Redirect"; - case 308: - return "Permanent Redirect"; - case 400: - return "Bad Request"; - case 401: - return "Unauthorized"; - case 402: - return "Payment Required"; - case 403: - return "Forbidden"; - case 404: - return "Not Found"; - case 405: - return "Method Not Allowed"; - case 406: - return "Not Acceptable"; - case 407: - return "Proxy Authentication Required"; - case 408: - return "Request Timeout"; - case 409: - return "Conflict"; - case 410: - return "Gone"; - case 411: - return "Length Required"; - case 412: - return "Precondition Failed"; - case 413: - return "Payload Too Large"; - case 414: - return "URI Too Long"; - case 415: - return "Unsupported Media Type"; - case 416: - return "Range Not Satisfiable"; - case 417: - return "Expectation Failed"; - case 421: - return "Misdirected Request"; - case 422: - return "Unprocessable Entity"; - case 423: - return "Locked"; - case 424: - return "Failed Dependency"; - case 426: - return "Upgrade Required"; - case 428: - return "Precondition Required"; - case 429: - return "Too Many Requests"; - case 431: - return "Request Header Fields Too Large"; - case 451: - return "Unavailable For Legal Reasons"; - case 500: - return "Internal Server Error"; - case 501: - return "Not Implemented"; - case 502: - return "Bad Gateway"; - case 503: - return "Service Unavailable"; - case 504: - return "Gateway Timeout"; - case 505: - return "HTTP Version Not Supported"; - case 506: - return "Variant Also Negotiates"; - case 507: - return "Insufficient Storage"; - case 508: - return "Loop Detected"; - case 510: - return "Not Extended"; - case 511: - return "Network Authentication Required"; - default : - return "Unknown"; - } -} - class HttpResponseBuilder { public: - HttpResponseBuilder(uint16_t a_status_code) - : status_code(a_status_code), status_message(get_http_status_string(a_status_code)) - { - } + HttpResponseBuilder(uint16_t a_status_code); /** * Set a header for the request * If the key already exists, it will be overwritten... */ - void set_header(string key, string value) - { - map::iterator it = headers.find(key); - - if (it != headers.end()) { - it->second = value; - } else { - headers.insert(headers.end(), pair(key, value)); - } - } - - char *build(const void *body, size_t body_size, size_t *size) - { - char buffer[10]; - snprintf(buffer, sizeof(buffer), "%d", body_size); - set_header("Content-Length", string(buffer)); - - char status_code_buffer[5]; - snprintf(status_code_buffer, sizeof(status_code_buffer), "%d", status_code /* max 5 digits */); - - *size = 0; - - // first line is HTTP/1.1 200 OK\r\n - *size += 8 + 1 + strlen(status_code_buffer) + 1 + strlen(status_message) + 2; - - // after that we'll do the headers - typedef map::iterator it_type; - for (it_type it = headers.begin(); it != headers.end(); it++) { - // line is KEY: VALUE\r\n - *size += it->first.length() + 1 + 1 + it->second.length() + 2; - } - - // then the body, first an extra newline - *size += 2; - - // body - *size += body_size; - - // Now let's print it - char *res = (char *)calloc(*size + 1, 1); - char *originalRes = res; - - res += sprintf(res, "HTTP/1.1 %s %s\r\n", status_code_buffer, status_message); - - typedef map::iterator it_type; - for (it_type it = headers.begin(); it != headers.end(); it++) { - // line is KEY: VALUE\r\n - res += sprintf(res, "%s: %s\r\n", it->first.c_str(), it->second.c_str()); - } - - res += sprintf(res, "\r\n"); - - if (body_size > 0) { - memcpy(res, body, body_size); - } - res += body_size; - - // Uncomment to debug... - // printf("----- BEGIN RESPONSE -----\n"); - // printf("%s", originalRes); - // printf("----- END RESPONSE -----\n"); - - return originalRes; - } - - nsapi_error_t send(TCPSocket *socket, const void *body, size_t body_size) - { - if (!socket) { - return NSAPI_ERROR_NO_SOCKET; - } - - size_t res_size; - char *response = build(body, body_size, &res_size); - - nsapi_error_t r = socket->send(response, res_size); - - free(response); - - return r; - } + void set_header(string key, string value); + char *build(const void *body, size_t body_size, size_t *size); + nsapi_error_t send(TCPSocket *socket, const void *body, size_t body_size); private: uint16_t status_code; diff --git a/HTTPServer/http_server.cpp b/HTTPServer/http_server.cpp new file mode 100644 index 0000000..ce19d60 --- /dev/null +++ b/HTTPServer/http_server.cpp @@ -0,0 +1,188 @@ +/* + * PackageLicenseDeclared: Apache-2.0 + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_server.h" + +HttpServer::HttpServer(NetworkInterface *network) : _network(network) +{ + _network = network; + _network->connect(); +} + +HttpServer::~HttpServer() +{ + for (size_t ix = 0; ix < HTTP_SERVER_MAX_CONCURRENT; ix++) { + if (socket_threads[ix]) { + delete socket_threads[ix]; + } + } +} + +nsapi_error_t HttpServer::start(uint16_t port, Callback a_handler) +{ + server = new TCPSocket(); + + nsapi_error_t ret; + + ret = server->open(_network); + if (ret != NSAPI_ERROR_OK) { + return ret; + } + + ret = server->bind(port); + if (ret != NSAPI_ERROR_OK) { + return ret; + } + + server->listen(HTTP_SERVER_MAX_CONCURRENT); + + handler = a_handler; + + server_thread.start(callback(this, &HttpServer::server_loop)); + + return NSAPI_ERROR_OK; +} + +void HttpServer::receive_data() +{ + TCPSocket *socket = sockets.back(); + + // needs to keep running until the socket gets closed + while (1) { + + ParsedHttpRequest *response = new ParsedHttpRequest(); + HttpParser *parser = new HttpParser(response, HTTP_REQUEST); + + // Set up a receive buffer (on the heap) + uint8_t *recv_buffer = (uint8_t *)malloc(HTTP_RECEIVE_BUFFER_SIZE); + + // TCPSocket::recv is called until we don't have any data anymore + nsapi_size_or_error_t recv_ret; + while ((recv_ret = socket->recv(recv_buffer, HTTP_RECEIVE_BUFFER_SIZE)) > 0) { + // Pass the chunk into the http_parser + int32_t nparsed = parser->execute((const char *)recv_buffer, recv_ret); + if (nparsed != recv_ret) { + printf("Parsing failed... parsed %ld bytes, received %d bytes\n", nparsed, recv_ret); + recv_ret = -2101; + break; + } + + if (response->is_message_complete()) { + break; + } + } + + if (recv_ret <= 0) { + if (recv_ret < 0) { + printf("Error reading from socket %d\n", recv_ret); + } + + // error = recv_ret; + delete response; + delete parser; + free(recv_buffer); + + return; + } + + // When done, call parser.finish() + parser->finish(); + + // Free the receive buffer + free(recv_buffer); + + // Let user application handle the request, if user needs a handle to response they need to memcpy themselves + if (recv_ret > 0) { + handler(response, socket); + } + + // Free the response and parser + delete response; + delete parser; + } +} + +void HttpServer::server_loop() +{ + while (1) { + TCPSocket *clt_sock = server->accept(); + if (clt_sock) { + log_server_state(); + + sockets.push_back(clt_sock); // so we can clear our disconnected sockets + + // and start listening for events there + Thread *t = new Thread(osPriorityNormal, 2048, NULL, NULL); + t->start(callback(this, &HttpServer::receive_data)); + + socket_thread_metadata_t *m = new socket_thread_metadata_t(); + m->socket = clt_sock; + m->thread = t; + socket_threads.push_back(m); + } else { + delete clt_sock; + } + + for (size_t ix = 0; ix < socket_threads.size(); ix++) { + if ((uint32_t)osThreadGetState(socket_threads[ix]->thread->get_id()) == osThreadTerminated) { + printf("Thread Deleted\r\n"); + socket_threads[ix]->thread->terminate(); + socket_threads[ix]->socket->close(); + delete socket_threads[ix]->thread; + socket_threads.erase(socket_threads.begin() + ix); + } + } + + } +} + +void HttpServer::log_server_state(void) +{ + uint8_t count = SocketStats::mbed_stats_socket_get_each(&stats[0], MBED_CONF_NSAPI_SOCKET_STATS_MAX_COUNT); + printf("%-15s%-15s%-15s%-15s%-15s%-15s\n", "ID", "State", "Proto", "Sent", "Recv", "Time"); + + for (int i = 0; i < count; i++) { + printf("%-15p", stats[i].reference_id); + + switch (stats[i].state) { + case SOCK_CLOSED: + printf("%-15s", "Closed"); + break; + case SOCK_OPEN: + printf("%-15s", "Open"); + break; + case SOCK_CONNECTED: + printf("%-15s", "Connected"); + break; + case SOCK_LISTEN: + printf("%-15s", "Listen"); + break; + default: + printf("%-15s", "Error"); + break; + } + + if (NSAPI_TCP == stats[i].proto) { + printf("%-15s", "TCP"); + } else { + printf("%-15s", "UDP"); + } + printf("%-15d", stats[i].sent_bytes); + printf("%-15d", stats[i].recv_bytes); + printf("%-15lld\n", stats[i].last_change_tick); + } +} diff --git a/HTTPServer/http_server.h b/HTTPServer/http_server.h index b562b98..f6f22a5 100644 --- a/HTTPServer/http_server.h +++ b/HTTPServer/http_server.h @@ -39,184 +39,22 @@ class HttpServer { * * @param[in] network The network interface */ - HttpServer(NetworkInterface *network) - { - _network = network; - _network->connect(); - } + HttpServer(NetworkInterface *network); - ~HttpServer() - { - for (size_t ix = 0; ix < HTTP_SERVER_MAX_CONCURRENT; ix++) { - if (socket_threads[ix]) { - delete socket_threads[ix]; - } - } - } + ~HttpServer(); /** * Start running the server (it will run on it's own thread) */ - nsapi_error_t start(uint16_t port, Callback a_handler) - { - server = new TCPSocket(); - - nsapi_error_t ret; - - ret = server->open(_network); - if (ret != NSAPI_ERROR_OK) { - return ret; - } - - ret = server->bind(port); - if (ret != NSAPI_ERROR_OK) { - return ret; - } - - server->listen(HTTP_SERVER_MAX_CONCURRENT); - - handler = a_handler; - - server_thread.start(callback(this, &HttpServer::server_loop)); - - return NSAPI_ERROR_OK; - } + nsapi_error_t start(uint16_t port, Callback a_handler); private: - void receive_data() - { - TCPSocket *socket = sockets.back(); - - // needs to keep running until the socket gets closed - while (1) { - - ParsedHttpRequest *response = new ParsedHttpRequest(); - HttpParser *parser = new HttpParser(response, HTTP_REQUEST); - - // Set up a receive buffer (on the heap) - uint8_t *recv_buffer = (uint8_t *)malloc(HTTP_RECEIVE_BUFFER_SIZE); - - // TCPSocket::recv is called until we don't have any data anymore - nsapi_size_or_error_t recv_ret; - while ((recv_ret = socket->recv(recv_buffer, HTTP_RECEIVE_BUFFER_SIZE)) > 0) { - // Pass the chunk into the http_parser - int32_t nparsed = parser->execute((const char *)recv_buffer, recv_ret); - if (nparsed != recv_ret) { - printf("Parsing failed... parsed %ld bytes, received %d bytes\n", nparsed, recv_ret); - recv_ret = -2101; - break; - } - - if (response->is_message_complete()) { - break; - } - } - // error? - if (recv_ret <= 0) { - if (recv_ret < 0) { - printf("Error reading from socket %d\n", recv_ret); - } - - // error = recv_ret; - delete response; - delete parser; - free(recv_buffer); - - if (recv_ret < -3000 || recv_ret == 0) { - return; - } else { - continue; - } - } - - // When done, call parser.finish() - parser->finish(); - - // Free the receive buffer - free(recv_buffer); - - // Let user application handle the request, if user needs a handle to response they need to memcpy themselves - if (recv_ret > 0) { - handler(response, socket); - } - - // Free the response and parser - delete response; - delete parser; - } - } - - void server_loop() - { - while (1) { - TCPSocket *clt_sock = server->accept(); - if (clt_sock) { - log_server_state(); - - sockets.push_back(clt_sock); // so we can clear our disconnected sockets - - // and start listening for events there - Thread *t = new Thread(osPriorityNormal, 2048, NULL, NULL); - t->start(callback(this, &HttpServer::receive_data)); - - socket_thread_metadata_t *m = new socket_thread_metadata_t(); - m->socket = clt_sock; - m->thread = t; - socket_threads.push_back(m); - } else { - delete clt_sock; - } - - for (size_t ix = 0; ix < socket_threads.size(); ix++) { - if ((uint32_t)osThreadGetState(socket_threads[ix]->thread->get_id()) == osThreadTerminated) { - printf("Thread Deleted\r\n"); - socket_threads[ix]->thread->terminate(); - socket_threads[ix]->socket->close(); - delete socket_threads[ix]->thread; - socket_threads.erase(socket_threads.begin() + ix); - } - } - - } - } - - void log_server_state(void) - { - uint8_t count = SocketStats::mbed_stats_socket_get_each(&stats[0], MBED_CONF_NSAPI_SOCKET_STATS_MAX_COUNT); - printf("%-15s%-15s%-15s%-15s%-15s%-15s\n", "ID", "State", "Proto", "Sent", "Recv", "Time"); - - for (int i = 0; i < count; i++) { - printf("%-15p", stats[i].reference_id); + void receive_data(); - switch (stats[i].state) { - case SOCK_CLOSED: - printf("%-15s", "Closed"); - break; - case SOCK_OPEN: - printf("%-15s", "Open"); - break; - case SOCK_CONNECTED: - printf("%-15s", "Connected"); - break; - case SOCK_LISTEN: - printf("%-15s", "Listen"); - break; - default: - printf("%-15s", "Error"); - break; - } + void server_loop(); - if (NSAPI_TCP == stats[i].proto) { - printf("%-15s", "TCP"); - } else { - printf("%-15s", "UDP"); - } - printf("%-15d", stats[i].sent_bytes); - printf("%-15d", stats[i].recv_bytes); - printf("%-15lld\n", stats[i].last_change_tick); - } - } + void log_server_state(void); typedef struct { TCPSocket *socket; diff --git a/HTTPServer/main.cpp b/HTTPServer/main.cpp index 189888b..aecd383 100644 --- a/HTTPServer/main.cpp +++ b/HTTPServer/main.cpp @@ -1,3 +1,8 @@ +/* mbed Microcontroller Library + * Copyright (c) 2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + */ + #include "mbed.h" #include "http_server.h" #include "http_response_builder.h" @@ -43,7 +48,7 @@ void request_handler(ParsedHttpRequest *request, TCPSocket *socket) report_thread_stats(); HttpResponseBuilder builder(200); - builder.send(socket, NULL, 0); + builder.send(socket, /*NULL, 0*/"HELLO!", 6); } else { HttpResponseBuilder builder(404); builder.send(socket, NULL, 0); @@ -52,7 +57,7 @@ void request_handler(ParsedHttpRequest *request, TCPSocket *socket) int main() { - printf("Running...3.0\r\n"); + printf("Starting Server...\r\n"); // Connect to the network (see mbed_app.json for the connectivity method used) NetworkInterface *network = NetworkInterface::get_default_instance(); diff --git a/HTTPServer/mbed_app.json b/HTTPServer/mbed_app.json index c383431..12a8f98 100644 --- a/HTTPServer/mbed_app.json +++ b/HTTPServer/mbed_app.json @@ -1,53 +1,16 @@ { "config": { - "network-interface": { + "network-default-interface-type": { "help": "options are ETHERNET, WIFI_ESP8266, WIFI_ODIN, WIFI_RTW, MESH_LOWPAN_ND, MESH_THREAD", "value": "ETHERNET" - }, - "mesh_radio_type": { - "help": "options are ATMEL, MCR20, SPIRIT1, EFR32", - "value": "ATMEL" - }, - "esp8266-tx": { - "help": "Pin used as TX (connects to ESP8266 RX)", - "value": "D1" - }, - "esp8266-rx": { - "help": "Pin used as RX (connects to ESP8266 TX)", - "value": "D0" - }, - "esp8266-debug": { - "value": true - }, - "wifi-ssid": { - "value": "\"SSID\"" - }, - "wifi-password": { - "value": "\"Password\"" } }, "target_overrides": { "*": { - "target.features_add": ["NANOSTACK", "LOWPAN_ROUTER", "COMMON_PAL"], - "mbed-mesh-api.6lowpan-nd-channel-page": 0, - "mbed-mesh-api.6lowpan-nd-channel": 12, - "mbed-trace.enable": 0, "mbed-http.http-buffer-size": 2048, "platform.stdio-baud-rate": 115200, "platform.thread-stats-enabled": 1, "nsapi.socket-stats-enable": 1 - }, - "HEXIWEAR": { - "esp8266-tx": "PTD3", - "esp8266-rx": "PTD2" - }, - "NUCLEO_F401RE": { - "esp8266-tx": "D8", - "esp8266-rx": "D2" - }, - "NUCLEO_F411RE": { - "esp8266-tx": "D8", - "esp8266-rx": "D2" } } } diff --git a/HTTPServer/webpage.h b/HTTPServer/webpage.h index bce7183..a713786 100644 --- a/HTTPServer/webpage.h +++ b/HTTPServer/webpage.h @@ -5,12 +5,12 @@ char response[] = "

mbed webserver

" "" "" "" - "" - "" ""; From 25796f4e5b79546c5911ac1cb52a51b147075f31 Mon Sep 17 00:00:00 2001 From: Jan Jongboom Date: Mon, 25 Feb 2019 12:25:05 +0100 Subject: [PATCH 3/8] Add default content type for responses --- HTTPServer/http_response_builder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/HTTPServer/http_response_builder.cpp b/HTTPServer/http_response_builder.cpp index e0b60e7..3dcb3c6 100644 --- a/HTTPServer/http_response_builder.cpp +++ b/HTTPServer/http_response_builder.cpp @@ -147,6 +147,7 @@ static const char *get_http_status_string(uint16_t status_code) HttpResponseBuilder::HttpResponseBuilder(uint16_t a_status_code) : status_code(a_status_code), status_message(get_http_status_string(a_status_code)) { + set_header("Content-Type", "text/plain"); } void HttpResponseBuilder::set_header(string key, string value) From 6b4246c906f17c9510a01126fe1e45b9dfc4483e Mon Sep 17 00:00:00 2001 From: Jan Jongboom Date: Mon, 25 Feb 2019 12:25:20 +0100 Subject: [PATCH 4/8] Don't call connect from the http_server --- HTTPServer/http_server.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/HTTPServer/http_server.cpp b/HTTPServer/http_server.cpp index ce19d60..2ccb379 100644 --- a/HTTPServer/http_server.cpp +++ b/HTTPServer/http_server.cpp @@ -20,7 +20,6 @@ HttpServer::HttpServer(NetworkInterface *network) : _network(network) { _network = network; - _network->connect(); } HttpServer::~HttpServer() @@ -48,7 +47,10 @@ nsapi_error_t HttpServer::start(uint16_t port, Callbacklisten(HTTP_SERVER_MAX_CONCURRENT); + ret = server->listen(HTTP_SERVER_MAX_CONCURRENT); + if (ret != NSAPI_ERROR_OK) { + return ret; + } handler = a_handler; From c24af71b42d2c53f9a45ac3715ba5dcbb1206b69 Mon Sep 17 00:00:00 2001 From: Jan Jongboom Date: Mon, 25 Feb 2019 12:25:44 +0100 Subject: [PATCH 5/8] Clean up the mbed_app.json from references to easy-connect --- HTTPServer/mbed_app.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/HTTPServer/mbed_app.json b/HTTPServer/mbed_app.json index 12a8f98..f1c6f83 100644 --- a/HTTPServer/mbed_app.json +++ b/HTTPServer/mbed_app.json @@ -1,16 +1,14 @@ { - "config": { - "network-default-interface-type": { - "help": "options are ETHERNET, WIFI_ESP8266, WIFI_ODIN, WIFI_RTW, MESH_LOWPAN_ND, MESH_THREAD", - "value": "ETHERNET" - } - }, "target_overrides": { "*": { "mbed-http.http-buffer-size": 2048, "platform.stdio-baud-rate": 115200, + "platform.stdio-convert-newlines": true, "platform.thread-stats-enabled": 1, - "nsapi.socket-stats-enable": 1 + "nsapi.socket-stats-enable": 1, + "nsapi.default-wifi-security": "WPA_WPA2", + "nsapi.default-wifi-ssid": "\"SSID\"", + "nsapi.default-wifi-password": "\"Password\"" } } } From f68c441a2bde85693dde75bb95252c01cfbbc9ce Mon Sep 17 00:00:00 2001 From: Jan Jongboom Date: Mon, 25 Feb 2019 12:25:55 +0100 Subject: [PATCH 6/8] Update to Mbed OS 5.11 --- HTTPServer/mbed-os.lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer/mbed-os.lib b/HTTPServer/mbed-os.lib index c4df2fc..efedfa8 100644 --- a/HTTPServer/mbed-os.lib +++ b/HTTPServer/mbed-os.lib @@ -1 +1 @@ -https://github.com/ARMmbed/mbed-os/#e2ae84ec5922cb5c0a0f204ce84049e84113862e +https://github.com/ARMmbed/mbed-os/#51d55508e8400b60af467005646c4e2164738d48 From a3e2e327d4fa6dc0fa3630f998a0827c9ca7c03d Mon Sep 17 00:00:00 2001 From: Jan Jongboom Date: Mon, 25 Feb 2019 12:26:11 +0100 Subject: [PATCH 7/8] Add JSON reply with thread stats and show them on the web page --- HTTPServer/.gitignore | 4 +++ HTTPServer/.mbedignore | 3 ++ HTTPServer/main.cpp | 63 ++++++++++++++++++++++++++-------------- HTTPServer/rapidjson.lib | 1 + HTTPServer/webpage.h | 63 ++++++++++++++++++++++++++++++---------- 5 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 HTTPServer/.gitignore create mode 100644 HTTPServer/.mbedignore create mode 100644 HTTPServer/rapidjson.lib diff --git a/HTTPServer/.gitignore b/HTTPServer/.gitignore new file mode 100644 index 0000000..5c1f8f1 --- /dev/null +++ b/HTTPServer/.gitignore @@ -0,0 +1,4 @@ +BUILD/ +mbed-http/ +mbed-os/ +rapidjson/ diff --git a/HTTPServer/.mbedignore b/HTTPServer/.mbedignore new file mode 100644 index 0000000..8530a26 --- /dev/null +++ b/HTTPServer/.mbedignore @@ -0,0 +1,3 @@ +rapidjson/test/* +rapidjson/example/* +rapidjson/include/rapidjson/msinttypes/* diff --git a/HTTPServer/main.cpp b/HTTPServer/main.cpp index aecd383..c7785e4 100644 --- a/HTTPServer/main.cpp +++ b/HTTPServer/main.cpp @@ -7,6 +7,11 @@ #include "http_server.h" #include "http_response_builder.h" #include "webpage.h" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +using namespace rapidjson; DigitalOut led(LED1); bool stats_enabled = false; @@ -14,23 +19,9 @@ bool stats_enabled = false; static uint8_t max_thread_count = 16; mbed_stats_thread_t *thread_stats = new mbed_stats_thread_t[max_thread_count]; -void report_thread_stats(void) -{ - printf("================ THREAD STATS ===============\r\n"); - // Collect and print running thread stats - int count = mbed_stats_thread_get_each(thread_stats, max_thread_count); - - for (int i = 0; i < count; i++) { - printf("Name: %s \r\n", thread_stats[i].name); - printf("State: %ld \r\n", thread_stats[i].state); - printf("Stack Space: %ld \r\n", thread_stats[i].stack_space); - } -} - // Requests come in here void request_handler(ParsedHttpRequest *request, TCPSocket *socket) { - printf("Request came in: %s %s\n", http_method_str(request->get_method()), request->get_url().c_str()); if (request->get_method() == HTTP_GET && request->get_url() == "/") { @@ -44,25 +35,55 @@ void request_handler(ParsedHttpRequest *request, TCPSocket *socket) HttpResponseBuilder builder(200); builder.send(socket, NULL, 0); - } else if (request->get_method() == HTTP_POST && request->get_url() == "/stats") { - report_thread_stats(); + } else if (request->get_method() == HTTP_GET && request->get_url() == "/api/stats") { + // create JSON object with information about the thread statistics + rapidjson::Document document; + document.SetArray(); + + int count = mbed_stats_thread_get_each(thread_stats, max_thread_count); + for (int i = 0; i < count; i++) { + Value thread_info(kObjectType); + Value thread_name; + thread_name.SetString(thread_stats[i].name, strlen(thread_stats[i].name), document.GetAllocator()); + + thread_info.AddMember("name", thread_name, document.GetAllocator()); + thread_info.AddMember("state", static_cast(thread_stats[i].state), document.GetAllocator()); + thread_info.AddMember("stack_space", static_cast(thread_stats[i].stack_space), document.GetAllocator()); + document.PushBack(thread_info, document.GetAllocator()); + } + + StringBuffer strbuf; + Writer writer(strbuf); + document.Accept(writer); HttpResponseBuilder builder(200); - builder.send(socket, /*NULL, 0*/"HELLO!", 6); + builder.set_header("Content-Type", "application/json"); + builder.send(socket, strbuf.GetString(), strbuf.GetSize()); } else { + char resp[512]; + int resp_length = snprintf(resp, sizeof(resp), "Page not found: '%s'", request->get_url().c_str()); + HttpResponseBuilder builder(404); - builder.send(socket, NULL, 0); + builder.send(socket, resp, resp_length); } } int main() { - printf("Starting Server...\r\n"); + printf("Connecting to network...\r\n"); - // Connect to the network (see mbed_app.json for the connectivity method used) + // Connect to the network with the default network interface + // If you use a shield or an external module you need to replace this line + // and load the driver yourself. See https://os.mbed.com/docs/mbed-os/v5.11/reference/ip-networking.html NetworkInterface *network = NetworkInterface::get_default_instance(); if (!network) { - printf("Cannot connect to the network, see serial output\n"); + printf("Could not find default network instance\n"); + return 1; + } + + nsapi_error_t connect_status = network->connect(); + if (connect_status != NSAPI_ERROR_OK) { + printf("Could not connect to network\n"); return 1; } diff --git a/HTTPServer/rapidjson.lib b/HTTPServer/rapidjson.lib new file mode 100644 index 0000000..eab01de --- /dev/null +++ b/HTTPServer/rapidjson.lib @@ -0,0 +1 @@ +https://github.com/Tencent/rapidjson/#7484e06c589873e1ed80382d262087e4fa80fb63 diff --git a/HTTPServer/webpage.h b/HTTPServer/webpage.h index a713786..42a3969 100644 --- a/HTTPServer/webpage.h +++ b/HTTPServer/webpage.h @@ -1,16 +1,47 @@ -char response[] = - "" - "Hello from mbed" - "" - "

mbed webserver

" - "" - "" - "" - "" - "" - ""; +const char response[] = + "\r\n" + "\r\n" + "\r\n" + " Hello from Mbed OS\r\n" + " \r\n" + "\r\n" + "\r\n" + "

Mbed OS Webserver

\r\n" + "\r\n" + "\r\n" + "
\r\n" + "\r\n" + "\r\n" + "\r\n" + ; From 0e1f40b98b44d7d850bc4d37de889d5db60cf61c Mon Sep 17 00:00:00 2001 From: kegilbert Date: Tue, 26 Feb 2019 18:25:44 -0600 Subject: [PATCH 8/8] Increase server_thread default stack size --- HTTPServer/http_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPServer/http_server.cpp b/HTTPServer/http_server.cpp index 2ccb379..bd632e2 100644 --- a/HTTPServer/http_server.cpp +++ b/HTTPServer/http_server.cpp @@ -17,7 +17,7 @@ #include "http_server.h" -HttpServer::HttpServer(NetworkInterface *network) : _network(network) +HttpServer::HttpServer(NetworkInterface *network) : _network(network), server_thread(osPriorityNormal, 2048, NULL, "server_thread") { _network = network; }