A more complicated Arduino-based Http-serving application starting point
 
 
Go to file
Kenneth Barbour 00f1145930 WIP on documentation: roughly written 2018-12-03 13:36:28 -05:00
examples some basic examples that toggle the builtin ESP8266 LED 2018-04-16 21:01:19 -04:00
src setMessage to nullptr should free memory 2018-11-29 21:48:14 -05:00
test Print::print needs a return value 2018-11-30 19:59:53 -05:00
.gitignore gitignore, simpler directory structure and Makefile 2018-02-20 19:13:20 -05:00
LICENSE.txt added license 2018-04-28 10:15:34 -04:00
Makefile Removed unused constructors for HttpResponse 2018-04-05 12:48:08 -04:00
README.markdown WIP on documentation: roughly written 2018-12-03 13:36:28 -05:00
library.properties Added a properties file for arduino ide 2018-03-02 18:26:04 -05:00

README.markdown

HttpServer

A Complicated Arduino Web Server Implementation

Features

  • Designed around the popular ESP8266 playform
  • Pattern-based request routing allows a callback to handle URL requests matching simple patterns
  • Basic Buffer class provided for Response content, as well as support for File and other Stream-based content
  • Simple request lifecycle control allows processing before and after requests are made
  • Error handling callback functions

Usage

Included here, a guide for implementing a Web Application as part of your Arduino project. A WebKernel instance is what handles incoming requests, and hands off control to user defined callback functions. For simplicity, this guide stores variables and implements functions globally.

Overview

A WebKernel object instance provides a method handleClients() that should be called in the sketch's loop function, and is responsible for parsing incoming HTTP requests and dispatching them to user-defined handlers.

In order to use WebKernel. your web application will:

  • Require an array of Route that defines which callback handlers get called for each request
  • Require callback functions for each route defined in the routes array
  • Likely need an initialization callback, performing tasks with the HttpRequest and HttpResponse objects before any routing takes place, such as configuring a buffer or reading cookies
  • Likely need a termination callback, performing tasks with the HttpRequest and HttpResponse after the response has been sent, such as logging or closing file pointers
  • Potentially benefit from custom handlers for File Not Found or Method Not Allowed errors.

Defining Routes

A WebKernel object needs an array of Routes in order to map incoming requests to a callback. A Route is a struct containing an 8-bit mask of method flags, a c-stype string pointer to a URL pattern, and a callback function to handle requests matching the route. The first route that matches the request will be used.

An example Route array:

Route routes[] = {
  { GET, "/", index },
  { GET|POST, "/foo", foo}       // GET or POST request will match
  { GET,PUT, "/bar/*/baz", bar}  // URL wildcard
  { GET, "/static/#", static}    // handle anything starting with /static/ ...
};

In the above example:

  • A GET request for the "/" URL will be handled by the index(HttpRequest&, HttpResponse&) callback.
  • A request for "/foo" with either GET or POST will be handled by foo(HttpRequest&, HttpResponse&).
  • Any GET or PUT request matching the "/bar/*/baz" pattern ('*' matches anything but '/') will be handled by bar(HttpRequest&, HttpResponse&). Multiple wildcards are allowed.
  • A GET request for any url starting with "/static/" ... will be handled by static(HttpRequest&, HttpResponse&). This is useful when content in a certain path is stored entirely on a filesystem.
  • All other requests will result in either a 404 (Not Found) or a 405 (Method Not Allowed).

Web callbacks

Each route defined will have a callback to a void function accepting the arguments: HttpRequest& and HttpResponse&. When a request matches the route, these callbacks are responsible for reading the data in the HttpRequest&, applying some side-effects for non-GET requests (such as writing to a file or changing some hardware state), and crafting a HttpResponse&.

HttpRequest provides some useful methods:

  • getMethod(): gets the HTTP method used in the request
  • getUrl(): gets the path and query part or the URL
  • getMessage(): gets the message part of the request for non-GET requests
  • getMessageLength(): gets the number of bytes in the message

HttpResponse provides:

  • int code: HTTP status code (defaults to 200 OK)
  • setReason(const char*), getReason(): custom reason code
  • setContent(Stream&): sets the content of the message to a Stream such as Buffer or File

Response Buffer & Init handler

