stm32plus::net HTTP module

As a protocol HTTP needs no introduction so I’ll get straight down to how stm32plus::net helps you write HTTP clients and servers.

HTTP clients

The HttpClient class is a high-level helper that performs GET or POST requests and reads the response for you. When you get down to it HTTP is little more than some text headers on top of a request-response TCP client. The HttpClient class does some of the repetitive work for you so that you can concentrate on sending and receiving data.

The net_web_pframe and net_web_client examples both use the HttpClient class to download web content. net_web_pframe goes into a loop reading and displaying a set of JPEG images from my website and net_web_client downloads a single file from ST-Micro’s website.

Here’s the basic workflow for using the HttpClient class.

Firstly, connect to the remote web server on its HTTP port using a regular TCP connection:

#include "config/net_http.h"

// [initialisation code removed]

TcpClientConnection *ptr;

if(!_net->tcpConnect<TcpClientConnection>(address,80,ptr)) {
  error();
}

Next, wrap the TcpClientConnection in an HttpClient class and use its methods to help you get a document from the web server:

// manage the connection pointer in a scoped_ptr so it's automatically deleted (and closed)
// when it goes out of scope

scoped_ptr<TcpClientConnection> conn(ptr);
HttpClient httpClient(*conn);

// set the parameters for the HTTP GET

httpClient.setUri("/");            // get the root document
httpClient.setHost("www.st.com");  // host header is mandatory for HTTP/1.1

// send it

if(!httpClient.sendRequest()) {
  error();
}

Now read the response back from the remote server. This code sample shows how to read it back in small chunks and output the data to a USART that’s assumed to attached to _outputStream. This code fragment comes from the net_web_client example.

// must have a content-length for this test call

int32_t contentLength;

if((contentLength=httpClient.getResponseContentLength())==-1) {
  error();
}

// read back the response in 100 byte chunks with a 60 second timeout

uint8_t buffer[100];
uint32_t actuallyRead;

while(contentLength) {

  // read a chunk

  if(!conn->receive(buffer,Min(contentLength,100L),actuallyRead,60000)) {
    error();
  }

  if(actuallyRead==0) {
    error();   // remote end has closed
  }

  // push out to the USART

  _outputStream->write(buffer,actuallyRead);

  // decrease amount remaining

  contentLength-=actuallyRead;
}

That’s it. If you don’t want to re-use the HTTP connection (HTTP/1.1 connections may be re-used if the server keeps the connection alive) then delete the HttpClient instance and the connection will be closed for you.

HttpClient public methods

The following methods are exposed by the HttpClient class.

bool sendRequest(uint32_t timeoutMillis=0);
bool readResponse(uint32_t timeoutMillis=0);

void setVersion(HttpVersion version);
void setMethod(HttpMethod method);
void setUri(const std::string& uri);
void setHost(const std::string& host);
void setRequestContentType(const std::string& contentType);
void setRequestContentLength(uint32_t contentLength);

uint16_t getResponseCode() const;
int32_t getResponseContentLength() const;
const std::string& getResponseContentType() const;
const std::slist<std::string>& getResponseHeaders() const;

We’ve already seen how the sendRequest() method is used to send data. You can see that there is an optional timeoutMillis parameter. If the default of zero is used then the method blocks until success or failure.

If you did a GET request then readResponse() is automatically called for you. It reads the response code and headers from the TCP stream. If you did any other type of request then you must call readResponse() manually to read the response and headers, leaving the TCP stream positioned to receive the response body.

setVersion() allows you to choose the HTTP protocol version. The default is HttpVersion::HTTP_1_1. You may also choose HttpVersion::HTTP_1_0 if your web server requires it.

setMethod() allows you to set the request method. The default is HttpMethod::GET. The most common other method is HttpMethod::POST. All other methods are defined and these may be useful to you if you’re calling a REST service, for example.

setUri() is a required call. You use it to set the URI (and query string, if applicable) that identifies the document you want to get back.

setHost() is a required call for HTTP/1.1 and should be set to the fully qualified name of the web server as the calling client sees it. e.g. “www.google.com”. Modern web servers tend to serve many virtual domains from a single IP address so they need to know what the name of the host was that the browser saw. Without this header the web server would not be able to differentiate between multiple virtual hosts.

If you’re using the POST, PUT or other method that requires you to send a request body to the server then you must use setRequestContentType() and setRequestContentLength() to tell the remote server what you’re sending and how big it is. Call these methods before calling sendRequest(). After calling the sendRequest() method you are expected to use the normal send methods available on the TcpConnection class to send your request body before attempting to read the response.

After the readResponse() method has been called (which happens automatically for a GET request) then the remaining methods may be used to get some information about the server’s response.

getResponseCode() gets you the HTTP response code, e.g. 200. getResponseContentLength() gets you the length of the response body or -1 if the server did not send the Content-Length header in the response headers.

getResponseContentType() gets you the Content-Type header from the server response, if it was set. It’ll be an empty string if the server didn’t set the Content-Type header.

getResponseHeaders() gets you a reference to a linked list of the response headers. Each entry in the list represents a header line from the response data.

HTTP servers

stm32plus::net contains support for writing multi-connection asynchronous HTTP servers through a derivation of the TcpConnection class called HttpServerConnection. The best way to see how this works is to take a look at the net_web_server example code. The remainder of this section is dedicated to explaining the options provided by the HttpServerConnection class and what you can do with them.

As always, the model is that you subclass the provided connection class (in this case HttpServerConnection) and use your subclass to customise default parameters and handle your logic.

Configuration parameters

The HttpServerConnection class subclasses the Parameters class defined by TcpConnection to add in the following options that you can customise in your connection constructor.

// are we operating in HTTP/1.1 mode? default is true.
bool http_version11;

// size includes the verb, URL and HTTP version. Default is 200
uint16_t http_maxRequestLineLength;

// buffer size of the stream-of-streams class. Default is 256
uint16_t http_outputStreamBufferMaxSize;	

// in http1.1, close connection after this many requests. 0 = never, default is 5.
uint16_t http_maxRequestsPerConnection;

If http_version11 is set to true (the default) then client connections are allowed to stay open after the response to a request has been sent to them. This presents a possible resource starvation issue for a small device such as the STM32 so we mitigate that somewhat by setting an upper limit on the number of requests that a single connection can be served before we close it through the http_maxRequestsPerConnection setting.

if http_version11 is set to false then connections will be closed immediately after each request is served.

http_maxRequestLineLength sets an upper limit on the number of characters that can be sent on the ‘request’ line (i.e. the first line sent by the browser that includes the verb, URI and http version). You might want to increase this if you forsee the need to handle long URIs.

http_outputStreamBufferMaxSize is an advanced performance tuning setting. To avoid the problems inherent in sending small packets across the network the TcpOutputStreamOfStreams class buffers data from the streams that receives before sending it. The size of that buffer is defined by the http_outputStreamBufferMaxSize option.

Implementation details

You will note that the HttpServerConnection template requires the name of your implementation class as one of its template parameters:

class MyHttpConnection :
  public HttpServerConnection<MyHttpConnection> {

This is called the Curiously Recurring Template Pattern (CRTP) and is used to achieve static polymorphism avoiding the overhead of virtual function tables. This means that you are required to implement some function calls defined by the base class. Those calls are:

handleRequestHeader(const std::string& header);
handleStateChange(State newState);

handleRequestHeader is an opportunity for you to peek at incoming request headers and take any appropriate action.

handleStateChange is where you perform the action of servicing a request. Take a look at the net_web_server example for how to deal with this callback.