
On 7/1/24 19:54, Peter Robinson wrote:
Hi Mikhail,
This patch adds HTTP/1.1 compatible web-server that can be used by other. Server supports GET, POST, and HEAD requests. On client request it will call user specified GET/POST callback. Then results will be transmitted to client.
Why are we adding a HTTP server? I don't see a cover letter explaining overall what you're attempting to achieve with this patch set so please add that. Also I suggest you look at the LWIP patch set [1] as that may make what you wish to achieve more straight forward.
Peter
[1] https://lists.denx.de/pipermail/u-boot/2024-June/556526.html
This patch series consist of * TCP fixes. Current U-Boot implementation of TCP is bad. It is especially bad for uploading. This patch series fixes TCP support. I know about attempts to add LWIP to u-Boot, but it's not in U-Boot yet.
* Rewrite of existing TCP clients (wget, fastboot_tcp) on the base of new code
* netcat client/server. It was written to test data downloading and uploading using TCP.
* HTTPD support. It consist of 2 parts: common code and sample web-server. Sample web-server can be used as a reference httpd implementation. We use this HTTPD support for our firmware upgrade web-server. It is similar to the sample web-server.
PS: Will resend patches with a cover letter tomorrow.
The following restrictions exist on the POST request at the moment:
- only multipart/form-data with a single file object
- object will be stored to a memory area specified in image_load_addr variable
Signed-off-by: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu
include/net.h | 2 +- include/net/httpd.h | 64 ++++ net/Kconfig | 14 + net/Makefile | 1 + net/httpd.c | 695 ++++++++++++++++++++++++++++++++++++++++++++ net/net.c | 6 + 6 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 include/net/httpd.h create mode 100644 net/httpd.c
diff --git a/include/net.h b/include/net.h index 235396a171b..6debbf8ed2a 100644 --- a/include/net.h +++ b/include/net.h @@ -516,7 +516,7 @@ extern int net_restart_wrap; /* Tried all network devices */ enum proto_t { BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP, NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, RS
WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, HTTPD, RS
};
extern char net_boot_file_name[1024];/* Boot File name */ diff --git a/include/net/httpd.h b/include/net/httpd.h new file mode 100644 index 00000000000..ff0dc93ecf5 --- /dev/null +++ b/include/net/httpd.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- httpd support header file
- Copyright (C) 2024 IOPSYS Software Solutions AB
- Author: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu
- */
+#ifndef __NET_HTTPD_COMMON_H__ +#define __NET_HTTPD_COMMON_H__
+struct http_reply {
int code;
const char *code_msg;
const char *data_type;
void *data;
u32 len;
+};
+struct httpd_post_data {
const char *name;
const char *filename;
void *addr;
u32 size;
+};
+enum httpd_req_check {
HTTPD_REQ_OK,
HTTPD_BAD_URL,
HTTPD_BAD_REQ,
HTTPD_CLNT_RST
+};
+struct httpd_config {
enum net_loop_state (*on_stop)(void);
void (*on_req_end)(void *req_id);
enum httpd_req_check (*pre_get)(void *req_id, const char *url);
enum httpd_req_check (*pre_post)(void *req_id, const char *url,
struct httpd_post_data *post);
struct http_reply * (*get)(void *req_id, const char *url);
struct http_reply * (*post)(void *req_id, const char *url,
struct httpd_post_data *post);
struct http_reply *error_400;
struct http_reply *error_404;
+};
+/**
- httpd_setup() - configure the webserver
- */
+void httpd_setup(struct httpd_config *config);
+/**
- httpd_stop() - start stopping of the webserver
- */
+void httpd_stop(void);
+/**
- httpd_start() - start the webserver
- */
+void httpd_start(void);
+#endif /* __NET_HTTPD_COMMON_H__ */ diff --git a/net/Kconfig b/net/Kconfig index 5dff6336293..424c5f0dae8 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -243,6 +243,20 @@ config PROT_TCP_SACK This option should be turn on if you want to achieve the fastest file transfer possible.
+config HTTPD_COMMON
bool "HTTP server common code"
depends on PROT_TCP
help
HTTP/1.1 compatible web-server common code. It supports standard
GET/POST requests. User MUST provide a configuration to the
web-server. On client request web-server will call user specified
GET/POST callback. Then results will be transmitted to the client.
The following restricions on the POST request are present at the
moment:
* only mulipart/form-data with a single binary object
* object will be stored to a memory area specified in
image_load_addr variable
config IPV6 bool "IPv6 support" help diff --git a/net/Makefile b/net/Makefile index dac7b4859fb..c1f491fad02 100644 --- a/net/Makefile +++ b/net/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o obj-$(CONFIG_PROT_TCP) += tcp.o obj-$(CONFIG_CMD_WGET) += wget.o obj-$(CONFIG_CMD_NETCAT) += netcat.o +obj-$(CONFIG_HTTPD_COMMON) += httpd.o
# Disable this warning as it is triggered by: # sprintf(buf, index ? "foo%d" : "foo", index) diff --git a/net/httpd.c b/net/httpd.c new file mode 100644 index 00000000000..31c10843a44 --- /dev/null +++ b/net/httpd.c @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- httpd support driver
- Copyright (C) 2024 IOPSYS Software Solutions AB
- Author: Mikhail Kshevetskiy mikhail.kshevetskiy@iopsys.eu
- */
+#include <command.h> +#include <version.h> +#include <display_options.h> +#include <env.h> +#include <image.h> +#include <mapmem.h> +#include <malloc.h> +#include <net.h> +#include <net/tcp.h> +#include <net/httpd.h>
+#define HTTP_PORT 80
+#define MAX_URL_LEN 128 +#define MAX_BOUNDARY_LEN 80 +#define MAX_MPART_NAME_LEN 80 +#define MAX_FILENAME_LEN 256 +#define BUFFER_LEN 2048
+enum http_req_state {
ST_REQ_LINE = 0,
ST_REQ_HDR,
ST_REQ_MPBOUNDARY,
ST_REQ_MPART,
ST_REQ_MPFILE,
ST_REQ_MPEND,
ST_REQ_DONE,
+};
+enum http_reply_state {
ST_REPLY_ERR = 0,
ST_REPLY_HDR,
ST_REPLY_BODY
+};
+enum http_method {
HTTP_UNKNOWN = -1,
HTTP_GET,
HTTP_POST,
HTTP_HEAD,
HTTP_OPTIONS,
+};
+struct httpd_priv {
enum http_req_state req_state;
enum http_reply_state reply_state;
struct tcp_stream *tcp;
enum http_method method;
char url[MAX_URL_LEN];
u32 version_major;
u32 version_minor;
u32 hdr_len;
u32 post_fstart;
u32 post_flen;
char post_fname[MAX_FILENAME_LEN];
char post_name[MAX_MPART_NAME_LEN];
char post_boundary[MAX_BOUNDARY_LEN];
int reply_code;
u32 reply_fstart;
u32 reply_flen;
void *reply_fdata;
u32 rx_processed;
char buf[BUFFER_LEN];
+};
+static struct http_reply options_reply = {
.code = 200,
.code_msg = "OK",
.data_type = "text/plain",
.data = NULL,
.len = 0
+};
+static int stop_server; +static int tsize_num_hash;
+static struct httpd_config *cfg;
+static void show_block_marker(u32 offs, u32 size) +{
int cnt;
if (offs > size)
offs = size;
cnt = offs * 50 / size;
while (tsize_num_hash < cnt) {
putc('#');
tsize_num_hash++;
}
if (cnt == 50)
putc('\n');
+}
+static void tcp_stream_on_closed(struct tcp_stream *tcp) +{
struct httpd_priv *priv = tcp->priv;
if ((priv->req_state != ST_REQ_DONE) &&
(priv->req_state >= ST_REQ_MPFILE)) {
printf("\nHTTPD: transfer was terminated\n");
}
if (cfg->on_req_end != NULL)
cfg->on_req_end(tcp->priv);
free(tcp->priv);
if (stop_server)
net_set_state(cfg->on_stop != NULL ?
cfg->on_stop() :
NETLOOP_SUCCESS);
+}
+static void http_make_reply(struct httpd_priv *priv, struct http_reply *reply,
int head_only)
+{
int offs = 0;
if (priv->version_major >= 1) {
offs = snprintf(priv->buf, sizeof(priv->buf),
"HTTP/%d.%d %d %s\r\n"
"Server: %s\r\n"
"Connection: close\r\n"
"Cache-Control: no-store\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n",
priv->version_major, priv->version_minor,
reply->code, reply->code_msg, U_BOOT_VERSION,
reply->data_type,
head_only ? 0 : reply->len);
if (priv->method == HTTP_OPTIONS)
offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
"Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST\r\n");
offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
"\r\n");
}
priv->reply_code = reply->code;
priv->reply_fstart = offs;
if (!head_only) {
priv->reply_flen = reply->len;
priv->reply_fdata = reply->data;
}
+}
+static enum httpd_req_check http_parse_line(struct httpd_priv *priv, char *line) +{
char *url, *version, *data, *end;
u32 len, tmp;
enum httpd_req_check ret;
struct httpd_post_data post;
switch (priv->req_state) {
case ST_REQ_LINE:
if (strncasecmp(line, "GET ", 4) == 0) {
priv->method = HTTP_GET;
url = line + 4;
} else if (strncasecmp(line, "POST ", 5) == 0) {
priv->method = HTTP_POST;
url = line + 5;
} else if (strncasecmp(line, "HEAD ", 5) == 0) {
priv->method = HTTP_HEAD;
url = line + 5;
} else if (strncasecmp(line, "OPTIONS ", 8) == 0) {
priv->method = HTTP_OPTIONS;
url = line + 8;
} else {
/* unknown request */
return HTTPD_CLNT_RST;
}
version = strstr(url, " ");
if (version == NULL) {
/* check for HTTP 0.9 */
if ((*url != '/') || (priv->method != HTTP_GET))
return HTTPD_CLNT_RST;
if (strlen(url) >= MAX_URL_LEN)
return HTTPD_CLNT_RST;
if (cfg->pre_get != NULL) {
ret = cfg->pre_get(priv, url);
if (ret != HTTPD_REQ_OK)
return ret;
}
priv->req_state = ST_REQ_DONE;
priv->hdr_len = strlen(line) + 2;
priv->version_major = 0;
priv->version_minor = 9;
strcpy(priv->url, url);
return HTTPD_REQ_OK;
}
if (strncasecmp(version + 1, "HTTP/", 5) != 0) {
/* version is required for HTTP >= 1.0 */
return HTTPD_CLNT_RST;
}
*version++ = '\0';
version += strlen("HTTP/");
priv->version_major = dectoul(version, &end);
switch (*end) {
case '\0':
priv->version_minor = 0;
break;
case '.':
priv->version_minor = dectoul(end + 1, &end);
if (*end == '\0')
break;
fallthrough;
default:
/* bad version format */
return HTTPD_CLNT_RST;
}
if (priv->version_major < 1) {
/* bad version */
return HTTPD_CLNT_RST;
}
if ((priv->version_major > 1) || (priv->version_minor > 1)) {
/* We support HTTP/1.1 or early standards only */
priv->version_major = 1;
priv->version_minor = 1;
}
if (*url != '/')
return HTTPD_CLNT_RST;
if (strlen(url) >= MAX_URL_LEN)
return HTTPD_CLNT_RST;
priv->req_state = ST_REQ_HDR;
strcpy(priv->url, url);
return HTTPD_REQ_OK;
case ST_REQ_HDR:
if (*line == '\0') {
priv->hdr_len = priv->rx_processed + 2;
switch (priv->method) {
case HTTP_GET:
case HTTP_HEAD:
if (cfg->pre_get != NULL) {
ret = cfg->pre_get(priv, priv->url);
if (ret != HTTPD_REQ_OK)
return ret;
}
fallthrough;
case HTTP_OPTIONS:
priv->req_state = ST_REQ_DONE;
return HTTPD_REQ_OK;
default:
break;
}
if (*priv->post_boundary != '\0') {
priv->req_state = ST_REQ_MPBOUNDARY;
return HTTPD_REQ_OK;
}
/* NOT multipart/form-data POST request */
return HTTPD_BAD_REQ;
}
if (priv->method != HTTP_POST)
return HTTPD_REQ_OK;
len = strlen("Content-Length: ");
if (strncasecmp(line, "Content-Length: ", len) == 0) {
data = line + len;
priv->post_flen = simple_strtol(data, &end, 10);
if (*end != '\0') {
/* bad Content-Length string */
return HTTPD_BAD_REQ;
}
return HTTPD_REQ_OK;
}
len = strlen("Content-Type: ");
if (strncasecmp(line, "Content-Type: ", len) == 0) {
data = strstr(line + len, " boundary=");
if (data == NULL) {
/* expect multipart/form-data format */
return HTTPD_BAD_REQ;
}
data += strlen(" boundary=");
if (strlen(data) >= sizeof(priv->post_boundary)) {
/* no space to keep boundary */
return HTTPD_BAD_REQ;
}
strcpy(priv->post_boundary, data);
return HTTPD_REQ_OK;
}
return HTTPD_REQ_OK;
case ST_REQ_MPBOUNDARY:
if (*line == '\0')
return HTTPD_REQ_OK;
if ((line[0] != '-') || (line[1] != '-') ||
(strcmp(line + 2, priv->post_boundary) != 0)) {
/* expect boundary line */
return HTTPD_BAD_REQ;
}
priv->req_state = ST_REQ_MPART;
return HTTPD_REQ_OK;
case ST_REQ_MPART:
if (*line == '\0') {
if (*priv->post_name == '\0')
return HTTPD_BAD_REQ;
priv->post_fstart = priv->rx_processed + 2;
priv->post_flen -= priv->post_fstart - priv->hdr_len;
/* expect: "\r\n--${boundary}--\r\n", so strlen() + 8 */
priv->post_flen -= strlen(priv->post_boundary) + 8;
if (cfg->pre_post != NULL) {
post.addr = NULL;
post.name = priv->post_name;
post.filename = priv->post_fname;
post.size = priv->post_flen;
ret = cfg->pre_post(priv, priv->url, &post);
if (ret != HTTPD_REQ_OK)
return ret;
}
tsize_num_hash = 0;
printf("File: %s, %u bytes\n", priv->post_fname, priv->post_flen);
printf("Loading: ");
priv->req_state = ST_REQ_MPFILE;
return HTTPD_REQ_OK;
}
len = strlen("Content-Disposition: ");
if (strncasecmp(line, "Content-Disposition: ", len) == 0) {
data = strstr(line + len, " name=\"");
if (data == NULL) {
/* name attribute not found */
return HTTPD_BAD_REQ;
}
data += strlen(" name=\"");
end = strstr(data, "\"");
if (end == NULL) {
/* bad name attribute format */
return HTTPD_BAD_REQ;
}
tmp = end - data;
if (tmp >= sizeof(priv->post_name)) {
/* multipart name is too long */
return HTTPD_BAD_REQ;
}
strncpy(priv->post_name, data, tmp);
priv->post_name[tmp] = '\0';
data = strstr(line + len, " filename=\"");
if (data == NULL) {
/* filename attribute not found */
return HTTPD_BAD_REQ;
}
data += strlen(" filename=\"");
end = strstr(data, "\"");
if (end == NULL) {
/* bad filename attribute format */
return HTTPD_BAD_REQ;
}
tmp = end - data;
if (tmp >= sizeof(priv->post_fname))
tmp = sizeof(priv->post_fname) - 1;
strncpy(priv->post_fname, data, tmp);
priv->post_fname[tmp] = '\0';
return HTTPD_REQ_OK;
}
return HTTPD_REQ_OK;
case ST_REQ_MPEND:
if (*line == '\0')
return HTTPD_REQ_OK;
len = strlen(priv->post_boundary);
if ((line[0] != '-') || (line[1] != '-') ||
(strncmp(line + 2, priv->post_boundary, len) != 0) ||
(line[len + 2] != '-') || (line[len + 3] != '-') ||
(line[len + 4] != '\0')) {
/* expect final boundary line */
return HTTPD_BAD_REQ;
}
priv->req_state = ST_REQ_DONE;
return HTTPD_REQ_OK;
default:
return HTTPD_BAD_REQ;
}
+}
+static enum httpd_req_check http_parse_buf(struct httpd_priv *priv,
char *buf, u32 size)
+{
char *eol_pos;
u32 len;
enum httpd_req_check ret;
buf[size] = '\0';
while (size > 0) {
eol_pos = strstr(buf, "\r\n");
if (eol_pos == NULL)
break;
*eol_pos = '\0';
len = eol_pos + 2 - buf;
ret = http_parse_line(priv, buf);
if (ret != HTTPD_REQ_OK) {
/* request processing error */
return ret;
}
priv->rx_processed += len;
buf += len;
size -= len;
if ((priv->req_state == ST_REQ_MPFILE) ||
(priv->req_state == ST_REQ_DONE))
return HTTPD_REQ_OK;
}
/* continue when more data becomes available */
return HTTPD_REQ_OK;
+}
+static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes) +{
struct httpd_priv *priv;
void *ptr;
u32 shift, size;
enum httpd_req_check ret;
struct http_reply *reply;
struct httpd_post_data post;
priv = tcp->priv;
switch (priv->req_state) {
case ST_REQ_DONE:
return;
case ST_REQ_MPFILE:
show_block_marker(rx_bytes - priv->post_fstart,
priv->post_flen);
if (rx_bytes < priv->post_fstart + priv->post_flen) {
priv->rx_processed = rx_bytes;
return;
}
priv->req_state = ST_REQ_MPEND;
priv->rx_processed = priv->post_fstart + priv->post_flen;
fallthrough;
case ST_REQ_MPEND:
shift = priv->rx_processed - priv->post_fstart;
ptr = map_sysmem(image_load_addr + shift,
rx_bytes - priv->rx_processed);
ret = http_parse_buf(priv, ptr,
rx_bytes - priv->rx_processed);
unmap_sysmem(ptr);
if (ret != HTTPD_REQ_OK)
goto error;
if (priv->req_state != ST_REQ_DONE)
return;
break;
default:
ret = http_parse_buf(priv, priv->buf + priv->rx_processed,
rx_bytes - priv->rx_processed);
if (ret != HTTPD_REQ_OK)
goto error;
if (priv->req_state == ST_REQ_MPFILE) {
/*
* We just switched from parsing of HTTP request
* headers to binary data reading. Our tcp->rx
* handler may put some binary data to priv->buf.
* It's time to copy these data to a proper place.
* It's required to copy whole buffer data starting
* from priv->rx_processed position. Otherwise we
* may miss data placed after the first hole.
*/
size = sizeof(priv->buf) - priv->rx_processed;
if (size > 0) {
ptr = map_sysmem(image_load_addr, size);
memcpy(ptr, priv->buf + priv->rx_processed, size);
unmap_sysmem(ptr);
}
show_block_marker(rx_bytes - priv->post_fstart,
priv->post_flen);
}
if (priv->req_state != ST_REQ_DONE)
return;
break;
}
switch (priv->method) {
case HTTP_OPTIONS:
reply = &options_reply;
break;
case HTTP_GET:
case HTTP_HEAD:
if (cfg->get == NULL) {
ret = HTTPD_BAD_REQ;
goto error;
}
reply = cfg->get(priv, priv->url);
break;
case HTTP_POST:
if (cfg->post == NULL) {
ret = HTTPD_BAD_REQ;
goto error;
}
post.name = priv->post_name;
post.filename = priv->post_fname;
post.size = priv->post_flen;
post.addr = map_sysmem(image_load_addr, post.size);
reply = cfg->post(priv, priv->url, &post);
unmap_sysmem(post.addr);
break;
default:
ret = HTTPD_BAD_REQ;
goto error;
}
http_make_reply(priv, reply, priv->method == HTTP_HEAD);
return;
+error:
priv->req_state = ST_REQ_DONE;
switch (ret) {
case HTTPD_BAD_URL:
http_make_reply(priv, cfg->error_404, 0);
break;
case HTTPD_BAD_REQ:
http_make_reply(priv, cfg->error_400, 0);
break;
default:
tcp_stream_reset(tcp);
break;
}
+}
+static u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len) +{
void *ptr;
struct httpd_priv *priv;
u32 shift;
priv = tcp->priv;
switch (priv->req_state) {
case ST_REQ_DONE:
return len;
case ST_REQ_MPFILE:
case ST_REQ_MPEND:
shift = rx_offs - priv->post_fstart;
ptr = map_sysmem(image_load_addr + shift, len);
memcpy(ptr, buf, len);
unmap_sysmem(ptr);
return len;
default:
/*
* accept data that fits to buffer,
* reserve space for end of line symbol
*/
if (rx_offs + len > sizeof(priv->buf) - 1)
len = sizeof(priv->buf) - rx_offs - 1;
memcpy(priv->buf + rx_offs, buf, len);
return len;
}
+}
+static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes) +{
struct httpd_priv *priv;
priv = tcp->priv;
if ((priv->req_state == ST_REQ_DONE) &&
(tx_bytes == priv->reply_fstart + priv->reply_flen))
tcp_stream_close(tcp);
+}
+static u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 maxlen) +{
struct httpd_priv *priv;
u32 len, bytes = 0;
char *ptr;
priv = tcp->priv;
if (priv->req_state != ST_REQ_DONE)
return 0;
if (tx_offs < priv->reply_fstart) {
len = maxlen;
if (len > priv->reply_fstart - tx_offs)
len = priv->reply_fstart - tx_offs;
memcpy(buf, priv->buf + tx_offs, len);
buf += len;
tx_offs += len;
bytes += len;
maxlen -= len;
}
if (tx_offs >= priv->reply_fstart) {
if (tx_offs + maxlen > priv->reply_fstart + priv->reply_flen)
maxlen = priv->reply_fstart + priv->reply_flen - tx_offs;
if (maxlen > 0) {
ptr = priv->reply_fdata + tx_offs - priv->reply_fstart;
memcpy(buf, ptr, maxlen);
bytes += maxlen;
}
}
return bytes;
+}
+static int tcp_stream_on_create(struct tcp_stream *tcp) +{
struct httpd_priv *priv;
if ((cfg == NULL) || stop_server ||
(tcp->lport != HTTP_PORT))
return 0;
priv = malloc(sizeof(struct httpd_priv));
if (priv == NULL)
return 0;
memset(priv, 0, sizeof(struct httpd_priv));
priv->tcp = tcp;
tcp->priv = priv;
tcp->on_closed = tcp_stream_on_closed;
tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
tcp->rx = tcp_stream_rx;
tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
tcp->tx = tcp_stream_tx;
return 1;
+}
+void httpd_setup(struct httpd_config *config) +{
cfg = config;
+}
+void httpd_stop(void) +{
stop_server = 1;
+}
+void httpd_start(void) +{
if (cfg == NULL) {
net_set_state(NETLOOP_FAIL);
return;
}
stop_server = 0;
memset(net_server_ethaddr, 0, 6);
tcp_stream_set_on_create_handler(tcp_stream_on_create);
printf("HTTPD listening on port %d...\n", HTTP_PORT);
+} diff --git a/net/net.c b/net/net.c index 809fe5c4792..ca761c57372 100644 --- a/net/net.c +++ b/net/net.c @@ -111,6 +111,7 @@ #include <net/tcp.h> #include <net/wget.h> #include <net/netcat.h> +#include <net/httpd.h> #include "arp.h" #include "bootp.h" #include "cdp.h" @@ -575,6 +576,11 @@ restart: netcat_store_start(); break; #endif +#if defined(CONFIG_HTTPD_COMMON)
case HTTPD:
httpd_start();
break;
+#endif #if defined(CONFIG_CMD_CDP) case CDP: cdp_start(); -- 2.43.0