From 8f8e6ca0c0b72c363c2e47b5f84ecde57f8b7af7 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 --- modules/Kconfig.civetweb | 8 + samples/net/sockets/civetweb/CMakeLists.txt | 8 + samples/net/sockets/civetweb/prj.conf | 34 ++++ .../civetweb/src/external_log_access.inl | 22 +++ .../src/external_mg_cry_internal_impl.inl | 15 ++ .../sockets/civetweb/src/libc_extensions.c | 178 ++++++++++++++++++ .../sockets/civetweb/src/libc_extensions.h | 32 ++++ samples/net/sockets/civetweb/src/main.c | 175 +++++++++++++++++ west.yml | 6 + 9 files changed, 478 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/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 00000000000000..30618e5fabd108 --- /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 00000000000000..119eb74c9ca269 --- /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 00000000000000..e8aff517b10a3c --- /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/src/external_log_access.inl b/samples/net/sockets/civetweb/src/external_log_access.inl new file mode 100644 index 00000000000000..b8ead6913ac406 --- /dev/null +++ b/samples/net/sockets/civetweb/src/external_log_access.inl @@ -0,0 +1,22 @@ +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 00000000000000..05ae341fca5ed4 --- /dev/null +++ b/samples/net/sockets/civetweb/src/external_mg_cry_internal_impl.inl @@ -0,0 +1,15 @@ +static void mg_cry_internal_impl(const struct mg_connection *conn, + const char *func, + unsigned line, + const char *fmt, + va_list ap) +{ + (void)conn; + (void)func; + (void)line; + (void)fmt; + (void)ap; + 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 00000000000000..b34144116fed5e --- /dev/null +++ b/samples/net/sockets/civetweb/src/libc_extensions.c @@ -0,0 +1,178 @@ +#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); +} + +struct tm *gmtime(const time_t *ptime) +{ + FN_MISSING(); + + return NULL; +} + +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; +} 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 00000000000000..4048c14e64428f --- /dev/null +++ b/samples/net/sockets/civetweb/src/libc_extensions.h @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#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); diff --git a/samples/net/sockets/civetweb/src/main.c b/samples/net/sockets/civetweb/src/main.c new file mode 100644 index 00000000000000..4f9b1cd358f112 --- /dev/null +++ b/samples/net/sockets/civetweb/src/main.c @@ -0,0 +1,175 @@ +#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) +{ + const char *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, 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() +{ + 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 1ce523029e3545..313f6dce25bba1 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 # The initial list of external projects is just Zephyr's net-tools # repository, which is useful for running the Zephyr networking @@ -68,6 +70,10 @@ manifest: - name: segger revision: 6fcf61606d6012d2c44129edc033f59331e268bc path: modules/debug/segger + - name: civetweb + remote: civetweb + revision: zephyr-sample + path: modules/lib/civetweb self: path: zephyr