net/http: Add the HTTP/1.1 API

This patch adds the HTTP/1.1 API for Zephyr. This API consists of client
and server context structures enabled via Kconfig variables.
HTTP parser support is enabled via the CONFIG_HTTP_PARSER configuration
variable.

Currently, this API only includes support for writing HTTP requests
(client mode) and HTTP responses (server mode). TLS support is not
considered in this iteration.

Supported HTTP methods:
	GET, HEAD, OPTIONS and POST.

Supported HTTP responses:
	400, 403 404. The http_response routine may be used to write
	any HTTP status code, for example 200 OK.

Jira: ZEP-1701

Change-Id: Ic9ccd4d4578d6d0f3a439976ea332b031644ca7d
Signed-off-by: Flavio Santes <flavio.santes@intel.com>
This commit is contained in:
Flavio Santes 2017-02-21 15:02:00 -06:00 committed by Jukka Rissanen
commit 8308b9bd2d
12 changed files with 448 additions and 4 deletions

123
include/net/http.h Normal file
View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __HTTP_H__
#define __HTTP_H__
#if defined(CONFIG_HTTP_CLIENT)
#include <net/net_context.h>
struct http_client_request {
/** The HTTP method: GET, HEAD, OPTIONS, POST */
char *method;
/** The URL for this request, for example: /index.html */
char *url;
/** The HTTP protocol: HTTP/1.1 */
char *protocol;
/** The HTTP header fields (application specific)
* The Content-Type may be specified here or in the next field.
* Depending on your application, the Content-Type may vary, however
* some header fields may remain constant through the application's
* life cycle.
*/
char *header_fields;
/** The value of the Content-Type header field, may be NULL */
char *content_type_value;
/** Payload, may be NULL */
char *payload;
/** Payload size, may be 0 */
uint16_t payload_size;
};
int http_request(struct net_context *net_ctx, int32_t timeout,
struct http_client_request *req);
int http_request_get(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields);
int http_request_head(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields);
int http_request_options(struct net_context *net_ctx, int32_t timeout,
char *url, char *header_fields);
int http_request_post(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields, char *content_type_value,
char *payload);
#endif
#if defined(CONFIG_HTTP_SERVER)
#include <net/net_context.h>
#if defined(CONFIG_HTTP_PARSER)
#include <net/http_parser.h>
#endif
/* HTTP server context state */
enum HTTP_CTX_STATE {
HTTP_CTX_FREE = 0,
HTTP_CTX_IN_USE
};
/* HTTP header fields struct */
struct http_field_value {
/** Field name, this variable will point to the beginning of the string
* containing the HTTP field name
*/
const char *key;
/** Length of the field name */
uint16_t key_len;
/** Value, this variable will point to the beginning of the string
* containing the field value
*/
const char *value;
/** Length of the field value */
uint16_t value_len;
};
/* The HTTP server context struct */
struct http_server_ctx {
uint8_t state;
/** Collection of header fields */
struct http_field_value field_values[CONFIG_HTTP_HEADER_FIELD_ITEMS];
/** Number of header field elements */
uint16_t field_values_ctr;
/** HTTP Request URL */
const char *url;
/** URL's length */
uint16_t url_len;
/**IP stack network context */
struct net_context *net_ctx;
/** Network timeout */
int32_t timeout;
#if defined(CONFIG_HTTP_PARSER)
/** HTTP parser */
struct http_parser parser;
/** HTTP parser settings */
struct http_parser_settings parser_settings;
#endif
};
int http_response(struct http_server_ctx *ctx, const char *http_header,
const char *html_payload);
int http_response_400(struct http_server_ctx *ctx, const char *html_payload);
int http_response_403(struct http_server_ctx *ctx, const char *html_payload);
int http_response_404(struct http_server_ctx *ctx, const char *html_payload);
#endif
#endif

View file

@ -16,6 +16,7 @@ CONFIG_NET_NBUF_TX_DATA_COUNT=16
CONFIG_NET_IPV4=n
CONFIG_NET_IPV6=y
CONFIG_HTTP=y
CONFIG_HTTP_PARSER=y
CONFIG_STDOUT_CONSOLE=y
@ -33,3 +34,4 @@ CONFIG_NET_SAMPLES_PEER_IPV4_ADDR="192.168.1.10"
# See the config.h file and the LINEARIZE_BUFFER define
#
#CONFIG_NET_NBUF_DATA_SIZE=512

View file

