diff --git a/README.markdown b/README.markdown index 09e1a7c..fdf80a9 100644 --- a/README.markdown +++ b/README.markdown @@ -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 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. @@ -33,6 +42,20 @@ In the above example: * 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. @@ -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 WebKernel.h 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 - -// 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 routes.h, and implement them in a file like routes.cpp. - -``` -// 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("Site Index

Site Index

"); + response.content = content; + content.clear(); } ``` @@ -107,23 +77,10 @@ Initialize the webKernel in your sketch's setup 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 webKernel.handleClients() as part of your sketch's loop 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 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 + +// 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.