| /* |
| * Copyright (c) 2017 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) |
| #if defined(CONFIG_HTTPS) |
| #define SYS_LOG_DOMAIN "https/server" |
| #else |
| #define SYS_LOG_DOMAIN "http/server" |
| #endif |
| #define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG |
| #define NET_LOG_ENABLED 1 |
| #endif |
| |
| #include <zephyr.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <version.h> |
| |
| #include <net/net_core.h> |
| #include <net/net_ip.h> |
| #include <net/http.h> |
| |
| #define BUF_ALLOC_TIMEOUT 100 |
| |
| #define HTTP_DEFAULT_PORT 80 |
| #define HTTPS_DEFAULT_PORT 443 |
| |
| #define RC_STR(rc) (rc == 0 ? "OK" : "ERROR") |
| |
| /* Max length of the error description in HTTP error reply */ |
| #define MAX_DESCRIPTION_LEN 20 |
| |
| #define HTTP_STATUS_400_BR "Bad Request" |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP_CONN) |
| /** List of http connections */ |
| static sys_slist_t http_conn; |
| |
| static http_server_cb_t ctx_mon; |
| static void *mon_user_data; |
| |
| void http_server_conn_add(struct http_ctx *ctx) |
| { |
| sys_slist_prepend(&http_conn, &ctx->node); |
| |
| if (ctx_mon) { |
| ctx_mon(ctx, mon_user_data); |
| } |
| } |
| |
| void http_server_conn_del(struct http_ctx *ctx) |
| { |
| sys_slist_find_and_remove(&http_conn, &ctx->node); |
| } |
| |
| void http_server_conn_foreach(http_server_cb_t cb, void *user_data) |
| { |
| struct http_ctx *ctx; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&http_conn, ctx, node) { |
| cb(ctx, user_data); |
| } |
| } |
| |
| void http_server_conn_monitor(http_server_cb_t cb, void *user_data) |
| { |
| ctx_mon = cb; |
| mon_user_data = user_data; |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP_CONN */ |
| |
| const char * const http_state_str(enum http_state state) |
| { |
| #if defined(CONFIG_NET_DEBUG_HTTP) |
| switch (state) { |
| case HTTP_STATE_CLOSED: |
| return "CLOSED"; |
| case HTTP_STATE_WAITING_HEADER: |
| return "WAITING_HEADER"; |
| case HTTP_STATE_RECEIVING_HEADER: |
| return "RECEIVING HEADER"; |
| case HTTP_STATE_HEADER_RECEIVED: |
| return "HEADER_RECEIVED"; |
| case HTTP_STATE_OPEN: |
| return "OPEN"; |
| } |
| #else /* CONFIG_NET_DEBUG_HTTP */ |
| ARG_UNUSED(state); |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| |
| return ""; |
| } |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) |
| static void validate_state_transition(struct http_ctx *ctx, |
| enum http_state current, |
| enum http_state new) |
| { |
| static const u16_t valid_transitions[] = { |
| [HTTP_STATE_CLOSED] = 1 << HTTP_STATE_WAITING_HEADER, |
| [HTTP_STATE_WAITING_HEADER] = |
| 1 << HTTP_STATE_RECEIVING_HEADER | |
| 1 << HTTP_STATE_CLOSED, |
| [HTTP_STATE_RECEIVING_HEADER] = |
| 1 << HTTP_STATE_HEADER_RECEIVED | |
| 1 << HTTP_STATE_CLOSED | |
| 1 << HTTP_STATE_OPEN, |
| [HTTP_STATE_HEADER_RECEIVED] = |
| 1 << HTTP_STATE_OPEN | |
| 1 << HTTP_STATE_CLOSED, |
| [HTTP_STATE_OPEN] = 1 << HTTP_STATE_CLOSED, |
| }; |
| |
| if (!(valid_transitions[current] & 1 << new)) { |
| NET_DBG("[%p] Invalid state transition: %s (%d) => %s (%d)", |
| ctx, http_state_str(current), current, |
| http_state_str(new), new); |
| } |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| |
| void _http_change_state(struct http_ctx *ctx, |
| enum http_state new_state, |
| const char *func, int line) |
| { |
| if (ctx->state == new_state) { |
| return; |
| } |
| |
| NET_ASSERT(new_state >= HTTP_STATE_CLOSED && |
| new_state <= HTTP_STATE_OPEN); |
| |
| NET_DBG("[%p] state %s (%d) => %s (%d) [%s():%d]", |
| ctx, http_state_str(ctx->state), ctx->state, |
| http_state_str(new_state), new_state, |
| func, line); |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) |
| validate_state_transition(ctx, ctx->state, new_state); |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| |
| ctx->state = new_state; |
| } |
| |
| static void http_data_sent(struct net_app_ctx *app_ctx, |
| int status, |
| void *user_data_send, |
| void *user_data) |
| { |
| struct http_ctx *ctx = user_data; |
| |
| if (!user_data_send) { |
| /* This is the token field in the net_context_send(). |
| * If this is not set, then it is TCP ACK messages |
| * that are generated by the stack. We just ignore those. |
| */ |
| return; |
| } |
| |
| if (ctx->state == HTTP_STATE_OPEN && ctx->cb.send) { |
| ctx->cb.send(ctx, status, user_data_send, ctx->user_data); |
| } |
| } |
| |
| int http_send_error(struct http_ctx *ctx, int code, const char *description, |
| u8_t *html_payload, size_t html_len) |
| { |
| char msg[sizeof(HTTP_PROTOCOL " xxx " HTTP_CRLF HTTP_CRLF) + |
| MAX_DESCRIPTION_LEN]; |
| int ret; |
| |
| if (ctx->pending) { |
| net_pkt_unref(ctx->pending); |
| ctx->pending = NULL; |
| } |
| |
| if (code < 100 || code > 999) { |
| return -EINVAL; |
| } |
| |
| snprintk(msg, sizeof(msg), "%s %d %s%s%s", HTTP_PROTOCOL, code, |
| description, HTTP_CRLF, HTTP_CRLF); |
| |
| ret = http_add_header(ctx, msg, NULL); |
| if (ret < 0) { |
| goto quit; |
| } |
| |
| if (html_payload) { |
| ret = http_prepare_and_send(ctx, html_payload, html_len, NULL); |
| if (ret < 0) { |
| goto quit; |
| } |
| } |
| |
| ret = http_send_flush(ctx, NULL); |
| |
| quit: |
| if (ret < 0) { |
| net_pkt_unref(ctx->pending); |
| ctx->pending = NULL; |
| } |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| static char *sprint_ipaddr(char *buf, int buflen, const struct sockaddr *addr) |
| { |
| if (addr->sa_family == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| char ipaddr[NET_IPV6_ADDR_LEN]; |
| |
| net_addr_ntop(addr->sa_family, |
| &net_sin6(addr)->sin6_addr, |
| ipaddr, sizeof(ipaddr)); |
| snprintk(buf, buflen, "[%s]:%u", ipaddr, |
| ntohs(net_sin6(addr)->sin6_port)); |
| #endif |
| } else if (addr->sa_family == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| char ipaddr[NET_IPV4_ADDR_LEN]; |
| |
| net_addr_ntop(addr->sa_family, |
| &net_sin(addr)->sin_addr, |
| ipaddr, sizeof(ipaddr)); |
| snprintk(buf, buflen, "%s:%u", ipaddr, |
| ntohs(net_sin(addr)->sin_port)); |
| #endif |
| } else { |
| snprintk(buf, buflen, "<AF_UNSPEC %d>", addr->sa_family); |
| } |
| |
| return buf; |
| } |
| |
| static struct net_context *get_server_ctx(struct net_app_ctx *ctx, |
| const struct sockaddr *dst) |
| { |
| int i; |
| |
| if (!dst) { |
| return NULL; |
| } |
| |
| for (i = 0; i < CONFIG_NET_APP_SERVER_NUM_CONN; i++) { |
| struct net_context *tmp; |
| u16_t port, rport; |
| |
| if (!ctx->server.net_ctxs[i]) { |
| continue; |
| } |
| |
| tmp = ctx->server.net_ctxs[i]; |
| |
| if (IS_ENABLED(CONFIG_NET_IPV4) && |
| tmp->remote.sa_family == AF_INET && |
| dst->sa_family == AF_INET) { |
| struct in_addr *addr4 = &net_sin(dst)->sin_addr; |
| struct in_addr *remote4; |
| |
| remote4 = &net_sin(&tmp->remote)->sin_addr; |
| rport = net_sin(&tmp->remote)->sin_port; |
| port = net_sin(dst)->sin_port; |
| |
| if (net_ipv4_addr_cmp(addr4, remote4) && |
| port == rport) { |
| return tmp; |
| } |
| |
| } else if (IS_ENABLED(CONFIG_NET_IPV6) && |
| tmp->remote.sa_family == AF_INET6 && |
| dst->sa_family == AF_INET6) { |
| struct in6_addr *addr6 = &net_sin6(dst)->sin6_addr; |
| struct in6_addr *remote6; |
| |
| remote6 = &net_sin6(&tmp->remote)->sin6_addr; |
| rport = net_sin6(&tmp->remote)->sin6_port; |
| port = net_sin6(dst)->sin6_port; |
| |
| if (net_ipv6_addr_cmp(addr6, remote6) && |
| port == rport) { |
| return tmp; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| |
| static inline void new_client(struct http_ctx *ctx, |
| enum http_connection_type type, |
| struct net_app_ctx *app_ctx) |
| { |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| #if defined(CONFIG_NET_IPV6) |
| #define PORT_LEN sizeof("[]:xxxxx") |
| #define ADDR_LEN NET_IPV6_ADDR_LEN |
| #elif defined(CONFIG_NET_IPV4) |
| #define PORT_LEN sizeof(":xxxxx") |
| #define ADDR_LEN NET_IPV4_ADDR_LEN |
| #endif |
| char buf[ADDR_LEN + PORT_LEN]; |
| struct net_context *net_ctx; |
| const char *type_str = "HTTP"; |
| |
| net_ctx = get_server_ctx(app_ctx, ctx->addr); |
| if (net_ctx) { |
| NET_INFO("[%p] %s connection from %s (%p)", ctx, type_str, |
| sprint_ipaddr(buf, sizeof(buf), &net_ctx->remote), |
| net_ctx); |
| } else { |
| NET_INFO("[%p] %s connection", ctx, type_str); |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| } |
| |
| static void url_connected(struct http_ctx *ctx, |
| enum http_connection_type type) |
| { |
| new_client(ctx, type, &ctx->app_ctx); |
| |
| if (ctx->cb.connect) { |
| ctx->cb.connect(ctx, type, ctx->user_data); |
| } |
| } |
| |
| struct http_root_url *http_server_add_url(struct http_server_urls *my, |
| const char *url, u8_t flags) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| if (my->urls[i].is_used) { |
| continue; |
| } |
| |
| my->urls[i].is_used = true; |
| my->urls[i].root = url; |
| |
| /* This will speed-up some future operations */ |
| my->urls[i].root_len = strlen(url); |
| my->urls[i].flags = flags; |
| |
| NET_DBG("[%d] %s URL %s", i, |
| flags == HTTP_URL_STANDARD ? "HTTP" : |
| (flags == HTTP_URL_WEBSOCKET ? "WS" : "<unknown>"), |
| url); |
| |
| return &my->urls[i]; |
| } |
| |
| return NULL; |
| } |
| |
| int http_server_del_url(struct http_server_urls *my, const char *url) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| if (!my->urls[i].is_used) { |
| continue; |
| } |
| |
| if (strncmp(my->urls[i].root, url, my->urls[i].root_len)) { |
| continue; |
| } |
| |
| my->urls[i].is_used = false; |
| my->urls[i].root = NULL; |
| |
| return 0; |
| } |
| |
| return -ENOENT; |
| } |
| |
| struct http_root_url *http_server_add_default(struct http_server_urls *my, |
| http_url_cb_t cb) |
| { |
| if (my->default_url.is_used) { |
| return NULL; |
| } |
| |
| my->default_url.is_used = true; |
| my->default_url.root = NULL; |
| my->default_url.root_len = 0; |
| my->default_url.flags = 0; |
| my->default_cb = cb; |
| |
| return &my->default_url; |
| } |
| |
| int http_server_del_default(struct http_server_urls *my) |
| { |
| if (!my->default_url.is_used) { |
| return -ENOENT; |
| } |
| |
| my->default_url.is_used = false; |
| |
| return 0; |
| } |
| |
| static int http_url_cmp(const char *url, u16_t url_len, |
| const char *root_url, u16_t root_url_len) |
| { |
| if (url_len < root_url_len) { |
| return -EINVAL; |
| } |
| |
| if (memcmp(url, root_url, root_url_len) == 0) { |
| if (url_len == root_url_len) { |
| return 0; |
| } |
| |
| /* Here we evaluate the following conditions: |
| * root_url = /images, url = /images/ -> OK |
| * root_url = /images/, url = /images/img.png -> OK |
| * root_url = /images/, url = /images_and_docs -> ERROR |
| */ |
| if (url_len > root_url_len) { |
| /* Do not match root_url = / and url = /foobar */ |
| if (root_url_len > 1 && |
| root_url[root_url_len - 1] == '/') { |
| return 0; |
| } |
| |
| if (url[root_url_len] == '/') { |
| return 0; |
| } |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| struct http_root_url *http_url_find(struct http_ctx *ctx, |
| enum http_url_flags flags) |
| { |
| u16_t url_len = ctx->http.url_len; |
| const char *url = ctx->http.url; |
| struct http_root_url *root_url; |
| u8_t i; |
| int ret; |
| |
| for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) { |
| if (!ctx->http.urls) { |
| continue; |
| } |
| |
| root_url = &ctx->http.urls->urls[i]; |
| if (!root_url || !root_url->is_used) { |
| continue; |
| } |
| |
| ret = http_url_cmp(url, url_len, |
| root_url->root, root_url->root_len); |
| if (!ret && flags == root_url->flags) { |
| return root_url; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int http_process_recv(struct http_ctx *ctx) |
| { |
| struct http_root_url *root_url; |
| int ret; |
| |
| root_url = http_url_find(ctx, HTTP_URL_STANDARD); |
| if (!root_url) { |
| if (!ctx->http.urls) { |
| NET_DBG("[%p] No URL handlers found", ctx); |
| ret = -ENOENT; |
| goto out; |
| } |
| |
| root_url = &ctx->http.urls->default_url; |
| if (!root_url || !root_url->is_used) { |
| NET_DBG("[%p] No default handler found", ctx); |
| ret = -ENOENT; |
| goto out; |
| } |
| |
| if (ctx->http.urls->default_cb) { |
| ret = ctx->http.urls->default_cb(ctx, |
| HTTP_CONNECTION); |
| if (ret != HTTP_VERDICT_ACCEPT) { |
| ret = -ECONNREFUSED; |
| goto out; |
| } |
| } |
| } |
| |
| http_change_state(ctx, HTTP_STATE_OPEN); |
| url_connected(ctx, HTTP_CONNECTION); |
| |
| ret = 0; |
| |
| out: |
| return ret; |
| } |
| |
| static void http_closed(struct net_app_ctx *app_ctx, |
| int status, |
| void *user_data) |
| { |
| struct http_ctx *ctx = user_data; |
| |
| ARG_UNUSED(app_ctx); |
| ARG_UNUSED(status); |
| |
| http_change_state(ctx, HTTP_STATE_CLOSED); |
| |
| NET_DBG("[%p] http closed", ctx); |
| |
| http_server_conn_del(ctx); |
| |
| if (ctx->cb.close) { |
| ctx->cb.close(ctx, 0, ctx->user_data); |
| } |
| } |
| |
| static void http_received(struct net_app_ctx *app_ctx, |
| struct net_pkt *pkt, |
| int status, |
| void *user_data) |
| { |
| struct http_ctx *ctx = user_data; |
| size_t start = ctx->http.data_len; |
| u16_t len = 0; |
| struct net_buf *frag; |
| int parsed_len; |
| size_t recv_len; |
| size_t pkt_len; |
| |
| recv_len = net_pkt_appdatalen(pkt); |
| if (recv_len == 0) { |
| /* don't print info about zero-length app data buffers */ |
| goto quit; |
| } |
| |
| if (status) { |
| NET_DBG("[%p] Status %d <%s>", ctx, status, RC_STR(status)); |
| goto out; |
| } |
| |
| /* Get rid of possible IP headers in the first fragment. */ |
| frag = pkt->frags; |
| |
| pkt_len = net_pkt_get_len(pkt); |
| |
| if (recv_len < pkt_len) { |
| net_buf_pull(frag, pkt_len - recv_len); |
| net_pkt_set_appdata(pkt, frag->data); |
| } |
| |
| NET_DBG("[%p] Received %zd bytes http data", ctx, recv_len); |
| |
| if (ctx->state == HTTP_STATE_OPEN) { |
| /* We have active websocket session and there is no longer |
| * any HTTP traffic in the connection. Give the data to |
| * application to send. |
| */ |
| goto http_only; |
| } |
| |
| while (frag) { |
| /* If this fragment cannot be copied to result buf, |
| * then parse what we have which will cause the callback to be |
| * called in function on_body(), and continue copying. |
| */ |
| if ((ctx->http.data_len + frag->len) > |
| ctx->http.request_buf_len) { |
| |
| if (ctx->state == HTTP_STATE_HEADER_RECEIVED) { |
| goto http_ready; |
| } |
| |
| /* If the caller has not supplied a callback, then |
| * we cannot really continue if the request buffer |
| * overflows. Set the data_len to mark how many bytes |
| * should be needed in the response_buf. |
| */ |
| if (http_process_recv(ctx) < 0) { |
| ctx->http.data_len = recv_len; |
| goto out; |
| } |
| |
| parsed_len = |
| http_parser_execute(&ctx->http.parser, |
| &ctx->http.parser_settings, |
| ctx->http.request_buf + |
| start, |
| len); |
| if (parsed_len <= 0) { |
| goto fail; |
| } |
| |
| ctx->http.data_len = 0; |
| len = 0; |
| start = 0; |
| } |
| |
| memcpy(ctx->http.request_buf + ctx->http.data_len, |
| frag->data, frag->len); |
| |
| ctx->http.data_len += frag->len; |
| len += frag->len; |
| frag = frag->frags; |
| } |
| |
| out: |
| parsed_len = http_parser_execute(&ctx->http.parser, |
| &ctx->http.parser_settings, |
| ctx->http.request_buf + start, |
| len); |
| if (parsed_len < 0) { |
| fail: |
| NET_DBG("[%p] Received %zd bytes, only parsed %d " |
| "bytes (%s %s)", |
| ctx, recv_len, parsed_len, |
| http_errno_name(ctx->http.parser.http_errno), |
| http_errno_description( |
| ctx->http.parser.http_errno)); |
| } |
| |
| if (ctx->http.parser.http_errno != HPE_OK) { |
| http_send_error(ctx, 400, HTTP_STATUS_400_BR, NULL, 0); |
| } else { |
| if (ctx->state == HTTP_STATE_HEADER_RECEIVED) { |
| goto http_ready; |
| } |
| |
| http_process_recv(ctx); |
| } |
| |
| quit: |
| http_parser_init(&ctx->http.parser, HTTP_REQUEST); |
| ctx->http.data_len = 0; |
| ctx->http.field_values_ctr = 0; |
| net_pkt_unref(pkt); |
| |
| return; |
| |
| http_only: |
| if (ctx->cb.recv) { |
| ctx->cb.recv(ctx, pkt, 0, 0, ctx->user_data); |
| } |
| |
| return; |
| |
| http_ready: |
| http_change_state(ctx, HTTP_STATE_OPEN); |
| url_connected(ctx, HTTP_CONNECT); |
| net_pkt_unref(pkt); |
| } |
| |
| #if defined(CONFIG_HTTPS) |
| int http_server_set_tls(struct http_ctx *ctx, |
| const char *server_banner, |
| u8_t *personalization_data, |
| size_t personalization_data_len, |
| net_app_cert_cb_t cert_cb, |
| net_app_entropy_src_cb_t entropy_src_cb, |
| struct k_mem_pool *pool, |
| k_thread_stack_t *stack, |
| size_t stack_len) |
| { |
| int ret; |
| |
| if (!ctx->is_tls) { |
| /* Change the default port if user did not set it */ |
| if (!ctx->server_addr) { |
| net_sin(&ctx->local)->sin_port = |
| htons(HTTPS_DEFAULT_PORT); |
| |
| #if defined(CONFIG_NET_IPV6) |
| net_sin6(&ctx->app_ctx.ipv6.local)->sin6_port = |
| htons(HTTPS_DEFAULT_PORT); |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| net_sin(&ctx->app_ctx.ipv4.local)->sin_port = |
| htons(HTTPS_DEFAULT_PORT); |
| #endif |
| } |
| |
| ret = net_app_server_tls(&ctx->app_ctx, |
| ctx->http.request_buf, |
| ctx->http.request_buf_len, |
| server_banner, |
| personalization_data, |
| personalization_data_len, |
| cert_cb, |
| entropy_src_cb, |
| pool, |
| stack, |
| stack_len); |
| if (ret < 0) { |
| NET_ERR("Cannot init TLS (%d)", ret); |
| goto quit; |
| } |
| |
| ctx->is_tls = true; |
| return 0; |
| } |
| |
| return -EALREADY; |
| |
| quit: |
| net_app_release(&ctx->app_ctx); |
| return ret; |
| } |
| #endif |
| |
| static int on_header_field(struct http_parser *parser, |
| const char *at, size_t length) |
| { |
| struct http_ctx *ctx = parser->data; |
| |
| if (ctx->http.field_values_ctr >= CONFIG_HTTP_HEADERS) { |
| return 0; |
| } |
| |
| http_change_state(ctx, HTTP_STATE_RECEIVING_HEADER); |
| |
| ctx->http.field_values[ctx->http.field_values_ctr].key = at; |
| ctx->http.field_values[ctx->http.field_values_ctr].key_len = length; |
| |
| return 0; |
| } |
| |
| static int on_header_value(struct http_parser *parser, |
| const char *at, size_t length) |
| { |
| struct http_ctx *ctx = parser->data; |
| |
| if (ctx->http.field_values_ctr >= CONFIG_HTTP_HEADERS) { |
| return 0; |
| } |
| |
| ctx->http.field_values[ctx->http.field_values_ctr].value = at; |
| ctx->http.field_values[ctx->http.field_values_ctr].value_len = length; |
| |
| ctx->http.field_values_ctr++; |
| |
| return 0; |
| } |
| |
| static int on_url(struct http_parser *parser, const char *at, size_t length) |
| { |
| struct http_ctx *ctx = parser->data; |
| |
| ctx->http.url = at; |
| ctx->http.url_len = length; |
| |
| http_change_state(ctx, HTTP_STATE_WAITING_HEADER); |
| |
| http_server_conn_add(ctx); |
| |
| return 0; |
| } |
| |
| static int on_headers_complete(struct http_parser *parser) |
| { |
| ARG_UNUSED(parser); |
| |
| return 0; |
| } |
| |
| static int init_http_parser(struct http_ctx *ctx) |
| { |
| memset(ctx->http.field_values, 0, sizeof(ctx->http.field_values)); |
| |
| ctx->http.parser_settings.on_header_field = on_header_field; |
| ctx->http.parser_settings.on_header_value = on_header_value; |
| ctx->http.parser_settings.on_url = on_url; |
| ctx->http.parser_settings.on_headers_complete = on_headers_complete; |
| |
| http_parser_init(&ctx->http.parser, HTTP_REQUEST); |
| |
| ctx->http.parser.data = ctx; |
| |
| return 0; |
| } |
| |
| static inline void new_server(struct http_ctx *ctx, |
| const char *server_banner, |
| const struct sockaddr *addr) |
| { |
| #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) |
| #if defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof("[]:xxxxx") |
| char buf[NET_IPV6_ADDR_LEN + PORT_STR]; |
| #elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6) |
| #define PORT_STR sizeof(":xxxxx") |
| char buf[NET_IPV4_ADDR_LEN + PORT_STR]; |
| #endif |
| |
| if (addr) { |
| NET_INFO("%s %s (%p)", server_banner, |
| sprint_ipaddr(buf, sizeof(buf), addr), ctx); |
| } else { |
| NET_INFO("%s (%p)", server_banner, ctx); |
| } |
| #endif /* CONFIG_NET_DEBUG_HTTP */ |
| } |
| |
| static void init_net(struct http_ctx *ctx, |
| struct sockaddr *server_addr, |
| u16_t port) |
| { |
| memset(&ctx->local, 0, sizeof(ctx->local)); |
| |
| if (server_addr) { |
| memcpy(&ctx->local, server_addr, sizeof(ctx->local)); |
| } else { |
| ctx->local.sa_family = AF_UNSPEC; |
| net_sin(&ctx->local)->sin_port = htons(port); |
| } |
| } |
| |
| int http_server_init(struct http_ctx *ctx, |
| struct http_server_urls *urls, |
| struct sockaddr *server_addr, |
| u8_t *request_buf, |
| size_t request_buf_len, |
| const char *server_banner, |
| void *user_data) |
| { |
| int ret = 0; |
| |
| if (!ctx) { |
| return -EINVAL; |
| } |
| |
| if (ctx->is_init) { |
| return -EALREADY; |
| } |
| |
| if (!request_buf || request_buf_len == 0) { |
| NET_ERR("Request buf must be set"); |
| return -EINVAL; |
| } |
| |
| memset(ctx, 0, sizeof(*ctx)); |
| |
| init_net(ctx, server_addr, HTTP_DEFAULT_PORT); |
| |
| if (server_banner) { |
| new_server(ctx, server_banner, server_addr); |
| } |
| |
| /* Timeout for network buffer allocations */ |
| ctx->timeout = BUF_ALLOC_TIMEOUT; |
| |
| ctx->http.request_buf = request_buf; |
| ctx->http.request_buf_len = request_buf_len; |
| ctx->http.urls = urls; |
| ctx->http.data_len = 0; |
| ctx->user_data = user_data; |
| ctx->server_addr = server_addr; |
| |
| ret = net_app_init_tcp_server(&ctx->app_ctx, |
| &ctx->local, |
| HTTP_DEFAULT_PORT, |
| ctx); |
| if (ret < 0) { |
| NET_ERR("Cannot create http server (%d)", ret); |
| return ret; |
| } |
| |
| ret = net_app_set_cb(&ctx->app_ctx, NULL, http_received, |
| http_data_sent, http_closed); |
| if (ret < 0) { |
| NET_ERR("Cannot set callbacks (%d)", ret); |
| goto quit; |
| } |
| |
| init_http_parser(ctx); |
| |
| ctx->is_init = true; |
| return 0; |
| |
| quit: |
| net_app_release(&ctx->app_ctx); |
| return ret; |
| } |
| |
| int http_server_enable(struct http_ctx *ctx) |
| { |
| int ret; |
| |
| NET_ASSERT(ctx); |
| |
| net_app_server_enable(&ctx->app_ctx); |
| |
| ret = net_app_listen(&ctx->app_ctx); |
| if (ret < 0) { |
| NET_ERR("Cannot wait connection (%d)", ret); |
| return false; |
| } |
| |
| return 0; |
| } |
| |
| int http_server_disable(struct http_ctx *ctx) |
| { |
| NET_ASSERT(ctx); |
| |
| net_app_server_disable(&ctx->app_ctx); |
| |
| return 0; |
| } |