Webserver example for Nuvoton NuMaker boards and mbed OS 5.15 - HTTP 1.1 and multi-threaded.

Dependencies:   mbed-http

For Mbed OS 6, please use the NuMaker-simple-httpd example.

This application demonstrates how to run an HTTP server on an mbed OS 5 device. It is derived from http-webserver-example Request parsing is done through nodejs/http-parser.

Fixed for Mbed OS 5.15 or later

Tested on

NuMaker IoT-M487 with Ethernet NuMaker PFM-M487 with Ethernet

Revision:
0:41f820ea137a
Child:
1:fe3df398bdf5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/http_response_builder.h	Mon Jul 31 15:41:22 2017 +0200
@@ -0,0 +1,181 @@
+/*
+ * 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 <string>
+#include <map>
+#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<string, string>::iterator it = headers.find(key);
+
+        if (it != headers.end()) {
+            it->second = value;
+        }
+        else {
+            headers.insert(headers.end(), pair<string, string>(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<string, string>::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<string, string>::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);
+
+        return socket->send(response, res_size);
+    }
+
+private:
+    uint16_t status_code;
+    const char* status_message;
+    map<string, string> headers;
+};
+
+#endif // _MBED_HTTP_RESPONSE_BUILDER_