Project

General

Profile

HTTP Server

Introduction

Getting input from the outside world is a key component to many embedded solutions.

In many cases, a few simple buttons or USB Communication can be enough.

However, sometimes it may be desirable to provide much more - a web interface.

The Dooba SDK includes a http library which allows building a web server to handle requests with just a few lines of code.

This page serves as documentation for the server part of the HTTP Library. The client part is documented here: HTTP Client.

Note: to keep things simple, the examples presented here will rely on the ESP8266 for internet connectivity, and assume they are being built for the Fiddle.

Using the library

To use the library, add http as a dependency to your definition file (dfe.conf):

# Firmware Element Configuration
# ...
deps:
  - http

You'll want to include the following header files in your code:

  • http/http.h
  • http/server.h
  • http/server_util.h

Creating a server

An HTTP server is created using the http_server_start method from http/server.h.

A callback needs to be provided to handle incoming requests.

uint8_t http_server_start(struct http_server *srv, uint16_t port, struct net_transport *nt, void (*req_handler)(struct http_server_client *c, struct http_req *r, char *b))
Arguments:

  • srv -> Server object (struct http_server)
  • port -> Listening port
  • nt -> Pointer to Network Transport
  • req_handler -> Request handler callback

Return Value:
0 on success, anything else on error.

request_handler arguments:

  • c -> Client object
  • r -> Request object
  • b -> Request data buffer

Once the server is successfully created, the http_server_update method needs to be called repeatedly from your main loop to actually run the server.

The server can be terminated with the http_server_stop method.

Let's create and run a simple HTTP server on port 80:

#include <avr/interrupt.h>
#include <scom/term.h>
#include <esp8266/esp8266.h>
#include <esp8266/net_transport.h>
#include <http/http.h>
#include <http/server.h>
#include <http/server_util.h>

// WiFi will need quite some space to store packets
#define wifi_buf_size            2048
uint8_t wifi_buf[wifi_buf_size];

// Server object
struct http_server srv;

// Request Handler
void req_handler(struct http_server_client *c, struct http_req *r, char *b)
{
    uint8_t i;
    struct http_arg *a;

    // Log it to serial output
    scom_term_printf("Got request!\n");
    scom_term_printf("Method: %s (%t)\n", http_code2cmd(r->cmd_code), http_req_cmd(c, r), r->cmd_len);
    scom_term_printf("URL: %t\n", http_req_url(c, r), r->url.url_len);
    scom_term_printf(" * Path: %t\n", http_req_url(c, r), r->url.obj_len);
    if(r->url.args_len) { scom_term_printf(" * Args: %t\n", http_req_url_args(c, r), r->url.args_len); }
    for(i = 0; i < r->url.arg_count; i = i + 1)
    {
        a = http_req_url_arg_i(c, r, i);
        scom_term_printf("   - %t -> %t\n", http_arg_name(c, a), a->name_len, http_arg_val(c, a), a->val_len);
    }
    scom_term_printf("Headers:\n");
    for(i = 0; i < r->header_count; i = i + 1)
    {
        a = http_req_head_i(c, r, i);
        scom_term_printf("   - %t -> %t\n", http_arg_name(c, a), a->name_len, http_arg_val(c, a), a->val_len);
    }
    if(r->field_count) { scom_term_printf("Form Fields:\n"); }
    for(i = 0; i < r->field_count; i = i + 1)
    {
        a = http_req_field_i(c, r, i);
        scom_term_printf("   - %t -> %t\n", http_arg_name(c, a), a->name_len, http_arg_val(c, a), a->val_len);
    }
    if(r->body_len) { scom_term_printf("Raw body:\n%t\n", http_req_body(c, r), r->body_len); }
}

void main()
{
    // Initialize Serial Terminal
    scom_term_init(0);

    // Initialize WiFi
    esp8266_init(wifi_buf, wifi_buf_size);

    // Enable Interrupts
    sei();

    // Configure WiFi & Join Access-Point
    scom_term_printf("Connecting to WiFi...\n");
    esp8266_set_mux(1);
    esp8266_set_mode(ESP8266_MODE_STA);
    if(esp8266_join_ap("YourNetwork", "YourNetworkPassword"))
    {
        // Failed - Something went wrong
        scom_term_printf("Failed to connect to WiFi!\n");
        for(;;);
    }

    // Create HTTP Server on port 80
    if(http_server_start(&srv, 80, &esp8266_net_transport, req_handler))
    {
        // Failed - Something went wrong
        scom_term_printf("Failed to create HTTP server :(\n");
        for(;;);
    }

    // Run server
    while(1)
    {
        // Update esp8266 WiFi
        esp8266_update();

        // Update Server
        http_server_update(&srv);
    }
}

You might notice in the example above that the request handler only "logs" the request through the USB serial terminal (scom), but does nothing to actually serve any kind of response.

By default, if the request handler does not explicitly respond, the server will automatically serve back a 500 Server error response.

First, let's have a look at how the code above works. The section below explains how to get all the information from a request.

Request details

The struct http_arg object

We'll see a few lines below that URL arguments, headers, as well as form fields are all stored as arrays of struct http_arg objects.
These always contain both a name and a value.

They should be accessed using:

  • char *http_arg_name(struct http_client_req *r, struct http_arg *a) -> Pointer to name
  • char *http_arg_val(struct http_client_req *r, struct http_arg *a) -> Pointer to value
  • a->name_len (uint16_t) -> Name length
  • a->val_len (uint16_t) -> Value length

HTTP method

First off, to get the method of a request, two ways are provided.
One way is to directly read the check text of the HTTP method:

