Webserver example for mbed OS 5 - HTTP 1.1 and multi-threaded

Dependencies:   mbed-http

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.

To build

  1. Open mbed_app.json and change the network-interface option to your connectivity method (more info).
  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.

But every networking stack that supports the mbed OS 5 NetworkInterface API should work.

source/http_response_builder.h

Committer:
Jan Jongboom
Date:
2017-07-31
Revision:
1:fe3df398bdf5
Parent:
0:41f820ea137a

File content as of revision 1:fe3df398bdf5:

/*
 * 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);

        nsapi_error_t r = socket->send(response, res_size);

        free(response);

        return r;
    }

private:
    uint16_t status_code;
    const char* status_message;
    map<string, string> headers;
};

#endif // _MBED_HTTP_RESPONSE_BUILDER_