@ -20,8 +20,8 @@ CONFIG_NET_IPV6=n
# Enable IPv4 support
CONFIG_NET_IPV4=y
CONFIG_HTTP=y
CONFIG_HTTP_PARSER=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_NET_SAMPLES_IP_ADDRESSES=y

View file

@ -16,6 +16,7 @@ CONFIG_NET_IFACE_UNICAST_IPV4_ADDR_COUNT=3
CONFIG_STDOUT_CONSOLE=y
CONFIG_HTTP=y
CONFIG_HTTP_PARSER=y
# Enable IPv6 support

View file

@ -17,6 +17,7 @@ CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2
CONFIG_STDOUT_CONSOLE=y
CONFIG_HTTP=y
CONFIG_HTTP_PARSER=y
# Enable IPv6 support

View file

@ -1,4 +1,4 @@
obj-$(CONFIG_ZOAP) += zoap/
obj-$(CONFIG_DNS_RESOLVER) += dns/
obj-$(CONFIG_MQTT_LIB) += mqtt/
obj-$(CONFIG_HTTP_PARSER) += http/
obj-$(CONFIG_HTTP) += http/

View file

@ -10,6 +10,6 @@ ifdef CONFIG_MQTT_LIB
include $(srctree)/subsys/net/lib/mqtt/Makefile
endif
ifdef CONFIG_HTTP_PARSER
ifdef CONFIG_HTTP
include $(srctree)/subsys/net/lib/http/Makefile
endif

View file

@ -3,10 +3,43 @@
# SPDX-License-Identifier: Apache-2.0
#
config HTTP
bool
prompt "HTTP support"
default n
help
This option enables the HTTP library
config HTTP_SERVER
bool
prompt "HTTP server support"
default n
depends on HTTP
help
Enables HTTP server routines
config HTTP_HEADER_FIELD_ITEMS
int
prompt "HTTP header field max number of items"
depends on HTTP_SERVER
default 8
help
Number of HTTP header field items that an HTTP server
application will handle
config HTTP_CLIENT
bool
prompt "HTTP client support"
default n
depends on HTTP
help
Enables HTTP client routines
config HTTP_PARSER
bool
prompt "HTTP Parser support"
default n
depends on HTTP
help
This option enables the http_parser library from nodejs.
This parser requires some string-related routines commonly

View file

