From f2bf9a17c51e574e2679cc1c934d477d34fa216a Mon Sep 17 00:00:00 2001 From: Tomasz Gorochowik Date: Tue, 18 Jun 2019 13:08:54 +0200 Subject: [PATCH] samples: net: Add civetweb HTTP sample This commit adds civetweb as a west module and a sample that uses it. Signed-off-by: Tomasz Gorochowik Signed-off-by: Piotr Zierhoffer --- modules/Kconfig.civetweb | 8 + samples/net/sockets/civetweb/CMakeLists.txt | 8 + samples/net/sockets/civetweb/prj.conf | 34 +++ samples/net/sockets/civetweb/sample.yaml | 6 + .../civetweb/src/external_log_access.inl | 28 ++ .../src/external_mg_cry_internal_impl.inl | 18 ++ .../sockets/civetweb/src/libc_extensions.c | 262 ++++++++++++++++++ .../sockets/civetweb/src/libc_extensions.h | 72 +++++ samples/net/sockets/civetweb/src/main.c | 181 ++++++++++++ west.yml | 6 + 10 files changed, 623 insertions(+) create mode 100644 modules/Kconfig.civetweb create mode 100644 samples/net/sockets/civetweb/CMakeLists.txt create mode 100644 samples/net/sockets/civetweb/prj.conf create mode 100644 samples/net/sockets/civetweb/sample.yaml create mode 100644 samples/net/sockets/civetweb/src/external_log_access.inl create mode 100644 samples/net/sockets/civetweb/src/external_mg_cry_internal_impl.inl create mode 100644 samples/net/sockets/civetweb/src/libc_extensions.c create mode 100644 samples/net/sockets/civetweb/src/libc_extensions.h create mode 100644 samples/net/sockets/civetweb/src/main.c diff --git a/modules/Kconfig.civetweb b/modules/Kconfig.civetweb new file mode 100644 index 00000000000..30618e5fabd --- /dev/null +++ b/modules/Kconfig.civetweb @@ -0,0 +1,8 @@ +# Copyright (c) 2019 Antmicro Ltd +# +# SPDX-License-Identifier: Apache-2.0 + +config CIVETWEB + bool "Civetweb Support" + help + This option enables the civetweb HTTP API. diff --git a/samples/net/sockets/civetweb/CMakeLists.txt b/samples/net/sockets/civetweb/CMakeLists.txt new file mode 100644 index 00000000000..119eb74c9ca --- /dev/null +++ b/samples/net/sockets/civetweb/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(hello_world) + +target_sources(app PRIVATE src/main.c src/libc_extensions.c) diff --git a/samples/net/sockets/civetweb/prj.conf b/samples/net/sockets/civetweb/prj.conf new file mode 100644 index 00000000000..e8aff517b10 --- /dev/null +++ b/samples/net/sockets/civetweb/prj.conf @@ -0,0 +1,34 @@ +# General config +CONFIG_CIVETWEB=y +CONFIG_JSON_LIBRARY=y + +# pthreads +CONFIG_POSIX_API=y +CONFIG_PTHREAD_IPC=y +CONFIG_POSIX_MQUEUE=y + +# networking +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +# CONFIG_NET_IPV6 is not set +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y +CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=131072 +CONFIG_NET_TX_STACK_SIZE=8192 +CONFIG_NET_RX_STACK_SIZE=8192 +CONFIG_ISR_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_IDLE_STACK_SIZE=2048 +CONFIG_SOC_SERIES_SAME70=y + +CONFIG_DNS_RESOLVER=y + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="10.0.0.111" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +CONFIG_NET_CONFIG_MY_IPV4_GW="10.0.0.116" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="10.0.0.116" + +# logging +CONFIG_NET_LOG=y diff --git a/samples/net/sockets/civetweb/sample.yaml b/samples/net/sockets/civetweb/sample.yaml new file mode 100644 index 00000000000..319db7219cd --- /dev/null +++ b/samples/net/sockets/civetweb/sample.yaml @@ -0,0 +1,6 @@ +sample: + description: Civetweb HTTP API sample + name: civetweb +tests: + sample.net.sockets.civetweb: + platform_whitelist: sam_e70_xplained diff --git a/samples/net/sockets/civetweb/src/external_log_access.inl b/samples/net/sockets/civetweb/src/external_log_access.inl new file mode 100644 index 00000000000..82475e3605a --- /dev/null +++ b/samples/net/sockets/civetweb/src/external_log_access.inl @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Antmicro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +static void log_access(const struct mg_connection *conn) +{ + const struct mg_request_info *ri; + char src_addr[IP_ADDR_STR_LEN]; + + if (!conn || !conn->dom_ctx) { + return; + } + + ri = &conn->request_info; + + sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa); + + printf("%s - \"%s %s%s%s HTTP/%s\" %d\n", + src_addr, + ri->request_method ? ri->request_method : "-", + ri->request_uri ? ri->request_uri : "-", + ri->query_string ? "?" : "", + ri->query_string ? ri->query_string : "", + ri->http_version, + conn->status_code); +} diff --git a/samples/net/sockets/civetweb/src/external_mg_cry_internal_impl.inl b/samples/net/sockets/civetweb/src/external_mg_cry_internal_impl.inl new file mode 100644 index 00000000000..e0959dbdfc4 --- /dev/null +++ b/samples/net/sockets/civetweb/src/external_mg_cry_internal_impl.inl @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Antmicro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +static void mg_cry_internal_impl(const struct mg_connection *conn, + const char *func, + unsigned line, + const char *fmt, + va_list ap) +{ + (void)conn; + + printf("[INTERNAL ERROR]: %s @ %d\n", func, line); + vprintf(fmt, ap); + printf("\n"); +} diff --git a/samples/net/sockets/civetweb/src/libc_extensions.c b/samples/net/sockets/civetweb/src/libc_extensions.c new file mode 100644 index 00000000000..dc50c870a04 --- /dev/null +++ b/samples/net/sockets/civetweb/src/libc_extensions.c @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2019 Antmicro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "libc_extensions.h" + +#define FN_MISSING() printf("[IMPLEMENTATION MISSING : %s]\n", __func__) + +size_t strcspn(const char *s1, const char *s2) +{ + int i, j; + + for (i = 0; i < strlen(s2); ++i) { + for (j = 0; j < strlen(s1); ++j) { + if (s1[j] == s2[i]) { + return j; + } + } + } + + return strlen(s1); +} + +int iscntrl(int c) +{ + /* All the characters placed before the space on the ASCII table + * and the 0x7F character (DEL) are control characters. + */ + return (int)(c < ' ' || c == 0x7F); +} + +size_t strftime(char *dst, size_t dst_size, + const char *fmt, + const struct tm *tm) +{ + FN_MISSING(); + + return 0; +} + +double difftime(time_t end, time_t beg) +{ + return end - beg; +} + +struct __strerr_wrap { + int err; + const char *errstr; +}; + +/* Implementation suggested by @rakons in #16527 */ +#define STRERR_DEFINE(e) {e, #e} + +static const struct __strerr_wrap error_strings[] = { + STRERR_DEFINE(EILSEQ), + STRERR_DEFINE(EDOM), + STRERR_DEFINE(ERANGE), + STRERR_DEFINE(ENOTTY), + STRERR_DEFINE(EACCES), + STRERR_DEFINE(EPERM), + STRERR_DEFINE(ENOENT), + STRERR_DEFINE(ESRCH), + STRERR_DEFINE(EEXIST), + STRERR_DEFINE(ENOSPC), + STRERR_DEFINE(ENOMEM), + STRERR_DEFINE(EBUSY), + STRERR_DEFINE(EINTR), + STRERR_DEFINE(EAGAIN), + STRERR_DEFINE(ESPIPE), + STRERR_DEFINE(EXDEV), + STRERR_DEFINE(EROFS), + STRERR_DEFINE(ENOTEMPTY), + STRERR_DEFINE(ECONNRESET), + STRERR_DEFINE(ETIMEDOUT), + STRERR_DEFINE(ECONNREFUSED), + STRERR_DEFINE(EHOSTDOWN), + STRERR_DEFINE(EHOSTUNREACH), + STRERR_DEFINE(EADDRINUSE), + STRERR_DEFINE(EPIPE), + STRERR_DEFINE(EIO), + STRERR_DEFINE(ENXIO), + STRERR_DEFINE(ENOTBLK), + STRERR_DEFINE(ENODEV), + STRERR_DEFINE(ENOTDIR), + STRERR_DEFINE(EISDIR), + STRERR_DEFINE(ETXTBSY), + STRERR_DEFINE(ENOEXEC), + STRERR_DEFINE(EINVAL), + STRERR_DEFINE(E2BIG), + STRERR_DEFINE(ELOOP), + STRERR_DEFINE(ENAMETOOLONG), + STRERR_DEFINE(ENFILE), + STRERR_DEFINE(EMFILE), + STRERR_DEFINE(EBADF), + STRERR_DEFINE(ECHILD), + STRERR_DEFINE(EFAULT), + STRERR_DEFINE(EFBIG), + STRERR_DEFINE(EMLINK), + STRERR_DEFINE(ENOLCK), + STRERR_DEFINE(EDEADLK), + STRERR_DEFINE(ECANCELED), + STRERR_DEFINE(ENOSYS), + STRERR_DEFINE(ENOMSG), + STRERR_DEFINE(ENOSTR), + STRERR_DEFINE(ENODATA), + STRERR_DEFINE(ETIME), + STRERR_DEFINE(ENOSR), + STRERR_DEFINE(EPROTO), + STRERR_DEFINE(EBADMSG), + STRERR_DEFINE(ENOTSOCK), + STRERR_DEFINE(EDESTADDRREQ), + STRERR_DEFINE(EMSGSIZE), + STRERR_DEFINE(EPROTOTYPE), + STRERR_DEFINE(ENOPROTOOPT), + STRERR_DEFINE(EPROTONOSUPPORT), + STRERR_DEFINE(ESOCKTNOSUPPORT), + STRERR_DEFINE(ENOTSUP), + STRERR_DEFINE(EPFNOSUPPORT), + STRERR_DEFINE(EAFNOSUPPORT), + STRERR_DEFINE(EADDRNOTAVAIL), + STRERR_DEFINE(ENETDOWN), + STRERR_DEFINE(ENETUNREACH), + STRERR_DEFINE(ENETRESET), + STRERR_DEFINE(ECONNABORTED), + STRERR_DEFINE(ENOBUFS), + STRERR_DEFINE(EISCONN), + STRERR_DEFINE(ENOTCONN), + STRERR_DEFINE(ESHUTDOWN), + STRERR_DEFINE(EALREADY), + STRERR_DEFINE(EINPROGRESS), +}; + +static char *strerr_unknown = "UNKNOWN"; + +char *strerror(int err) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(error_strings); ++i) { + if (error_strings[i].err == err) { + return (char *)error_strings[i].errstr; + } + } + + return strerr_unknown; +} + +int sscanf(const char *s, const char *format, ...) +{ + FN_MISSING(); + + return 0; +} + +double atof(const char *str) +{ + /* XXX good enough for civetweb uses */ + return (double)atoi(str); +} + +long long int strtoll(const char *str, char **endptr, int base) +{ + /* XXX good enough for civetweb uses */ + return (long long int)strtol(str, endptr, base); +} + +time_t time(time_t *t) +{ + return 0; +} + +/* + * Most of the wrappers below are copies of the wrappers in net/sockets.h, + * but they are available only if CONFIG_NET_SOCKETS_POSIX_NAMES is enabled + * which is impossible here. + */ + +int getsockname(int sock, struct sockaddr *addr, + socklen_t *addrlen) +{ + return zsock_getsockname(sock, addr, addrlen); +} + +int poll(struct zsock_pollfd *fds, int nfds, int timeout) +{ + return zsock_poll(fds, nfds, timeout); +} + +int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, + char *host, socklen_t hostlen, + char *serv, socklen_t servlen, int flags) +{ + return zsock_getnameinfo(addr, addrlen, host, hostlen, + serv, servlen, flags); +} + +ssize_t send(int sock, const void *buf, size_t len, int flags) +{ + return zsock_send(sock, buf, len, flags); +} + +ssize_t recv(int sock, void *buf, size_t max_len, int flags) +{ + return zsock_recv(sock, buf, max_len, flags); +} + +int socket(int family, int type, int proto) +{ + return zsock_socket(family, type, proto); +} + +int getaddrinfo(const char *host, const char *service, + const struct zsock_addrinfo *hints, + struct zsock_addrinfo **res) +{ + return zsock_getaddrinfo(host, service, hints, res); +} + +void freeaddrinfo(struct zsock_addrinfo *ai) +{ + zsock_freeaddrinfo(ai); +} + +int connect(int sock, const struct sockaddr *addr, + socklen_t addrlen) +{ + return zsock_connect(sock, addr, addrlen); +} + +int getsockopt(int sock, int level, int optname, + void *optval, socklen_t *optlen) +{ + return zsock_getsockopt(sock, level, optname, optval, optlen); +} + +int setsockopt(int sock, int level, int optname, + const void *optval, socklen_t optlen) +{ + return zsock_setsockopt(sock, level, optname, optval, optlen); +} + +int listen(int sock, int backlog) +{ + return zsock_listen(sock, backlog); +} + +int bind(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + return zsock_bind(sock, addr, addrlen); +} + +int accept(int sock, struct sockaddr *addr, socklen_t *addrlen) +{ + return zsock_accept(sock, addr, addrlen); +} diff --git a/samples/net/sockets/civetweb/src/libc_extensions.h b/samples/net/sockets/civetweb/src/libc_extensions.h new file mode 100644 index 00000000000..75f29c514bb --- /dev/null +++ b/samples/net/sockets/civetweb/src/libc_extensions.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Antmicro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#define pollfd zsock_pollfd +#define fcntl zsock_fcntl + +#define POLLIN ZSOCK_POLLIN +#define POLLOUT ZSOCK_POLLOUT + +#define addrinfo zsock_addrinfo + +#define F_SETFD 2 +#define FD_CLOEXEC 1 + +size_t strcspn(const char *s1, const char *s2); +size_t strspn(const char *s1, const char *s2); +int iscntrl(int c); + +double atof(const char *str); +long long int strtoll(const char *str, char **endptr, int base); +int sscanf(const char *s, const char *format, ...); +char *strerror(int err); +unsigned long long int strtoull(const char *str, char **endptr, int base); + +time_t time(time_t *t); +struct tm *gmtime(const time_t *ptime); +size_t strftime(char *dst, size_t dst_size, const char *fmt, + const struct tm *tm); +double difftime(time_t end, time_t beg); +struct tm *localtime(const time_t *timer); + +int fileno(FILE *stream); +int ferror(FILE *stream); +int fclose(FILE *stream); +int fseeko(FILE *stream, off_t offset, int whence); +FILE *fopen(const char *filename, const char *mode); +char *fgets(char *str, int num, FILE *stream); +size_t fread(void *ptr, size_t size, size_t count, FILE *stream); +int remove(const char *filename); + +int getsockname(int sock, struct sockaddr *addr, socklen_t *addrlen); +int poll(struct zsock_pollfd *fds, int nfds, int timeout); + +int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, + char *host, socklen_t hostlen, + char *serv, socklen_t servlen, int flags); + +ssize_t send(int sock, const void *buf, size_t len, int flags); +ssize_t recv(int sock, void *buf, size_t max_len, int flags); +int socket(int family, int type, int proto); +int getaddrinfo(const char *host, const char *service, + const struct zsock_addrinfo *hints, + struct zsock_addrinfo **res); + +void freeaddrinfo(struct zsock_addrinfo *ai); +int connect(int sock, const struct sockaddr *addr, socklen_t addrlen); +int getsockopt(int sock, int level, int optname, + void *optval, socklen_t *optlen); +int setsockopt(int sock, int level, int optname, + const void *optval, socklen_t optlen); +int listen(int sock, int backlog); +int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); +int bind(int sock, const struct sockaddr *addr, socklen_t addrlen); diff --git a/samples/net/sockets/civetweb/src/main.c b/samples/net/sockets/civetweb/src/main.c new file mode 100644 index 00000000000..a3008a980b3 --- /dev/null +++ b/samples/net/sockets/civetweb/src/main.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019 Antmicro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "civetweb.h" + +#define CIVETWEB_MAIN_THREAD_STACK_SIZE 4096 + +K_THREAD_STACK_DEFINE(civetweb_stack, CIVETWEB_MAIN_THREAD_STACK_SIZE); + +struct civetweb_info { + const char *version; + const char *os; + u32_t features; + const char *feature_list; + const char *build; + const char *compiler; + const char *data_model; +}; + +#define FIELD(struct_, member_, type_) { \ + .field_name = #member_, \ + .field_name_len = sizeof(#member_) - 1, \ + .offset = offsetof(struct_, member_), \ + .type = type_ \ +} + +void send_ok(struct mg_connection *conn) +{ + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n\r\n"); +} + +int hello_world_handler(struct mg_connection *conn, void *cbdata) +{ + send_ok(conn); + mg_printf(conn, ""); + mg_printf(conn, "

