WIP on documentation: roughly written

documentation
Kenneth Barbour 2018-12-03 13:36:28 -05:00
parent b8b7420597
commit 00f1145930
1 changed files with 101 additions and 70 deletions

View File

@ -11,8 +11,17 @@ HttpServer
## 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.
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 <tt>WebKernel</tt> object instance provides a method <tt>handleClients()</tt> that should be called in the sketch's <tt>loop</tt> function, and is responsible for parsing incoming HTTP requests and dispatching them to user-defined handlers.
In order to use <tt>WebKernel</tt>. your web application will:
* Require an array of <tt>Route</tt> 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 <tt>HttpRequest</tt> and <tt>HttpResponse</tt> objects before any routing takes place, such as configuring a buffer or reading cookies
* Likely need a termination callback, performing tasks with the <tt>HttpRequest</tt> and <tt>HttpResponse</tt> after the response has been sent, such as logging or closing file pointers
* Potentially benefit from custom handlers for <em>File Not Found</em> or <em>Method Not Allowed</em> errors.
### Defining Routes
A <tt>WebKernel</t> object needs an array of <tt>Route</tt>s in order to map incoming requests to a callback. A <tt>Route</tt> 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.
@ -33,6 +42,20 @@ In the above example:
* A GET request for any url starting with "/static/" ... will be handled by <tt>static(HttpRequest&, HttpResponse&)</tt>. 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 <em>void</em> function accepting the arguments: <tt>HttpRequest&</tt> and <tt>HttpResponse&</tt>. When a request matches the route, these callbacks are responsible for reading the data in the <tt>HttpRequest&</tt>, applying some side-effects for non-GET requests (such as writing to a file or changing some hardware state), and crafting a <tt>HttpResponse&</tt>.
<tt>HttpRequest</tt> provides some useful methods:
* <tt>getMethod()</tt>: gets the HTTP method used in the request
* <tt>getUrl()</tt>: gets the path and query part or the URL
* <tt>getMessage()</tt>: gets the message part of the request for non-GET requests
* <tt>getMessageLength()</tt>: gets the number of bytes in the message
<tt>HttpResponse</tt> provides:
* <tt>int code</tt>: HTTP status code (defaults to 200 OK)
* <tt>setReason(const char*), getReason()</tt>: custom reason code
* <tt>setContent(Stream&)</tt>: sets the content of the message to a <tt>Stream</tt> such as <tt>Buffer</tt> or <tt>File</tt>
### Response Buffer & Init handler
While it is not required, it is recommended to declare a <tt>Buffer</tt> to hold response data in scope, and a <tt>WebKernel</tt> init handler to manage it. <tt>HTTPResponse.content</tt> represents the content of the message returned to the user agent, and can be any <tt>Stream</tt> object. For convenience, <tt>Buffer</tt> is a <tt>Stream</tt> that allows response content to be built using the <tt>print</tt> functions. The init handler can clear the buffer with each new request.
@ -42,61 +65,8 @@ uint8_t _buffer_data[512]; // 512 bytes of buffer
Buffer content(_buffer_data, sizeof(_buffer_data));
void init_handler(HttpRequest& request, HttpResponse& response) {
}
```
### Define
Include <tt>WebKernel.h</tt> and define a _Routes_ array, _WebKernel_ object, and a _Response Buffer_ to store
response data. The _WebKernel_ contructor needs the _port_ number, _routes_ array, and the _number_ of routes in the array.
```
/*
* File: example.ino
*/
#include <WebKernel.h>
// Define maximum size of content buffer
#define CONTENT_SIZE 512
// TODO: put function prototypes here
Routes routes = {
{ GET, "/", handle_index },
{ GET|POST, "/foo", handle_foo },
{ POST, "/bar", handle_post_bar },
{ GET, "/bar", handle_get_bar }
};
WebKernel webKernel(80, routes, sizeof(routes)/sizeof(routes[0]));
uint8_t content_data[CONTENT_SIZE]; // Global block of memory to store content
Buffer content(content_data, CONTENT_SIZE); // Buffer to write data to previously allocated content_data
```
### Implement
Each route defined in the routes array will need a function implemented to handle requests for that route.
You can put them in the main sketch .ino file anywhere as long as you include a function prototype before the Routes definition.
Alternatively, you could write prototypes in a file like <tt>routes.h</tt>, and implement them in a file like <tt>routes.cpp</tt>.
```
// Function prototypes
void handle_index (HttpRequest&, HttpResponse&); // handle GET request for '/'
void handle_foo (HttpRequest&, HttpResponse&); // handle GET or POST request for '/foo'
void handle_post_bar (HttpRequest&, HttpResponse&); // handle POST request for '/bar'
void handle_get_bar (HttpRequest&, HttpResponse&); // handle GET request for '/bar'
```
Function prototypes should be put before any other reference to one of the functions is made. In this example, they should be defined before defining the _Route_ array.
Next, implement each of the functions we just defined:
```
void handle_index(HttpRequest& request, HttpResponse& response)
{
response.setContent(content);
content.println("<!doctype html><html><head><title>Site Index</title></head><body><h1>Site Index</h1></body></html>");
response.content = content;
content.clear();
}
```
@ -107,23 +77,10 @@ Initialize the webKernel in your sketch's <tt>setup</tt> function.
void setup()
{
// initialize Serial, WiFi, Ethernet, etc
webKernel.begin();
}
```
### Drive
The WebKernel has everything it needs to handle requests now. The only thing left is to make a call to <tt>webKernel.handleClients()</tt> as part of your sketch's <tt>loop</tt> function.
```
void loop()
{
webKernel.handleClients();
// ... include other sketch routines
}
```
### 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.
@ -144,8 +101,82 @@ void setup()
}
```
### Serving Files
Since response content is a <tt>Stream</tt>, <tt>File</tt> objects can be used instead of a <tt>Buffer</tt>. In order to not fall out of scope once the handler finishes, the <tt>File</tt> 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 <tt>WebKernel</tt> has everything it needs to handle requests now. The only thing left is to make a call to <tt>webKernel.handleClients()</tt> as part of your sketch's <tt>loop()</tt> 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.