char *http_req_cmd(struct http_server_client *c, struct http_req *r)
Arguments:

  • c -> Client object
  • r -> Request object

Return Value:
Method string

r->cmd_len (uint8_t)
Length of method string

Another way is to check the single-byte code of the method that has been detected by the http library itself:

r->cmd_code (uint8_t)
Method code - possible values:

  • HTTP_CMD_CODE_NONE
  • HTTP_CMD_CODE_GET
  • HTTP_CMD_CODE_HEAD
  • HTTP_CMD_CODE_POST
  • HTTP_CMD_CODE_PUT
  • HTTP_CMD_CODE_DELETE
  • HTTP_CMD_CODE_OPTIONS
  • HTTP_CMD_CODE_PATCH

URL

The full url string can be obtained using:

char *http_req_url(struct http_server_client *c, struct http_req *r)
Arguments:

  • c -> Client object
  • r -> Request object

Return Value:
URL string

r->url.url_len (uint16_t)
Length of URL string

However, it may be more interesting to obtain the actual request path and arguments separately:

  • r->url.obj_len (uint16_t) -> Length of request path part of URL
  • char *http_req_url_args(struct http_server_client *c, struct http_req *r) -> Get URL argument string
  • r->url.args_len (uint16_t) -> Length of URL argument string

If any arguments are present, the http library will attempt to split them into an array of struct http_arg objects:

  • r->url.args (struct http_arg []) -> Array of URL arguments
  • r->url.arg_count (uint8_t) -> Number of URL arguments

The following methods provide simplified access:

  • struct http_arg *http_req_url_arg_i(struct http_server_client *c, struct http_req *r, uint8_t index) -> Get URL argument by index in array
  • struct http_arg *http_req_url_arg(struct http_server_client *c, struct http_req *r, char *name) -> Find URL argument by name

Headers

Request headers are stored just like URL arguments:

  • r->headers (struct http_arg []) -> Array of headers
  • r->header_count (uint8_t) -> Number of headers
  • struct http_arg *http_req_head_i(struct http_server_client *c, struct http_req *r, uint8_t index) -> Get header by index in array
  • struct http_arg *http_req_head(struct http_server_client *c, struct http_req *r, char *name) -> Find header by name

Body

The body of the request is available as:

  • char *http_req_body(struct http_server_client *c, struct http_req *r) -> Get request body
  • r->body_len (uint16_t) -> Length of request body

Form Fields

If the request body consists of URL-Encoded form data, the fields will be available as:

  • r->fields (struct http_arg []) -> Array of form fields
  • r->field_count (uint8_t) -> Number of form fields
  • struct http_arg *http_req_field_i(struct http_server_client *c, struct http_req *r, uint8_t index) -> Get form field by index in array
  • struct http_arg *http_req_field(struct http_server_client *c, struct http_req *r, char *name) -> Find form field by name

Responding to a request

Now that we've seen how to extract information from the request object, let's see how to respond.

Responses are built in three phases:

  • Setting a status code
  • Setting some headers
  • Adding a body

These must be performed in this order.

Setting a status code

First, we need to call http_res_start(struct http_server_client *c, uint16_t code):

void http_res_start(struct http_server_client *c, uint16_t code)
Arguments:

  • c -> Client object (provided as first argument to the request handler callback)
  • port -> HTTP status code (200 / 404 / 500 / etc...)

Setting some headers

Then, the http_res_head method and friends allow setting some response headers:

void http_res_head(struct http_server_client *c, char *h, char *v)
void http_res_head_i(struct http_server_client *c, char *h, int v)
void http_res_head_n(struct http_server_client *c, char *h, char *v, uint16_t l)
void http_res_head_f(struct http_server_client *c, char *h, char *fmt, ...)
Arguments:

  • c -> Client object (provided as first argument to the request handler callback)
  • h -> Header name
  • v -> Header value
  • l -> Header value length
  • fmt -> Header value format string

Adding a body

Once all the desired headers have been set (if any), a body may be added (if desired) with one of the following methods:

void http_res_body(struct http_server_client *c, char *body)
void http_res_body_n(struct http_server_client *c, char *body, uint16_t len)
Arguments:

  • c -> Client object (provided as first argument to the request handler callback)
  • body -> Pointer to body data
  • len -> Body length

Going back to the example above, we can easily modify our request handler to serve a response:

// Request Handler
void req_handler(struct http_server_client *c, struct http_req *r, char *b)
{
    // ...

    // Respond with 200 - OK
    http_res_start(c, HTTP_RES_OK);

    // Set a 'Server' header
    http_res_head_f(c, HTTP_HEADER_SERVER, "Example HTTP Server v.%i.%i", 0, 1);

    // Add a body - say hello!
    http_res_body(c, "Hello world!");
}

Redirecting

Finally, to perform a redirect, one could set the response code to 302 and add a "Location" header to point to the redirection target.

However three methods are provided as shortcuts to do exactly this:

void http_redirect(struct http_server_client *c, char *t)
void http_redirect_n(struct http_server_client *c, char *t, uint16_t l)
void http_redirect_f(struct http_server_client *c, char *fmt, ...)
Arguments:

  • c -> Client object (provided as first argument to the request handler callback)
  • t -> Redirection target
  • l -> Redirection target length
  • fmt -> Redirection target format string

Let's modify our example again. But this time, let's have request handler redirect all requests to http://www.example.com/:

// Request Handler
void req_handler(struct http_server_client *c, struct http_req *r, char *b)
{
    // ...

    // Redirect to example.com
    http_redirect(c, "http://www.example.com/");
}