From 77dd3987f9a8023c65a71438a7ac4f1606acf379 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Thu, 31 Dec 2020 20:33:20 +0800 Subject: [PATCH 01/55] More clear comments Signed-off-by: Jianhui Zhao --- src/connection.c | 2 +- src/connection.h | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/connection.c b/src/connection.c index d3298bc..4d2e054 100644 --- a/src/connection.c +++ b/src/connection.c @@ -52,7 +52,7 @@ static void conn_done(struct uh_connection *conn) ev_timer_stop(loop, &conn->timer); - /* This is needed for a connection requested multiple times */ + /* This is needed for a connection requested multiple times on different path */ conn->handler = NULL; } diff --git a/src/connection.h b/src/connection.h index ed43a6b..9ec6bb9 100644 --- a/src/connection.h +++ b/src/connection.h @@ -99,7 +99,11 @@ struct uh_connection { struct uh_connection *prev; struct uh_connection *next; void (*handler)(struct uh_connection *conn, int event); - void (*done)(struct uh_connection *conn); /* Must be called at last, if not call 'error', 'redirect' and 'serve_file' */ + /* + ** Indicates the end of request processing + ** Must be called at last, if not call 'error', 'redirect' and 'serve_file' + */ + void (*done)(struct uh_connection *conn); void (*send)(struct uh_connection *conn, const void *data, ssize_t len); void (*send_file)(struct uh_connection *conn, const char *path); void (*printf)(struct uh_connection *conn, const char *format, ...); From 637d78b6b2d7ea0843a81f790a6b9cbaeba7b2e2 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Thu, 31 Dec 2020 21:03:22 +0800 Subject: [PATCH 02/55] buffer: update to latest Signed-off-by: Jianhui Zhao --- src/buffer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer b/src/buffer index 55e4d3b..84e1b93 160000 --- a/src/buffer +++ b/src/buffer @@ -1 +1 @@ -Subproject commit 55e4d3bddc1cd258766448788f8fa3e406e25065 +Subproject commit 84e1b93c110b8a886fadab0563319506edb66741 From 1e20eca97df4f46ab4fe8f9c7c1d8ac8f384a03d Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 1 Jan 2021 00:14:31 +0800 Subject: [PATCH 03/55] Fix bug: handle fail when multi requests sent at a time Signed-off-by: Jianhui Zhao --- src/connection.c | 93 +++++++++++++++++++++++++++++++++--------------- src/connection.h | 2 ++ 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/connection.c b/src/connection.c index 4d2e054..3e8449f 100644 --- a/src/connection.c +++ b/src/connection.c @@ -40,8 +40,6 @@ static void conn_done(struct uh_connection *conn) { struct ev_loop *loop = conn->srv->loop; - buffer_pull(&conn->rb, NULL, buffer_length(&conn->rb)); - if (!http_should_keep_alive(&conn->parser)) conn->flags |= CONN_F_SEND_AND_CLOSE; @@ -273,15 +271,9 @@ static struct uh_str conn_get_body(struct uh_connection *conn) static struct uh_str conn_extract_body(struct uh_connection *conn) { - struct uh_request *req = &conn->req; - struct uh_str body; + struct uh_str body = conn_get_body(conn); - body.p = O2D(conn, req->body.offset); - body.len = req->body.length; - - req->body.length = 0; - - buffer_discard(&conn->rb, body.len); + conn->req.body.consumed = true; return body; } @@ -411,6 +403,13 @@ static int on_body_cb(struct http_parser *parser, const char *at, size_t length) if (conn->flags & CONN_F_SEND_AND_CLOSE) return -1; + if (req->body.consumed) { + req->body.consumed = false; + buffer_discard(&conn->rb, req->body.length); + req->length -= req->body.length; + req->body.length = 0; + } + return 0; } @@ -423,6 +422,8 @@ static int on_message_complete_cb(struct http_parser *parser) conn->handler(conn, UH_EV_COMPLETE); + http_parser_pause(parser, true); + return 0; } @@ -475,6 +476,43 @@ void conn_free(struct uh_connection *conn) free(conn); } +static void conn_http_parse(struct uh_connection *conn) +{ + struct http_parser *parser = &conn->parser; + struct uh_request *req = &conn->req; + struct buffer *rb = &conn->rb; + uint8_t *data = buffer_data(rb) + req->length; + size_t length = buffer_length(rb) - req->length; + size_t nparsed; + + if (parser->http_errno == HPE_PAUSED) + return; + + nparsed = http_parser_execute(parser, &settings, (const char *)data, length); + + switch (parser->http_errno) { + case HPE_PAUSED: + case HPE_OK: + if (parser->upgrade) { + conn_error(conn, HTTP_STATUS_NOT_IMPLEMENTED, NULL); + return; + } + + req->length += nparsed; + + /* paused in on_message_complete */ + if (parser->http_errno == HPE_PAUSED) { + buffer_pull(rb, NULL, req->length); + req->length = 0; + } + return; + + default: + conn_error(conn, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno)); + return; + } +} + #if UHTTPD_SSL_SUPPORT static int conn_ssl_write(int fd, void *buf, size_t count, void *ssl) { @@ -527,10 +565,16 @@ static void conn_write_cb(struct ev_loop *loop, struct ev_io *w, int revents) conn->file.fd = -1; } - if (conn->flags & CONN_F_SEND_AND_CLOSE) + if (conn->flags & CONN_F_SEND_AND_CLOSE) { conn_free(conn); - else + } else { ev_io_stop(loop, w); + + http_parser_pause(&conn->parser, false); + + if (buffer_length(&conn->rb) > 0) + conn_http_parse(conn); + } } } @@ -550,10 +594,9 @@ static int conn_ssl_read(int fd, void *buf, size_t count, void *ssl) static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct uh_connection *conn = container_of(w, struct uh_connection, ior); - struct http_parser *parser = &conn->parser; struct buffer *rb = &conn->rb; - int ret, nread, length, nparsed; bool eof; + int ret; if (conn->flags & CONN_F_SEND_AND_CLOSE) { ev_io_stop(loop, w); @@ -575,8 +618,6 @@ static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) conn->activity = ev_now(loop); - length = buffer_length(rb); - #if UHTTPD_SSL_SUPPORT if (conn->ssl) ret = buffer_put_fd_ex(rb, w->fd, -1, &eof, conn_ssl_read, conn->ssl); @@ -585,23 +626,19 @@ static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) ret = buffer_put_fd(rb, w->fd, -1, &eof); if (ret < 0) { - conn_error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL); uh_log_err("read error: %s\n", strerror(errno)); - return; + goto done; } - if (eof) { - conn_free(conn); - return; - } + if (eof) + goto done; - nread = buffer_length(rb) - length; + conn_http_parse(conn); - nparsed = http_parser_execute(parser, &settings, (const char *)rb->data + length, nread); - if (parser->upgrade) - conn_error(conn, HTTP_STATUS_NOT_IMPLEMENTED, NULL); - else if (nparsed != nread) - conn_error(conn, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno)); + return; + +done: + conn_free(conn); } static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents) diff --git a/src/connection.h b/src/connection.h index 9ec6bb9..3326430 100644 --- a/src/connection.h +++ b/src/connection.h @@ -47,6 +47,7 @@ struct uh_str { }; struct uh_request { + size_t length; /* The total length of the request which still remain in buffer */ struct { ssize_t offset; size_t length; @@ -66,6 +67,7 @@ struct uh_request { } headers[UHTTPD_MAX_HEADER_NUM]; struct { + bool consumed; /* Indicates whether the extract_body is called */ ssize_t offset; size_t length; } body; From 8931fb63e1d4d7586e742817973970de674a1cc4 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 1 Jan 2021 16:02:47 +0800 Subject: [PATCH 04/55] Update README Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d88ece4..dc150c1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on # Dependencies * [libev] -* [http-parser] - A parser for HTTP messages written in C +* [http-parser] - A high performance parser for HTTP messages written in C * [mbedtls] - If you choose mbedtls as your SSL backend * [wolfssl] - If you choose wolfssl as your SSL backend * [openssl] - If you choose openssl as your SSL backend diff --git a/README_ZH.md b/README_ZH.md index 61a2537..f52cd95 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -37,7 +37,7 @@ # 依赖 * [libev] -* [http-parser] - 已经集成到源码里面 +* [http-parser] - 一个用 C 语言编写的高性能的 HTTP 消息解析器 * [mbedtls] - 如果你选择mbedtls作为你的SSL后端 * [wolfssl] - 如果你选择wolfssl作为你的SSL后端 * [openssl] - 如果你选择openssl作为你的SSL后端 From 2fd658c34737554b7b296fe4553ba74e80a4dae7 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 1 Jan 2021 16:53:14 +0800 Subject: [PATCH 05/55] configure the docroot and index page globally. Signed-off-by: Jianhui Zhao --- example/example.c | 31 +++++++++++++++++++++---------- src/connection.h | 2 +- src/file.c | 7 +++++-- src/file.h | 2 +- src/uhttpd.c | 37 +++++++++++++++++++++++++++++++++++++ src/uhttpd.h | 4 ++++ 6 files changed, 69 insertions(+), 14 deletions(-) diff --git a/example/example.c b/example/example.c index 2befc6e..4fe325b 100644 --- a/example/example.c +++ b/example/example.c @@ -32,8 +32,6 @@ #include "uhttpd.h" static bool serve_file = false; -static const char *docroot = "."; -static const char *index_page = "index.html"; static void default_handler(struct uh_connection *conn, int event) { @@ -56,7 +54,7 @@ static void default_handler(struct uh_connection *conn, int event) conn->chunk_end(conn); conn->done(conn); } else { - conn->serve_file(conn, docroot, index_page); + conn->serve_file(conn); } } @@ -126,12 +124,14 @@ static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) static void usage(const char *prog) { fprintf(stderr, "Usage: %s [option]\n" - " -a addr # Default addr is localhost\n" - " -p port # Default port is 8080\n" - " -s # SSl on\n" - " -f # Serve file\n" - " -P # plugin path\n" - " -v # verbose\n", prog); + " -h docroot # Document root, default is .\n" + " -i index_page # Index page, default is index.html\n" + " -a addr # Default addr is localhost\n" + " -p port # Default port is 8080\n" + " -s # SSl on\n" + " -f # Serve file\n" + " -P # plugin path\n" + " -v # verbose\n", prog); exit(1); } @@ -143,12 +143,20 @@ int main(int argc, char **argv) const char *plugin_path = NULL; bool verbose = false; bool ssl = false; + const char *docroot = "."; + const char *index_page = "index.html"; const char *addr = "localhost"; int port = 8080; int opt; - while ((opt = getopt(argc, argv, "a:p:sfP:v")) != -1) { + while ((opt = getopt(argc, argv, "h:i:a:p:sfP:v")) != -1) { switch (opt) { + case 'h': + docroot = optarg; + break; + case 'i': + index_page = optarg; + break; case 'a': addr = optarg; break; @@ -188,6 +196,9 @@ int main(int argc, char **argv) goto err; #endif + srv->set_docroot(srv, docroot); + srv->set_index_page(srv, index_page); + srv->default_handler = default_handler; srv->add_path_handler(srv, "/upload", upload_handler); diff --git a/src/connection.h b/src/connection.h index 3326430..9f376c6 100644 --- a/src/connection.h +++ b/src/connection.h @@ -114,7 +114,7 @@ struct uh_connection { void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); void (*error)(struct uh_connection *conn, int code, const char *reason); void (*redirect)(struct uh_connection *conn, int code, const char *location, ...); - void (*serve_file)(struct uh_connection *conn, const char *docroot, const char *index_page); + void (*serve_file)(struct uh_connection *conn); void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); void (*chunk_printf)(struct uh_connection *conn, const char *format, ...); void (*chunk_vprintf)(struct uh_connection *conn, const char *format, va_list arg); diff --git a/src/file.c b/src/file.c index 8e8e428..9562745 100644 --- a/src/file.c +++ b/src/file.c @@ -35,8 +35,8 @@ #include #include -#include "connection.h" #include "mimetypes.h" +#include "uhttpd.h" #include "log.h" static const char *file_mktag(struct stat *s, char *buf, int len) @@ -152,9 +152,12 @@ static void file_if_gzip(struct uh_connection *conn, const char *path, const cha conn->printf(conn, "Content-Encoding: gzip\r\n"); } -void serve_file(struct uh_connection *conn, const char *docroot, const char *index_page) +void serve_file(struct uh_connection *conn) { const struct uh_str path = conn->get_path(conn); + struct uh_server *srv = conn->srv; + const char *docroot = srv->docroot; + const char *index_page = srv->index_page; static char fullpath[512]; const char *mime; struct stat st; diff --git a/src/file.h b/src/file.h index 6fa524f..c0a6dd9 100644 --- a/src/file.h +++ b/src/file.h @@ -27,6 +27,6 @@ #include "connection.h" -void serve_file(struct uh_connection *conn, const char *docroot, const char *index_page); +void serve_file(struct uh_connection *conn); #endif diff --git a/src/uhttpd.c b/src/uhttpd.c index 60ce767..32a2c47 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -53,6 +53,12 @@ static void uh_server_free(struct uh_server *srv) if (srv->sock > 0) close(srv->sock); + if (srv->docroot) + free(srv->docroot); + + if (srv->index_page) + free(srv->index_page); + while (conn) { struct uh_connection *next = conn->next; conn_free(conn); @@ -218,6 +224,34 @@ static int uh_add_path_handler(struct uh_server *srv, const char *path, uh_path_ return 0; } +static int uh_set_docroot(struct uh_server *srv, const char *path) +{ + if (srv->docroot) + free(srv->docroot); + + srv->docroot = strdup(path); + if (!srv->docroot) { + uh_log_err("strdup: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +static int uh_set_index_page(struct uh_server *srv, const char *name) +{ + if (srv->index_page) + free(srv->index_page); + + srv->index_page = strdup(name); + if (!srv->index_page) { + uh_log_err("strdup: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port) { union { @@ -306,6 +340,9 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->add_path_handler = uh_add_path_handler; + srv->set_docroot = uh_set_docroot; + srv->set_index_page = uh_set_index_page; + ev_io_init(&srv->ior, uh_accept_cb, sock, EV_READ); ev_io_start(srv->loop, &srv->ior); diff --git a/src/uhttpd.h b/src/uhttpd.h index db1659b..5e2442d 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -58,6 +58,8 @@ struct uh_path_handler { struct uh_server { int sock; + char *docroot; + char *index_page; struct ev_loop *loop; struct ev_io ior; struct uh_connection *conns; @@ -71,6 +73,8 @@ struct uh_server { int (*load_plugin)(struct uh_server *srv, const char *path); struct uh_path_handler *handlers; int (*add_path_handler)(struct uh_server *srv, const char *path, uh_path_handler_prototype handler); + int (*set_docroot)(struct uh_server *srv, const char *path); + int (*set_index_page)(struct uh_server *srv, const char *name); }; /* From 01bfeb620b74f1a29dc236e791fca23d9204c9d5 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 1 Jan 2021 18:16:07 +0800 Subject: [PATCH 06/55] OOP: Only expose APIs Hide the implementation details of the structure Signed-off-by: Jianhui Zhao --- example/example.c | 2 +- src/CMakeLists.txt | 1 - src/connection.c | 203 ++++++++++++++++++++++++------------------ src/connection.h | 52 +++-------- src/file.c | 7 +- src/uhttpd.c | 99 +++++++++++--------- src/uhttpd.h | 84 +++++++++++------ src/uhttpd_internal.h | 48 ++++++++++ 8 files changed, 292 insertions(+), 204 deletions(-) create mode 100644 src/uhttpd_internal.h diff --git a/example/example.c b/example/example.c index 4fe325b..f54f8eb 100644 --- a/example/example.c +++ b/example/example.c @@ -199,7 +199,7 @@ int main(int argc, char **argv) srv->set_docroot(srv, docroot); srv->set_index_page(srv, index_page); - srv->default_handler = default_handler; + srv->set_default_handler(srv, default_handler); srv->add_path_handler(srv, "/upload", upload_handler); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8594fee..a80becf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,7 +141,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DI install( FILES uhttpd.h - connection.h log.h buffer/buffer.h http-parser/http_parser.h diff --git a/src/connection.c b/src/connection.c index 3e8449f..8e8b647 100644 --- a/src/connection.c +++ b/src/connection.c @@ -30,65 +30,71 @@ #include #include -#include "connection.h" -#include "uhttpd.h" +#include "uhttpd_internal.h" #include "utils.h" #include "file.h" #include "ssl.h" static void conn_done(struct uh_connection *conn) { - struct ev_loop *loop = conn->srv->loop; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct ev_loop *loop = conni->srv->loop; - if (!http_should_keep_alive(&conn->parser)) - conn->flags |= CONN_F_SEND_AND_CLOSE; + if (!http_should_keep_alive(&conni->parser)) + conni->flags |= CONN_F_SEND_AND_CLOSE; - if (conn->flags & CONN_F_SEND_AND_CLOSE) - ev_io_stop(loop, &conn->ior); + if (conni->flags & CONN_F_SEND_AND_CLOSE) + ev_io_stop(loop, &conni->ior); - ev_io_start(loop, &conn->iow); + ev_io_start(loop, &conni->iow); - ev_timer_stop(loop, &conn->timer); + ev_timer_stop(loop, &conni->timer); /* This is needed for a connection requested multiple times on different path */ - conn->handler = NULL; + conni->handler = NULL; } static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) { - buffer_put_data(&conn->wb, data, len); - ev_io_start(conn->srv->loop, &conn->iow); + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + buffer_put_data(&conni->wb, data, len); + ev_io_start(conni->srv->loop, &conni->iow); } static void conn_send_file(struct uh_connection *conn, const char *path) { + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct stat st; - conn->file.fd = open(path, O_RDONLY); + conni->file.fd = open(path, O_RDONLY); - fstat(conn->file.fd, &st); + fstat(conni->file.fd, &st); - conn->file.size = st.st_size; + conni->file.size = st.st_size; - ev_io_start(conn->srv->loop, &conn->iow); + ev_io_start(conni->srv->loop, &conni->iow); } static void conn_printf(struct uh_connection *conn, const char *format, ...) { - struct buffer *wb = &conn->wb; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct buffer *wb = &conni->wb; va_list arg; va_start(arg, format); buffer_put_vprintf(wb, format, arg); va_end(arg); - ev_io_start(conn->srv->loop, &conn->iow); + ev_io_start(conni->srv->loop, &conni->iow); } static void conn_vprintf(struct uh_connection *conn, const char *format, va_list arg) { - buffer_put_vprintf(&conn->wb, format, arg); - ev_io_start(conn->srv->loop, &conn->iow); + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + buffer_put_vprintf(&conni->wb, format, arg); + ev_io_start(conni->srv->loop, &conni->iow); } static void conn_chunk_send(struct uh_connection *conn, const void *data, ssize_t len) @@ -139,13 +145,15 @@ static void conn_send_status_line(struct uh_connection *conn, int code, const ch static void conn_send_head(struct uh_connection *conn, int code, int content_length, const char *extra_headers) { + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + conn_send_status_line(conn, code, extra_headers); if (content_length < 0) conn_printf(conn, "%s", "Transfer-Encoding: chunked\r\n"); else conn_printf(conn, "Content-Length: %d\r\n", content_length); - if (!http_should_keep_alive(&conn->parser)) + if (!http_should_keep_alive(&conni->parser)) conn_printf(conn, "%s", "Connection: close\r\n"); conn_send(conn, "\r\n", 2); @@ -153,7 +161,9 @@ static void conn_send_head(struct uh_connection *conn, int code, int content_len static void conn_error(struct uh_connection *conn, int code, const char *reason) { - if (conn->flags & CONN_F_SEND_AND_CLOSE) + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + if (conni->flags & CONN_F_SEND_AND_CLOSE) return; if (!reason) @@ -161,14 +171,15 @@ static void conn_error(struct uh_connection *conn, int code, const char *reason) conn_send_head(conn, code, strlen(reason), "Content-Type: text/plain\r\nConnection: close\r\n"); conn_send(conn, reason, strlen(reason)); - conn->flags |= CONN_F_SEND_AND_CLOSE; + conni->flags |= CONN_F_SEND_AND_CLOSE; conn_done(conn); } static void conn_redirect(struct uh_connection *conn, int code, const char *location, ...) { - struct buffer *wb = &conn->wb; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct buffer *wb = &conni->wb; va_list arg; assert((code == HTTP_STATUS_MOVED_PERMANENTLY || code == HTTP_STATUS_FOUND) && location); @@ -189,17 +200,23 @@ static void conn_redirect(struct uh_connection *conn, int code, const char *loca static const struct sockaddr *conn_get_addr(struct uh_connection *conn) { - return &conn->addr.sa; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return &conni->addr.sa; } static enum http_method conn_get_method(struct uh_connection *conn) { - return conn->parser.method; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return conni->parser.method; } static const char *conn_get_method_str(struct uh_connection *conn) { - return http_method_str(conn->parser.method); + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return http_method_str(conni->parser.method); } /* offset of the request field */ @@ -210,11 +227,12 @@ static const char *conn_get_method_str(struct uh_connection *conn) static struct uh_str conn_get_path(struct uh_connection *conn) { - struct http_parser_url *u = &conn->url_parser; - struct uh_request *req = &conn->req; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct http_parser_url *u = &conni->url_parser; + struct uh_request *req = &conni->req; struct uh_str path; - path.p = O2D(conn, u->field_data[UF_PATH].off) + req->url.offset; + path.p = O2D(conni, u->field_data[UF_PATH].off) + req->url.offset; path.len = u->field_data[UF_PATH].len; return path; @@ -222,14 +240,15 @@ static struct uh_str conn_get_path(struct uh_connection *conn) static struct uh_str conn_get_query(struct uh_connection *conn) { - struct http_parser_url *u = &conn->url_parser; - struct uh_request *req = &conn->req; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct http_parser_url *u = &conni->url_parser; + struct uh_request *req = &conni->req; struct uh_str query = {}; if (!(u->field_set & (1 << UF_QUERY))) return query; - query.p = O2D(conn, u->field_data[UF_QUERY].off) + req->url.offset; + query.p = O2D(conni, u->field_data[UF_QUERY].off) + req->url.offset; query.len = u->field_data[UF_QUERY].len; return query; @@ -237,7 +256,8 @@ static struct uh_str conn_get_query(struct uh_connection *conn) static struct uh_str conn_get_header(struct uh_connection *conn, const char *name) { - struct uh_request *req = &conn->req; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct uh_request *req = &conni->req; int name_len = strlen(name); struct uh_str value = {}; int i; @@ -249,8 +269,8 @@ static struct uh_str conn_get_header(struct uh_connection *conn, const char *nam if (req->headers[i].field.length != name_len) continue; - if (!strncasecmp(O2D(conn, req->headers[i].field.offset), name, name_len)) { - value.p = O2D(conn, req->headers[i].value.offset); + if (!strncasecmp(O2D(conni, req->headers[i].field.offset), name, name_len)) { + value.p = O2D(conni, req->headers[i].value.offset); value.len = req->headers[i].value.length; } } @@ -260,10 +280,11 @@ static struct uh_str conn_get_header(struct uh_connection *conn, const char *nam static struct uh_str conn_get_body(struct uh_connection *conn) { - struct uh_request *req = &conn->req; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct uh_request *req = &conni->req; struct uh_str body; - body.p = O2D(conn, req->body.offset); + body.p = O2D(conni, req->body.offset); body.len = req->body.length; return body; @@ -271,16 +292,17 @@ static struct uh_str conn_get_body(struct uh_connection *conn) static struct uh_str conn_extract_body(struct uh_connection *conn) { + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct uh_str body = conn_get_body(conn); - conn->req.body.consumed = true; + conni->req.body.consumed = true; return body; } static int on_message_begin_cb(struct http_parser *parser) { - struct uh_connection *conn = (struct uh_connection *)parser->data; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; memset(req, 0, sizeof(struct uh_request)); @@ -296,7 +318,7 @@ static int on_message_begin_cb(struct http_parser *parser) static int on_url_cb(struct http_parser *parser, const char *at, size_t length) { - struct uh_connection *conn = (struct uh_connection *)parser->data; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->url.offset == 0) @@ -308,7 +330,7 @@ static int on_url_cb(struct http_parser *parser, const char *at, size_t length) static int on_header_field_cb(struct http_parser *parser, const char *at, size_t length) { - struct uh_connection *conn = (struct uh_connection *)parser->data; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->last_was_header_value) { @@ -330,7 +352,7 @@ static int on_header_field_cb(struct http_parser *parser, const char *at, size_t static int on_header_value_cb(struct http_parser *parser, const char *at, size_t length) { - struct uh_connection *conn = (struct uh_connection *)parser->data; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (!req->last_was_header_value) { @@ -345,8 +367,8 @@ static int on_header_value_cb(struct http_parser *parser, const char *at, size_t static int on_headers_complete(struct http_parser *parser) { - struct uh_connection *conn = (struct uh_connection *)parser->data; - struct uh_server *srv = conn->srv; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; + struct uh_server_internal *srv = conn->srv; struct uh_request *req = &conn->req; struct uh_path_handler *h = srv->handlers; struct uh_plugin *p = srv->plugins; @@ -354,7 +376,7 @@ static int on_headers_complete(struct http_parser *parser) http_parser_parse_url(O2D(conn, req->url.offset), req->url.length, false, &conn->url_parser); - path = conn->get_path(conn); + path = conn->com.get_path(&conn->com); while (h) { if (strlen(h->path) == path.len && !strncmp(path.p, h->path, path.len)) { @@ -377,11 +399,11 @@ done: conn->handler = srv->default_handler; if (!conn->handler) { - conn_error(conn, HTTP_STATUS_NOT_FOUND, NULL); + conn_error(&conn->com, HTTP_STATUS_NOT_FOUND, NULL); return -1; } - conn->handler(conn, UH_EV_HEAD_COMPLETE); + conn->handler(&conn->com, UH_EV_HEAD_COMPLETE); if (conn->flags & CONN_F_SEND_AND_CLOSE) return -1; @@ -391,14 +413,14 @@ done: static int on_body_cb(struct http_parser *parser, const char *at, size_t length) { - struct uh_connection *conn = (struct uh_connection *)parser->data; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->body.offset == 0) req->body.offset = ROF(conn, at); req->body.length += length; - conn->handler(conn, UH_EV_BODY); + conn->handler(&conn->com, UH_EV_BODY); if (conn->flags & CONN_F_SEND_AND_CLOSE) return -1; @@ -415,12 +437,12 @@ static int on_body_cb(struct http_parser *parser, const char *at, size_t length) static int on_message_complete_cb(struct http_parser *parser) { - struct uh_connection *conn = (struct uh_connection *)parser->data; - struct uh_server *srv = conn->srv; + struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; + struct uh_server_internal *srv = conn->srv; ev_timer_stop(srv->loop, &conn->timer); - conn->handler(conn, UH_EV_COMPLETE); + conn->handler(&conn->com, UH_EV_COMPLETE); http_parser_pause(parser, true); @@ -437,7 +459,7 @@ static struct http_parser_settings settings = { .on_message_complete = on_message_complete_cb }; -void conn_free(struct uh_connection *conn) +void conn_free(struct uh_connection_internal *conn) { struct ev_loop *loop = conn->srv->loop; char addr_str[INET6_ADDRSTRLEN]; @@ -476,7 +498,7 @@ void conn_free(struct uh_connection *conn) free(conn); } -static void conn_http_parse(struct uh_connection *conn) +static void conn_http_parse(struct uh_connection_internal *conn) { struct http_parser *parser = &conn->parser; struct uh_request *req = &conn->req; @@ -494,7 +516,7 @@ static void conn_http_parse(struct uh_connection *conn) case HPE_PAUSED: case HPE_OK: if (parser->upgrade) { - conn_error(conn, HTTP_STATUS_NOT_IMPLEMENTED, NULL); + conn_error(&conn->com, HTTP_STATUS_NOT_IMPLEMENTED, NULL); return; } @@ -508,7 +530,7 @@ static void conn_http_parse(struct uh_connection *conn) return; default: - conn_error(conn, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno)); + conn_error(&conn->com, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno)); return; } } @@ -529,7 +551,7 @@ static int conn_ssl_write(int fd, void *buf, size_t count, void *ssl) static void conn_write_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - struct uh_connection *conn = container_of(w, struct uh_connection, iow); + struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, iow); int ret; #if UHTTPD_SSL_SUPPORT @@ -593,7 +615,7 @@ static int conn_ssl_read(int fd, void *buf, size_t count, void *ssl) static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - struct uh_connection *conn = container_of(w, struct uh_connection, ior); + struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, ior); struct buffer *rb = &conn->rb; bool eof; int ret; @@ -643,7 +665,7 @@ done: static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents) { - struct uh_connection *conn = container_of(w, struct uh_connection, timer); + struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, timer); ev_tstamp after = conn->activity + UHTTPD_CONNECTION_TIMEOUT - ev_now(loop); if (conn->flags & CONN_F_SEND_AND_CLOSE) { @@ -657,14 +679,42 @@ static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents) return; } - conn_error(conn, HTTP_STATUS_REQUEST_TIMEOUT, NULL); + conn_error(&conn->com, HTTP_STATUS_REQUEST_TIMEOUT, NULL); } -struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct sockaddr *addr) +static void conn_init_cb(struct uh_connection *conn) { - struct uh_connection *conn; + conn->done = conn_done; + conn->send = conn_send; + conn->send_file = conn_send_file; + conn->printf = conn_printf; + conn->vprintf = conn_vprintf; + conn->send_status_line = conn_send_status_line; + conn->send_head = conn_send_head; + conn->error = conn_error; + conn->redirect = conn_redirect; + conn->serve_file = serve_file; - conn = calloc(1, sizeof(struct uh_connection)); + conn->chunk_send = conn_chunk_send; + conn->chunk_printf = conn_chunk_printf; + conn->chunk_vprintf = conn_chunk_vprintf; + conn->chunk_end = conn_chunk_end; + + conn->get_addr = conn_get_addr; + conn->get_method = conn_get_method; + conn->get_method_str = conn_get_method_str; + conn->get_path = conn_get_path; + conn->get_query = conn_get_query; + conn->get_header = conn_get_header; + conn->get_body = conn_get_body; + conn->extract_body = conn_extract_body; +} + +struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, int sock, struct sockaddr *addr) +{ + struct uh_connection_internal *conn; + + conn = calloc(1, sizeof(struct uh_connection_internal)); if (!conn) { uh_log_err("malloc: %s\n", strerror(errno)); return NULL; @@ -696,30 +746,7 @@ struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct conn->parser.data = conn; - conn->done = conn_done; - conn->send = conn_send; - conn->send_file = conn_send_file; - conn->printf = conn_printf; - conn->vprintf = conn_vprintf; - conn->send_status_line = conn_send_status_line; - conn->send_head = conn_send_head; - conn->error = conn_error; - conn->redirect = conn_redirect; - conn->serve_file = serve_file; - - conn->chunk_send = conn_chunk_send; - conn->chunk_printf = conn_chunk_printf; - conn->chunk_vprintf = conn_chunk_vprintf; - conn->chunk_end = conn_chunk_end; - - conn->get_addr = conn_get_addr; - conn->get_method = conn_get_method; - conn->get_method_str = conn_get_method_str; - conn->get_path = conn_get_path; - conn->get_query = conn_get_query; - conn->get_header = conn_get_header; - conn->get_body = conn_get_body; - conn->extract_body = conn_extract_body; + conn_init_cb(&conn->com); return conn; } diff --git a/src/connection.h b/src/connection.h index 9f376c6..a596616 100644 --- a/src/connection.h +++ b/src/connection.h @@ -25,13 +25,10 @@ #ifndef LIBUHTTPD_CONNECTION_H #define LIBUHTTPD_CONNECTION_H -#include -#include #include -#include "http_parser.h" #include "buffer.h" -#include "config.h" +#include "uhttpd.h" #define UHTTPD_CONNECTION_TIMEOUT 30.0 #define UHTTPD_MAX_HEADER_NUM 50 @@ -39,12 +36,7 @@ #define CONN_F_SEND_AND_CLOSE (1 << 0) /* Push remaining data and close */ #define CONN_F_SSL_HANDSHAKE_DONE (1 << 1) /* SSL hanshake has completed */ -struct uh_server; - -struct uh_str { - const char *p; - size_t len; -}; +struct uh_server_internal; struct uh_request { size_t length; /* The total length of the request which still remain in buffer */ @@ -73,7 +65,8 @@ struct uh_request { } body; }; -struct uh_connection { +struct uh_connection_internal { + struct uh_connection com; int sock; #if UHTTPD_SSL_SUPPORT void *ssl; @@ -90,7 +83,7 @@ struct uh_connection { ev_tstamp activity; struct ev_timer timer; struct uh_request req; - struct uh_server *srv; + struct uh_server_internal *srv; union { struct sockaddr sa; struct sockaddr_in sin; @@ -98,38 +91,13 @@ struct uh_connection { } addr; /* peer address */ struct http_parser parser; struct http_parser_url url_parser; - struct uh_connection *prev; - struct uh_connection *next; + struct uh_connection_internal *prev; + struct uh_connection_internal *next; void (*handler)(struct uh_connection *conn, int event); - /* - ** Indicates the end of request processing - ** Must be called at last, if not call 'error', 'redirect' and 'serve_file' - */ - void (*done)(struct uh_connection *conn); - void (*send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*send_file)(struct uh_connection *conn, const char *path); - void (*printf)(struct uh_connection *conn, const char *format, ...); - void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); - void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); - void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); - void (*error)(struct uh_connection *conn, int code, const char *reason); - void (*redirect)(struct uh_connection *conn, int code, const char *location, ...); - void (*serve_file)(struct uh_connection *conn); - void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*chunk_printf)(struct uh_connection *conn, const char *format, ...); - void (*chunk_vprintf)(struct uh_connection *conn, const char *format, va_list arg); - void (*chunk_end)(struct uh_connection *conn); - const struct sockaddr *(*get_addr)(struct uh_connection *conn); /* peer address */ - enum http_method (*get_method)(struct uh_connection *conn); - const char *(*get_method_str)(struct uh_connection *conn); - struct uh_str (*get_path)(struct uh_connection *conn); - struct uh_str (*get_query)(struct uh_connection *conn); - struct uh_str (*get_header)(struct uh_connection *conn, const char *name); - struct uh_str (*get_body)(struct uh_connection *conn); - /* The remain body data will be discurd after this function called */ - struct uh_str (*extract_body)(struct uh_connection *conn); }; -struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct sockaddr *addr); +struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, int sock, struct sockaddr *addr); + +void conn_free(struct uh_connection_internal *conn); #endif diff --git a/src/file.c b/src/file.c index 9562745..ccbcc51 100644 --- a/src/file.c +++ b/src/file.c @@ -35,9 +35,9 @@ #include #include +#include "uhttpd_internal.h" #include "mimetypes.h" -#include "uhttpd.h" -#include "log.h" +#include "file.h" static const char *file_mktag(struct stat *s, char *buf, int len) { @@ -154,8 +154,9 @@ static void file_if_gzip(struct uh_connection *conn, const char *path, const cha void serve_file(struct uh_connection *conn) { + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; const struct uh_str path = conn->get_path(conn); - struct uh_server *srv = conn->srv; + struct uh_server_internal *srv = conni->srv; const char *docroot = srv->docroot; const char *index_page = srv->index_page; static char fullpath[512]; diff --git a/src/uhttpd.c b/src/uhttpd.c index 32a2c47..70358db 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -33,34 +33,33 @@ #include #endif -#include "uhttpd.h" +#include "uhttpd_internal.h" +#include "connection.h" #include "utils.h" #include "ssl.h" -#include "log.h" - -void conn_free(struct uh_connection *conn); static void uh_server_free(struct uh_server *srv) { - struct uh_connection *conn = srv->conns; - struct uh_path_handler *h = srv->handlers; + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + struct uh_connection_internal *conn = srvi->conns; + struct uh_path_handler *h = srvi->handlers; #ifdef HAVE_DLOPEN - struct uh_plugin *p = srv->plugins; + struct uh_plugin *p = srvi->plugins; #endif - ev_io_stop(srv->loop, &srv->ior); + ev_io_stop(srvi->loop, &srvi->ior); - if (srv->sock > 0) - close(srv->sock); + if (srvi->sock > 0) + close(srvi->sock); - if (srv->docroot) - free(srv->docroot); + if (srvi->docroot) + free(srvi->docroot); - if (srv->index_page) - free(srv->index_page); + if (srvi->index_page) + free(srvi->index_page); while (conn) { - struct uh_connection *next = conn->next; + struct uh_connection_internal *next = conn->next; conn_free(conn); conn = next; } @@ -81,14 +80,14 @@ static void uh_server_free(struct uh_server *srv) #endif #if UHTTPD_SSL_SUPPORT - uh_ssl_ctx_free(srv->ssl_ctx); + uh_ssl_ctx_free(srvi->ssl_ctx); #endif } static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - struct uh_server *srv = container_of(w, struct uh_server, ior); - struct uh_connection *conn; + struct uh_server_internal *srv = container_of(w, struct uh_server_internal, ior); + struct uh_connection_internal *conn; union { struct sockaddr sa; struct sockaddr_in sin; @@ -128,7 +127,7 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port { struct uh_server *srv; - srv = malloc(sizeof(struct uh_server)); + srv = malloc(sizeof(struct uh_server_internal)); if (!srv) { uh_log_err("malloc: %s\n", strerror(errno)); return NULL; @@ -145,13 +144,15 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port #if UHTTPD_SSL_SUPPORT static int uh_server_ssl_init(struct uh_server *srv, const char *cert, const char *key) { - srv->ssl_ctx = uh_ssl_ctx_init(cert, key); - return srv->ssl_ctx ? 0 : -1; + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + srvi->ssl_ctx = uh_ssl_ctx_init(cert, key); + return srvi->ssl_ctx ? 0 : -1; } #endif static int uh_load_plugin(struct uh_server *srv, const char *path) { + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; #ifdef HAVE_DLOPEN struct uh_plugin_handler *h; struct uh_plugin *p; @@ -185,13 +186,13 @@ static int uh_load_plugin(struct uh_server *srv, const char *path) p->h = h; p->dlh = dlh; - if (!srv->plugins) { - srv->plugins = p; + if (!srvi->plugins) { + srvi->plugins = p; return 0; } - p->next = srv->plugins; - srv->plugins = p; + p->next = srvi->plugins; + srvi->plugins = p; return 0; #else @@ -202,6 +203,7 @@ static int uh_load_plugin(struct uh_server *srv, const char *path) static int uh_add_path_handler(struct uh_server *srv, const char *path, uh_path_handler_prototype handler) { + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; struct uh_path_handler *h; h = calloc(1, sizeof(struct uh_path_handler) + strlen(path) + 1); @@ -213,24 +215,33 @@ static int uh_add_path_handler(struct uh_server *srv, const char *path, uh_path_ h->handler = handler; strcpy(h->path, path); - if (!srv->handlers) { - srv->handlers = h; + if (!srvi->handlers) { + srvi->handlers = h; return 0; } - h->next = srv->handlers; - srv->handlers = h; + h->next = srvi->handlers; + srvi->handlers = h; return 0; } +static void uh_set_default_handler(struct uh_server *srv, uh_path_handler_prototype handler) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + + srvi->default_handler = handler; +} + static int uh_set_docroot(struct uh_server *srv, const char *path) { - if (srv->docroot) - free(srv->docroot); + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - srv->docroot = strdup(path); - if (!srv->docroot) { + if (srvi->docroot) + free(srvi->docroot); + + srvi->docroot = strdup(path); + if (!srvi->docroot) { uh_log_err("strdup: %s\n", strerror(errno)); return -1; } @@ -240,11 +251,13 @@ static int uh_set_docroot(struct uh_server *srv, const char *path) static int uh_set_index_page(struct uh_server *srv, const char *name) { - if (srv->index_page) - free(srv->index_page); + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - srv->index_page = strdup(name); - if (!srv->index_page) { + if (srvi->index_page) + free(srvi->index_page); + + srvi->index_page = strdup(name); + if (!srvi->index_page) { uh_log_err("strdup: %s\n", strerror(errno)); return -1; } @@ -254,6 +267,7 @@ static int uh_set_index_page(struct uh_server *srv, const char *name) int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port) { + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; union { struct sockaddr sa; struct sockaddr_in sin; @@ -326,10 +340,10 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host uh_log_debug("Listen on: %s %d\n", addr_str, port); } - memset(srv, 0, sizeof(struct uh_server)); + memset(srvi, 0, sizeof(struct uh_server_internal)); - srv->loop = loop ? loop : EV_DEFAULT; - srv->sock = sock; + srvi->loop = loop ? loop : EV_DEFAULT; + srvi->sock = sock; srv->free = uh_server_free; #if UHTTPD_SSL_SUPPORT @@ -338,13 +352,14 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->load_plugin = uh_load_plugin; + srv->set_default_handler = uh_set_default_handler; srv->add_path_handler = uh_add_path_handler; srv->set_docroot = uh_set_docroot; srv->set_index_page = uh_set_index_page; - ev_io_init(&srv->ior, uh_accept_cb, sock, EV_READ); - ev_io_start(srv->loop, &srv->ior); + ev_io_init(&srvi->ior, uh_accept_cb, sock, EV_READ); + ev_io_start(srvi->loop, &srvi->ior); return 0; diff --git a/src/uhttpd.h b/src/uhttpd.h index 5e2442d..b07e41e 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -25,14 +25,70 @@ #ifndef LIBUHTTPD_UHTTPD_H #define LIBUHTTPD_UHTTPD_H +#include +#include +#include #include -#include "connection.h" +#include "http_parser.h" #include "config.h" #include "log.h" +struct uh_str { + const char *p; + size_t len; +}; + +enum { + UH_EV_HEAD_COMPLETE, + UH_EV_BODY, + UH_EV_COMPLETE +}; + +struct uh_connection { + /* + ** Indicates the end of request processing + ** Must be called at last, if not call 'error', 'redirect' and 'serve_file' + */ + void (*done)(struct uh_connection *conn); + void (*send)(struct uh_connection *conn, const void *data, ssize_t len); + void (*send_file)(struct uh_connection *conn, const char *path); + void (*printf)(struct uh_connection *conn, const char *format, ...); + void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); + void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); + void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); + void (*error)(struct uh_connection *conn, int code, const char *reason); + void (*redirect)(struct uh_connection *conn, int code, const char *location, ...); + void (*serve_file)(struct uh_connection *conn); + void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); + void (*chunk_printf)(struct uh_connection *conn, const char *format, ...); + void (*chunk_vprintf)(struct uh_connection *conn, const char *format, va_list arg); + void (*chunk_end)(struct uh_connection *conn); + const struct sockaddr *(*get_addr)(struct uh_connection *conn); /* peer address */ + enum http_method (*get_method)(struct uh_connection *conn); + const char *(*get_method_str)(struct uh_connection *conn); + struct uh_str (*get_path)(struct uh_connection *conn); + struct uh_str (*get_query)(struct uh_connection *conn); + struct uh_str (*get_header)(struct uh_connection *conn, const char *name); + struct uh_str (*get_body)(struct uh_connection *conn); + /* The remain body data will be discurd after this function called */ + struct uh_str (*extract_body)(struct uh_connection *conn); +}; + typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event); +struct uh_server { + void (*free)(struct uh_server *srv); +#if UHTTPD_SSL_SUPPORT + int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); +#endif + int (*load_plugin)(struct uh_server *srv, const char *path); + void (*set_default_handler)(struct uh_server *srv, uh_path_handler_prototype handler); + int (*add_path_handler)(struct uh_server *srv, const char *path, uh_path_handler_prototype handler); + int (*set_docroot)(struct uh_server *srv, const char *path); + int (*set_index_page)(struct uh_server *srv, const char *name); +}; + struct uh_plugin_handler { const char *path; uh_path_handler_prototype handler; @@ -44,38 +100,12 @@ struct uh_plugin { struct uh_plugin *next; }; -enum { - UH_EV_HEAD_COMPLETE, - UH_EV_BODY, - UH_EV_COMPLETE -}; - struct uh_path_handler { uh_path_handler_prototype handler; struct uh_path_handler *next; char path[0]; }; -struct uh_server { - int sock; - char *docroot; - char *index_page; - struct ev_loop *loop; - struct ev_io ior; - struct uh_connection *conns; - void (*free)(struct uh_server *srv); - void (*default_handler)(struct uh_connection *conn, int event); -#if UHTTPD_SSL_SUPPORT - void *ssl_ctx; - int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); -#endif - struct uh_plugin *plugins; - int (*load_plugin)(struct uh_server *srv, const char *path); - struct uh_path_handler *handlers; - int (*add_path_handler)(struct uh_server *srv, const char *path, uh_path_handler_prototype handler); - int (*set_docroot)(struct uh_server *srv, const char *path); - int (*set_index_page)(struct uh_server *srv, const char *name); -}; /* * uh_server_new - creat an uh_server struct and init it diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h new file mode 100644 index 0000000..818a7a8 --- /dev/null +++ b/src/uhttpd_internal.h @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIBUHTTPD_UHTTPD_INTERNAL_H +#define LIBUHTTPD_UHTTPD_INTERNAL_H + +#include "uhttpd.h" + +struct uh_connection_internal; + +struct uh_server_internal { + struct uh_server com; + int sock; + char *docroot; + char *index_page; + struct ev_loop *loop; + struct ev_io ior; + struct uh_connection_internal *conns; + void (*default_handler)(struct uh_connection *conn, int event); +#if UHTTPD_SSL_SUPPORT + void *ssl_ctx; +#endif + struct uh_plugin *plugins; + struct uh_path_handler *handlers; +}; + +#endif \ No newline at end of file From f32ae1188afc148f6c1fe65ed1214cd87cdf6340 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 1 Jan 2021 18:39:28 +0800 Subject: [PATCH 07/55] Update README Signed-off-by: Jianhui Zhao --- README.md | 3 ++- README_ZH.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc150c1..b2e3c56 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on * Lightweight and fully asynchronous * Use [libev] as its event backend * Support HTTPS - OpenSSL, mbedtls and CyaSSl(wolfssl) +* Support HTTP pipelining * Support IPv6 * Support plugin * Support upload large file @@ -36,7 +37,7 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on * Code structure is concise and understandable, also suitable for learning # Dependencies -* [libev] +* [libev] - A full-featured and high-performance event loop * [http-parser] - A high performance parser for HTTP messages written in C * [mbedtls] - If you choose mbedtls as your SSL backend * [wolfssl] - If you choose wolfssl as your SSL backend diff --git a/README_ZH.md b/README_ZH.md index f52cd95..454c1a3 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -29,6 +29,7 @@ * 轻量、全异步 * 使用[libev]作为其事件后端 * 支持HTTPS - OpenSSL, mbedtls 和 CyaSSl(wolfssl) +* 支持 HTTP 流水线 * 支持 IPv6 * 支持插件 * 支持上传大文件 @@ -36,7 +37,7 @@ * 代码结构简洁通俗易懂,亦适合学习 # 依赖 -* [libev] +* [libev] - 一个全功能和高性能的事件循环库 * [http-parser] - 一个用 C 语言编写的高性能的 HTTP 消息解析器 * [mbedtls] - 如果你选择mbedtls作为你的SSL后端 * [wolfssl] - 如果你选择wolfssl作为你的SSL后端 From 805b1589f06e4eba4276cbe5d41a5246740499d2 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 2 Jan 2021 19:22:07 +0800 Subject: [PATCH 08/55] Improve the file response performance Signed-off-by: Jianhui Zhao --- src/buffer | 2 +- src/connection.c | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/buffer b/src/buffer index 84e1b93..1bd1b6d 160000 --- a/src/buffer +++ b/src/buffer @@ -1 +1 @@ -Subproject commit 84e1b93c110b8a886fadab0563319506edb66741 +Subproject commit 1bd1b6d192eda9e5bc24582e7ef3a4a017f9b064 diff --git a/src/connection.c b/src/connection.c index 8e8b647..3d2b452 100644 --- a/src/connection.c +++ b/src/connection.c @@ -66,6 +66,7 @@ static void conn_send_file(struct uh_connection *conn, const char *path) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct stat st; + int ret; conni->file.fd = open(path, O_RDONLY); @@ -73,6 +74,10 @@ static void conn_send_file(struct uh_connection *conn, const char *path) conni->file.size = st.st_size; + /* If the file is not greater than 8K, then append it to the HTTP head, send once */ + ret = buffer_put_fd(&conni->wb, conni->file.fd, 8192, NULL); + conni->file.size -= ret; + ev_io_start(conni->srv->loop, &conni->iow); } From f767b6cd5f761ac11d35015ae0857276080c939f Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 2 Jan 2021 20:41:59 +0800 Subject: [PATCH 09/55] conn_send_file: improve Signed-off-by: Jianhui Zhao --- src/connection.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/connection.c b/src/connection.c index 3d2b452..b6aeb74 100644 --- a/src/connection.c +++ b/src/connection.c @@ -66,17 +66,24 @@ static void conn_send_file(struct uh_connection *conn, const char *path) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct stat st; - int ret; + int fd; - conni->file.fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY); + if (fd < 0) { + uh_log_err("open: %s\n", strerror(errno)); + return; + } - fstat(conni->file.fd, &st); - - conni->file.size = st.st_size; + fstat(fd, &st); /* If the file is not greater than 8K, then append it to the HTTP head, send once */ - ret = buffer_put_fd(&conni->wb, conni->file.fd, 8192, NULL); - conni->file.size -= ret; + st.st_size -= buffer_put_fd(&conni->wb, fd, 8192, NULL); + if (st.st_size > 0) { + conni->file.size = st.st_size; + conni->file.fd = fd; + } else { + close(fd); + } ev_io_start(conni->srv->loop, &conni->iow); } From 0755e549ff88bad62045219acef0ca70796adcc5 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 2 Jan 2021 23:30:39 +0800 Subject: [PATCH 10/55] file: support range requests Signed-off-by: Jianhui Zhao --- src/connection.c | 26 ++++++++--- src/file.c | 118 +++++++++++++++++++++++++++++++++++++++++++++-- src/uhttpd.h | 2 +- 3 files changed, 134 insertions(+), 12 deletions(-) diff --git a/src/connection.c b/src/connection.c index b6aeb74..6c9c31e 100644 --- a/src/connection.c +++ b/src/connection.c @@ -62,9 +62,10 @@ static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) ev_io_start(conni->srv->loop, &conni->iow); } -static void conn_send_file(struct uh_connection *conn, const char *path) +static void conn_send_file(struct uh_connection *conn, const char *path, size_t offset, size_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + size_t min = 8192; struct stat st; int fd; @@ -76,13 +77,24 @@ static void conn_send_file(struct uh_connection *conn, const char *path) fstat(fd, &st); - /* If the file is not greater than 8K, then append it to the HTTP head, send once */ - st.st_size -= buffer_put_fd(&conni->wb, fd, 8192, NULL); - if (st.st_size > 0) { - conni->file.size = st.st_size; - conni->file.fd = fd; - } else { + if (offset >= st.st_size) { close(fd); + return; + } + + lseek(fd, offset, SEEK_SET); + st.st_size -= offset; + + if (len == 0 || len > st.st_size) + len = st.st_size; + + /* If the file is not greater than 8K, then append it to the HTTP head, send once */ + if (len <= min) { + buffer_put_fd(&conni->wb, fd, len, NULL); + close(fd); + } else { + conni->file.size = len; + conni->file.fd = fd; } ev_io_start(conni->srv->loop, &conni->iow); diff --git a/src/file.c b/src/file.c index ccbcc51..068b2b7 100644 --- a/src/file.c +++ b/src/file.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "uhttpd_internal.h" #include "mimetypes.h" @@ -152,6 +153,102 @@ static void file_if_gzip(struct uh_connection *conn, const char *path, const cha conn->printf(conn, "Content-Encoding: gzip\r\n"); } +static bool file_range(struct uh_connection *conn, size_t size, size_t *start, size_t *end, bool *ranged) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + const struct uh_str hdr = conn->get_header(conn, "Range"); + int content_length; + const char *reason; + const char *p, *e; + char buf[32]; + int i; + + *start = 0; + *end = size - 1; + + if (!hdr.p) { + *ranged = false; + return true; + } + + if (hdr.len < 8) + goto err; + + p = hdr.p; + e = hdr.p + hdr.len; + + if (strncmp(p, "bytes=", 6)) + goto err; + + p += 6; + i = 0; + + while (p < e) { + if (i >= sizeof(buf) - 1) + goto err; + + if (isdigit(*p)) { + buf[i++] = *p++; + continue; + } + + if (*p != '-') + goto err; + + p++; + buf[i] = '\0'; + + break; + } + + *start = strtoul(buf, NULL, 0); + + i = 0; + + while (p < e) { + if (i >= (sizeof(buf) - 1) || !isdigit(*p)) + goto err; + buf[i++] = *p++; + } + + buf[i] = '\0'; + *end = strtoul(buf, NULL, 0); + + if (*start >= size) + goto err; + + if (*end == 0) + *end = size - 1; + + if (*end < *start) + goto err; + + if (*end > size - 1) + *end = size - 1; + + *ranged = true; + + return true; + +err: + reason = http_status_str(HTTP_STATUS_RANGE_NOT_SATISFIABLE); + content_length = strlen(reason); + + conn->send_status_line(conn, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Content-Type: text/plain\r\nConnection: close\r\n"); + conn->printf(conn, "Content-Length: %d\r\n", content_length); + conn->printf(conn, "Content-Range: bytes */%" PRIu64 "\r\n", size); + + conn->send(conn, "\r\n", 2); + + conn->send(conn, reason, content_length); + + conni->flags |= CONN_F_SEND_AND_CLOSE; + + conn->done(conn); + + return false; +} + void serve_file(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; @@ -160,8 +257,10 @@ void serve_file(struct uh_connection *conn) const char *docroot = srv->docroot; const char *index_page = srv->index_page; static char fullpath[512]; + size_t start, end; const char *mime; struct stat st; + bool ranged; if (!docroot || !docroot[0]) docroot = "."; @@ -210,6 +309,9 @@ void serve_file(struct uh_connection *conn) return; } + if (!file_range(conn, st.st_size, &start, &end, &ranged)) + return; + if (!file_if_modified_since(conn, &st) || !file_if_range(conn, &st) || !file_if_unmodified_since(conn, &st)) { @@ -217,21 +319,29 @@ void serve_file(struct uh_connection *conn) return; } - conn->send_status_line(conn, HTTP_STATUS_OK, NULL); + if (ranged) + conn->send_status_line(conn, HTTP_STATUS_PARTIAL_CONTENT, NULL); + else + conn->send_status_line(conn, HTTP_STATUS_OK, NULL); + file_response_ok_hdrs(conn, &st); mime = file_mime_lookup(fullpath); conn->printf(conn, "Content-Type: %s\r\n", mime); - conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", st.st_size); + conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", end - start + 1); - file_if_gzip(conn, fullpath, mime); + if (ranged) + conn->printf(conn, "Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 "\r\n", start, end, st.st_size); + else + file_if_gzip(conn, fullpath, mime); conn->printf(conn, "\r\n"); if (conn->get_method(conn) == HTTP_HEAD) return; - conn->send_file(conn, fullpath); + conn->send_file(conn, fullpath, start, end - start + 1); + conn->done(conn); } diff --git a/src/uhttpd.h b/src/uhttpd.h index b07e41e..aecec9a 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -52,7 +52,7 @@ struct uh_connection { */ void (*done)(struct uh_connection *conn); void (*send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*send_file)(struct uh_connection *conn, const char *path); + void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, size_t len); void (*printf)(struct uh_connection *conn, const char *format, ...); void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); From 55a9b04d317e61181e112b6bad5a1430ac337200 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 2 Jan 2021 23:31:51 +0800 Subject: [PATCH 11/55] fix bug: response large(> 4294967295) file fail Signed-off-by: Jianhui Zhao --- src/connection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connection.h b/src/connection.h index a596616..d564bf1 100644 --- a/src/connection.h +++ b/src/connection.h @@ -74,7 +74,7 @@ struct uh_connection_internal { uint8_t flags; struct { int fd; - int size; + size_t size; } file; struct ev_io ior; struct ev_io iow; From ffa199a64761c5b1e6a4a715999b0c606c951385 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 16:19:24 +0800 Subject: [PATCH 12/55] Support multi-process model The same multi-process model as Nginx Signed-off-by: Jianhui Zhao --- README.md | 1 + README_ZH.md | 1 + example/example.c | 2 ++ src/uhttpd.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- src/uhttpd.h | 5 +++++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b2e3c56..e86d862 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on * Support IPv6 * Support plugin * Support upload large file +* Support multi-process model - The same multi-process model as Nginx * Flexible - you can easily extend your application to have HTTP/HTTPS services * Code structure is concise and understandable, also suitable for learning diff --git a/README_ZH.md b/README_ZH.md index 454c1a3..61a4bb6 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -33,6 +33,7 @@ * 支持 IPv6 * 支持插件 * 支持上传大文件 +* 支持多进程模型 - 和 Nginx 一样的多进程模型 * 可伸缩 - 你可以非常方便的扩展你的应用程序,使之具备HTTP/HTTPS服务 * 代码结构简洁通俗易懂,亦适合学习 diff --git a/example/example.c b/example/example.c index f54f8eb..b7f3223 100644 --- a/example/example.c +++ b/example/example.c @@ -196,6 +196,8 @@ int main(int argc, char **argv) goto err; #endif + srv->start_worker(srv, -1); /* -1 means automatically to available CPUs */ + srv->set_docroot(srv, docroot); srv->set_index_page(srv, index_page); diff --git a/src/uhttpd.c b/src/uhttpd.c index 70358db..e68dc9e 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -32,6 +32,7 @@ #ifdef HAVE_DLOPEN #include #endif +#include #include "uhttpd_internal.h" #include "connection.h" @@ -100,7 +101,8 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) sock = accept4(srv->sock, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); if (sock < 0) { - uh_log_err("accept: %s\n", strerror(errno)); + if (errno != EAGAIN) + uh_log_err("accept: %s\n", strerror(errno)); return; } @@ -123,6 +125,43 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) srv->conns = conn; } +static void uh_start_accept(struct uh_server_internal *srv) +{ + ev_io_init(&srv->ior, uh_accept_cb, srv->sock, EV_READ); + ev_io_start(srv->loop, &srv->ior); +} + +static void uh_stop_accept(struct uh_server_internal *srv) +{ + ev_io_stop(srv->loop, &srv->ior); +} + +static void uh_start_worker(struct uh_server *srv, int n) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + pid_t pid; + int i; + + if (n < 0) + n = get_nprocs(); + + if (n > 0) + uh_stop_accept(srvi); + + for (i = 0; i < n; i++) { + pid = fork(); + switch (pid) { + case -1: + uh_log_err("fork: %s\n", strerror(errno)); + return; + case 0: + ev_loop_fork(srvi->loop); + uh_start_accept(srvi); + return; + } + } +} + struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port) { struct uh_server *srv; @@ -345,6 +384,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srvi->loop = loop ? loop : EV_DEFAULT; srvi->sock = sock; srv->free = uh_server_free; + srv->start_worker = uh_start_worker; #if UHTTPD_SSL_SUPPORT srv->ssl_init = uh_server_ssl_init; @@ -358,8 +398,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->set_docroot = uh_set_docroot; srv->set_index_page = uh_set_index_page; - ev_io_init(&srvi->ior, uh_accept_cb, sock, EV_READ); - ev_io_start(srvi->loop, &srvi->ior); + uh_start_accept(srvi); return 0; diff --git a/src/uhttpd.h b/src/uhttpd.h index aecec9a..d90cd7d 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -79,6 +79,11 @@ typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event) struct uh_server { void (*free)(struct uh_server *srv); + /* + ** Start n worker processes to process the requests + ** If n is -1, automatically to available CPUs + */ + void (*start_worker)(struct uh_server *srv, int n); #if UHTTPD_SSL_SUPPORT int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); #endif From 8070cc20c990557c42d418a8307472ae59bad41b Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 16:54:31 +0800 Subject: [PATCH 13/55] README: Add benchmark Signed-off-by: Jianhui Zhao --- README.md | 25 +++++++++++++++++++++++++ README_ZH.md | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/README.md b/README.md index e86d862..2879f99 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,31 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on * [wolfssl] - If you choose wolfssl as your SSL backend * [openssl] - If you choose openssl as your SSL backend +# Benchmark +## Nginx + + $ wrk -t4 -c400 -d10s http://localhost:80/test.html + Running 10s test @ http://localhost:80/test.html + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.54ms 7.32ms 224.58ms 93.30% + Req/Sec 40.63k 12.49k 96.29k 74.50% + 1622012 requests in 10.05s, 385.09MB read + Requests/sec: 161390.39 + Transfer/sec: 38.32MB + +## libuhttpd + + $ wrk -t4 -c400 -d10s http://localhost:8080/test.html + Running 10s test @ http://localhost:8080/test.html + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.12ms 3.01ms 31.30ms 89.26% + Req/Sec 70.87k 12.53k 142.54k 79.75% + 2826394 requests in 10.05s, 547.18MB read + Requests/sec: 281328.83 + Transfer/sec: 54.46MB + # Configure See which configuration are supported diff --git a/README_ZH.md b/README_ZH.md index 61a4bb6..c7f65eb 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -44,6 +44,31 @@ * [wolfssl] - 如果你选择wolfssl作为你的SSL后端 * [openssl] - 如果你选择openssl作为你的SSL后端 +# 基准测试 +## Nginx + + $ wrk -t4 -c400 -d10s http://localhost:80/test.html + Running 10s test @ http://localhost:80/test.html + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.54ms 7.32ms 224.58ms 93.30% + Req/Sec 40.63k 12.49k 96.29k 74.50% + 1622012 requests in 10.05s, 385.09MB read + Requests/sec: 161390.39 + Transfer/sec: 38.32MB + +## libuhttpd + + $ wrk -t4 -c400 -d10s http://localhost:8080/test.html + Running 10s test @ http://localhost:8080/test.html + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 2.12ms 3.01ms 31.30ms 89.26% + Req/Sec 70.87k 12.53k 142.54k 79.75% + 2826394 requests in 10.05s, 547.18MB read + Requests/sec: 281328.83 + Transfer/sec: 54.46MB + # 配置 查看支持哪些配置选项 From 143e0d8abe3275c11ba350ce03448360b977a8f4 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 17:25:00 +0800 Subject: [PATCH 14/55] Update README Signed-off-by: Jianhui Zhao --- README.md | 1 + README_ZH.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 2879f99..f204dc5 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ A very flexible, lightweight and fully asynchronous HTTP server library based on * Support IPv6 * Support plugin * Support upload large file +* Support HTTP range requests * Support multi-process model - The same multi-process model as Nginx * Flexible - you can easily extend your application to have HTTP/HTTPS services * Code structure is concise and understandable, also suitable for learning diff --git a/README_ZH.md b/README_ZH.md index c7f65eb..4a38eac 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -33,6 +33,7 @@ * 支持 IPv6 * 支持插件 * 支持上传大文件 +* 支持 HTTP 范围请求 * 支持多进程模型 - 和 Nginx 一样的多进程模型 * 可伸缩 - 你可以非常方便的扩展你的应用程序,使之具备HTTP/HTTPS服务 * 代码结构简洁通俗易懂,亦适合学习 From c11052e30273f617d6b0d6509e20f77378552080 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 17:38:36 +0800 Subject: [PATCH 15/55] uh_start_worker: Improve The child process is only created when the number of workers is greater than 1 Signed-off-by: Jianhui Zhao --- src/uhttpd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uhttpd.c b/src/uhttpd.c index e68dc9e..0f3ded4 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -145,8 +145,10 @@ static void uh_start_worker(struct uh_server *srv, int n) if (n < 0) n = get_nprocs(); - if (n > 0) - uh_stop_accept(srvi); + if (n < 2) + return; + + uh_stop_accept(srvi); for (i = 0; i < n; i++) { pid = fork(); From d6868b6ab36377ce430e0059a1ba91c7e63d0a19 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 18:15:06 +0800 Subject: [PATCH 16/55] multi-process model: Improve Signed-off-by: Jianhui Zhao --- example/example.c | 8 ++++++-- src/uhttpd.c | 1 + src/uhttpd.h | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/example/example.c b/example/example.c index b7f3223..1a29fed 100644 --- a/example/example.c +++ b/example/example.c @@ -196,8 +196,6 @@ int main(int argc, char **argv) goto err; #endif - srv->start_worker(srv, -1); /* -1 means automatically to available CPUs */ - srv->set_docroot(srv, docroot); srv->set_index_page(srv, index_page); @@ -208,6 +206,12 @@ int main(int argc, char **argv) if (plugin_path) srv->load_plugin(srv, plugin_path); + /* + ** -1 means automatically to available CPUs + ** This function must be called after the Server has been initialized + */ + srv->start_worker(srv, -1); + ev_signal_init(&signal_watcher, signal_cb, SIGINT); ev_signal_start(loop, &signal_watcher); diff --git a/src/uhttpd.c b/src/uhttpd.c index 0f3ded4..b75acdc 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -159,6 +159,7 @@ static void uh_start_worker(struct uh_server *srv, int n) case 0: ev_loop_fork(srvi->loop); uh_start_accept(srvi); + ev_run(srvi->loop, 0); return; } } diff --git a/src/uhttpd.h b/src/uhttpd.h index d90cd7d..d747b98 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -81,6 +81,7 @@ struct uh_server { void (*free)(struct uh_server *srv); /* ** Start n worker processes to process the requests + ** Must be called after the Server has been initialized ** If n is -1, automatically to available CPUs */ void (*start_worker)(struct uh_server *srv, int n); From 9ae7b25f84c10789714e3d738cb45e286d09d2f1 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 18:19:44 +0800 Subject: [PATCH 17/55] example.c: Worker processes number is configurable Signed-off-by: Jianhui Zhao --- example/example.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/example/example.c b/example/example.c index 1a29fed..81e97ef 100644 --- a/example/example.c +++ b/example/example.c @@ -131,6 +131,7 @@ static void usage(const char *prog) " -s # SSl on\n" " -f # Serve file\n" " -P # plugin path\n" + " -w # worker process number, default is equal to available CPUs\n" " -v # verbose\n", prog); exit(1); } @@ -146,10 +147,11 @@ int main(int argc, char **argv) const char *docroot = "."; const char *index_page = "index.html"; const char *addr = "localhost"; + int nworker = -1; int port = 8080; int opt; - while ((opt = getopt(argc, argv, "h:i:a:p:sfP:v")) != -1) { + while ((opt = getopt(argc, argv, "h:i:a:p:sfP:w:v")) != -1) { switch (opt) { case 'h': docroot = optarg; @@ -172,6 +174,8 @@ int main(int argc, char **argv) case 'P': plugin_path = optarg; break; + case 'w': + nworker = atoi(optarg); case 'v': verbose = true; break; @@ -210,7 +214,7 @@ int main(int argc, char **argv) ** -1 means automatically to available CPUs ** This function must be called after the Server has been initialized */ - srv->start_worker(srv, -1); + srv->start_worker(srv, nworker); ev_signal_init(&signal_watcher, signal_cb, SIGINT); ev_signal_start(loop, &signal_watcher); From f044dc97a9429f33762f1ab20784c618d7232e19 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 18:36:29 +0800 Subject: [PATCH 18/55] Watching worker processes Signed-off-by: Jianhui Zhao --- src/uhttpd.c | 25 ++++++++++++++++++++++--- src/uhttpd_internal.h | 5 +++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/uhttpd.c b/src/uhttpd.c index b75acdc..f3868e8 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -136,10 +136,19 @@ static void uh_stop_accept(struct uh_server_internal *srv) ev_io_stop(srv->loop, &srv->ior); } +static void uh_worker_exit(struct ev_loop *loop, struct ev_child *w, int revents) +{ + struct worker *wk = container_of(w, struct worker, w); + + uh_log_info("worker %d exit\n", wk->i); + + free(wk); +} + static void uh_start_worker(struct uh_server *srv, int n) { struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - pid_t pid; + pid_t pids[20]; int i; if (n < 0) @@ -151,18 +160,28 @@ static void uh_start_worker(struct uh_server *srv, int n) uh_stop_accept(srvi); for (i = 0; i < n; i++) { - pid = fork(); - switch (pid) { + pids[i] = fork(); + switch (pids[i]) { case -1: uh_log_err("fork: %s\n", strerror(errno)); return; case 0: ev_loop_fork(srvi->loop); uh_start_accept(srvi); + + uh_log_info("worker %d started\n", i); + ev_run(srvi->loop, 0); return; } } + + while (i-- > 0) { + struct worker *w = calloc(1, sizeof(struct worker)); + w->i = i; + ev_child_init(&w->w, uh_worker_exit, pids[i], 0); + ev_child_start(srvi->loop, &w->w); + } } struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port) diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h index 818a7a8..8f017b4 100644 --- a/src/uhttpd_internal.h +++ b/src/uhttpd_internal.h @@ -45,4 +45,9 @@ struct uh_server_internal { struct uh_path_handler *handlers; }; +struct worker { + struct ev_child w; + int i; +}; + #endif \ No newline at end of file From b2ce003bcc9d96c566e478eba487c6a362b4c505 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 19:58:35 +0800 Subject: [PATCH 19/55] Improve variable name Signed-off-by: Jianhui Zhao --- src/uhttpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uhttpd.c b/src/uhttpd.c index f3868e8..e82a70e 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -337,7 +337,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host char addr_str[INET6_ADDRSTRLEN]; socklen_t addrlen; int sock = -1; - int opt = 1; + int on = 1; if (!host || *host == '\0') { addr.sin.sin_family = AF_INET; @@ -383,7 +383,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host return -1; } - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) < 0) { + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { uh_log_err("setsockopt: %s\n", strerror(errno)); goto err; } From db85c135cb5390c7ca081ea513aa2052af6a0766 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sun, 3 Jan 2021 21:53:20 +0800 Subject: [PATCH 20/55] Use SO_REUSEPORT Signed-off-by: Jianhui Zhao --- src/uhttpd.c | 78 +++++++++++++++++++++++++++++++------------ src/uhttpd_internal.h | 9 +++++ src/utils.c | 17 ++++++++++ src/utils.h | 2 ++ 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/uhttpd.c b/src/uhttpd.c index e82a70e..a5fb931 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #ifdef HAVE_DLOPEN #include @@ -145,6 +144,40 @@ static void uh_worker_exit(struct ev_loop *loop, struct ev_child *w, int revents free(wk); } +static int uh_socket(int family) +{ + int on = 1; + int sock; + + sock = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (sock < 0) { + uh_log_err("socket: %s\n", strerror(errno)); + return -1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { + uh_log_err("setsockopt: %s\n", strerror(errno)); + close(sock); + return -1; + } + + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); + + return sock; +} + +static int uh_listen(int sock, struct sockaddr *addr, socklen_t addrlen) +{ + if (bind(sock, addr, addrlen) < 0) { + uh_log_err("bind: %s\n", strerror(errno)); + return -1; + } + + listen(sock, SOMAXCONN); + + return 0; +} + static void uh_start_worker(struct uh_server *srv, int n) { struct uh_server_internal *srvi = (struct uh_server_internal *)srv; @@ -159,6 +192,9 @@ static void uh_start_worker(struct uh_server *srv, int n) uh_stop_accept(srvi); + if (srvi->reuseport) + close(srvi->sock); + for (i = 0; i < n; i++) { pids[i] = fork(); switch (pids[i]) { @@ -167,6 +203,12 @@ static void uh_start_worker(struct uh_server *srv, int n) return; case 0: ev_loop_fork(srvi->loop); + + if (srvi->reuseport) { + srvi->sock = uh_socket(srvi->addr.sa.sa_family); + uh_listen(srvi->sock, &srvi->addr.sa, srvi->addrlen); + } + uh_start_accept(srvi); uh_log_info("worker %d started\n", i); @@ -337,7 +379,6 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host char addr_str[INET6_ADDRSTRLEN]; socklen_t addrlen; int sock = -1; - int on = 1; if (!host || *host == '\0') { addr.sin.sin_family = AF_INET; @@ -377,34 +418,31 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host inet_ntop(AF_INET6, &addr.sin6.sin6_addr, addr_str, sizeof(addr_str)); } - sock = socket(addr.sa.sa_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (sock < 0) { - uh_log_err("socket: %s\n", strerror(errno)); + memset(srvi, 0, sizeof(struct uh_server_internal)); + + srvi->loop = loop ? loop : EV_DEFAULT; + + srvi->reuseport = support_so_reuseport(); + + sock = uh_socket(addr.sa.sa_family); + if (sock < 0) return -1; - } - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { - uh_log_err("setsockopt: %s\n", strerror(errno)); + if (uh_listen(sock, &addr.sa, addrlen) < 0) goto err; - } - if (bind(sock, &addr.sa, addrlen) < 0) { - close(sock); - uh_log_err("bind: %s\n", strerror(errno)); - goto err; - } + srvi->sock = sock; - listen(sock, SOMAXCONN); + uh_start_accept(srvi); + + srvi->addrlen = addrlen; + memcpy(&srvi->addr, &addr, sizeof(addr)); if (uh_log_get_threshold() == LOG_DEBUG) { saddr2str(&addr.sa, addr_str, sizeof(addr_str), &port); uh_log_debug("Listen on: %s %d\n", addr_str, port); } - memset(srvi, 0, sizeof(struct uh_server_internal)); - - srvi->loop = loop ? loop : EV_DEFAULT; - srvi->sock = sock; srv->free = uh_server_free; srv->start_worker = uh_start_worker; @@ -420,8 +458,6 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->set_docroot = uh_set_docroot; srv->set_index_page = uh_set_index_page; - uh_start_accept(srvi); - return 0; err: diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h index 8f017b4..619853a 100644 --- a/src/uhttpd_internal.h +++ b/src/uhttpd_internal.h @@ -25,6 +25,8 @@ #ifndef LIBUHTTPD_UHTTPD_INTERNAL_H #define LIBUHTTPD_UHTTPD_INTERNAL_H +#include + #include "uhttpd.h" struct uh_connection_internal; @@ -32,6 +34,13 @@ struct uh_connection_internal; struct uh_server_internal { struct uh_server com; int sock; + bool reuseport; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + socklen_t addrlen; char *docroot; char *index_page; struct ev_loop *loop; diff --git a/src/utils.c b/src/utils.c index c51f1a0..77b38c1 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "utils.h" @@ -44,3 +45,19 @@ const char *saddr2str(struct sockaddr *addr, char buf[], int len, int *port) return buf; } + +bool support_so_reuseport() +{ + bool ok = false; + int on = 1; + int sock; + + sock = socket(AF_INET, SOCK_STREAM, 0); + + if (!setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int))) + ok = true; + + close(sock); + + return ok; +} diff --git a/src/utils.h b/src/utils.h index 705a6cd..220f087 100644 --- a/src/utils.h +++ b/src/utils.h @@ -41,4 +41,6 @@ const char *saddr2str(struct sockaddr *addr, char buf[], int len, int *port); +bool support_so_reuseport(); + #endif From 8c2e9c3a57bf5cc43517767e73cf35da49b6e6e9 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Mon, 4 Jan 2021 15:48:09 +0800 Subject: [PATCH 21/55] Fix bug: Parent process exits but child process didn't Signed-off-by: Jianhui Zhao --- src/uhttpd.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uhttpd.c b/src/uhttpd.c index a5fb931..1753482 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -32,6 +32,7 @@ #include #endif #include +#include #include "uhttpd_internal.h" #include "connection.h" @@ -202,6 +203,8 @@ static void uh_start_worker(struct uh_server *srv, int n) uh_log_err("fork: %s\n", strerror(errno)); return; case 0: + prctl(PR_SET_PDEATHSIG, SIGKILL); + ev_loop_fork(srvi->loop); if (srvi->reuseport) { From b0bed674343af92421be84c1d0da353bcde6c0a6 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 6 Jan 2021 00:49:32 +0800 Subject: [PATCH 22/55] Add some api Signed-off-by: Jianhui Zhao --- src/connection.c | 16 ++++++++++++++++ src/uhttpd.c | 8 ++++++++ src/uhttpd.h | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/src/connection.c b/src/connection.c index 6c9c31e..b640923 100644 --- a/src/connection.c +++ b/src/connection.c @@ -706,8 +706,24 @@ static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents) conn_error(&conn->com, HTTP_STATUS_REQUEST_TIMEOUT, NULL); } +static struct uh_server *conn_get_server(struct uh_connection *conn) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return &conni->srv->com; +} + +static struct ev_loop *conn_get_loop(struct uh_connection *conn) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return conni->srv->loop; +} + static void conn_init_cb(struct uh_connection *conn) { + conn->get_server = conn_get_server; + conn->get_loop = conn_get_loop; conn->done = conn_done; conn->send = conn_send; conn->send_file = conn_send_file; diff --git a/src/uhttpd.c b/src/uhttpd.c index 1753482..bee0024 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -371,6 +371,13 @@ static int uh_set_index_page(struct uh_server *srv, const char *name) return 0; } +static struct ev_loop *uh_get_loop(struct uh_server *srv) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + + return srvi->loop; +} + int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port) { struct uh_server_internal *srvi = (struct uh_server_internal *)srv; @@ -446,6 +453,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host uh_log_debug("Listen on: %s %d\n", addr_str, port); } + srv->get_loop = uh_get_loop; srv->free = uh_server_free; srv->start_worker = uh_start_worker; diff --git a/src/uhttpd.h b/src/uhttpd.h index d747b98..3bf1cb9 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -45,7 +45,11 @@ enum { UH_EV_COMPLETE }; +struct uh_server; + struct uh_connection { + struct uh_server *(*get_server)(struct uh_connection *conn); + struct ev_loop *(*get_loop)(struct uh_connection *conn); /* ** Indicates the end of request processing ** Must be called at last, if not call 'error', 'redirect' and 'serve_file' @@ -78,6 +82,7 @@ struct uh_connection { typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event); struct uh_server { + struct ev_loop *(*get_loop)(struct uh_server *srv); void (*free)(struct uh_server *srv); /* ** Start n worker processes to process the requests From de6bd1bf9269cd806bf162ea66f57d4da839f38f Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 6 Jan 2021 01:02:59 +0800 Subject: [PATCH 23/55] improve conn_send_file Signed-off-by: Jianhui Zhao --- src/connection.c | 7 +++++-- src/uhttpd.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/connection.c b/src/connection.c index b640923..fec7f20 100644 --- a/src/connection.c +++ b/src/connection.c @@ -62,13 +62,16 @@ static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) ev_io_start(conni->srv->loop, &conni->iow); } -static void conn_send_file(struct uh_connection *conn, const char *path, size_t offset, size_t len) +static void conn_send_file(struct uh_connection *conn, const char *path, size_t offset, ssize_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; size_t min = 8192; struct stat st; int fd; + if (len == 0) + return; + fd = open(path, O_RDONLY); if (fd < 0) { uh_log_err("open: %s\n", strerror(errno)); @@ -85,7 +88,7 @@ static void conn_send_file(struct uh_connection *conn, const char *path, size_t lseek(fd, offset, SEEK_SET); st.st_size -= offset; - if (len == 0 || len > st.st_size) + if (len < 0 || len > st.st_size) len = st.st_size; /* If the file is not greater than 8K, then append it to the HTTP head, send once */ diff --git a/src/uhttpd.h b/src/uhttpd.h index 3bf1cb9..71859ad 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -56,7 +56,7 @@ struct uh_connection { */ void (*done)(struct uh_connection *conn); void (*send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, size_t len); + void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, ssize_t len); void (*printf)(struct uh_connection *conn, const char *format, ...); void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); From e7f5ca114cec9a086c20d2c8d04b13293557455e Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 6 Jan 2021 01:43:05 +0800 Subject: [PATCH 24/55] fix bug: format number abnormal on some platform Signed-off-by: Jianhui Zhao --- src/file.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/file.c b/src/file.c index 068b2b7..b7fd529 100644 --- a/src/file.c +++ b/src/file.c @@ -43,7 +43,7 @@ static const char *file_mktag(struct stat *s, char *buf, int len) { snprintf(buf, len, "\"%" PRIx64 "-%" PRIx64 "-%" PRIx64 "\"", - (uint64_t)s->st_ino, s->st_size, (uint64_t)s->st_mtime); + (uint64_t)s->st_ino, (uint64_t)s->st_size, (uint64_t)s->st_mtime); return buf; } @@ -166,6 +166,8 @@ static bool file_range(struct uh_connection *conn, size_t size, size_t *start, s *start = 0; *end = size - 1; + uh_log_info("%lu - %lu\n", *start, *end); + if (!hdr.p) { *ranged = false; return true; @@ -236,7 +238,7 @@ err: conn->send_status_line(conn, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Content-Type: text/plain\r\nConnection: close\r\n"); conn->printf(conn, "Content-Length: %d\r\n", content_length); - conn->printf(conn, "Content-Range: bytes */%" PRIu64 "\r\n", size); + conn->printf(conn, "Content-Range: bytes */%zu\r\n", size); conn->send(conn, "\r\n", 2); @@ -329,10 +331,10 @@ void serve_file(struct uh_connection *conn) mime = file_mime_lookup(fullpath); conn->printf(conn, "Content-Type: %s\r\n", mime); - conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", end - start + 1); + conn->printf(conn, "Content-Length: %zu\r\n", end - start + 1); if (ranged) - conn->printf(conn, "Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 "\r\n", start, end, st.st_size); + conn->printf(conn, "Content-Range: bytes %zu-%zu/%zu\r\n", start, end, st.st_size); else file_if_gzip(conn, fullpath, mime); From d53a954794eb8cf78539434826879f74c910deca Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 6 Jan 2021 23:02:25 +0800 Subject: [PATCH 25/55] check the arguments like printf Signed-off-by: Jianhui Zhao --- example/example.c | 8 ++++---- example/test_plugin.c | 2 +- src/uhttpd.h | 10 ++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/example/example.c b/example/example.c index 81e97ef..5cc8785 100644 --- a/example/example.c +++ b/example/example.c @@ -47,10 +47,10 @@ static void default_handler(struct uh_connection *conn, int event) conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); - conn->chunk_printf(conn, "Path: %.*s\n", path.len ,path.p); - conn->chunk_printf(conn, "Query: %.*s\n", query.len, query.p); - conn->chunk_printf(conn, "User-Agent: %.*s\n", ua.len, ua.p); - conn->chunk_printf(conn, "Body: %.*s\n", body.len, body.p); + conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len ,path.p); + conn->chunk_printf(conn, "Query: %.*s\n", (int)query.len, query.p); + conn->chunk_printf(conn, "User-Agent: %.*s\n", (int)ua.len, ua.p); + conn->chunk_printf(conn, "Body: %.*s\n", (int)body.len, body.p); conn->chunk_end(conn); conn->done(conn); } else { diff --git a/example/test_plugin.c b/example/test_plugin.c index 85a887c..ca5a098 100644 --- a/example/test_plugin.c +++ b/example/test_plugin.c @@ -34,7 +34,7 @@ static void test_handler(struct uh_connection *conn, int event) path = conn->get_path(conn); conn->send_head(conn, 200, -1, NULL); - conn->chunk_printf(conn, "Path: %.*s\n", path.len, path.p); + conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len, path.p); conn->chunk_end(conn); } diff --git a/src/uhttpd.h b/src/uhttpd.h index 71859ad..866b956 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -57,18 +57,18 @@ struct uh_connection { void (*done)(struct uh_connection *conn); void (*send)(struct uh_connection *conn, const void *data, ssize_t len); void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, ssize_t len); - void (*printf)(struct uh_connection *conn, const char *format, ...); + void (*printf)(struct uh_connection *conn, const char *format, ...) __attribute__((format(printf, 2, 3))); void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); void (*error)(struct uh_connection *conn, int code, const char *reason); - void (*redirect)(struct uh_connection *conn, int code, const char *location, ...); + void (*redirect)(struct uh_connection *conn, int code, const char *location, ...) __attribute__((format(printf, 3, 4))); void (*serve_file)(struct uh_connection *conn); void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*chunk_printf)(struct uh_connection *conn, const char *format, ...); + void (*chunk_printf)(struct uh_connection *conn, const char *format, ...) __attribute__((format(printf, 2, 3))); void (*chunk_vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*chunk_end)(struct uh_connection *conn); - const struct sockaddr *(*get_addr)(struct uh_connection *conn); /* peer address */ + const struct sockaddr *(*get_addr)(struct uh_connection *conn); /* peer address */ enum http_method (*get_method)(struct uh_connection *conn); const char *(*get_method_str)(struct uh_connection *conn); struct uh_str (*get_path)(struct uh_connection *conn); @@ -117,7 +117,6 @@ struct uh_path_handler { char path[0]; }; - /* * uh_server_new - creat an uh_server struct and init it * @loop: If NULL will use EV_DEFAULT @@ -129,4 +128,3 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port); #endif - From a1a43f837028eb15bce0ec7e09d44f5ee72add59 Mon Sep 17 00:00:00 2001 From: Fabrice Fontaine Date: Fri, 8 Jan 2021 08:01:26 +0100 Subject: [PATCH 26/55] CMakeLists.txt: add BUILD_EXAMPLE Allow the user to disable example Signed-off-by: Fabrice Fontaine --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d399caa..36f5df5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,14 @@ cmake_minimum_required(VERSION 2.8) project(libuhttpd C) +option(BUILD_EXAMPLE "Build example" ON) + INCLUDE(CheckLibraryExists) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/") add_subdirectory(src) -add_subdirectory(example) +if(BUILD_EXAMPLE) + add_subdirectory(example) +endif() From 33339863b5129afc30a9831747396ec31087b5e1 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 8 Jan 2021 23:27:15 +0800 Subject: [PATCH 27/55] clean debug code Signed-off-by: Jianhui Zhao --- src/file.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/file.c b/src/file.c index b7fd529..174f640 100644 --- a/src/file.c +++ b/src/file.c @@ -166,8 +166,6 @@ static bool file_range(struct uh_connection *conn, size_t size, size_t *start, s *start = 0; *end = size - 1; - uh_log_info("%lu - %lu\n", *start, *end); - if (!hdr.p) { *ranged = false; return true; From 808bb3785b170c71829e078c1fd87aff697b9e4a Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 8 Jan 2021 23:58:08 +0800 Subject: [PATCH 28/55] Improve code Signed-off-by: Jianhui Zhao --- src/ssl.c | 1 + src/ssl.h | 1 - src/utils.h | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 9030942..156abe9 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -26,6 +26,7 @@ #include #include "ssl.h" +#include "log.h" #if UHTTPD_SSL_SUPPORT diff --git a/src/ssl.h b/src/ssl.h index dce413b..8301add 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -26,7 +26,6 @@ #define LIBUHTTPD_SSL_H #include "config.h" -#include "log.h" #if UHTTPD_SSL_SUPPORT diff --git a/src/utils.h b/src/utils.h index 220f087..e9314c1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -28,8 +28,7 @@ #include #include #include - -#include "config.h" +#include #ifndef container_of #define container_of(ptr, type, member) \ From c28eaa2bb76eac571c3a9d9811fe67718120f796 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 9 Jan 2021 00:01:32 +0800 Subject: [PATCH 29/55] Multi-process operation are not directly integrated The multi-process model is presented as sample code. Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- example/CMakeLists.txt | 15 +- example/handler.c | 114 +++++++++++ example/handler.h | 34 ++++ example/{example.c => multi_process_server.c} | 131 ++++--------- example/multi_process_server_reuseport.c | 183 ++++++++++++++++++ example/simple_server.c | 135 +++++++++++++ src/CMakeLists.txt | 1 + src/uhttpd.c | 145 +++----------- src/uhttpd.h | 7 +- src/uhttpd_internal.h | 7 - 12 files changed, 545 insertions(+), 231 deletions(-) create mode 100644 example/handler.c create mode 100644 example/handler.h rename example/{example.c => multi_process_server.c} (59%) create mode 100644 example/multi_process_server_reuseport.c create mode 100644 example/simple_server.c diff --git a/README.md b/README.md index f204dc5..23a1386 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ See which configuration are supported # Run Example Run - ~/libuhttpd/build$ ./example/example + ~/libuhttpd/build$ ./example/simple_server -v Then use the command curl or browser to test diff --git a/README_ZH.md b/README_ZH.md index 4a38eac..55fa038 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -84,7 +84,7 @@ # 运行示例程序 运行 - ~/libuhttpd/build$ ./example/example -v + ~/libuhttpd/build$ ./example/simple_server -v 然后使用命令curl或者浏览器进行测试 diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index cbe0a9e..0161a7a 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,14 +5,23 @@ include_directories( ${CMAKE_BINARY_DIR}/src ${LIBEV_INCLUDE_DIR}) -add_executable(example example.c) +set(LIBS ${LIBEV_LIBRARY}) if(BUILD_SHARED_LIBS) - target_link_libraries(example uhttpd ${LIBEV_LIBRARY}) + list(APPEND LIBS uhttpd) else() - target_link_libraries(example uhttpd_s ${LIBEV_LIBRARY}) + list(APPEND LIBS uhttpd_s) endif() +add_executable(simple_server simple_server.c handler.c) +target_link_libraries(simple_server ${LIBS}) + +add_executable(multi_process_server_reuseport multi_process_server_reuseport.c handler.c) +target_link_libraries(multi_process_server_reuseport ${LIBS}) + +add_executable(multi_process_server multi_process_server.c handler.c) +target_link_libraries(multi_process_server ${LIBS}) + if(HAVE_DLOPEN) add_library(test_plugin MODULE test_plugin.c) set_target_properties(test_plugin PROPERTIES OUTPUT_NAME test_plugin PREFIX "") diff --git a/example/handler.c b/example/handler.c new file mode 100644 index 0000000..628a50f --- /dev/null +++ b/example/handler.c @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "uhttpd.h" + +void default_handler(struct uh_connection *conn, int event) +{ + if (event != UH_EV_COMPLETE) + return; + + conn->serve_file(conn); +} + +void echo_handler(struct uh_connection *conn, int event) +{ + if (event == UH_EV_COMPLETE) { + struct uh_str path = conn->get_path(conn); + struct uh_str query = conn->get_query(conn); + struct uh_str ua = conn->get_header(conn, "User-Agent"); + struct uh_str body = conn->get_body(conn); + + conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); + conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); + conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); + conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len ,path.p); + conn->chunk_printf(conn, "Query: %.*s\n", (int)query.len, query.p); + conn->chunk_printf(conn, "User-Agent: %.*s\n", (int)ua.len, ua.p); + conn->chunk_printf(conn, "Body: %.*s\n", (int)body.len, body.p); + conn->chunk_end(conn); + conn->done(conn); + } +} + +void upload_handler(struct uh_connection *conn, int event) +{ + static int fd = -1; + + if (event == UH_EV_HEAD_COMPLETE) { + struct uh_str str = conn->get_header(conn, "Content-Length"); + int content_length; + char buf[128]; + + sprintf(buf, "%.*s\n", (int)str.len, str.p); + + content_length = atoi(buf); + + if (content_length > 1024 * 1024 * 1024) { + conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Too big"); + return; + } + + } if (event == UH_EV_BODY) { + struct uh_str body = conn->extract_body(conn); + + if (fd < 0) { + fd = open("upload.bin", O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, strerror(errno)); + return; + } + } + + if (write(fd, body.p, body.len) < 0) { + conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, strerror(errno)); + close(fd); + return; + } + } else if (event == UH_EV_COMPLETE) { + struct stat st; + size_t size = 0; + + conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); + + if (fd > 0) { + fstat(fd, &st); + close(fd); + + fd = -1; + size = st.st_size; + } + + conn->chunk_printf(conn, "Upload size: %zd\n", size); + conn->chunk_end(conn); + conn->done(conn); + } +} diff --git a/example/handler.h b/example/handler.h new file mode 100644 index 0000000..647a4b3 --- /dev/null +++ b/example/handler.h @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _EXAMPLE_HANDLER_H +#define _EXAMPLE_HANDLER_H + +#include "uhttpd.h" + +void default_handler(struct uh_connection *conn, int event); +void echo_handler(struct uh_connection *conn, int event); +void upload_handler(struct uh_connection *conn, int event); + +#endif \ No newline at end of file diff --git a/example/example.c b/example/multi_process_server.c similarity index 59% rename from example/example.c rename to example/multi_process_server.c index 5cc8785..efc76e8 100644 --- a/example/example.c +++ b/example/multi_process_server.c @@ -22,6 +22,8 @@ * SOFTWARE. */ +#include +#include #include #include #include @@ -29,92 +31,23 @@ #include #include -#include "uhttpd.h" +#include "handler.h" -static bool serve_file = false; - -static void default_handler(struct uh_connection *conn, int event) -{ - if (event != UH_EV_COMPLETE) - return; - - if (!serve_file) { - struct uh_str path = conn->get_path(conn); - struct uh_str query = conn->get_query(conn); - struct uh_str ua = conn->get_header(conn, "User-Agent"); - struct uh_str body = conn->get_body(conn); - - conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); - conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); - conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); - conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len ,path.p); - conn->chunk_printf(conn, "Query: %.*s\n", (int)query.len, query.p); - conn->chunk_printf(conn, "User-Agent: %.*s\n", (int)ua.len, ua.p); - conn->chunk_printf(conn, "Body: %.*s\n", (int)body.len, body.p); - conn->chunk_end(conn); - conn->done(conn); - } else { - conn->serve_file(conn); - } -} - -static void upload_handler(struct uh_connection *conn, int event) -{ - static int fd = -1; - - if (event == UH_EV_HEAD_COMPLETE) { - struct uh_str str = conn->get_header(conn, "Content-Length"); - int content_length; - char buf[128]; - - sprintf(buf, "%.*s\n", (int)str.len, str.p); - - content_length = atoi(buf); - - if (content_length > 1024 * 1024 * 1024) { - conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Too big"); - return; - } - - } if (event == UH_EV_BODY) { - struct uh_str body = conn->extract_body(conn); - - if (fd < 0) { - fd = open("upload.bin", O_RDWR | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, strerror(errno)); - return; - } - } - - if (write(fd, body.p, body.len) < 0) { - conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, strerror(errno)); - close(fd); - return; - } - } else if (event == UH_EV_COMPLETE) { - struct stat st; - size_t size = 0; - - conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); - - if (fd > 0) { - fstat(fd, &st); - close(fd); - - fd = -1; - size = st.st_size; - } - - conn->chunk_printf(conn, "Upload size: %zd\n", size); - conn->chunk_end(conn); - conn->done(conn); - } -} +#define MAX_WORKER 10 static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { + int i; + if (w->signum == SIGINT) { + pid_t *workers = w->data; + + for (i = 0; i < MAX_WORKER; i++) { + if (workers[i] == 0) + break; + kill(workers[i], SIGKILL); + } + ev_break(loop, EVBREAK_ALL); uh_log_info("Normal quit\n"); } @@ -129,7 +62,6 @@ static void usage(const char *prog) " -a addr # Default addr is localhost\n" " -p port # Default port is 8080\n" " -s # SSl on\n" - " -f # Serve file\n" " -P # plugin path\n" " -w # worker process number, default is equal to available CPUs\n" " -v # verbose\n", prog); @@ -147,9 +79,10 @@ int main(int argc, char **argv) const char *docroot = "."; const char *index_page = "index.html"; const char *addr = "localhost"; - int nworker = -1; + pid_t workers[MAX_WORKER] = {}; + int nworker = get_nprocs(); int port = 8080; - int opt; + int opt, i; while ((opt = getopt(argc, argv, "h:i:a:p:sfP:w:v")) != -1) { switch (opt) { @@ -168,9 +101,6 @@ int main(int argc, char **argv) case 's': ssl = true; break; - case 'f': - serve_file = true; - break; case 'P': plugin_path = optarg; break; @@ -189,6 +119,9 @@ int main(int argc, char **argv) uh_log_info("libuhttpd version: %s\n", UHTTPD_VERSION_STRING); + if (nworker < 1) + return 0; + signal(SIGPIPE, SIG_IGN); srv = uh_server_new(loop, addr, port); @@ -204,19 +137,31 @@ int main(int argc, char **argv) srv->set_index_page(srv, index_page); srv->set_default_handler(srv, default_handler); - + srv->add_path_handler(srv, "/echo", echo_handler); srv->add_path_handler(srv, "/upload", upload_handler); if (plugin_path) srv->load_plugin(srv, plugin_path); - /* - ** -1 means automatically to available CPUs - ** This function must be called after the Server has been initialized - */ - srv->start_worker(srv, nworker); + for (i = 0; i < nworker - 1; i++) { + pid_t pid = fork(); + if (pid < 0) { + uh_log_info("fork: %s\n", strerror(errno)); + break; + } + + if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + ev_loop_fork(loop); + ev_run(loop, 0); + return 0; + } + + workers[i] = pid; + } ev_signal_init(&signal_watcher, signal_cb, SIGINT); + signal_watcher.data = workers; ev_signal_start(loop, &signal_watcher); ev_run(loop, 0); diff --git a/example/multi_process_server_reuseport.c b/example/multi_process_server_reuseport.c new file mode 100644 index 0000000..ad95f7e --- /dev/null +++ b/example/multi_process_server_reuseport.c @@ -0,0 +1,183 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "handler.h" + +#define MAX_WORKER 10 + +static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + int i; + + if (w->signum == SIGINT) { + pid_t *workers = w->data; + + for (i = 0; i < MAX_WORKER; i++) { + if (workers[i] == 0) + break; + kill(workers[i], SIGKILL); + } + + ev_break(loop, EVBREAK_ALL); + uh_log_info("Normal quit\n"); + } +} + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [option]\n" + " -h docroot # Document root, default is .\n" + " -i index_page # Index page, default is index.html\n" + " -a addr # Default addr is localhost\n" + " -p port # Default port is 8080\n" + " -s # SSl on\n" + " -P # plugin path\n" + " -w # worker process number, default is equal to available CPUs\n" + " -v # verbose\n", prog); + exit(1); +} + +static void start_server(const char *addr, int port, const char *docroot, const char *index_page, const char *plugin, bool ssl) +{ + struct ev_loop *loop = ev_loop_new(0); + struct uh_server *srv = NULL; + + signal(SIGPIPE, SIG_IGN); + + srv = uh_server_new(loop, addr, port); + if (!srv) + return; + +#if UHTTPD_SSL_SUPPORT + if (ssl && srv->ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) + return; +#endif + + srv->set_docroot(srv, docroot); + srv->set_index_page(srv, index_page); + + srv->set_default_handler(srv, default_handler); + srv->add_path_handler(srv, "/echo", echo_handler); + srv->add_path_handler(srv, "/upload", upload_handler); + + if (plugin) + srv->load_plugin(srv, plugin); + + ev_run(loop, 0); +} + +int main(int argc, char **argv) +{ + struct ev_loop *loop = EV_DEFAULT; + struct ev_signal signal_watcher; + const char *plugin_path = NULL; + bool verbose = false; + bool ssl = false; + const char *docroot = "."; + const char *index_page = "index.html"; + const char *addr = "localhost"; + pid_t workers[MAX_WORKER] = {}; + int nworker = get_nprocs(); + int port = 8080; + int opt, i; + + while ((opt = getopt(argc, argv, "h:i:a:p:sP:w:v")) != -1) { + switch (opt) { + case 'h': + docroot = optarg; + break; + case 'i': + index_page = optarg; + break; + case 'a': + addr = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 's': + ssl = true; + break; + case 'P': + plugin_path = optarg; + break; + case 'w': + nworker = atoi(optarg); + break; + case 'v': + verbose = true; + break; + default: /* '?' */ + usage(argv[0]); + } + } + + if (!verbose) + uh_log_threshold(LOG_ERR); + + uh_log_info("libuhttpd version: %s\n", UHTTPD_VERSION_STRING); + + if (!support_so_reuseport()) { + uh_log_err("Not support SO_REUSEPORT\n"); + return -1; + } + + if (nworker < 1) + return 0; + + for (i = 0; i < nworker; i++) { + pid_t pid = fork(); + if (pid < 0) { + uh_log_info("fork: %s\n", strerror(errno)); + break; + } + + if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + start_server(addr, port, docroot, index_page, plugin_path, ssl); + return 0; + } + + workers[i] = pid; + } + + ev_signal_init(&signal_watcher, signal_cb, SIGINT); + signal_watcher.data = workers; + ev_signal_start(loop, &signal_watcher); + + ev_run(loop, 0); + + ev_loop_destroy(loop); + + return 0; +} diff --git a/example/simple_server.c b/example/simple_server.c new file mode 100644 index 0000000..5bcd2ce --- /dev/null +++ b/example/simple_server.c @@ -0,0 +1,135 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "handler.h" + +static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + if (w->signum == SIGINT) { + ev_break(loop, EVBREAK_ALL); + uh_log_info("Normal quit\n"); + } +} + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [option]\n" + " -h docroot # Document root, default is .\n" + " -i index_page # Index page, default is index.html\n" + " -a addr # Default addr is localhost\n" + " -p port # Default port is 8080\n" + " -s # SSl on\n" + " -P # plugin path\n" + " -v # verbose\n", prog); + exit(1); +} + +int main(int argc, char **argv) +{ + struct ev_loop *loop = EV_DEFAULT; + struct ev_signal signal_watcher; + struct uh_server *srv = NULL; + const char *plugin_path = NULL; + bool verbose = false; + bool ssl = false; + const char *docroot = "."; + const char *index_page = "index.html"; + const char *addr = "localhost"; + int port = 8080; + int opt; + + while ((opt = getopt(argc, argv, "h:i:a:p:sP:v")) != -1) { + switch (opt) { + case 'h': + docroot = optarg; + break; + case 'i': + index_page = optarg; + break; + case 'a': + addr = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 's': + ssl = true; + break; + case 'P': + plugin_path = optarg; + break; + case 'v': + verbose = true; + break; + default: /* '?' */ + usage(argv[0]); + } + } + + if (!verbose) + uh_log_threshold(LOG_ERR); + + uh_log_info("libuhttpd version: %s\n", UHTTPD_VERSION_STRING); + + signal(SIGPIPE, SIG_IGN); + + srv = uh_server_new(loop, addr, port); + if (!srv) + return -1; + +#if UHTTPD_SSL_SUPPORT + if (ssl && srv->ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) + goto err; +#endif + + srv->set_docroot(srv, docroot); + srv->set_index_page(srv, index_page); + + srv->set_default_handler(srv, default_handler); + srv->add_path_handler(srv, "/echo", echo_handler); + srv->add_path_handler(srv, "/upload", upload_handler); + + if (plugin_path) + srv->load_plugin(srv, plugin_path); + + ev_signal_init(&signal_watcher, signal_cb, SIGINT); + ev_signal_start(loop, &signal_watcher); + + ev_run(loop, 0); + +err: + srv->free(srv); + free(srv); + + ev_loop_destroy(loop); + + return 0; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a80becf..29950b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ install( FILES uhttpd.h log.h + utils.h buffer/buffer.h http-parser/http_parser.h ${CMAKE_CURRENT_BINARY_DIR}/config.h diff --git a/src/uhttpd.c b/src/uhttpd.c index bee0024..2a59c26 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -31,8 +31,6 @@ #ifdef HAVE_DLOPEN #include #endif -#include -#include #include "uhttpd_internal.h" #include "connection.h" @@ -125,110 +123,6 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) srv->conns = conn; } -static void uh_start_accept(struct uh_server_internal *srv) -{ - ev_io_init(&srv->ior, uh_accept_cb, srv->sock, EV_READ); - ev_io_start(srv->loop, &srv->ior); -} - -static void uh_stop_accept(struct uh_server_internal *srv) -{ - ev_io_stop(srv->loop, &srv->ior); -} - -static void uh_worker_exit(struct ev_loop *loop, struct ev_child *w, int revents) -{ - struct worker *wk = container_of(w, struct worker, w); - - uh_log_info("worker %d exit\n", wk->i); - - free(wk); -} - -static int uh_socket(int family) -{ - int on = 1; - int sock; - - sock = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (sock < 0) { - uh_log_err("socket: %s\n", strerror(errno)); - return -1; - } - - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { - uh_log_err("setsockopt: %s\n", strerror(errno)); - close(sock); - return -1; - } - - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); - - return sock; -} - -static int uh_listen(int sock, struct sockaddr *addr, socklen_t addrlen) -{ - if (bind(sock, addr, addrlen) < 0) { - uh_log_err("bind: %s\n", strerror(errno)); - return -1; - } - - listen(sock, SOMAXCONN); - - return 0; -} - -static void uh_start_worker(struct uh_server *srv, int n) -{ - struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - pid_t pids[20]; - int i; - - if (n < 0) - n = get_nprocs(); - - if (n < 2) - return; - - uh_stop_accept(srvi); - - if (srvi->reuseport) - close(srvi->sock); - - for (i = 0; i < n; i++) { - pids[i] = fork(); - switch (pids[i]) { - case -1: - uh_log_err("fork: %s\n", strerror(errno)); - return; - case 0: - prctl(PR_SET_PDEATHSIG, SIGKILL); - - ev_loop_fork(srvi->loop); - - if (srvi->reuseport) { - srvi->sock = uh_socket(srvi->addr.sa.sa_family); - uh_listen(srvi->sock, &srvi->addr.sa, srvi->addrlen); - } - - uh_start_accept(srvi); - - uh_log_info("worker %d started\n", i); - - ev_run(srvi->loop, 0); - return; - } - } - - while (i-- > 0) { - struct worker *w = calloc(1, sizeof(struct worker)); - w->i = i; - ev_child_init(&w->w, uh_worker_exit, pids[i], 0); - ev_child_start(srvi->loop, &w->w); - } -} - struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port) { struct uh_server *srv; @@ -389,6 +283,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host char addr_str[INET6_ADDRSTRLEN]; socklen_t addrlen; int sock = -1; + int on = 1; if (!host || *host == '\0') { addr.sin.sin_family = AF_INET; @@ -428,34 +323,41 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host inet_ntop(AF_INET6, &addr.sin6.sin6_addr, addr_str, sizeof(addr_str)); } - memset(srvi, 0, sizeof(struct uh_server_internal)); - - srvi->loop = loop ? loop : EV_DEFAULT; - - srvi->reuseport = support_so_reuseport(); - - sock = uh_socket(addr.sa.sa_family); - if (sock < 0) + sock = socket(addr.sa.sa_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (sock < 0) { + uh_log_err("socket: %s\n", strerror(errno)); return -1; + } - if (uh_listen(sock, &addr.sa, addrlen) < 0) + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { + uh_log_err("setsockopt: %s\n", strerror(errno)); goto err; + } - srvi->sock = sock; + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); - uh_start_accept(srvi); + if (bind(sock, &addr.sa, addrlen) < 0) { + uh_log_err("bind: %s\n", strerror(errno)); + goto err; + } - srvi->addrlen = addrlen; - memcpy(&srvi->addr, &addr, sizeof(addr)); + if (listen(sock, SOMAXCONN) < 0) { + uh_log_err("bind: %s\n", strerror(errno)); + goto err; + } if (uh_log_get_threshold() == LOG_DEBUG) { saddr2str(&addr.sa, addr_str, sizeof(addr_str), &port); uh_log_debug("Listen on: %s %d\n", addr_str, port); } + memset(srvi, 0, sizeof(struct uh_server_internal)); + + srvi->loop = loop ? loop : EV_DEFAULT; + srvi->sock = sock; + srv->get_loop = uh_get_loop; srv->free = uh_server_free; - srv->start_worker = uh_start_worker; #if UHTTPD_SSL_SUPPORT srv->ssl_init = uh_server_ssl_init; @@ -469,6 +371,9 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->set_docroot = uh_set_docroot; srv->set_index_page = uh_set_index_page; + ev_io_init(&srvi->ior, uh_accept_cb, sock, EV_READ); + ev_io_start(srvi->loop, &srvi->ior); + return 0; err: diff --git a/src/uhttpd.h b/src/uhttpd.h index 866b956..5d2a5a9 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -32,6 +32,7 @@ #include "http_parser.h" #include "config.h" +#include "utils.h" #include "log.h" struct uh_str { @@ -84,12 +85,6 @@ typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event) struct uh_server { struct ev_loop *(*get_loop)(struct uh_server *srv); void (*free)(struct uh_server *srv); - /* - ** Start n worker processes to process the requests - ** Must be called after the Server has been initialized - ** If n is -1, automatically to available CPUs - */ - void (*start_worker)(struct uh_server *srv, int n); #if UHTTPD_SSL_SUPPORT int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); #endif diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h index 619853a..4c00245 100644 --- a/src/uhttpd_internal.h +++ b/src/uhttpd_internal.h @@ -34,13 +34,6 @@ struct uh_connection_internal; struct uh_server_internal { struct uh_server com; int sock; - bool reuseport; - union { - struct sockaddr sa; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - } addr; - socklen_t addrlen; char *docroot; char *index_page; struct ev_loop *loop; From 293a39f201e588af18ce705c95dce0b8940ff004 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 9 Jan 2021 22:06:23 +0800 Subject: [PATCH 30/55] fix compile error for 32bit os Signed-off-by: Jianhui Zhao --- src/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file.c b/src/file.c index 174f640..2f6140d 100644 --- a/src/file.c +++ b/src/file.c @@ -332,7 +332,7 @@ void serve_file(struct uh_connection *conn) conn->printf(conn, "Content-Length: %zu\r\n", end - start + 1); if (ranged) - conn->printf(conn, "Content-Range: bytes %zu-%zu/%zu\r\n", start, end, st.st_size); + conn->printf(conn, "Content-Range: bytes %zu-%zu/%"PRIx64"\r\n", start, end, (uint64_t)st.st_size); else file_if_gzip(conn, fullpath, mime); From edb1721c4a32016f9ff00121e669a57ab086dc29 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Mon, 11 Jan 2021 21:41:47 +0800 Subject: [PATCH 31/55] Bump version 3.8.0 Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- src/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 23a1386..4812383 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.7.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://travis-ci.org/zhaojh329/libuhttpd.svg?branch=master [10]: https://travis-ci.org/zhaojh329/libuhttpd diff --git a/README_ZH.md b/README_ZH.md index 55fa038..94fbe0e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.7.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://travis-ci.org/zhaojh329/libuhttpd.svg?branch=master [10]: https://travis-ci.org/zhaojh329/libuhttpd diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29950b5..7642090 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE) # The version number. set(UHTTPD_VERSION_MAJOR 3) -set(UHTTPD_VERSION_MINOR 7) +set(UHTTPD_VERSION_MINOR 8) set(UHTTPD_VERSION_PATCH 0) # Check the third party Libraries From 563ec0ff4251359f04f959657a8cda79ee27bc62 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 12 Jan 2021 10:39:06 +0800 Subject: [PATCH 32/55] Update .gitignore Signed-off-by: Jianhui Zhao --- .gitignore | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.gitignore b/.gitignore index 8550171..c6127b3 100644 --- a/.gitignore +++ b/.gitignore @@ -50,14 +50,3 @@ modules.order Module.symvers Mkfile.old dkms.conf - -server-cert.pem -server-key.pem - -build/ - -CMakeFiles -CMakeCache.txt -cmake_install.cmake -Makefile -helloworld From 971602f45f97f3f4d16067fbc6c832e045176de6 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 11:48:21 +0800 Subject: [PATCH 33/55] use url decode while handle file Signed-off-by: Jianhui Zhao --- src/file.c | 17 +++++++++++------ src/utils.c | 28 ++++++++++++++++++++++++++++ src/utils.h | 7 +++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/file.c b/src/file.c index 2f6140d..05babb8 100644 --- a/src/file.c +++ b/src/file.c @@ -38,6 +38,7 @@ #include "uhttpd_internal.h" #include "mimetypes.h" +#include "utils.h" #include "file.h" static const char *file_mktag(struct stat *s, char *buf, int len) @@ -256,7 +257,8 @@ void serve_file(struct uh_connection *conn) struct uh_server_internal *srv = conni->srv; const char *docroot = srv->docroot; const char *index_page = srv->index_page; - static char fullpath[512]; + static char fullpath[PATH_MAX]; + int docroot_len; size_t start, end; const char *mime; struct stat st; @@ -268,13 +270,16 @@ void serve_file(struct uh_connection *conn) if (!index_page || !index_page[0]) index_page = "index.html"; - strcpy(fullpath, docroot); + docroot_len = strlen(docroot); + + memcpy(fullpath, docroot, docroot_len); if (!strncmp(path.p, "/", path.len)) { - strcat(fullpath, "/"); - strcat(fullpath, index_page); - } else { - strncat(fullpath, path.p, path.len); + fullpath[docroot_len] = '/'; + strcpy(fullpath + docroot_len + 1, index_page); + } else if (urldecode(fullpath + docroot_len, PATH_MAX - docroot_len, path.p, path.len) < 0) { + conn->error(conn, HTTP_STATUS_NOT_FOUND, NULL); + return; } if (stat(fullpath, &st) < 0) { diff --git a/src/utils.c b/src/utils.c index 77b38c1..6189972 100644 --- a/src/utils.c +++ b/src/utils.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "utils.h" @@ -61,3 +62,30 @@ bool support_so_reuseport() return ok; } + +int urldecode(char *buf, int blen, const char *src, int slen) +{ + int i; + int len = 0; + +#define hex(x) \ + (((x) <= '9') ? ((x) - '0') : \ + (((x) <= 'F') ? ((x) - 'A' + 10) : \ + ((x) - 'a' + 10))) + + for (i = 0; (i < slen) && (len < blen); i++) { + if (src[i] != '%') { + buf[len++] = src[i]; + continue; + } + + if (i + 2 >= slen || !isxdigit(src[i + 1]) || !isxdigit(src[i + 2])) + return -2; + + buf[len++] = (char)(16 * hex(src[i+1]) + hex(src[i+2])); + i += 2; + } + buf[len] = 0; + + return (i == slen) ? len : -1; +} diff --git a/src/utils.h b/src/utils.h index e9314c1..e65bdac 100644 --- a/src/utils.h +++ b/src/utils.h @@ -42,4 +42,11 @@ const char *saddr2str(struct sockaddr *addr, char buf[], int len, int *port); bool support_so_reuseport(); +/* +** blen is the size of buf; slen is the length of src. The input-string need +** not be, and the output string will not be, null-terminated. Returns the +** length of the decoded string, -1 on buffer overflow, -2 on malformed string. +*/ +int urldecode(char *buf, int blen, const char *src, int slen); + #endif From 6cf45fb0a2076512f908b9921e0d963adc0f7a6b Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 12:02:10 +0800 Subject: [PATCH 34/55] uh_connection: Add a pointer to save context for user userdata Signed-off-by: Jianhui Zhao --- src/uhttpd.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uhttpd.h b/src/uhttpd.h index 5d2a5a9..d36dca4 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -78,6 +78,7 @@ struct uh_connection { struct uh_str (*get_body)(struct uh_connection *conn); /* The remain body data will be discurd after this function called */ struct uh_str (*extract_body)(struct uh_connection *conn); + void *userdata; }; typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event); From afeee7ec2f77dc0a78e245ac6cdb8d907a60801b Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 13:07:37 +0800 Subject: [PATCH 35/55] Support set a callback for connection closed Signed-off-by: Jianhui Zhao --- example/simple_server.c | 5 +++++ src/connection.c | 3 +++ src/uhttpd.c | 8 ++++++++ src/uhttpd.h | 2 ++ src/uhttpd_internal.h | 1 + 5 files changed, 19 insertions(+) diff --git a/example/simple_server.c b/example/simple_server.c index 5bcd2ce..fd3fe2a 100644 --- a/example/simple_server.c +++ b/example/simple_server.c @@ -31,6 +31,10 @@ #include "handler.h" +static void conn_closed_cb(struct uh_connection *conn) +{ +} + static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { if (w->signum == SIGINT) { @@ -113,6 +117,7 @@ int main(int argc, char **argv) srv->set_docroot(srv, docroot); srv->set_index_page(srv, index_page); + srv->set_conn_closed_cb(srv, conn_closed_cb); srv->set_default_handler(srv, default_handler); srv->add_path_handler(srv, "/echo", echo_handler); srv->add_path_handler(srv, "/upload", upload_handler); diff --git a/src/connection.c b/src/connection.c index fec7f20..d5f737f 100644 --- a/src/connection.c +++ b/src/connection.c @@ -514,6 +514,9 @@ void conn_free(struct uh_connection_internal *conn) uh_ssl_free(conn->ssl); #endif + if (conn->srv->conn_closed_cb) + conn->srv->conn_closed_cb(&conn->com); + if (conn->sock > 0) close(conn->sock); diff --git a/src/uhttpd.c b/src/uhttpd.c index 2a59c26..e43f6bd 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -226,6 +226,13 @@ static int uh_add_path_handler(struct uh_server *srv, const char *path, uh_path_ return 0; } +static void uh_set_conn_abort_cb(struct uh_server *srv, uh_con_closed_cb_prototype cb) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + + srvi->conn_closed_cb = cb; +} + static void uh_set_default_handler(struct uh_server *srv, uh_path_handler_prototype handler) { struct uh_server_internal *srvi = (struct uh_server_internal *)srv; @@ -365,6 +372,7 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->load_plugin = uh_load_plugin; + srv->set_conn_closed_cb = uh_set_conn_abort_cb; srv->set_default_handler = uh_set_default_handler; srv->add_path_handler = uh_add_path_handler; diff --git a/src/uhttpd.h b/src/uhttpd.h index d36dca4..2a6a4ef 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -81,6 +81,7 @@ struct uh_connection { void *userdata; }; +typedef void (*uh_con_closed_cb_prototype)(struct uh_connection *conn); typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event); struct uh_server { @@ -90,6 +91,7 @@ struct uh_server { int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); #endif int (*load_plugin)(struct uh_server *srv, const char *path); + void (*set_conn_closed_cb)(struct uh_server *srv, uh_con_closed_cb_prototype cb); void (*set_default_handler)(struct uh_server *srv, uh_path_handler_prototype handler); int (*add_path_handler)(struct uh_server *srv, const char *path, uh_path_handler_prototype handler); int (*set_docroot)(struct uh_server *srv, const char *path); diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h index 4c00245..4ce99a9 100644 --- a/src/uhttpd_internal.h +++ b/src/uhttpd_internal.h @@ -39,6 +39,7 @@ struct uh_server_internal { struct ev_loop *loop; struct ev_io ior; struct uh_connection_internal *conns; + void (*conn_closed_cb)(struct uh_connection *conn); void (*default_handler)(struct uh_connection *conn, int event); #if UHTTPD_SSL_SUPPORT void *ssl_ctx; From b0d35ed4fba4505b802a60090985f68fa877e20d Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 15:35:35 +0800 Subject: [PATCH 36/55] example: Improve code Signed-off-by: Jianhui Zhao --- example/handler.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/example/handler.c b/example/handler.c index 628a50f..a37a00f 100644 --- a/example/handler.c +++ b/example/handler.c @@ -61,8 +61,6 @@ void echo_handler(struct uh_connection *conn, int event) void upload_handler(struct uh_connection *conn, int event) { - static int fd = -1; - if (event == UH_EV_HEAD_COMPLETE) { struct uh_str str = conn->get_header(conn, "Content-Length"); int content_length; @@ -77,8 +75,11 @@ void upload_handler(struct uh_connection *conn, int event) return; } + conn->userdata = (void *)(intptr_t)-1; + } if (event == UH_EV_BODY) { struct uh_str body = conn->extract_body(conn); + int fd = (intptr_t)conn->userdata; if (fd < 0) { fd = open("upload.bin", O_RDWR | O_CREAT | O_TRUNC, 0644); @@ -93,7 +94,10 @@ void upload_handler(struct uh_connection *conn, int event) close(fd); return; } + + conn->userdata = (void *)(intptr_t)fd; } else if (event == UH_EV_COMPLETE) { + int fd = (intptr_t)conn->userdata; struct stat st; size_t size = 0; From fa6248b1f19538c9ca2d954bfbea3aba8138c0f9 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 15:40:21 +0800 Subject: [PATCH 37/55] uh_log_xx: check the arguments like printf Signed-off-by: Jianhui Zhao --- src/log.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/log.h b/src/log.h index 96a6647..772b08d 100644 --- a/src/log.h +++ b/src/log.h @@ -40,6 +40,7 @@ void uh_log_close(); #define uh_log_info(fmt...) uh_log(LOG_INFO, fmt) #define uh_log_err(fmt...) uh_log(LOG_ERR, fmt) -void __uh_log(const char *filename, int line, int priority, const char *fmt, ...); +void __uh_log(const char *filename, int line, int priority, const char *fmt, ...) + __attribute__((format(printf, 4, 5))); #endif From 2f12b7d434eeea8acdd8e99c34f2435a430bd93a Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 15 Jan 2021 17:17:23 +0800 Subject: [PATCH 38/55] connection: New API: get_content_length Signed-off-by: Jianhui Zhao --- example/handler.c | 8 +------- src/connection.c | 8 ++++++++ src/uhttpd.h | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/example/handler.c b/example/handler.c index a37a00f..1de62bc 100644 --- a/example/handler.c +++ b/example/handler.c @@ -62,13 +62,7 @@ void echo_handler(struct uh_connection *conn, int event) void upload_handler(struct uh_connection *conn, int event) { if (event == UH_EV_HEAD_COMPLETE) { - struct uh_str str = conn->get_header(conn, "Content-Length"); - int content_length; - char buf[128]; - - sprintf(buf, "%.*s\n", (int)str.len, str.p); - - content_length = atoi(buf); + uint64_t content_length = conn->get_content_length(conn); if (content_length > 1024 * 1024 * 1024) { conn->error(conn, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Too big"); diff --git a/src/connection.c b/src/connection.c index d5f737f..ce5d593 100644 --- a/src/connection.c +++ b/src/connection.c @@ -305,6 +305,13 @@ static struct uh_str conn_get_header(struct uh_connection *conn, const char *nam return value; } +static uint64_t conn_get_content_length(struct uh_connection *conn) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + return conni->parser.content_length; +} + static struct uh_str conn_get_body(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; @@ -752,6 +759,7 @@ static void conn_init_cb(struct uh_connection *conn) conn->get_path = conn_get_path; conn->get_query = conn_get_query; conn->get_header = conn_get_header; + conn->get_content_length = conn_get_content_length; conn->get_body = conn_get_body; conn->extract_body = conn_extract_body; } diff --git a/src/uhttpd.h b/src/uhttpd.h index 2a6a4ef..32de6e1 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -75,6 +75,7 @@ struct uh_connection { struct uh_str (*get_path)(struct uh_connection *conn); struct uh_str (*get_query)(struct uh_connection *conn); struct uh_str (*get_header)(struct uh_connection *conn, const char *name); + uint64_t (*get_content_length)(struct uh_connection *conn); struct uh_str (*get_body)(struct uh_connection *conn); /* The remain body data will be discurd after this function called */ struct uh_str (*extract_body)(struct uh_connection *conn); From 117ae812a4eb114f46f4d9dfd395618c74836710 Mon Sep 17 00:00:00 2001 From: Fabrice Fontaine Date: Mon, 18 Jan 2021 07:33:34 +0100 Subject: [PATCH 39/55] src/uhttpd.c: fix build without dlopen Fix the following build failure without dlopen: /home/buildroot/autobuild/instance-3/output-1/build/libuhttpd-3.8.0/src/uhttpd.c: In function 'uh_load_plugin': /home/buildroot/autobuild/instance-3/output-1/build/libuhttpd-3.8.0/src/uhttpd.c:155:32: error: unused variable 'srvi' [-Werror=unused-variable] struct uh_server_internal *srvi = (struct uh_server_internal *)srv; ^~~~ Fixes: - http://autobuild.buildroot.org/results/00e5b5e1da782dcedda48777cbb0a2cba9c98cec Signed-off-by: Fabrice Fontaine --- src/uhttpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uhttpd.c b/src/uhttpd.c index e43f6bd..cda979f 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -152,8 +152,8 @@ static int uh_server_ssl_init(struct uh_server *srv, const char *cert, const cha static int uh_load_plugin(struct uh_server *srv, const char *path) { - struct uh_server_internal *srvi = (struct uh_server_internal *)srv; #ifdef HAVE_DLOPEN + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; struct uh_plugin_handler *h; struct uh_plugin *p; void *dlh; From f2c55aff777f02dbad3680d7c3332ca17152d768 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 19 Jan 2021 20:25:33 +0800 Subject: [PATCH 40/55] file: fix bug: send "Content-Range" error Signed-off-by: Jianhui Zhao --- src/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/file.c b/src/file.c index 05babb8..20800c0 100644 --- a/src/file.c +++ b/src/file.c @@ -337,7 +337,7 @@ void serve_file(struct uh_connection *conn) conn->printf(conn, "Content-Length: %zu\r\n", end - start + 1); if (ranged) - conn->printf(conn, "Content-Range: bytes %zu-%zu/%"PRIx64"\r\n", start, end, (uint64_t)st.st_size); + conn->printf(conn, "Content-Range: bytes %zu-%zu/%" PRIu64 "\r\n", start, end, (uint64_t)st.st_size); else file_if_gzip(conn, fullpath, mime); From 81db1fa371bc80f40c1bfe60010e7d64bdf30b46 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 19 Jan 2021 20:35:50 +0800 Subject: [PATCH 41/55] file: support handle large file Signed-off-by: Jianhui Zhao --- src/CMakeLists.txt | 2 +- src/connection.c | 2 +- src/connection.h | 2 +- src/file.c | 14 +++++++------- src/uhttpd.h | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7642090..55174ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ -add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE) +add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64) # The version number. set(UHTTPD_VERSION_MAJOR 3) diff --git a/src/connection.c b/src/connection.c index ce5d593..37fb8fd 100644 --- a/src/connection.c +++ b/src/connection.c @@ -62,7 +62,7 @@ static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) ev_io_start(conni->srv->loop, &conni->iow); } -static void conn_send_file(struct uh_connection *conn, const char *path, size_t offset, ssize_t len) +static void conn_send_file(struct uh_connection *conn, const char *path, off_t offset, int64_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; size_t min = 8192; diff --git a/src/connection.h b/src/connection.h index d564bf1..cfe8f26 100644 --- a/src/connection.h +++ b/src/connection.h @@ -74,7 +74,7 @@ struct uh_connection_internal { uint8_t flags; struct { int fd; - size_t size; + uint64_t size; } file; struct ev_io ior; struct ev_io iow; diff --git a/src/file.c b/src/file.c index 20800c0..70f4a57 100644 --- a/src/file.c +++ b/src/file.c @@ -154,7 +154,7 @@ static void file_if_gzip(struct uh_connection *conn, const char *path, const cha conn->printf(conn, "Content-Encoding: gzip\r\n"); } -static bool file_range(struct uh_connection *conn, size_t size, size_t *start, size_t *end, bool *ranged) +static bool file_range(struct uh_connection *conn, uint64_t size, uint64_t *start, uint64_t *end, bool *ranged) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; const struct uh_str hdr = conn->get_header(conn, "Range"); @@ -202,7 +202,7 @@ static bool file_range(struct uh_connection *conn, size_t size, size_t *start, s break; } - *start = strtoul(buf, NULL, 0); + *start = strtoull(buf, NULL, 0); i = 0; @@ -213,7 +213,7 @@ static bool file_range(struct uh_connection *conn, size_t size, size_t *start, s } buf[i] = '\0'; - *end = strtoul(buf, NULL, 0); + *end = strtoull(buf, NULL, 0); if (*start >= size) goto err; @@ -237,7 +237,7 @@ err: conn->send_status_line(conn, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Content-Type: text/plain\r\nConnection: close\r\n"); conn->printf(conn, "Content-Length: %d\r\n", content_length); - conn->printf(conn, "Content-Range: bytes */%zu\r\n", size); + conn->printf(conn, "Content-Range: bytes */%" PRIu64 "\r\n", size); conn->send(conn, "\r\n", 2); @@ -258,8 +258,8 @@ void serve_file(struct uh_connection *conn) const char *docroot = srv->docroot; const char *index_page = srv->index_page; static char fullpath[PATH_MAX]; + uint64_t start, end; int docroot_len; - size_t start, end; const char *mime; struct stat st; bool ranged; @@ -334,10 +334,10 @@ void serve_file(struct uh_connection *conn) mime = file_mime_lookup(fullpath); conn->printf(conn, "Content-Type: %s\r\n", mime); - conn->printf(conn, "Content-Length: %zu\r\n", end - start + 1); + conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", end - start + 1); if (ranged) - conn->printf(conn, "Content-Range: bytes %zu-%zu/%" PRIu64 "\r\n", start, end, (uint64_t)st.st_size); + conn->printf(conn, "Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 "\r\n", start, end, (uint64_t)st.st_size); else file_if_gzip(conn, fullpath, mime); diff --git a/src/uhttpd.h b/src/uhttpd.h index 32de6e1..87c977c 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -57,7 +57,7 @@ struct uh_connection { */ void (*done)(struct uh_connection *conn); void (*send)(struct uh_connection *conn, const void *data, ssize_t len); - void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, ssize_t len); + void (*send_file)(struct uh_connection *conn, const char *path, off_t offset, int64_t len); void (*printf)(struct uh_connection *conn, const char *format, ...) __attribute__((format(printf, 2, 3))); void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); From fb5c0337a80b1e8a10b48789087998447e89f34f Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 20 Jan 2021 22:34:15 +0800 Subject: [PATCH 42/55] Bump CMake minimum to 2.8.12 Fix: CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required) Signed-off-by: Jianhui Zhao --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36f5df5..632af74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) project(libuhttpd C) From b2e52fe1af41fa157f3b400db1e7e3c8c8b6860a Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 20 Jan 2021 22:36:48 +0800 Subject: [PATCH 43/55] Use github actions Signed-off-by: Jianhui Zhao --- .github/workflows/build.yml | 12 ++++++++++++ .travis.yml | 13 ------------- README.md | 5 ++--- README_ZH.md | 5 ++--- 4 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100755 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..47a1010 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,12 @@ +name: build +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: sudo apt install -y libev-dev libssl-dev + - uses: actions/checkout@v2 + with: + submodules: true + - run: cmake . + - run: make diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index c81f9e7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: c - -os: - - linux - -before_install: - - sudo apt-get -qq update - - sudo apt-get install -y libssl-dev libev-dev - -script: - - mkdir build && cd build - - cmake .. - - make && sudo make install diff --git a/README.md b/README.md index 4812383..2634e63 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,13 @@ [6]: https://github.com/zhaojh329/libuhttpd/issues/new [7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases -[9]: https://travis-ci.org/zhaojh329/libuhttpd.svg?branch=master -[10]: https://travis-ci.org/zhaojh329/libuhttpd +[9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg [![license][1]][2] [![PRs Welcome][3]][4] [![Issue Welcome][5]][6] [![Release Version][7]][8] -[![Build Status][9]][10] +![Build Status][9] [libev]: http://software.schmorp.de/pkg/libev.html [http-parser]: https://github.com/nodejs/http-parser diff --git a/README_ZH.md b/README_ZH.md index 94fbe0e..a21c3e1 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -8,14 +8,13 @@ [6]: https://github.com/zhaojh329/libuhttpd/issues/new [7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases -[9]: https://travis-ci.org/zhaojh329/libuhttpd.svg?branch=master -[10]: https://travis-ci.org/zhaojh329/libuhttpd +[9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg [![license][1]][2] [![PRs Welcome][3]][4] [![Issue Welcome][5]][6] [![Release Version][7]][8] -[![Build Status][9]][10] +![Build Status][9] [libev]: http://software.schmorp.de/pkg/libev.html [http-parser]: https://github.com/nodejs/http-parser From 2f951e5166226f9f53f07f8cfb0802533a8aa873 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Thu, 21 Jan 2021 16:02:22 +0800 Subject: [PATCH 44/55] send_head: use int64_t for content_length Signed-off-by: Jianhui Zhao --- src/connection.c | 4 ++-- src/uhttpd.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connection.c b/src/connection.c index 37fb8fd..a23a126 100644 --- a/src/connection.c +++ b/src/connection.c @@ -170,7 +170,7 @@ static void conn_send_status_line(struct uh_connection *conn, int code, const ch conn_send(conn, extra_headers, strlen(extra_headers)); } -static void conn_send_head(struct uh_connection *conn, int code, int content_length, const char *extra_headers) +static void conn_send_head(struct uh_connection *conn, int code, int64_t content_length, const char *extra_headers) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; @@ -178,7 +178,7 @@ static void conn_send_head(struct uh_connection *conn, int code, int content_len if (content_length < 0) conn_printf(conn, "%s", "Transfer-Encoding: chunked\r\n"); else - conn_printf(conn, "Content-Length: %d\r\n", content_length); + conn_printf(conn, "Content-Length: %" PRIu64 "\r\n", content_length); if (!http_should_keep_alive(&conni->parser)) conn_printf(conn, "%s", "Connection: close\r\n"); diff --git a/src/uhttpd.h b/src/uhttpd.h index 87c977c..2d3d3cf 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -61,7 +61,7 @@ struct uh_connection { void (*printf)(struct uh_connection *conn, const char *format, ...) __attribute__((format(printf, 2, 3))); void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg); void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers); - void (*send_head)(struct uh_connection *conn, int code, int content_length, const char *extra_headers); + void (*send_head)(struct uh_connection *conn, int code, int64_t content_length, const char *extra_headers); void (*error)(struct uh_connection *conn, int code, const char *reason); void (*redirect)(struct uh_connection *conn, int code, const char *location, ...) __attribute__((format(printf, 3, 4))); void (*serve_file)(struct uh_connection *conn); From 6e84f2a73896d222d26b7e3c1313d7ef4f8357a0 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 27 Jan 2021 13:18:27 +0800 Subject: [PATCH 45/55] connection: add api: close close low level TCP connection Signed-off-by: Jianhui Zhao --- src/connection.c | 30 ++++++++++++++++++++++++++++++ src/connection.h | 3 ++- src/uhttpd.h | 1 + 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/connection.c b/src/connection.c index a23a126..ea76315 100644 --- a/src/connection.c +++ b/src/connection.c @@ -40,6 +40,9 @@ static void conn_done(struct uh_connection *conn) struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct ev_loop *loop = conni->srv->loop; + if (conni->flags & CONN_F_CLOSED) + return; + if (!http_should_keep_alive(&conni->parser)) conni->flags |= CONN_F_SEND_AND_CLOSE; @@ -58,6 +61,9 @@ static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + if (conni->flags & CONN_F_CLOSED) + return; + buffer_put_data(&conni->wb, data, len); ev_io_start(conni->srv->loop, &conni->iow); } @@ -69,6 +75,9 @@ static void conn_send_file(struct uh_connection *conn, const char *path, off_t o struct stat st; int fd; + if (conni->flags & CONN_F_CLOSED) + return; + if (len == 0) return; @@ -109,6 +118,9 @@ static void conn_printf(struct uh_connection *conn, const char *format, ...) struct buffer *wb = &conni->wb; va_list arg; + if (conni->flags & CONN_F_CLOSED) + return; + va_start(arg, format); buffer_put_vprintf(wb, format, arg); va_end(arg); @@ -120,6 +132,9 @@ static void conn_vprintf(struct uh_connection *conn, const char *format, va_list { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + if (conni->flags & CONN_F_CLOSED) + return; + buffer_put_vprintf(&conni->wb, format, arg); ev_io_start(conni->srv->loop, &conni->iow); } @@ -334,6 +349,15 @@ static struct uh_str conn_extract_body(struct uh_connection *conn) return body; } +static void conn_close(struct uh_connection *conn) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + + http_parser_pause(&conni->parser, true); + + conni->flags |= CONN_F_CLOSED; +} + static int on_message_begin_cb(struct http_parser *parser) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; @@ -548,6 +572,10 @@ static void conn_http_parse(struct uh_connection_internal *conn) return; nparsed = http_parser_execute(parser, &settings, (const char *)data, length); + if (conn->flags & CONN_F_CLOSED) { + conn_free(conn); + return; + } switch (parser->http_errno) { case HPE_PAUSED: @@ -762,6 +790,8 @@ static void conn_init_cb(struct uh_connection *conn) conn->get_content_length = conn_get_content_length; conn->get_body = conn_get_body; conn->extract_body = conn_extract_body; + + conn->close = conn_close; } struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, int sock, struct sockaddr *addr) diff --git a/src/connection.h b/src/connection.h index cfe8f26..aaead79 100644 --- a/src/connection.h +++ b/src/connection.h @@ -34,7 +34,8 @@ #define UHTTPD_MAX_HEADER_NUM 50 #define CONN_F_SEND_AND_CLOSE (1 << 0) /* Push remaining data and close */ -#define CONN_F_SSL_HANDSHAKE_DONE (1 << 1) /* SSL hanshake has completed */ +#define CONN_F_CLOSED (1 << 1) /* closed */ +#define CONN_F_SSL_HANDSHAKE_DONE (1 << 2) /* SSL hanshake has completed */ struct uh_server_internal; diff --git a/src/uhttpd.h b/src/uhttpd.h index 2d3d3cf..49acfd9 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -79,6 +79,7 @@ struct uh_connection { struct uh_str (*get_body)(struct uh_connection *conn); /* The remain body data will be discurd after this function called */ struct uh_str (*extract_body)(struct uh_connection *conn); + void (*close)(struct uh_connection *conn); /* close low level TCP connection */ void *userdata; }; From 930f47282b27682aeea28395e471e5db97e1a4ca Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Wed, 27 Jan 2021 13:21:02 +0800 Subject: [PATCH 46/55] Bump version 3.9.0 Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- src/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2634e63..6b45248 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.9.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/README_ZH.md b/README_ZH.md index a21c3e1..4acd9ff 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.8.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.9.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55174ea..5aea7dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=6 # The version number. set(UHTTPD_VERSION_MAJOR 3) -set(UHTTPD_VERSION_MINOR 8) +set(UHTTPD_VERSION_MINOR 9) set(UHTTPD_VERSION_PATCH 0) # Check the third party Libraries From f34f8cbb8ad0a0258292a7e3fa16c2b90751b847 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 19 Feb 2021 15:06:33 +0800 Subject: [PATCH 47/55] Improve gen_cert.sh Signed-off-by: Jianhui Zhao --- gen_cert.sh | 2 +- openssl.cnf | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 openssl.cnf diff --git a/gen_cert.sh b/gen_cert.sh index 9bea3a1..5eac75b 100755 --- a/gen_cert.sh +++ b/gen_cert.sh @@ -1,3 +1,3 @@ #!/bin/sh -openssl req -x509 -newkey rsa:2048 -nodes -keyout server-key.pem -out server-cert.pem -config openssl.cnf +openssl req -x509 -newkey rsa:2048 -nodes -keyout server-key.pem -out server-cert.pem -subj "/C=CZ/O=Acme Inc./OU=ACME/CN=ACME-DEV-123" diff --git a/openssl.cnf b/openssl.cnf deleted file mode 100644 index db0efd1..0000000 --- a/openssl.cnf +++ /dev/null @@ -1,5 +0,0 @@ -[ req ] -distinguished_name = req_distinguished_name -prompt = no -[ req_distinguished_name ] -CN = DONT USE - test cert for libuhttp From 175f7c8f55052bd20c91603be8547d545675bdf3 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 20 Feb 2021 10:48:05 +0800 Subject: [PATCH 48/55] ci: build: test all supported ssl libraries Signed-off-by: Jianhui Zhao --- .github/workflows/build.yml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47a1010..5ba367d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,11 +2,31 @@ name: build on: push jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 + strategy: + matrix: + include: + - ssl: none + name: none + - ssl: openssl + name: OPENSSL + pkg: libssl-dev + - ssl: mbedtls + name: MBEDTLS + pkg: libmbedtls-dev + - ssl: wolfssl + name: WOLFSSL + pkg: libwolfssl-dev steps: - - run: sudo apt install -y libev-dev libssl-dev - uses: actions/checkout@v2 with: submodules: true - - run: cmake . - - run: make + - name: build + env: + name: ${{ matrix.name }} + pkg: ${{ matrix.pkg }} + run: | + sudo apt install -y libev-dev + [ -n "$pkg" ] && sudo apt install -y $pkg + [ "$name" = "none" ] && cmake . -DUHTTPD_SSL_SUPPORT=OFF || cmake . -DUHTTPD_USE_$name=ON + make From 9166d3ab807a55ef445172b52c15d5b78b1d7c7f Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Fri, 26 Feb 2021 13:41:35 +0800 Subject: [PATCH 49/55] feat: multiple listen Signed-off-by: Jianhui Zhao --- example/multi_process_server.c | 32 ++- example/multi_process_server_reuseport.c | 34 ++-- example/simple_server.c | 34 ++-- gen_cert.sh | 2 +- src/connection.c | 5 +- src/connection.h | 2 +- src/uhttpd.c | 247 +++++++++++++++-------- src/uhttpd.h | 14 +- src/uhttpd_internal.h | 17 +- 9 files changed, 232 insertions(+), 155 deletions(-) diff --git a/example/multi_process_server.c b/example/multi_process_server.c index efc76e8..9d8319b 100644 --- a/example/multi_process_server.c +++ b/example/multi_process_server.c @@ -59,9 +59,8 @@ static void usage(const char *prog) fprintf(stderr, "Usage: %s [option]\n" " -h docroot # Document root, default is .\n" " -i index_page # Index page, default is index.html\n" - " -a addr # Default addr is localhost\n" - " -p port # Default port is 8080\n" - " -s # SSl on\n" + " -a addr # address to listen\n" + " -s addr # address to listen with ssl\n" " -P # plugin path\n" " -w # worker process number, default is equal to available CPUs\n" " -v # verbose\n", prog); @@ -75,16 +74,17 @@ int main(int argc, char **argv) struct uh_server *srv = NULL; const char *plugin_path = NULL; bool verbose = false; - bool ssl = false; const char *docroot = "."; const char *index_page = "index.html"; - const char *addr = "localhost"; pid_t workers[MAX_WORKER] = {}; int nworker = get_nprocs(); - int port = 8080; int opt, i; - while ((opt = getopt(argc, argv, "h:i:a:p:sfP:w:v")) != -1) { + srv = uh_server_new(loop); + if (!srv) + return -1; + + while ((opt = getopt(argc, argv, "h:i:a:s:P:w:v")) != -1) { switch (opt) { case 'h': docroot = optarg; @@ -93,13 +93,12 @@ int main(int argc, char **argv) index_page = optarg; break; case 'a': - addr = optarg; + if (srv->listen(srv, optarg, false) < 1) + goto err; break; - case 'p': - port = atoi(optarg); - break; - case 's': - ssl = true; + case 's': + if (srv->listen(srv, optarg, true) < 1) + goto err; break; case 'P': plugin_path = optarg; @@ -124,13 +123,8 @@ int main(int argc, char **argv) signal(SIGPIPE, SIG_IGN); - srv = uh_server_new(loop, addr, port); - if (!srv) - return -1; - #if UHTTPD_SSL_SUPPORT - if (ssl && srv->ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) - goto err; + srv->ssl_init(srv, "cert.pem", "key.pem"); #endif srv->set_docroot(srv, docroot); diff --git a/example/multi_process_server_reuseport.c b/example/multi_process_server_reuseport.c index ad95f7e..ca360f8 100644 --- a/example/multi_process_server_reuseport.c +++ b/example/multi_process_server_reuseport.c @@ -58,28 +58,37 @@ static void usage(const char *prog) fprintf(stderr, "Usage: %s [option]\n" " -h docroot # Document root, default is .\n" " -i index_page # Index page, default is index.html\n" - " -a addr # Default addr is localhost\n" - " -p port # Default port is 8080\n" - " -s # SSl on\n" + " -a addr # address to listen\n" + " -s addr # address to listen with ssl\n" " -P # plugin path\n" " -w # worker process number, default is equal to available CPUs\n" " -v # verbose\n", prog); exit(1); } -static void start_server(const char *addr, int port, const char *docroot, const char *index_page, const char *plugin, bool ssl) +static void start_server(const char *addr, const char *addrs, const char *docroot, const char *index_page, const char *plugin, bool ssl) { struct ev_loop *loop = ev_loop_new(0); struct uh_server *srv = NULL; signal(SIGPIPE, SIG_IGN); - srv = uh_server_new(loop, addr, port); + srv = uh_server_new(loop); if (!srv) return; + if (addr) { + if (srv->listen(srv, addrs, false) < 0) + return; + } else if (addrs) { + if (srv->listen(srv, addrs, true) < 0) + return; + } else { + return; + } + #if UHTTPD_SSL_SUPPORT - if (ssl && srv->ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) + if (ssl && srv->ssl_init(srv, "cert.pem", "key.pem") < 0) return; #endif @@ -105,13 +114,13 @@ int main(int argc, char **argv) bool ssl = false; const char *docroot = "."; const char *index_page = "index.html"; - const char *addr = "localhost"; + const char *addr = NULL; + const char *addrs = NULL; pid_t workers[MAX_WORKER] = {}; int nworker = get_nprocs(); - int port = 8080; int opt, i; - while ((opt = getopt(argc, argv, "h:i:a:p:sP:w:v")) != -1) { + while ((opt = getopt(argc, argv, "h:i:a:s:P:w:v")) != -1) { switch (opt) { case 'h': docroot = optarg; @@ -122,11 +131,8 @@ int main(int argc, char **argv) case 'a': addr = optarg; break; - case 'p': - port = atoi(optarg); - break; case 's': - ssl = true; + addrs = optarg; break; case 'P': plugin_path = optarg; @@ -164,7 +170,7 @@ int main(int argc, char **argv) if (pid == 0) { prctl(PR_SET_PDEATHSIG, SIGKILL); - start_server(addr, port, docroot, index_page, plugin_path, ssl); + start_server(addr, addrs, docroot, index_page, plugin_path, ssl); return 0; } diff --git a/example/simple_server.c b/example/simple_server.c index fd3fe2a..d675006 100644 --- a/example/simple_server.c +++ b/example/simple_server.c @@ -48,9 +48,8 @@ static void usage(const char *prog) fprintf(stderr, "Usage: %s [option]\n" " -h docroot # Document root, default is .\n" " -i index_page # Index page, default is index.html\n" - " -a addr # Default addr is localhost\n" - " -p port # Default port is 8080\n" - " -s # SSl on\n" + " -a addr # address to listen\n" + " -s addr # address to listen with ssl\n" " -P # plugin path\n" " -v # verbose\n", prog); exit(1); @@ -63,14 +62,15 @@ int main(int argc, char **argv) struct uh_server *srv = NULL; const char *plugin_path = NULL; bool verbose = false; - bool ssl = false; const char *docroot = "."; const char *index_page = "index.html"; - const char *addr = "localhost"; - int port = 8080; int opt; - while ((opt = getopt(argc, argv, "h:i:a:p:sP:v")) != -1) { + srv = uh_server_new(loop); + if (!srv) + return -1; + + while ((opt = getopt(argc, argv, "h:i:a:s:P:v")) != -1) { switch (opt) { case 'h': docroot = optarg; @@ -79,13 +79,12 @@ int main(int argc, char **argv) index_page = optarg; break; case 'a': - addr = optarg; + if (srv->listen(srv, optarg, false) < 1) + goto err; break; - case 'p': - port = atoi(optarg); - break; - case 's': - ssl = true; + case 's': + if (srv->listen(srv, optarg, true) < 1) + goto err; break; case 'P': plugin_path = optarg; @@ -93,7 +92,7 @@ int main(int argc, char **argv) case 'v': verbose = true; break; - default: /* '?' */ + default: usage(argv[0]); } } @@ -105,13 +104,8 @@ int main(int argc, char **argv) signal(SIGPIPE, SIG_IGN); - srv = uh_server_new(loop, addr, port); - if (!srv) - return -1; - #if UHTTPD_SSL_SUPPORT - if (ssl && srv->ssl_init(srv, "server-cert.pem", "server-key.pem") < 0) - goto err; + srv->ssl_init(srv, "cert.pem", "key.pem"); #endif srv->set_docroot(srv, docroot); diff --git a/gen_cert.sh b/gen_cert.sh index 5eac75b..2d3d408 100755 --- a/gen_cert.sh +++ b/gen_cert.sh @@ -1,3 +1,3 @@ #!/bin/sh -openssl req -x509 -newkey rsa:2048 -nodes -keyout server-key.pem -out server-cert.pem -subj "/C=CZ/O=Acme Inc./OU=ACME/CN=ACME-DEV-123" +openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -subj "/C=CZ/O=Acme Inc./OU=ACME/CN=ACME-DEV-123" diff --git a/src/connection.c b/src/connection.c index ea76315..58870e4 100644 --- a/src/connection.c +++ b/src/connection.c @@ -794,8 +794,9 @@ static void conn_init_cb(struct uh_connection *conn) conn->close = conn_close; } -struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, int sock, struct sockaddr *addr) +struct uh_connection_internal *uh_new_connection(struct uh_listener *l, int sock, struct sockaddr *addr) { + struct uh_server_internal *srv = l->srv; struct uh_connection_internal *conn; conn = calloc(1, sizeof(struct uh_connection_internal)); @@ -822,7 +823,7 @@ struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, ev_timer_start(srv->loop, &conn->timer); #if UHTTPD_SSL_SUPPORT - if (srv->ssl_ctx) + if (l->ssl) conn->ssl = uh_ssl_new(srv->ssl_ctx, sock); #endif diff --git a/src/connection.h b/src/connection.h index aaead79..0b249e8 100644 --- a/src/connection.h +++ b/src/connection.h @@ -97,7 +97,7 @@ struct uh_connection_internal { void (*handler)(struct uh_connection *conn, int event); }; -struct uh_connection_internal *uh_new_connection(struct uh_server_internal *srv, int sock, struct sockaddr *addr); +struct uh_connection_internal *uh_new_connection(struct uh_listener *l, int sock, struct sockaddr *addr); void conn_free(struct uh_connection_internal *conn); diff --git a/src/uhttpd.c b/src/uhttpd.c index cda979f..33a6fee 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -42,15 +42,11 @@ static void uh_server_free(struct uh_server *srv) struct uh_server_internal *srvi = (struct uh_server_internal *)srv; struct uh_connection_internal *conn = srvi->conns; struct uh_path_handler *h = srvi->handlers; + struct uh_listener *l = srvi->listeners; #ifdef HAVE_DLOPEN struct uh_plugin *p = srvi->plugins; #endif - ev_io_stop(srvi->loop, &srvi->ior); - - if (srvi->sock > 0) - close(srvi->sock); - if (srvi->docroot) free(srvi->docroot); @@ -69,6 +65,18 @@ static void uh_server_free(struct uh_server *srv) free(temp); } + while (l) { + struct uh_listener *temp = l; + + ev_io_stop(srvi->loop, &l->ior); + + if (l->sock > 0) + close(l->sock); + + l = l->next; + free(temp); + } + #ifdef HAVE_DLOPEN while (p) { struct uh_plugin *temp = p; @@ -85,7 +93,8 @@ static void uh_server_free(struct uh_server *srv) static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - struct uh_server_internal *srv = container_of(w, struct uh_server_internal, ior); + struct uh_listener *l = container_of(w, struct uh_listener, ior); + struct uh_server_internal *srv = l->srv; struct uh_connection_internal *conn; union { struct sockaddr sa; @@ -97,7 +106,7 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) int port; int sock; - sock = accept4(srv->sock, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); + sock = accept4(l->sock, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); if (sock < 0) { if (errno != EAGAIN) uh_log_err("accept: %s\n", strerror(errno)); @@ -109,7 +118,21 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) uh_log_debug("New Connection from: %s %d\n", addr_str, port); } - conn = uh_new_connection(srv, sock, &addr.sa); + if (l->ssl) { +#if UHTTPD_SSL_SUPPORT + if (!srv->ssl_ctx) { + uh_log_err("SSL not initialized\n"); + close(sock); + return; + } +#else + close(sock); + uh_log_err("SSL not enabled when build\n"); + return; +#endif + } + + conn = uh_new_connection(l, sock, &addr.sa); if (!conn) return; @@ -123,7 +146,7 @@ static void uh_accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) srv->conns = conn; } -struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port) +struct uh_server *uh_server_new(struct ev_loop *loop) { struct uh_server *srv; @@ -133,10 +156,7 @@ struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port return NULL; } - if (uh_server_init(srv, loop, host, port) < 0) { - free(srv); - return NULL; - } + uh_server_init(srv, loop); return srv; } @@ -279,93 +299,155 @@ static struct ev_loop *uh_get_loop(struct uh_server *srv) return srvi->loop; } -int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port) +static int parse_address(const char *addr, char **host, char **port) +{ + static char buf[256]; + char *s; + int l; + + strcpy(buf, addr); + + *host = NULL; + *port = buf; + + s = strrchr(buf, ':'); + if (!s) + return -1; + + *host = buf; + *port = s + 1; + *s = 0; + + if (*host && **host == '[') { + l = strlen(*host); + if (l >= 2) { + (*host)[l - 1] = 0; + (*host)++; + } + } + + if ((*host)[0] == '\0') + *host = "0"; + + return 0; +} + +static int uh_server_listen(struct uh_server *srv, const char *addr, bool ssl) { struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - union { - struct sockaddr sa; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - } addr; + struct uh_listener *l; + char *host, *port; + struct addrinfo *addrs = NULL, *p = NULL; + static struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE, + }; char addr_str[INET6_ADDRSTRLEN]; - socklen_t addrlen; - int sock = -1; + int bound = 0; int on = 1; + int status; + int sock; - if (!host || *host == '\0') { - addr.sin.sin_family = AF_INET; - addr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - } - - if (inet_pton(AF_INET, host, &addr.sin.sin_addr) == 1) { - addr.sa.sa_family = AF_INET; - } else if (inet_pton(AF_INET6, host, &addr.sin6.sin6_addr) == 1) { - addr.sa.sa_family = AF_INET6; - } else { - static struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE - }; - struct addrinfo *ais; - int status; - - status = getaddrinfo(host, NULL, &hints, &ais); - if (status != 0) { - uh_log_err("getaddrinfo(): %s\n", gai_strerror(status)); - return -1; - } - - memcpy(&addr, ais->ai_addr, ais->ai_addrlen); - freeaddrinfo(ais); - } - - if (addr.sa.sa_family == AF_INET) { - addr.sin.sin_port = ntohs(port); - addrlen = sizeof(addr.sin); - inet_ntop(AF_INET, &addr.sin.sin_addr, addr_str, sizeof(addr_str)); - } else { - addr.sin6.sin6_port = ntohs(port); - addrlen = sizeof(addr.sin6); - inet_ntop(AF_INET6, &addr.sin6.sin6_addr, addr_str, sizeof(addr_str)); - } - - sock = socket(addr.sa.sa_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (sock < 0) { - uh_log_err("socket: %s\n", strerror(errno)); + if (parse_address(addr, &host, &port) < 0) { + uh_log_err("invalid address\n"); return -1; } - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { - uh_log_err("setsockopt: %s\n", strerror(errno)); - goto err; + status = getaddrinfo(host, port, &hints, &addrs); + if (status != 0) { + uh_log_err("getaddrinfo(): %s\n", gai_strerror(status)); + return -1; } - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); + /* try to bind a new socket to each found address */ + for (p = addrs; p; p = p->ai_next) { + sock = socket(p->ai_family, p->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, p->ai_protocol); + if (sock < 0) { + uh_log_err("socket: %s\n", strerror(errno)); + continue; + } - if (bind(sock, &addr.sa, addrlen) < 0) { - uh_log_err("bind: %s\n", strerror(errno)); - goto err; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0) { + uh_log_err("setsockopt: %s\n", strerror(errno)); + goto err; + } + + /* required to get parallel v4 + v6 working */ + if (p->ai_family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(int)) < 0) { + uh_log_err("setsockopt: %s\n", strerror(errno)); + goto err; + } + + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int)); + + if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) { + uh_log_err("bind: %s\n", strerror(errno)); + goto err; + } + + if (listen(sock, SOMAXCONN) < 0) { + uh_log_err("bind: %s\n", strerror(errno)); + goto err; + } + + l = calloc(1, sizeof(struct uh_listener)); + if (!l) { + uh_log_err("calloc: %s\n", strerror(errno)); + goto err; + } + + l->sock = sock; + l->ssl = ssl; + l->srv = srvi; + + ev_io_init(&l->ior, uh_accept_cb, sock, EV_READ); + ev_io_start(srvi->loop, &l->ior); + + if (!srvi->listeners) { + srvi->listeners = l; + } else { + l->next = srvi->listeners; + srvi->listeners = l; + } + + if (p->ai_family == AF_INET) { + struct sockaddr_in *ina = (struct sockaddr_in *)p->ai_addr; + inet_ntop(p->ai_family, &ina->sin_addr, addr_str, sizeof(addr_str)); + uh_log_debug("Listen on: %s:%d with ssl %s\n", addr_str, ntohs(ina->sin_port), ssl ? "on" : "off"); + } else { + struct sockaddr_in6 *in6a = (struct sockaddr_in6 *)p->ai_addr; + inet_ntop(p->ai_family, &in6a->sin6_addr, addr_str, sizeof(addr_str)); + uh_log_debug("Listen on: [%s]:%d with ssl %s\n", addr_str, ntohs(in6a->sin6_port), ssl ? "on" : "off"); + } + + bound++; + + continue; + +err: + if (sock > -1) + close(sock); } - if (listen(sock, SOMAXCONN) < 0) { - uh_log_err("bind: %s\n", strerror(errno)); - goto err; - } + freeaddrinfo(addrs); - if (uh_log_get_threshold() == LOG_DEBUG) { - saddr2str(&addr.sa, addr_str, sizeof(addr_str), &port); - uh_log_debug("Listen on: %s %d\n", addr_str, port); - } + return bound; +} + +void uh_server_init(struct uh_server *srv, struct ev_loop *loop) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; memset(srvi, 0, sizeof(struct uh_server_internal)); srvi->loop = loop ? loop : EV_DEFAULT; - srvi->sock = sock; srv->get_loop = uh_get_loop; srv->free = uh_server_free; + srv->listen = uh_server_listen; + #if UHTTPD_SSL_SUPPORT srv->ssl_init = uh_server_ssl_init; #endif @@ -378,13 +460,4 @@ int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host srv->set_docroot = uh_set_docroot; srv->set_index_page = uh_set_index_page; - - ev_io_init(&srvi->ior, uh_accept_cb, sock, EV_READ); - ev_io_start(srvi->loop, &srvi->ior); - - return 0; - -err: - close(sock); - return -1; } diff --git a/src/uhttpd.h b/src/uhttpd.h index 49acfd9..bcc77fa 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -89,6 +89,14 @@ typedef void (*uh_path_handler_prototype)(struct uh_connection *conn, int event) struct uh_server { struct ev_loop *(*get_loop)(struct uh_server *srv); void (*free)(struct uh_server *srv); + /* + ** listen an address, multiple call allowed + ** returns the number of successful listen + ** + ** :80 0:80 0.0.0.0:80 [::]:80 + ** localhost:80 [::1]:80 + */ + int (*listen)(struct uh_server *srv, const char *addr, bool ssl); #if UHTTPD_SSL_SUPPORT int (*ssl_init)(struct uh_server *srv, const char *cert, const char *key); #endif @@ -120,11 +128,9 @@ struct uh_path_handler { /* * uh_server_new - creat an uh_server struct and init it * @loop: If NULL will use EV_DEFAULT - * @host: If NULL will listen on "0.0.0.0" - * @port: port to listen on */ -struct uh_server *uh_server_new(struct ev_loop *loop, const char *host, int port); +struct uh_server *uh_server_new(struct ev_loop *loop); -int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port); +void uh_server_init(struct uh_server *srv, struct ev_loop *loop); #endif diff --git a/src/uhttpd_internal.h b/src/uhttpd_internal.h index 4ce99a9..4e9f80d 100644 --- a/src/uhttpd_internal.h +++ b/src/uhttpd_internal.h @@ -29,15 +29,23 @@ #include "uhttpd.h" +struct uh_server_internal; struct uh_connection_internal; +struct uh_listener { + int sock; + bool ssl; + struct ev_io ior; + struct uh_server_internal *srv; + struct uh_listener *next; +}; + struct uh_server_internal { struct uh_server com; - int sock; char *docroot; char *index_page; struct ev_loop *loop; - struct ev_io ior; + struct uh_listener *listeners; struct uh_connection_internal *conns; void (*conn_closed_cb)(struct uh_connection *conn); void (*default_handler)(struct uh_connection *conn, int event); @@ -48,9 +56,4 @@ struct uh_server_internal { struct uh_path_handler *handlers; }; -struct worker { - struct ev_child w; - int i; -}; - #endif \ No newline at end of file From 93b0e7e3c5ad83e239af53b6a9099ea5b7a53def Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 27 Feb 2021 00:29:53 +0800 Subject: [PATCH 50/55] ci: auto release Signed-off-by: Jianhui Zhao --- .github/workflows/release.yml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fd0f762 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: release +on: + push: + tags: + - 'v*' +jobs: + release: + runs-on: ubuntu-20.04 + steps: + - id: changelog + uses: zhaojh329/auto-changelog@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + - id: release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: true + body: ${{steps.changelog.outputs.changelog}} + - uses: actions/checkout@v2 + with: + submodules: true + - id: get-version + uses: battila7/get-version-action@v2 + - id: release-asset + run: | + version=${{ steps.get-version.outputs.version-without-v }} + cd .. + cp -r libuhttpd libuhttpd-$version + rm -rf libuhttpd-$version/.git* libuhttpd-$version/src/buffer/.git* libuhttpd-$version/src/http-parser/{.git*,.mailmap,.travis.yml} + tar zcfv libuhttpd-$version.tar.gz libuhttpd-$version + echo "::set-output name=asset-path::../libuhttpd-$version.tar.gz" + echo "::set-output name=asset-name::libuhttpd-$version.tar.gz" + - id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ${{ steps.release-asset.outputs.asset-path }} + asset_name: ${{ steps.release-asset.outputs.asset-name }} + asset_content_type: application/gzip From e281f44bde2a258169118fb25df85b45810f68f1 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Sat, 27 Feb 2021 00:30:50 +0800 Subject: [PATCH 51/55] Bump version 3.10.0 Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- src/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6b45248..a28cf09 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.9.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.10.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/README_ZH.md b/README_ZH.md index 4acd9ff..e2c362f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.9.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.10.0-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5aea7dc..2e87ade 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=6 # The version number. set(UHTTPD_VERSION_MAJOR 3) -set(UHTTPD_VERSION_MINOR 9) +set(UHTTPD_VERSION_MINOR 10) set(UHTTPD_VERSION_PATCH 0) # Check the third party Libraries From c35cee771c2d578ed213b347c56da843b1693740 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Mon, 1 Mar 2021 17:10:09 +0800 Subject: [PATCH 52/55] fix: Misuse of sendfile when SSL is enabled Signed-off-by: Jianhui Zhao --- src/connection.c | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/connection.c b/src/connection.c index 58870e4..937f28e 100644 --- a/src/connection.c +++ b/src/connection.c @@ -107,6 +107,10 @@ static void conn_send_file(struct uh_connection *conn, const char *path, off_t o } else { conni->file.size = len; conni->file.fd = fd; +#if UHTTPD_SSL_SUPPORT + if (conni->ssl) + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); +#endif } ev_io_start(conni->srv->loop, &conni->iow); @@ -634,22 +638,35 @@ static void conn_write_cb(struct ev_loop *loop, struct ev_io *w, int revents) if (buffer_length(&conn->wb) == 0) { if (conn->file.fd > 0) { - ret = sendfile(w->fd, conn->file.fd, NULL, conn->file.size); - if (ret < 0) { - if (errno != EAGAIN) { - uh_log_err("write error: %s\n", strerror(errno)); - conn_free(conn); +#if UHTTPD_SSL_SUPPORT + if (conn->ssl) { + bool eof; + if (buffer_put_fd(&conn->wb, conn->file.fd, 8192, &eof) < 0 || eof) { + close(conn->file.fd); + conn->file.fd = -1; } return; - } + } else { +#endif + ret = sendfile(w->fd, conn->file.fd, NULL, conn->file.size); + if (ret < 0) { + if (errno != EAGAIN) { + uh_log_err("write error: %s\n", strerror(errno)); + conn_free(conn); + } + return; + } - if (ret < conn->file.size) { - conn->file.size -= ret; - return; - } + if (ret < conn->file.size) { + conn->file.size -= ret; + return; + } - close(conn->file.fd); - conn->file.fd = -1; + close(conn->file.fd); + conn->file.fd = -1; +#if UHTTPD_SSL_SUPPORT + } +#endif } if (conn->flags & CONN_F_SEND_AND_CLOSE) { From 6c7a255c718661eda8fb7d7a20adcb222581d364 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Mon, 1 Mar 2021 17:12:42 +0800 Subject: [PATCH 53/55] Bump version 3.10.1 Signed-off-by: Jianhui Zhao --- README.md | 2 +- README_ZH.md | 2 +- src/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a28cf09..0c85ba7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.10.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.10.1-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/README_ZH.md b/README_ZH.md index e2c362f..100ddce 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,7 +6,7 @@ [4]: https://github.com/zhaojh329/libuhttpd/pulls [5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg?style=plastic [6]: https://github.com/zhaojh329/libuhttpd/issues/new -[7]: https://img.shields.io/badge/release-3.10.0-blue.svg?style=plastic +[7]: https://img.shields.io/badge/release-3.10.1-blue.svg?style=plastic [8]: https://github.com/zhaojh329/libuhttpd/releases [9]: https://github.com/zhaojh329/libuhttpd/workflows/build/badge.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e87ade..de7d41a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=6 # The version number. set(UHTTPD_VERSION_MAJOR 3) set(UHTTPD_VERSION_MINOR 10) -set(UHTTPD_VERSION_PATCH 0) +set(UHTTPD_VERSION_PATCH 1) # Check the third party Libraries find_package(Libev REQUIRED) From 83f888096c419297f36d8bf2d2c8a5664e1c5256 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 2 Mar 2021 20:51:37 +0800 Subject: [PATCH 54/55] style: fix Signed-off-by: Jianhui Zhao --- example/handler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/handler.c b/example/handler.c index 1de62bc..be9d230 100644 --- a/example/handler.c +++ b/example/handler.c @@ -50,7 +50,7 @@ void echo_handler(struct uh_connection *conn, int event) conn->send_head(conn, HTTP_STATUS_OK, -1, NULL); conn->chunk_printf(conn, "I'm Libuhttpd: %s\n", UHTTPD_VERSION_STRING); conn->chunk_printf(conn, "Method: %s\n", conn->get_method_str(conn)); - conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len ,path.p); + conn->chunk_printf(conn, "Path: %.*s\n", (int)path.len, path.p); conn->chunk_printf(conn, "Query: %.*s\n", (int)query.len, query.p); conn->chunk_printf(conn, "User-Agent: %.*s\n", (int)ua.len, ua.p); conn->chunk_printf(conn, "Body: %.*s\n", (int)body.len, body.p); From 7f5b41317b2ee45328c9b4a78b9bb76fa95a3843 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Tue, 2 Mar 2021 20:53:05 +0800 Subject: [PATCH 55/55] feat: connection: new api: traverse_headers Signed-off-by: Jianhui Zhao --- src/connection.c | 24 ++++++++++++++++++++++++ src/uhttpd.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/src/connection.c b/src/connection.c index 937f28e..c7df54b 100644 --- a/src/connection.c +++ b/src/connection.c @@ -324,6 +324,29 @@ static struct uh_str conn_get_header(struct uh_connection *conn, const char *nam return value; } +static void conn_traverse_headers(struct uh_connection *conn, + bool (*cb)(const struct uh_str name, const struct uh_str value, void *arg), void *arg) +{ + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; + struct uh_request *req = &conni->req; + struct uh_str name, value; + int i; + + for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) { + if (req->headers[i].field.offset == 0) + return; + + name.p = O2D(conni, req->headers[i].field.offset); + name.len = req->headers[i].field.length; + + value.p = O2D(conni, req->headers[i].value.offset); + value.len = req->headers[i].value.length; + + if (!cb(name, value, arg)) + return; + } +} + static uint64_t conn_get_content_length(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; @@ -804,6 +827,7 @@ static void conn_init_cb(struct uh_connection *conn) conn->get_path = conn_get_path; conn->get_query = conn_get_query; conn->get_header = conn_get_header; + conn->traverse_headers = conn_traverse_headers; conn->get_content_length = conn_get_content_length; conn->get_body = conn_get_body; conn->extract_body = conn_extract_body; diff --git a/src/uhttpd.h b/src/uhttpd.h index bcc77fa..6a758b0 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -75,6 +75,8 @@ struct uh_connection { struct uh_str (*get_path)(struct uh_connection *conn); struct uh_str (*get_query)(struct uh_connection *conn); struct uh_str (*get_header)(struct uh_connection *conn, const char *name); + void (*traverse_headers)(struct uh_connection *conn, + bool (*cb)(const struct uh_str name, const struct uh_str value, void *arg), void *arg); uint64_t (*get_content_length)(struct uh_connection *conn); struct uh_str (*get_body)(struct uh_connection *conn); /* The remain body data will be discurd after this function called */