Hello World from Zephyr!

"); + mg_printf(conn, "See also:\n"); + mg_printf(conn, "\n"); + mg_printf(conn, "\n"); + + return 200; +} + +int system_info_handler(struct mg_connection *conn, void *cbdata) +{ + static const struct json_obj_descr descr[] = { + FIELD(struct civetweb_info, version, JSON_TOK_STRING), + FIELD(struct civetweb_info, os, JSON_TOK_STRING), + FIELD(struct civetweb_info, feature_list, JSON_TOK_STRING), + FIELD(struct civetweb_info, build, JSON_TOK_STRING), + FIELD(struct civetweb_info, compiler, JSON_TOK_STRING), + FIELD(struct civetweb_info, data_model, JSON_TOK_STRING), + }; + + struct civetweb_info info = {}; + char info_str[1024] = {}; + int ret; + int size; + + size = mg_get_system_info(info_str, sizeof(info_str)); + + ret = json_obj_parse(info_str, size, descr, ARRAY_SIZE(descr), &info); + + send_ok(conn); + + if (ret < 0) { + mg_printf(conn, "Could not retrieve: %d\n", ret); + return 500; + } + + + mg_printf(conn, ""); + + mg_printf(conn, "

Server info

"); + mg_printf(conn, "
    \n"); + mg_printf(conn, "
  • host os - %s
  • \n", info.os); + mg_printf(conn, "
  • server - civetweb %s
  • \n", info.version); + mg_printf(conn, "
  • compiler - %s
  • \n", info.compiler); + mg_printf(conn, "
