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.

Files at this revision

API Documentation at this revision

Comitter:
Jan Jongboom
Date:
Mon Jul 31 17:12:21 2017 +0200
Parent:
0:41f820ea137a
Commit message:
Compatibility with LwIP. Fix memory leaks.

Changed in this revision

.gitignore Show annotated file Show diff for this revision Revisions of this file
.hgignore Show annotated file Show diff for this revision Revisions of this file
mbed_app.json Show annotated file Show diff for this revision Revisions of this file
source/http_response_builder.h Show annotated file Show diff for this revision Revisions of this file
source/http_server.h Show annotated file Show diff for this revision Revisions of this file
--- a/.gitignore	Mon Jul 31 15:41:22 2017 +0200
+++ b/.gitignore	Mon Jul 31 17:12:21 2017 +0200
@@ -1,4 +1,9 @@
 .mbed
 BUILD/
 mbed_settings.*
+.hgignore
+.hg/
+mbed_config.h
+Makefile
+mbed-memory-status.lib
 
--- a/.hgignore	Mon Jul 31 15:41:22 2017 +0200
+++ b/.hgignore	Mon Jul 31 17:12:21 2017 +0200
@@ -6,3 +6,8 @@
 easy-connect/
 mbed-http/
 .git/
+Makefile
+mbed-memory-status/
+mbed-memory-status.lib
+mbed_config.
+
--- a/mbed_app.json	Mon Jul 31 15:41:22 2017 +0200
+++ b/mbed_app.json	Mon Jul 31 17:12:21 2017 +0200
@@ -2,7 +2,7 @@
     "config": {
         "network-interface": {
             "help": "options are ETHERNET, WIFI_ESP8266, WIFI_ODIN, WIFI_RTW, MESH_LOWPAN_ND, MESH_THREAD",
-            "value": "WIFI_ESP8266"
+            "value": "ETHERNET"
         },
         "mesh_radio_type": {
         	"help": "options are ATMEL, MCR20, SPIRIT1, EFR32",
--- a/source/http_response_builder.h	Mon Jul 31 15:41:22 2017 +0200
+++ b/source/http_response_builder.h	Mon Jul 31 17:12:21 2017 +0200
@@ -169,7 +169,11 @@
         size_t res_size;
         char* response = build(body, body_size, &res_size);
 
-        return socket->send(response, res_size);
+        nsapi_error_t r = socket->send(response, res_size);
+
+        free(response);
+
+        return r;
     }
 
 private:
--- a/source/http_server.h	Mon Jul 31 15:41:22 2017 +0200
+++ b/source/http_server.h	Mon Jul 31 17:12:21 2017 +0200
@@ -23,7 +23,9 @@
 #include "http_response.h"
 #include "http_response_builder.h"
 
+#ifndef HTTP_SERVER_MAX_CONCURRENT
 #define HTTP_SERVER_MAX_CONCURRENT      5
+#endif
 
 typedef HttpResponse ParsedHttpRequest;
 
@@ -37,8 +39,8 @@
      *
      * @param[in] network The network interface
     */
-    HttpServer(NetworkInterface* network) : server(network) {
-
+    HttpServer(NetworkInterface* network) {
+        _network = network;
     }
 
     ~HttpServer() {
@@ -50,26 +52,25 @@
     }
 
     /**
-     * Start listening
-     *
-     * @param[in] port The port to listen on
-     */
-    nsapi_error_t bind(uint16_t port) {
-        server.listen(HTTP_SERVER_MAX_CONCURRENT); // max. concurrent connections...
-        return server.bind(port);
-    }
-
-    /**
      * Start running the server (it will run on it's own thread)
      */
     nsapi_error_t start(uint16_t port, Callback<void(ParsedHttpRequest* request, TCPSocket* socket)> a_handler) {
-        server.listen(HTTP_SERVER_MAX_CONCURRENT); // max. concurrent connections...
+        server = new TCPServer();
 
-        nsapi_error_t ret = server.bind(port);
+        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); // max. concurrent connections...
+
         handler = a_handler;
 
         main_thread.start(callback(this, &HttpServer::main));
@@ -108,8 +109,10 @@
                 }
             }
             // error?
-            if (recv_ret < 0) {
-                printf("Error reading from socket %d\n", recv_ret);
+            if (recv_ret <= 0) {
+                if (recv_ret < 0) {
+                    printf("Error reading from socket %d\n", recv_ret);
+                }
 
                 // error = recv_ret;
                 delete response;
@@ -118,7 +121,7 @@
 
                 // q; should we always break out of the thread or only if NO_SOCKET ?
                 // should we delete socket here? the socket seems already gone...
-                if (recv_ret < -3000) {
+                if (recv_ret < -3000 || recv_ret == 0) {
                     return;
                 }
                 else {
@@ -133,7 +136,9 @@
             free(recv_buffer);
 
             // Let user application handle the request, if user needs a handle to response they need to memcpy themselves
-            handler(response, socket);
+            if (recv_ret > 0) {
+                handler(response, socket);
+            }
 
             // Free the response and parser
             delete response;
@@ -146,7 +151,7 @@
             TCPSocket* clt_sock = new TCPSocket(); // Q: when should these be cleared? When not connected anymore?
             SocketAddress clt_addr;
 
-            nsapi_error_t accept_res = server.accept(clt_sock, &clt_addr);
+            nsapi_error_t accept_res = server->accept(clt_sock, &clt_addr);
             if (accept_res == NSAPI_ERROR_OK) {
                 sockets.push_back(clt_sock); // so we can clear our disconnected sockets
 
@@ -154,12 +159,20 @@
                 Thread* t = new Thread(osPriorityNormal, 2048);
                 t->start(callback(this, &HttpServer::receive_data));
 
-                socket_threads.push_back(t);
+                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 (socket_threads[ix]->get_state() == Thread::Deleted) {
-                    delete socket_threads[ix];
+                if (socket_threads[ix]->thread->get_state() == Thread::Deleted) {
+                    socket_threads[ix]->thread->terminate();
+                    delete socket_threads[ix]->thread;
+                    delete socket_threads[ix]->socket; // does this work on esp8266?
                     socket_threads.erase(socket_threads.begin() + ix); // what if there are two?
                 }
             }
@@ -167,11 +180,17 @@
         }
     }
 
-    TCPServer server;
+    typedef struct {
+        TCPSocket* socket;
+        Thread*    thread;
+    } socket_thread_metadata_t;
+
+    TCPServer* server;
+    NetworkInterface* _network;
     Thread main_thread;
     vector<TCPSocket*> sockets;
-    vector<Thread*> socket_threads;
-    Callback<void(ParsedHttpRequest* request, TCPSocket* socket)> handler = 0;
+    vector<socket_thread_metadata_t*> socket_threads;
+    Callback<void(ParsedHttpRequest* request, TCPSocket* socket)> handler;
 };
 
 #endif // _HTTP_SERVER