coreHTTP Demo (without TLS)
Notice: We recommend that you use mutual authentication in any Internet of
Things (IoT) application. The demo on this page is only meant for educational purposes because it demonstrates
HTTP communication prior to introducing encryption and authentication. It is not intended to be suitable for
production use.
Introduction
This demo shows how to use the coreHTTP library to establish a connection with an HTTP server to demonstrate a simple request/response workflow. After it creates a request, the request is sent, and the demo synchronously waits for the response to be received.
This example project introduces the concepts described on the "TLS Introduction" page one at a time. The first example (this page) demonstrates unencrypted HTTP communication. The second example builds on
this to introduce strong mutual authentication (where the HTTP server also authenticates the IoT client connecting to it).
This demo does not create a secure connection and therefore it is not suitable for production use -
do not send any confidential information on an unencrypted network connection. The demo demonstrates how to
connect using an exponential backoff time (including timing jitter) in case of a connection failure. Exponentially
increasing the time between connection attempts, and including some random timing jitter, are best practices for
large IoT device fleets because it prevents all the IoT devices attempting to reconnect at the same time if they
all became disconnected at the same time.
This basic HTTP demo project uses the
FreeRTOS Windows port,
so it can be built and evaluated with the free Community version of Visual Studio on Windows, without the need for any
particular MCU hardware.
The coreHTTP library is an MIT licensed, open source HTTP client C library for
microcontroller and small microprocessor based IoT devices.
Single Threaded Vs Multi Threaded
There are two coreHTTP usage models, single threaded and multithreaded (multitasking). Although
the demo on this page runs the HTTP library in a thread, it actually demonstrates how to use coreHTTP in a
single threaded environment (that is, only one task uses the HTTP API in the demo). Although single threaded
applications must repeatedly call the HTTP library, multithreaded applications can instead send HTTP requests
in the background within an agent (or daemon) task.
Source Code Organization
The demo project is called http_plain_text_demo.sln
and can be found in the
FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_Plaintext
directory of the
main FreeRTOS download (and it can be found in Github, linked from the download page).
Configuring the Demo Project
The demo uses the FreeRTOS-Plus-TCP TCP/IP stack, so
follow the instructions provided for the
TCP/IP starter project to ensure you:
- Have the
pre-requisite components installed (such as WinPCap).
- Optionally
set a static or dynamic IP address, gateway address and netmask.
- Optionally
set a MAC address.
-
Select an Ethernet network interface on your host machine.
- …and importantly
test your network connection before you attempt to run the HTTP demo.
Each demo project has its own configuration settings. So when you follow these network configuration instructions,
make sure to apply the settings in the HTTP demo project only, and not in the TCP/IP starter project. By default, the
TCP/IP stack is configured to use a dynamic IP address.
Configuring the HTTP server Connection
Option 1: Using a publicly hosted HTTP server
The demo project can communicate with the publicly hosted HTTP server at "httpbin.org". This should work if the
demo connects to a network that has a DHCP service and Internet access. Note that the FreeRTOS Windows port only
works with a wired Ethernet network adapter, which can be a virtual Ethernet adapter. You should use a separate
HTTP client, such as your current web browser, to test the HTTP connection from your host machine to the public
HTTP server. To use the hosted HTTP server:
- Open your local copy of
/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_Plaintext/demo_config.h
.
- Add the following lines:
#define democonfigSERVER_HOSTNAME "httpbin.org"
#define democonfigHTTP_PORT ( 80 )
Note: httpbin is an open source HTTP server that supports HTTP/1.1. It is part of Postmanlabs and
can be found here.
The httpbin.org server is not affiliated with, or maintained by, FreeRTOS and may be unavailable at any time.
Option 2: Using a locally hosted HTTP server
The httpbin server can also run locally, or on another machine on your local network. To do this:
- Install Docker.
- Run httpbin from port 80 using the following commands:
docker pull kennethreitz/httpbin
docker run -p 80:80 kennethreitz/httpbin
- Verify that the httpbin server is running locally and listening on port 8080 by following these steps:
- Open PowerShell.
- To check if there is an active connection listening on port 80, type in the command
netstat -a -p TCP | findstr 80
- Verify that you see output like this:
TCP 0.0.0.0:80 <HOST-NAME>:0 LISTENING
- Open
FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_Plaintext/demo_config.h
.
- Add the following lines:
#define democonfigSERVER_HOSTNAME "ipv4_address_of_your_machine"
#define democonfigHTTP_PORT ( 80 )
You should use a separate HTTP client, such as your web browser, to test the HTTP connection from your host
machine to the installed HTTP client.
Note: Port number 80 is the default port number for unencrypted HTTP. If you cannot use that
port (for example, if it is blocked by your IT security policy or used by another system process in Windows), then
change the port used by the docker container to a high port number (for example, something in the 50000 to 55000
range), and set democonfigHTTP_PORT
accordingly. To make this change, you can run
docker run -p <HIGH_PORT_NUMBER>:80 kennethreitz/httpbin
Option 3: Any other unencrypted HTTP server of your choosing:
Any HTTP server that supports unencrypted TCP/IP communication can also be used with this demo. To do this:
- Open your local copy of
/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_Plaintext/demo_config.h
.
- Add the following lines with settings specific to your chosen server:
#define democonfigSERVER_HOSTNAME "your-desired-endpoint"
#define democonfigHTTP_PORT ( 80 )
Building the Demo Project
The demo project uses the free community edition of Visual Studio. To build the demo:
- Open the
/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_Plaintext/http_plain_text_demo.sln
Visual Studio solution file from within the Visual Studio IDE
- Select 'build solution' from the IDE's 'build' menu.
Functionality
The demo creates a single application task that demonstrates how to:
- connect to the AWS IoT HTTP server,
- create an HTTP request,
- send the HTTP request and
- receive the HTTP response, then finally,
- disconnect from the server.
The structure of the demo is:
static void prvHTTPDemoTask( void * pvParameters )
{
TransportInterface_t xTransportInterface;
NetworkContext_t xNetworkContext = { 0 };
PlaintextTransportParams_t xPlaintextTransportParams = { 0 };
const httpPathStrings_t xHttpMethodPaths[] =
{
{ democonfigGET_PATH, httpexampleGET_PATH_LENGTH },
{ democonfigHEAD_PATH, httpexampleHEAD_PATH_LENGTH },
{ democonfigPUT_PATH, httpexamplePUT_PATH_LENGTH },
{ democonfigPOST_PATH, httpexamplePOST_PATH_LENGTH }
};
const httpMethodStrings_t xHttpMethods[] =
{
{ HTTP_METHOD_GET, httpexampleHTTP_METHOD_GET_LENGTH },
{ HTTP_METHOD_HEAD, httpexampleHTTP_METHOD_HEAD_LENGTH },
{ HTTP_METHOD_PUT, httpexampleHTTP_METHOD_PUT_LENGTH },
{ HTTP_METHOD_POST, httpexampleHTTP_METHOD_POST_LENGTH }
};
BaseType_t xIsConnectionEstablished = pdFALSE;
UBaseType_t uxHttpPathCount = 0U;
BaseType_t xDemoStatus = pdPASS;
( void ) pvParameters;
xNetworkContext.pParams = &xPlaintextTransportParams;
xDemoStatus = connectToServerWithBackoffRetries( prvConnectToServer,
&xNetworkContext );
if( xDemoStatus == pdPASS )
{
xIsConnectionEstablished = pdTRUE;
xTransportInterface.pNetworkContext = &xNetworkContext;
xTransportInterface.send = Plaintext_FreeRTOS_send;
xTransportInterface.recv = Plaintext_FreeRTOS_recv;
}
else
{
LogError( ( "Failed to connect to HTTP server %.*s.",
( int32_t ) httpexampleSERVER_HOSTNAME_LENGTH,
democonfigSERVER_HOSTNAME ) );
}
for( uxHttpPathCount = 0;
uxHttpPathCount < httpexampleNUMBER_HTTP_PATHS;
++uxHttpPathCount )
{
if( xDemoStatus == pdPASS )
{
xDemoStatus = prvSendHttpRequest(
&xTransportInterface,
xHttpMethods[ uxHttpPathCount ].pcHttpMethod,
xHttpMethods[ uxHttpPathCount ].ulHttpMethodLength,
xHttpMethodPaths[ uxHttpPathCount ].pcHttpPath,
xHttpMethodPaths[ uxHttpPathCount ].ulHttpPathLength );
}
else
{
break;
}
}
if( xIsConnectionEstablished == pdTRUE )
{
Plaintext_FreeRTOS_Disconnect( &xNetworkContext );
}
if( xDemoStatus == pdPASS )
{
LogInfo( ( "prvHTTPDemoTask() completed successfully. "
"Total free heap is %u.\r\n",
xPortGetFreeHeapSize() ) );
LogInfo( ( "Demo completed successfully.\r\n" ) );
}
}
Connecting to the HTTP server
In the function above, connectToServerWithBackoffRetries()
attempts to make a TCP connection to the
HTTP server. If the connection fails, it retries after a timeout. The timeout value will exponentially increase and
include some randomised jitter until the maximum number of attempts are reached or the maximum timeout value is
reached. This type of backoff is used in production devices to ensure that if a fleet of IoT devices all get
disconnected at the same time, they do not all try to re-connect at the same time - and, in so doing, overwhelm the
server. If the connection is successful, then the connected TCP socket is returned in the
xNetworkContext
parameter.
The function prvConnectToServer()
demonstrates how to establish an unencrypted connection to an HTTP
server. It uses the FreeRTOS-Plus-TCP transport interface, which is implemented in
the file FreeRTOS-Plus/Source/Application-Protocols/network_transport/freertos_plus_tcp/using_plaintext/using_plaintext.c
.
The definition of prvConnectToServer()
is shown here:
static BaseType_t prvConnectToServer( NetworkContext_t * pxNetworkContext )
{
BaseType_t xStatus = pdPASS;
PlaintextTransportStatus_t xNetworkStatus;
configASSERT( pxNetworkContext != NULL );
LogInfo( ( "Establishing a TCP connection to %.*s:%d.",
( int32_t ) httpexampleSERVER_HOSTNAME_LENGTH,
democonfigSERVER_HOSTNAME,
democonfigHTTP_PORT ) );
xNetworkStatus = Plaintext_FreeRTOS_Connect(
pxNetworkContext,
democonfigSERVER_HOSTNAME,
democonfigHTTP_PORT,
democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS,
democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS );
if( xNetworkStatus != PLAINTEXT_TRANSPORT_SUCCESS )
{
xStatus = pdFAIL;
}
return xStatus;
}
Sending an HTTP request and receiving the response
The function prvSendHttpRequest()
demonstrates how to create an HTTP request then send it to
the server. The response is received synchronously in the same API call to HTTPClient_Send()
.
static BaseType_t prvSendHttpRequest(
const TransportInterface_t * pxTransportInterface,
const char * pcMethod,
size_t xMethodLen,
const char * pcPath,
size_t xPathLen )
{
BaseType_t xStatus = pdPASS;
HTTPRequestInfo_t xRequestInfo;
HTTPResponse_t xResponse;
HTTPRequestHeaders_t xRequestHeaders;
HTTPStatus_t xHTTPStatus = HTTPSuccess;
configASSERT( pcMethod != NULL );
configASSERT( pcPath != NULL );
( void ) memset( &xRequestInfo, 0, sizeof( xRequestInfo ) );
( void ) memset( &xResponse, 0, sizeof( xResponse ) );
( void ) memset( &xRequestHeaders, 0, sizeof( xRequestHeaders ) );
xRequestInfo.pHost = democonfigSERVER_HOSTNAME;
xRequestInfo.hostLen = httpexampleSERVER_HOSTNAME_LENGTH;
xRequestInfo.pMethod = pcMethod;
xRequestInfo.methodLen = xMethodLen;
xRequestInfo.pPath = pcPath;
xRequestInfo.pathLen = xPathLen;
xRequestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG;
xRequestHeaders.pBuffer = ucUserBuffer;
xRequestHeaders.bufferLen = democonfigUSER_BUFFER_LENGTH;
xHTTPStatus = HTTPClient_InitializeRequestHeaders( &xRequestHeaders,
&xRequestInfo );
if( xHTTPStatus == HTTPSuccess )
{
xResponse.pBuffer = ucUserBuffer;
xResponse.bufferLen = democonfigUSER_BUFFER_LENGTH;
LogInfo( ( "Sending HTTP %.*s request to %.*s%.*s...",
( int32_t ) xRequestInfo.methodLen, xRequestInfo.pMethod,
( int32_t ) httpexampleSERVER_HOSTNAME_LENGTH, democonfigSERVER_HOSTNAME,
( int32_t ) xRequestInfo.pathLen, xRequestInfo.pPath ) );
LogInfo( ( "Request Headers:\n%.*s\n"
"Request Body:\n%.*s\n",
( int32_t ) xRequestHeaders.headersLen,
( char * ) xRequestHeaders.pBuffer,
( int32_t ) httpexampleREQUEST_BODY_LENGTH, democonfigREQUEST_BODY ) );
xHTTPStatus = HTTPClient_Send( pxTransportInterface,
&xRequestHeaders,
( uint8_t * ) democonfigREQUEST_BODY,
httpexampleREQUEST_BODY_LENGTH,
&xResponse,
0 );
}
else
{
LogError( ( "Failed to initialize HTTP request headers: Error=%s.",
HTTPClient_strerror( xHTTPStatus ) ) );
}
if( xHTTPStatus == HTTPSuccess )
{
LogInfo( ( "Received HTTP response from %.*s%.*s...\n",
( int32_t ) httpexampleSERVER_HOSTNAME_LENGTH,
democonfigSERVER_HOSTNAME,
( int32_t ) xRequestInfo.pathLen, xRequestInfo.pPath ) );
LogDebug( ( "Response Headers:\n%.*s\n",
( int32_t ) xResponse.headersLen, xResponse.pHeaders ) );
LogDebug( ( "Status Code:\n%u\n",
xResponse.statusCode ) );
LogDebug( ( "Response Body:\n%.*s\n",
( int32_t ) xResponse.bodyLen, xResponse.pBody ) );
}
else
{
LogError( ( "Failed to send HTTP %.*s request to %.*s%.*s: Error=%s.",
( int32_t ) xRequestInfo.methodLen,
xRequestInfo.pMethod,
( int32_t ) httpexampleSERVER_HOSTNAME_LENGTH,
democonfigSERVER_HOSTNAME,
( int32_t ) xRequestInfo.pathLen,
xRequestInfo.pPath,
HTTPClient_strerror( xHTTPStatus ) ) );
}
if( xHTTPStatus != HTTPSuccess )
{
xStatus = pdFAIL;
}
return xStatus;
}
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.