\n"); + + mg_printf(conn, "\n"); + + return 200; +} + +int history_handler(struct mg_connection *conn, void *cbdata) +{ + const struct mg_request_info *req_info = mg_get_request_info(conn); + const char *cookie = mg_get_header(conn, "Cookie"); + char history_str[64]; + + mg_get_cookie(cookie, "history", history_str, sizeof(history_str)); + + mg_printf(conn, "HTTP/1.1 200 OK\r\n"); + mg_printf(conn, "Connection: close\r\n"); + mg_printf(conn, "Set-Cookie: history='%s'\r\n", req_info->local_uri); + mg_printf(conn, "Content-Type: text/html\r\n\r\n"); + + mg_printf(conn, ""); + + mg_printf(conn, "

Your URI is: %s

\n", req_info->local_uri); + + if (history_str[0] == 0) { + mg_printf(conn, "

This is your first visit.
\n"); + } else { + mg_printf(conn, "
your last /history visit was: %s
\n", + history_str); + } + + mg_printf(conn, "Some cookie-saving links to try:\n"); + mg_printf(conn, "
    \n"); + mg_printf(conn, "
  • first
  • \n"); + mg_printf(conn, "
  • second
  • \n"); + mg_printf(conn, "
  • third
  • \n"); + mg_printf(conn, "
  • fourth
  • \n"); + mg_printf(conn, "
  • fifth
  • \n"); + mg_printf(conn, "
