diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5ba367d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: build +on: push +jobs: + build: + 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: + - uses: actions/checkout@v2 + with: + submodules: true + - 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 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 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 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/CMakeLists.txt b/CMakeLists.txt index d399caa..632af74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,15 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) 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() diff --git a/README.md b/README.md index c047395..335eb01 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,15 @@ [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.10.1-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 @@ -32,19 +31,47 @@ 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 +* 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 # Dependencies -* [libev] -* [http-parser] - A parser for HTTP messages written in C +* [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 * [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 @@ -59,7 +86,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 61a2537..100ddce 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -6,16 +6,15 @@ [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.10.1-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 @@ -29,19 +28,47 @@ * 轻量、全异步 * 使用[libev]作为其事件后端 * 支持HTTPS - OpenSSL, mbedtls 和 CyaSSl(wolfssl) +* 支持 HTTP 流水线 * 支持 IPv6 * 支持插件 * 支持上传大文件 +* 支持 HTTP 范围请求 +* 支持多进程模型 - 和 Nginx 一样的多进程模型 * 可伸缩 - 你可以非常方便的扩展你的应用程序,使之具备HTTP/HTTPS服务 * 代码结构简洁通俗易懂,亦适合学习 # 依赖 -* [libev] -* [http-parser] - 已经集成到源码里面 +* [libev] - 一个全功能和高性能的事件循环库 +* [http-parser] - 一个用 C 语言编写的高性能的 HTTP 消息解析器 * [mbedtls] - 如果你选择mbedtls作为你的SSL后端 * [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 + # 配置 查看支持哪些配置选项 @@ -56,7 +83,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/example.c b/example/example.c deleted file mode 100644 index 2befc6e..0000000 --- a/example/example.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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" - -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) -{ - 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", 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_end(conn); - conn->done(conn); - } else { - conn->serve_file(conn, docroot, index_page); - } -} - -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); - } -} - -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" - " -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); -} - -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 *addr = "localhost"; - int port = 8080; - int opt; - - while ((opt = getopt(argc, argv, "a:p:sfP:v")) != -1) { - switch (opt) { - case 'a': - addr = optarg; - break; - case 'p': - port = atoi(optarg); - break; - case 's': - ssl = true; - break; - case 'f': - serve_file = 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->default_handler = default_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/example/handler.c b/example/handler.c new file mode 100644 index 0000000..be9d230 --- /dev/null +++ b/example/handler.c @@ -0,0 +1,112 @@ +/* + * 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) +{ + if (event == UH_EV_HEAD_COMPLETE) { + 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"); + 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); + 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; + } + + 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; + + 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/multi_process_server.c b/example/multi_process_server.c new file mode 100644 index 0000000..9d8319b --- /dev/null +++ b/example/multi_process_server.c @@ -0,0 +1,170 @@ +/* + * 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 # 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); +} + +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; + const char *docroot = "."; + const char *index_page = "index.html"; + pid_t workers[MAX_WORKER] = {}; + int nworker = get_nprocs(); + int opt, i; + + 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; + break; + case 'i': + index_page = optarg; + break; + case 'a': + if (srv->listen(srv, optarg, false) < 1) + goto err; + break; + case 's': + if (srv->listen(srv, optarg, true) < 1) + goto err; + break; + case 'P': + plugin_path = optarg; + break; + case 'w': + nworker = atoi(optarg); + 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 (nworker < 1) + return 0; + + signal(SIGPIPE, SIG_IGN); + +#if UHTTPD_SSL_SUPPORT + srv->ssl_init(srv, "cert.pem", "key.pem"); +#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); + + 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); + +err: + srv->free(srv); + free(srv); + + ev_loop_destroy(loop); + + return 0; +} diff --git a/example/multi_process_server_reuseport.c b/example/multi_process_server_reuseport.c new file mode 100644 index 0000000..ca360f8 --- /dev/null +++ b/example/multi_process_server_reuseport.c @@ -0,0 +1,189 @@ +/* + * 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 # 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, 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); + 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, "cert.pem", "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 = NULL; + const char *addrs = NULL; + pid_t workers[MAX_WORKER] = {}; + int nworker = get_nprocs(); + int opt, i; + + while ((opt = getopt(argc, argv, "h:i:a:s:P:w:v")) != -1) { + switch (opt) { + case 'h': + docroot = optarg; + break; + case 'i': + index_page = optarg; + break; + case 'a': + addr = optarg; + break; + case 's': + addrs = optarg; + 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, addrs, 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..d675006 --- /dev/null +++ b/example/simple_server.c @@ -0,0 +1,134 @@ +/* + * 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 conn_closed_cb(struct uh_connection *conn) +{ +} + +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 # address to listen\n" + " -s addr # address to listen with ssl\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; + const char *docroot = "."; + const char *index_page = "index.html"; + int opt; + + 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; + break; + case 'i': + index_page = optarg; + break; + case 'a': + if (srv->listen(srv, optarg, false) < 1) + goto err; + break; + case 's': + if (srv->listen(srv, optarg, true) < 1) + goto err; + 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); + +#if UHTTPD_SSL_SUPPORT + srv->ssl_init(srv, "cert.pem", "key.pem"); +#endif + + 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); + + 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/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/gen_cert.sh b/gen_cert.sh index 9bea3a1..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 -config openssl.cnf +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/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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8594fee..de7d41a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,10 @@ -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) -set(UHTTPD_VERSION_MINOR 7) -set(UHTTPD_VERSION_PATCH 0) +set(UHTTPD_VERSION_MINOR 10) +set(UHTTPD_VERSION_PATCH 1) # Check the third party Libraries find_package(Libev REQUIRED) @@ -141,8 +141,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DI install( FILES uhttpd.h - connection.h log.h + utils.h buffer/buffer.h http-parser/http_parser.h ${CMAKE_CURRENT_BINARY_DIR}/config.h diff --git a/src/buffer b/src/buffer index 55e4d3b..1bd1b6d 160000 --- a/src/buffer +++ b/src/buffer @@ -1 +1 @@ -Subproject commit 55e4d3bddc1cd258766448788f8fa3e406e25065 +Subproject commit 1bd1b6d192eda9e5bc24582e7ef3a4a017f9b064 diff --git a/src/connection.c b/src/connection.c index d3298bc..c7df54b 100644 --- a/src/connection.c +++ b/src/connection.c @@ -30,67 +30,117 @@ #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; - buffer_pull(&conn->rb, NULL, buffer_length(&conn->rb)); + if (conni->flags & CONN_F_CLOSED) + return; - 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 */ - conn->handler = NULL; + /* This is needed for a connection requested multiple times on different path */ + 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; + + if (conni->flags & CONN_F_CLOSED) + return; + + 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) +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; struct stat st; + int fd; - conn->file.fd = open(path, O_RDONLY); + if (conni->flags & CONN_F_CLOSED) + return; - fstat(conn->file.fd, &st); + if (len == 0) + return; - conn->file.size = st.st_size; + fd = open(path, O_RDONLY); + if (fd < 0) { + uh_log_err("open: %s\n", strerror(errno)); + return; + } - ev_io_start(conn->srv->loop, &conn->iow); + fstat(fd, &st); + + 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; +#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); } 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; + if (conni->flags & CONN_F_CLOSED) + return; + 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; + + if (conni->flags & CONN_F_CLOSED) + return; + + 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,15 +189,17 @@ 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; + 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); + conn_printf(conn, "Content-Length: %" PRIu64 "\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); @@ -155,7 +207,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) @@ -163,14 +217,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); @@ -191,17 +246,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 */ @@ -212,11 +273,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; @@ -224,14 +286,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; @@ -239,7 +302,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; @@ -251,8 +315,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,12 +324,43 @@ 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; + + return conni->parser.content_length; +} + 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; @@ -273,22 +368,26 @@ 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_connection_internal *conni = (struct uh_connection_internal *)conn; + 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); + conni->req.body.consumed = true; 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 *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)); @@ -304,7 +403,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) @@ -316,7 +415,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) { @@ -338,7 +437,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) { @@ -353,8 +452,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; @@ -362,7 +461,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)) { @@ -385,11 +484,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; @@ -399,29 +498,38 @@ 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; + 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; } 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); return 0; } @@ -436,7 +544,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]; @@ -464,6 +572,9 @@ void conn_free(struct uh_connection *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); @@ -475,6 +586,47 @@ void conn_free(struct uh_connection *conn) free(conn); } +static void conn_http_parse(struct uh_connection_internal *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); + if (conn->flags & CONN_F_CLOSED) { + conn_free(conn); + return; + } + + switch (parser->http_errno) { + case HPE_PAUSED: + case HPE_OK: + if (parser->upgrade) { + conn_error(&conn->com, 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->com, 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) { @@ -491,7 +643,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 @@ -509,28 +661,47 @@ 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) + 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); + } } } @@ -549,11 +720,10 @@ 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 uh_connection_internal *conn = container_of(w, struct uh_connection_internal, ior); 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 +745,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,28 +753,24 @@ 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) { - 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) { @@ -620,14 +784,63 @@ 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 struct uh_server *conn_get_server(struct uh_connection *conn) { - struct uh_connection *conn; + struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; - conn = calloc(1, sizeof(struct uh_connection)); + 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; + 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->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; + + conn->close = conn_close; +} + +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)); if (!conn) { uh_log_err("malloc: %s\n", strerror(errno)); return NULL; @@ -651,7 +864,7 @@ struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct 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 @@ -659,30 +872,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 ed43a6b..0b249e8 100644 --- a/src/connection.h +++ b/src/connection.h @@ -25,28 +25,22 @@ #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 #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; - -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 */ struct { ssize_t offset; size_t length; @@ -66,12 +60,14 @@ 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; }; -struct uh_connection { +struct uh_connection_internal { + struct uh_connection com; int sock; #if UHTTPD_SSL_SUPPORT void *ssl; @@ -79,7 +75,7 @@ struct uh_connection { uint8_t flags; struct { int fd; - int size; + uint64_t size; } file; struct ev_io ior; struct ev_io iow; @@ -88,7 +84,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; @@ -96,34 +92,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); - void (*done)(struct uh_connection *conn); /* Must be called at last, if not call 'error', 'redirect' and 'serve_file' */ - 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, const char *docroot, const char *index_page); - 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_listener *l, 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 8e8e428..70f4a57 100644 --- a/src/file.c +++ b/src/file.c @@ -34,15 +34,17 @@ #include #include #include +#include -#include "connection.h" +#include "uhttpd_internal.h" #include "mimetypes.h" -#include "log.h" +#include "utils.h" +#include "file.h" 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; } @@ -152,12 +154,115 @@ 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) +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"); + 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 = strtoull(buf, NULL, 0); + + i = 0; + + while (p < e) { + if (i >= (sizeof(buf) - 1) || !isdigit(*p)) + goto err; + buf[i++] = *p++; + } + + buf[i] = '\0'; + *end = strtoull(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; const struct uh_str path = conn->get_path(conn); - static char fullpath[512]; + struct uh_server_internal *srv = conni->srv; + const char *docroot = srv->docroot; + const char *index_page = srv->index_page; + static char fullpath[PATH_MAX]; + uint64_t start, end; + int docroot_len; const char *mime; struct stat st; + bool ranged; if (!docroot || !docroot[0]) docroot = "."; @@ -165,13 +270,16 @@ void serve_file(struct uh_connection *conn, const char *docroot, const char *ind 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) { @@ -206,6 +314,9 @@ void serve_file(struct uh_connection *conn, const char *docroot, const char *ind 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)) { @@ -213,21 +324,29 @@ void serve_file(struct uh_connection *conn, const char *docroot, const char *ind 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, (uint64_t)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/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/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 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/uhttpd.c b/src/uhttpd.c index 60ce767..33a6fee 100644 --- a/src/uhttpd.c +++ b/src/uhttpd.c @@ -27,34 +27,34 @@ #include #include #include -#include #include #ifdef HAVE_DLOPEN #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; + struct uh_listener *l = srvi->listeners; #ifdef HAVE_DLOPEN - struct uh_plugin *p = srv->plugins; + struct uh_plugin *p = srvi->plugins; #endif - ev_io_stop(srv->loop, &srv->ior); + if (srvi->docroot) + free(srvi->docroot); - if (srv->sock > 0) - close(srv->sock); + 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; } @@ -65,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; @@ -75,14 +87,15 @@ 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_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; struct sockaddr_in sin; @@ -93,9 +106,10 @@ 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) { - uh_log_err("accept: %s\n", strerror(errno)); + if (errno != EAGAIN) + uh_log_err("accept: %s\n", strerror(errno)); return; } @@ -104,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; @@ -118,20 +146,17 @@ 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; - 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; } - if (uh_server_init(srv, loop, host, port) < 0) { - free(srv); - return NULL; - } + uh_server_init(srv, loop); return srv; } @@ -139,14 +164,16 @@ 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) { #ifdef HAVE_DLOPEN + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; struct uh_plugin_handler *h; struct uh_plugin *p; void *dlh; @@ -179,13 +206,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 @@ -196,6 +223,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); @@ -207,111 +235,229 @@ 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; } -int uh_server_init(struct uh_server *srv, struct ev_loop *loop, const char *host, int port) +static void uh_set_conn_abort_cb(struct uh_server *srv, uh_con_closed_cb_prototype cb) { - union { - struct sockaddr sa; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - } addr; - char addr_str[INET6_ADDRSTRLEN]; - socklen_t addrlen; - int sock = -1; - int opt = 1; + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - if (!host || *host == '\0') { - addr.sin.sin_family = AF_INET; - addr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - } + srvi->conn_closed_cb = cb; +} - 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; +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; - status = getaddrinfo(host, NULL, &hints, &ais); - if (status != 0) { - uh_log_err("getaddrinfo(): %s\n", gai_strerror(status)); - return -1; - } + srvi->default_handler = handler; +} - memcpy(&addr, ais->ai_addr, ais->ai_addrlen); - freeaddrinfo(ais); - } +static int uh_set_docroot(struct uh_server *srv, const char *path) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; - 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)); - } + if (srvi->docroot) + free(srvi->docroot); - sock = socket(addr.sa.sa_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (sock < 0) { - uh_log_err("socket: %s\n", strerror(errno)); + srvi->docroot = strdup(path); + if (!srvi->docroot) { + uh_log_err("strdup: %s\n", strerror(errno)); return -1; } - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) < 0) { - uh_log_err("setsockopt: %s\n", strerror(errno)); - goto err; + return 0; +} + +static int uh_set_index_page(struct uh_server *srv, const char *name) +{ + struct uh_server_internal *srvi = (struct uh_server_internal *)srv; + + 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; } - if (bind(sock, &addr.sa, addrlen) < 0) { - close(sock); - uh_log_err("bind: %s\n", strerror(errno)); - goto err; + 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; +} + +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)++; + } } - listen(sock, SOMAXCONN); + if ((*host)[0] == '\0') + *host = "0"; - 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 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; + 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]; + int bound = 0; + int on = 1; + int status; + int sock; + + if (parse_address(addr, &host, &port) < 0) { + uh_log_err("invalid address\n"); + return -1; } - memset(srv, 0, sizeof(struct uh_server)); + status = getaddrinfo(host, port, &hints, &addrs); + if (status != 0) { + uh_log_err("getaddrinfo(): %s\n", gai_strerror(status)); + return -1; + } - srv->loop = loop ? loop : EV_DEFAULT; - srv->sock = sock; + /* 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 (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); + } + + freeaddrinfo(addrs); + + 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; + + 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 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; - ev_io_init(&srv->ior, uh_accept_cb, sock, EV_READ); - ev_io_start(srv->loop, &srv->ior); - - return 0; - -err: - close(sock); - return -1; + srv->set_docroot = uh_set_docroot; + srv->set_index_page = uh_set_index_page; } diff --git a/src/uhttpd.h b/src/uhttpd.h index db1659b..6a758b0 100644 --- a/src/uhttpd.h +++ b/src/uhttpd.h @@ -25,14 +25,91 @@ #ifndef LIBUHTTPD_UHTTPD_H #define LIBUHTTPD_UHTTPD_H +#include +#include +#include #include -#include "connection.h" +#include "http_parser.h" #include "config.h" +#include "utils.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_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' + */ + 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, 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); + 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); + void (*chunk_send)(struct uh_connection *conn, const void *data, ssize_t len); + 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 */ + 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); + 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 */ + struct uh_str (*extract_body)(struct uh_connection *conn); + void (*close)(struct uh_connection *conn); /* close low level TCP 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 { + 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 + 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); + int (*set_index_page)(struct uh_server *srv, const char *name); +}; + struct uh_plugin_handler { const char *path; uh_path_handler_prototype handler; @@ -44,44 +121,18 @@ 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; - 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); -}; - /* * 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 new file mode 100644 index 0000000..4e9f80d --- /dev/null +++ b/src/uhttpd_internal.h @@ -0,0 +1,59 @@ +/* + * 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 + +#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; + char *docroot; + char *index_page; + struct ev_loop *loop; + 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); +#if UHTTPD_SSL_SUPPORT + void *ssl_ctx; +#endif + struct uh_plugin *plugins; + struct uh_path_handler *handlers; +}; + +#endif \ No newline at end of file diff --git a/src/utils.c b/src/utils.c index c51f1a0..6189972 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include "utils.h" @@ -44,3 +46,46 @@ 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; +} + +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 705a6cd..e65bdac 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) \ @@ -41,4 +40,13 @@ 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