While it is not required, it is recommended to declare a Buffer to hold response data in scope, and a WebKernel init handler to manage it. HTTPResponse.content represents the content of the message returned to the user agent, and can be any Stream object. For convenience, Buffer is a Stream that allows response content to be built using the print functions. The init handler can clear the buffer with each new request.

// Outside of function definitions, so the Buffer isn't destroyed
uint8_t _buffer_data[512];    // 512 bytes of buffer
Buffer content(_buffer_data, sizeof(_buffer_data));

void init_handler(HttpRequest& request, HttpResponse& response) {
  response.content = content;
  content.clear();
}

Initialize

Initialize the webKernel in your sketch's setup function.

void setup()
{
	// initialize Serial, WiFi, Ethernet, etc
	webKernel.begin();
}

Additional Callbacks

For more complicated projects, such as projects also serving static files or other Stream objects, its helpful to have more control over the WebKernel throughout the request handling process. It is also helpful to have control over Method Not Allowed and File Not Found errors.

Each of the callbacks listed below are of the form void handler (HttpRequest&, HttpResponse&) with the exception of the Terminate Handler, which is void terminate_handler (const HttpRequest&, const HttpResponse&).

void setup()
{
	// ... other setup routines

	webKernel.begin();
	webKernel.setInitHandler(init_webkernel); // called before dispatching to the routes handler
	webKernel.setTerminateHandler(term_webkernel); // called after sending response to client
	webKernel.setNotFoundHandler(handle_not_found); // called if a route is not found for the request
	webKernel.setMethodNotAllowedHandler(handle_method_not_allowed); // called if a route is found, but the request method is not allowed

}

Serving Files

Since response content is a Stream, File objects can be used instead of a Buffer. In order to not fall out of scope once the handler finishes, the File should be declared outside of the route handler, and use the terminate handler to close the file. A simple way to do this:

// Routes and WebKernel defined above

uint8_t _buffer_data[512];
Buffer content(_buffer_data, sizeof(_buffer_data)); // Buffer defined as usual

File contentFile;     // File declared

void kernel_terminate(const HttpRequest& request, const HttpResponse& response) {
  if (contentFile)  // If file is open, close it
    contentFile.close();
}

void setup() {
  webKernel.begin();
  webKernel.setTerminateHandler(kernel_terminate);
  // ...
}

Drive

The WebKernel has everything it needs to handle requests now. The only thing left is to make a call to webKernel.handleClients() as part of your sketch's loop() function.

void loop()
{
	webKernel.handleClients();
	// ... include other sketch routines
}

Examples

Minimal Example

Here is a minimal example, without any WiFi or other arduino features, of a basic implementation with a single function that handles GET requests for any URL.

#include <WebKernel.h>

// Content Buffer
uint8_t _buffer_data[100];
Buffer content(_buffer_data, sizeof(_buffer_data)); // content buffer

// Initialize WebKernel
void init_webkernel(HttpRequest& req, HttpResponse& res) {
  content.clear();
  res.content = content;
}

// Handle Requests
void handleURL(HttpRequest& req, HttpResponse& res) {
  res.headers.set("Content-Type","text/plain");
  content.printf("It works! The current url is %s", req.getUrl());
}

// Define WebKernel and Routes
Route routes[] = {
  {GET, "/#", handleURL}
};
size_t numRoutes = sizeof(routes) / sizeof(Route);
WebKernel webKernel(80, routes, numRoutes);  // Run webKernel on port 80 with routes array

void setup() {
  webKernel.begin();
  webKernel.setInitHandler(init_webkernel);
}

void loop() {
  webKernel.handleClients();
}

Basic Web Application

Open the basic example in your Arduino IDE and upload it to your ESP8266. These examples use the ESP8266 as a Standalone Access Point, so refer to the sketch for the SSID and password, or connect it to a Serial monitor before it starts.

To test, connect to its wireless access point and go to (http://192.168.1.4/), the default IP address for the ESP.

Filesystem Web Application

Similar to the basic example, except this example also includes static content in the data directory that needs to be uploaded separately.

To upload the static content to an ESP8266, you will need to use the ESP8266FS tool described here: (https://esp8266.github.io/Arduino/versions/2.0.0/doc/filesystem.html#uploading-files-to-file-system)