\n"); + + mg_printf(conn, "\n"); + + return 200; +} + +void *main_pthread(void *arg) +{ + static const char * const options[] = { + "listening_ports", + "8080", + "num_threads", + "1", + "max_request_size", + "2048", + 0 + }; + + struct mg_callbacks callbacks; + struct mg_context *ctx; + + (void)arg; + + memset(&callbacks, 0, sizeof(callbacks)); + ctx = mg_start(&callbacks, 0, (const char **)options); + + if (ctx == NULL) { + printf("Unable to start the server."); + return 0; + } + + mg_set_request_handler(ctx, "/$", hello_world_handler, 0); + mg_set_request_handler(ctx, "/info$", system_info_handler, 0); + mg_set_request_handler(ctx, "/history", history_handler, 0); + + return 0; +} + +int main(void) +{ + pthread_attr_t civetweb_attr; + pthread_t civetweb_thread; + + pthread_attr_init(&civetweb_attr); + pthread_attr_setstack(&civetweb_attr, &civetweb_stack, + CIVETWEB_MAIN_THREAD_STACK_SIZE); + + pthread_create(&civetweb_thread, &civetweb_attr, &main_pthread, 0); + + return 0; +} diff --git a/west.yml b/west.yml index 16a4aede5e9..0aefca8f3e2 100644 --- a/west.yml +++ b/west.yml @@ -25,6 +25,8 @@ manifest: remotes: - name: upstream url-base: https://github.com/zephyrproject-rtos + - name: civetweb + url-base: https://github.com/antmicro # # Please add items below based on alphabetical order @@ -32,6 +34,10 @@ manifest: - name: ci-tools revision: d56f2dd3510e20fa8cf4aad442495c08a658113f path: tools/ci-tools + - name: civetweb + remote: civetweb + revision: 7ffad765f9a63a7bab3432ca45248981f559d559 + path: modules/lib/civetweb - name: esp-idf revision: 6835bfc741bf15e98fb7971293913f770df6081f path: modules/hal/esp-idf