@ -1,3 +1,5 @@
ccflags-$(CONFIG_HTTP_PARSER_STRICT) += -DHTTP_PARSER_STRICT
obj-y := http_parser.o
obj-$(CONFIG_HTTP_PARSER) := http_parser.o
obj-$(CONFIG_HTTP_CLIENT) += http_client.o
obj-$(CONFIG_HTTP_SERVER) += http_server.o

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <net/http.h>
#include <net/nbuf.h>
#include <misc/printk.h>
/* HTTP client defines */
#define HTTP_PROTOCOL "HTTP/1.1"
#define HTTP_EOF "\r\n\r\n"
#define HTTP_CONTENT_TYPE "Content-Type: "
#define HTTP_CONT_LEN_SIZE 64
int http_request(struct net_context *net_ctx, int32_t timeout,
struct http_client_request *req)
{
struct net_buf *tx;
int rc = -ENOMEM;
tx = net_nbuf_get_tx(net_ctx, timeout);
if (!tx) {
return -ENOMEM;
}
if (!net_nbuf_append(tx, strlen(req->method), (uint8_t *)req->method,
timeout)) {
goto lb_exit;
}
if (!net_nbuf_append(tx, strlen(req->url), (uint8_t *)req->url,
timeout)) {
goto lb_exit;
}
if (!net_nbuf_append(tx, strlen(req->protocol),
(uint8_t *)req->protocol, timeout)) {
goto lb_exit;
}
if (!net_nbuf_append(tx, strlen(req->header_fields),
(uint8_t *)req->header_fields,
timeout)) {
goto lb_exit;
}
if (req->content_type_value) {
if (!net_nbuf_append(tx, strlen(HTTP_CONTENT_TYPE),
(uint8_t *)HTTP_CONTENT_TYPE,
timeout)) {
goto lb_exit;
}
if (!net_nbuf_append(tx, strlen(req->content_type_value),
(uint8_t *)req->content_type_value,
timeout)) {
goto lb_exit;
}
}
if (req->payload && req->payload_size) {
char content_len_str[HTTP_CONT_LEN_SIZE];
rc = snprintk(content_len_str, HTTP_CONT_LEN_SIZE,
"\r\nContent-Length: %u\r\n\r\n",
req->payload_size);
if (rc <= 0 || rc >= HTTP_CONT_LEN_SIZE) {
rc = -ENOMEM;
goto lb_exit;
}
if (!net_nbuf_append(tx, rc, (uint8_t *)content_len_str,
timeout)) {
rc = -ENOMEM;
goto lb_exit;
}
if (!net_nbuf_append(tx, req->payload_size,
(uint8_t *)req->payload,
timeout)) {
rc = -ENOMEM;
goto lb_exit;
}
} else {
if (!net_nbuf_append(tx, strlen(HTTP_EOF),
(uint8_t *)HTTP_EOF,
timeout)) {
goto lb_exit;
}
}
return net_context_send(tx, NULL, timeout, NULL, NULL);
lb_exit:
net_buf_unref(tx);
return rc;
}
int http_request_get(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields)
{
struct http_client_request req = {
.method = "GET ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
return http_request(net_ctx, timeout, &req);
}
int http_request_head(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields)
{
struct http_client_request req = {
.method = "HEAD ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
return http_request(net_ctx, timeout, &req);
}
int http_request_options(struct net_context *net_ctx, int32_t timeout,
char *url, char *header_fields)
{
struct http_client_request req = {
.method = "OPTIONS ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields };
return http_request(net_ctx, timeout, &req);
}
int http_request_post(struct net_context *net_ctx, int32_t timeout, char *url,
char *header_fields, char *content_type_value,
char *payload)
{
struct http_client_request req = {
.method = "POST ",
.url = url,
.protocol = " "HTTP_PROTOCOL"\r\n",
.header_fields = header_fields,
.content_type_value = content_type_value,
.payload = payload };
return http_request(net_ctx, timeout, &req);
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <net/http.h>
#include <misc/printk.h>
#include <net/nbuf.h>
#include <net/net_context.h>
#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Transfer-Encoding: chunked\r\n" \
"\r\n"
#define HTTP_STATUS_400_BR "HTTP/1.1 400 Bad Request\r\n" \
"\r\n"
#define HTTP_STATUS_403_FBD "HTTP/1.1 403 Forbidden\r\n" \
"\r\n"
#define HTTP_STATUS_404_NF "HTTP/1.1 404 Not Found\r\n" \
"\r\n"
static inline uint16_t http_strlen(const char *str)
{
if (str) {
return strlen(str);
}
return 0;
}
static int http_add_header(struct net_buf *tx, int32_t timeout, const char *str)
{
if (net_nbuf_append(tx, strlen(str), (uint8_t *)str, timeout)) {
return 0;
}
return -ENOMEM;
}
static int http_add_chunk(struct net_buf *tx, int32_t timeout, const char *str)
{
char chunk_header[16];
char *rn = "\r\n";
uint16_t str_len;
str_len = http_strlen(str);
snprintk(chunk_header, sizeof(chunk_header), "%x\r\n", str_len);
if (!net_nbuf_append(tx, strlen(chunk_header), chunk_header, timeout)) {
return -ENOMEM;
}
if (str_len > 0) {
if (!net_nbuf_append(tx, str_len, (uint8_t *)str, timeout)) {
return -ENOMEM;
}
}
if (!net_nbuf_append(tx, strlen(rn), rn, timeout)) {
return -ENOMEM;
}
return 0;
}
int http_response(struct http_server_ctx *ctx, const char *http_header,
const char *html_payload)
{
struct net_buf *tx;
int rc = -EINVAL;
tx = net_nbuf_get_tx(ctx->net_ctx, ctx->timeout);
if (!tx) {
goto exit_routine;
}
rc = http_add_header(tx, ctx->timeout, http_header);
if (rc != 0) {
goto exit_routine;
}
if (html_payload) {
rc = http_add_chunk(tx, ctx->timeout, html_payload);
if (rc != 0) {
goto exit_routine;
}
}
/* like EOF */
rc = http_add_chunk(tx, ctx->timeout, NULL);
if (rc != 0) {
goto exit_routine;
}
rc = net_context_send(tx, NULL, 0, NULL, NULL);
if (rc != 0) {
goto exit_routine;
}
tx = NULL;
exit_routine:
/* unref can handle NULL buffers, so we are covered */
net_nbuf_unref(tx);
return rc;
}
int http_response_400(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_400_BR, html_payload);
}
int http_response_403(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_403_FBD, html_payload);
}
int http_response_404(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_404_NF, html_payload);
}

View file

@ -2,6 +2,7 @@ CONFIG_NETWORKING=y
CONFIG_RANDOM_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_HTTP=y
CONFIG_HTTP_PARSER=y
# Enable strict parser by uncommenting the following line
# CONFIG_HTTP_PARSER_STRICT=y