From 8806960d2b3522dc573baef28d8e37b64e124fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <> Date: Sat, 6 Apr 2024 20:17:51 -0400 Subject: [PATCH] http: migrate from http-parser to llhttp --- .github/workflows/ccpp.yml | 51 ++++++++++++++++++++++------ .github/workflows/clang-analyzer.yml | 26 ++++++++------ CMakeLists.txt | 41 +++++++++++++++++++--- configure.ac | 10 +++--- docker/DockerfileAlpine | 4 +-- docker/DockerfileDeps | 22 +++++++----- docker/DockerfileDepsAlpine | 24 +++++++------ docker/DockerfileDepsBionic | 20 ++++++----- docker/DockerfileDepsFocal | 22 +++++++----- docker/DockerfileDepsLlvm | 20 ++++++----- include/opendht/dht_proxy_server.h | 10 +++--- include/opendht/http.h | 26 ++++---------- meson.build | 16 +++------ opendht.pc.in | 2 +- src/dht_proxy_client.cpp | 2 +- src/dht_proxy_server.cpp | 15 +++++++- src/http.cpp | 38 ++++++++++----------- 17 files changed, 214 insertions(+), 135 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 1f236139..08ad4f90 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -12,9 +12,32 @@ jobs: run: | sudo apt install libncurses5-dev libreadline-dev nettle-dev libasio-dev \ libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \ - autotools-dev autoconf libfmt-dev libhttp-parser-dev libmsgpack-dev libargon2-0-dev + autotools-dev autoconf libfmt-dev libmsgpack-dev libargon2-0-dev - name: Cython run: sudo pip3 install Cython + - name: llhttp + run: | + mkdir llhttp + wget https://github.com/nodejs/llhttp/archive/refs/tags/release/v9.2.0.tar.gz -O llhttp.tar.gz + tar -xzf llhttp.tar.gz -C llhttp --strip-components=1 + cd llhttp && cmake -DCMAKE_INSTALL_PREFIX=/usr . && make -j2 && sudo make install + cd ../ && rm -rf llhttp* + - name: expected-lite + run: | + sudo mkdir /usr/local/include/nonstd && \ + sudo wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/local/include/nonstd/expected.hpp + - name: restinio + run: | + mkdir restinio && cd restinio + wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 + ls -l && tar -xjf restinio-0.7.2.tar.bz2 + cd restinio-0.7.2/dev + cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . + sudo make install + cd ../../ && rm -rf restinio* - name: Configure run: | ./autogen.sh @@ -31,7 +54,7 @@ jobs: run: | sudo apt install ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev \ libgnutls28-dev libuv1-dev python3-dev python3-setuptools python3-pip \ - libcppunit-dev libjsoncpp-dev libfmt-dev libhttp-parser-dev libmsgpack-dev libargon2-0-dev + libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-0-dev - name: Python dependencies run: sudo pip3 install meson Cython - name: Configure @@ -66,19 +89,25 @@ jobs: - uses: actions/checkout@v3 - name: Dependencies run: | - brew install msgpack-cxx asio gnutls nettle readline fmt jsoncpp argon2 openssl http-parser cppunit + brew install msgpack-cxx asio gnutls nettle readline fmt jsoncpp argon2 openssl cppunit + + - name: expected-lite + run: | + sudo mkdir /usr/local/include/nonstd && \ + sudo wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/local/include/nonstd/expected.hpp - name: restinio run: | mkdir restinio && cd restinio - wget https://github.com/aberaud/restinio/archive/6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz - ls -l && tar -xzf 6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz - cd restinio-6fd08b65f6f15899dd0de3c801f6a5462b811c64/dev - cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . - make -j8 && sudo make install - cd ../../.. && rm -rf restinio + wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 + ls -l && tar -xjf restinio-0.7.2.tar.bz2 + cd restinio-0.7.2/dev + cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . + make -j2 && sudo make install + cd ../../ && rm -rf restinio* - name: Configure run: | diff --git a/.github/workflows/clang-analyzer.yml b/.github/workflows/clang-analyzer.yml index aa97de67..d275c9d8 100644 --- a/.github/workflows/clang-analyzer.yml +++ b/.github/workflows/clang-analyzer.yml @@ -11,21 +11,27 @@ jobs: run: | sudo apt install libncurses5-dev libreadline-dev nettle-dev \ libgnutls28-dev libuv1-dev python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \ - autotools-dev autoconf libfmt-dev libhttp-parser-dev libmsgpack-dev libargon2-0-dev libasio-dev \ + autotools-dev autoconf libfmt-dev libmsgpack-dev libargon2-0-dev libasio-dev \ llvm llvm-dev clang clang-tools && \ sudo apt remove gcc g++ + - name: expected-lite + run: | + sudo mkdir /usr/include/nonstd && \ + sudo wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + - name: restinio run: | - mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && ls -l && tar -xzf 6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && cd restinio-6fd08b65f6f15899dd0de3c801f6a5462b811c64/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && sudo make install \ - && cd ../../.. && rm -rf restinio + mkdir restinio && cd restinio + wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 + ls -l && tar -xjf restinio-0.7.2.tar.bz2 + cd restinio-0.7.2/dev + cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . + make -j2 && sudo make install + cd ../../ && rm -rf restinio* - name: cmake run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ea5193c..9aa71256 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,7 @@ cmake_minimum_required (VERSION 3.16..3.28) +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() project (opendht) include(CMakePackageConfigHelpers) @@ -7,6 +10,7 @@ include(CheckIncludeFileCXX) include(FindPkgConfig) include(cmake/CheckAtomic.cmake) include(CTest) +include(FetchContent) set (opendht_VERSION_MAJOR 3) set (opendht_VERSION_MINOR 1.11) @@ -87,9 +91,35 @@ if (NOT MSVC) if (OPENDHT_HTTP) find_package(Restinio REQUIRED) - find_library(HTTP_PARSER_LIBRARY http_parser) - add_library(http_parser SHARED IMPORTED) - set(http_parser_lib "-lhttp_parser") + + # llhttp + find_path(LLHTTP_INCLUDE_DIR llhttp.h) + find_library(LLHTTP_LIBRARY libllhttp.a) + if (LLHTTP_INCLUDE_DIR AND LLHTTP_LIBRARY) + message(STATUS "Found llhttp ${LLHTTP_INCLUDE_DIR} ${LLHTTP_LIBRARY}") + add_library(llhttp_static STATIC IMPORTED) + set_target_properties(llhttp_static PROPERTIES + IMPORTED_LOCATION ${LLHTTP_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES ${LLHTTP_INCLUDE_DIR} + ) + set(llhttp_target llhttp_static) + else() + FetchContent_Declare(llhttp-local URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v9.2.0.tar.gz") + if (BUILD_SHARED_LIBS) + set(BUILD_SHARED_LIBS ON CACHE INTERNAL "") + else() + set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") + set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") + endif() + FetchContent_MakeAvailable(llhttp-local) + if (BUILD_SHARED_LIBS) + set(llhttp_target llhttp_shared) + else() + set(llhttp_target llhttp_static) + endif() + endif() + set(http_lib "-lllhttp") + if (NOT Jsoncpp_FOUND) message(SEND_ERROR "Jsoncpp is required for DHT proxy support") endif() @@ -204,6 +234,7 @@ list (APPEND opendht_SOURCES ) list (APPEND opendht_HEADERS + include/opendht.h include/opendht/def.h include/opendht/utils.h include/opendht/sockaddr.h @@ -356,12 +387,14 @@ else() PRIVATE PkgConfig::argon2 PkgConfig::Nettle - ${HTTP_PARSER_LIBRARY} PUBLIC ${CMAKE_THREAD_LIBS_INIT} PkgConfig::GnuTLS fmt::fmt ) + if (OPENDHT_HTTP) + target_link_libraries(opendht PUBLIC ${llhttp_target}) + endif() if (Jsoncpp_FOUND) target_link_libraries(opendht PUBLIC PkgConfig::Jsoncpp) endif() diff --git a/configure.ac b/configure.ac index f2605707..1523574a 100644 --- a/configure.ac +++ b/configure.ac @@ -175,11 +175,11 @@ AS_IF([test "x$have_fmt" = "xyes"], [ AM_COND_IF([PROXY_CLIENT_OR_SERVER], [ AC_CHECK_HEADERS([asio.hpp],, AC_MSG_ERROR([Missing Asio headers files])) CXXFLAGS="${CXXFLAGS} -DASIO_STANDALONE" - # http_parser has no pkgconfig, instead we check with: - AC_CHECK_LIB(http_parser, exit,, AC_MSG_ERROR([Missing HttpParser library files])) - AC_CHECK_HEADERS([http_parser.h], [http_parser_headers=yes; break;]) - AC_SUBST(http_parser_lib, ["-lhttp_parser"]) - AS_IF([test "x$http_parser_headers" != "xyes"], AC_MSG_ERROR([Missing HttpParser headers files])) + # llhttp has no pkgconfig, instead we check with: + AC_CHECK_LIB(llhttp, exit,, AC_MSG_ERROR([Missing llhttp library files])) + AC_CHECK_HEADERS([llhttp.h], [llhttp_headers=yes; break;]) + AC_SUBST(http_lib, ["-lllhttp"]) + AS_IF([test "x$llhttp_headers" != "xyes"], AC_MSG_ERROR([Missing llhttp headers files])) ]) CXXFLAGS="${CXXFLAGS} -DMSGPACK_NO_BOOST -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT" diff --git a/docker/DockerfileAlpine b/docker/DockerfileAlpine index dde0fa87..33df2f24 100644 --- a/docker/DockerfileAlpine +++ b/docker/DockerfileAlpine @@ -19,7 +19,7 @@ RUN cd opendht && mkdir build && cd build \ -DOPENDHT_SYSTEMD=On \ && make -j8 && make install -FROM alpine:3.18 AS install +FROM alpine:3.19 AS install COPY --from=build /install / RUN apk add --no-cache \ libstdc++ \ @@ -28,8 +28,8 @@ RUN apk add --no-cache \ openssl \ argon2-dev \ jsoncpp \ + llhttp \ fmt \ - http-parser \ readline \ ncurses CMD ["dhtnode", "-b", "bootstrap.jami.net", "-p", "4222", "--proxyserver", "8080"] diff --git a/docker/DockerfileDeps b/docker/DockerfileDeps index 83201417..97928dd4 100644 --- a/docker/DockerfileDeps +++ b/docker/DockerfileDeps @@ -13,18 +13,22 @@ RUN apt-get update && apt-get install -y \ python3-pip python3-dev python3-setuptools python3-build python3-virtualenv \ libncurses5-dev libreadline-dev nettle-dev libcppunit-dev \ libgnutls28-dev libuv1-dev libjsoncpp-dev libargon2-dev \ - libssl-dev libfmt-dev libhttp-parser-dev libasio-dev libmsgpack-dev \ + libssl-dev libfmt-dev libasio-dev libmsgpack-dev \ && apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/* RUN pip3 install meson Cython +RUN mkdir /usr/include/nonstd \ + && wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + RUN echo "*** Downloading RESTinio ***" \ && mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && ls -l && tar -xzf 6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && cd restinio-6fd08b65f6f15899dd0de3c801f6a5462b811c64/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && make install \ - && cd ../../.. && rm -rf restinio + && wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 \ + && ls -l && tar -xjf restinio-0.7.2.tar.bz2 \ + && cd restinio-0.7.2/dev \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . \ + && make -j2 && make install \ + && cd ../../ && rm -rf restinio* diff --git a/docker/DockerfileDepsAlpine b/docker/DockerfileDepsAlpine index d4f41f2f..0fa34bd0 100644 --- a/docker/DockerfileDepsAlpine +++ b/docker/DockerfileDepsAlpine @@ -1,4 +1,4 @@ -FROM alpine:3.18 +FROM alpine:3.19 LABEL maintainer="Adrien Béraud <adrien.beraud@savoirfairelinux.com>" LABEL org.opencontainers.image.source https://github.com/savoirfairelinux/opendht @@ -8,16 +8,20 @@ RUN apk add --no-cache \ ncurses-dev readline-dev nettle-dev \ cppunit-dev gnutls-dev jsoncpp-dev \ argon2-dev openssl-dev fmt-dev \ - http-parser-dev asio-dev msgpack-cxx-dev \ + llhttp-dev asio-dev msgpack-cxx-dev \ && rm -rf /var/cache/apk/* +RUN mkdir /usr/include/nonstd \ + && wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + RUN echo "*** Downloading RESTinio ***" \ && mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && tar -xzf 6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && cd restinio-6fd08b65f6f15899dd0de3c801f6a5462b811c64/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && make install \ - && cd ../../.. && rm -rf restinio + && wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 \ + && ls -l && tar -xjf restinio-0.7.2.tar.bz2 \ + && cd restinio-0.7.2/dev \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . \ + && make -j2 && make install \ + && cd ../../ && rm -rf restinio* diff --git a/docker/DockerfileDepsBionic b/docker/DockerfileDepsBionic index 02565bd6..73673986 100644 --- a/docker/DockerfileDepsBionic +++ b/docker/DockerfileDepsBionic @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y \ apt-transport-https build-essential pkg-config git wget libncurses5-dev libreadline-dev nettle-dev \ libgnutls28-dev libuv1-dev python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \ libargon2-0-dev \ - autotools-dev autoconf libfmt-dev libhttp-parser-dev libmsgpack-dev libssl-dev python3-pip \ + autotools-dev autoconf libfmt-dev libmsgpack-dev libssl-dev python3-pip \ && apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/* RUN pip3 install --upgrade cmake meson Cython @@ -20,13 +20,17 @@ RUN echo "** Building a recent version of asio ***" \ && make install \ && cd ../../ && rm -rf asio* +RUN mkdir /usr/include/nonstd \ + && wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + RUN echo "*** Downloading RESTinio ***" \ && mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/8d5d3e8237e0947adb9ba1ffc8281f4ad7cb2a59.tar.gz \ - && ls -l && tar -xzf 8d5d3e8237e0947adb9ba1ffc8281f4ad7cb2a59.tar.gz \ - && cd restinio-8d5d3e8237e0947adb9ba1ffc8281f4ad7cb2a59/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && make install \ + && wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 \ + && ls -l && tar -xjf restinio-0.7.2.tar.bz2 \ + && cd restinio-0.7.2/dev \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . \ + && make -j2 && make install \ && cd ../../ && rm -rf restinio* diff --git a/docker/DockerfileDepsFocal b/docker/DockerfileDepsFocal index 43d58016..da05fd5d 100644 --- a/docker/DockerfileDepsFocal +++ b/docker/DockerfileDepsFocal @@ -13,18 +13,22 @@ RUN apt-get update && apt-get install -y \ python3-dev python3-setuptools python3-pip \ libncurses5-dev libreadline-dev nettle-dev libcppunit-dev \ libgnutls28-dev libuv1-dev libjsoncpp-dev libargon2-dev \ - libssl-dev libfmt-dev libhttp-parser-dev libasio-dev libmsgpack-dev \ + libssl-dev libfmt-dev libasio-dev libmsgpack-dev \ && apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/* RUN pip3 install meson Cython +RUN mkdir /usr/include/nonstd \ + && wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + RUN echo "*** Downloading RESTinio ***" \ && mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/e0a261dd8488246a3cb8bbb3ea781ea5139c3c94.tar.gz \ - && ls -l && tar -xzf e0a261dd8488246a3cb8bbb3ea781ea5139c3c94.tar.gz \ - && cd restinio-e0a261dd8488246a3cb8bbb3ea781ea5139c3c94/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && make install \ - && cd ../../.. && rm -rf restinio + && wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 \ + && ls -l && tar -xjf restinio-0.7.2.tar.bz2 \ + && cd restinio-0.7.2/dev \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . \ + && make -j2 && make install \ + && cd ../../ && rm -rf restinio* diff --git a/docker/DockerfileDepsLlvm b/docker/DockerfileDepsLlvm index c6457b00..3342ab20 100644 --- a/docker/DockerfileDepsLlvm +++ b/docker/DockerfileDepsLlvm @@ -13,7 +13,7 @@ RUN apt-get update \ nettle-dev libgnutls28-dev libuv1-dev libmsgpack-dev libjsoncpp-dev python3-dev \ python3-setuptools libcppunit-dev python3-pip python3-build python3-virtualenv \ autotools-dev autoconf libssl-dev libargon2-dev \ - libfmt-dev libhttp-parser-dev libasio-dev \ + libfmt-dev libasio-dev \ && apt-get remove -y gcc g++ && apt-get autoremove -y \ && apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/* @@ -22,13 +22,17 @@ RUN pip3 install meson Cython ENV CC cc ENV CXX c++ +RUN mkdir /usr/include/nonstd \ + && wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp \ + -O /usr/include/nonstd/expected.hpp + RUN echo "*** Downloading RESTinio ***" \ && mkdir restinio && cd restinio \ - && wget https://github.com/aberaud/restinio/archive/6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && ls -l && tar -xzf 6fd08b65f6f15899dd0de3c801f6a5462b811c64.tar.gz \ - && cd restinio-6fd08b65f6f15899dd0de3c801f6a5462b811c64/dev \ - && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=OFF -DRESTINIO_SAMPLE=OFF \ - -DRESTINIO_INSTALL_SAMPLES=OFF -DRESTINIO_BENCH=OFF -DRESTINIO_INSTALL_BENCHES=OFF \ - -DRESTINIO_FIND_DEPS=ON -DRESTINIO_ALLOW_SOBJECTIZER=Off -DRESTINIO_USE_BOOST_ASIO=none . \ - && make -j8 && make install \ + && wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.2/restinio-0.7.2.tar.bz2 \ + && ls -l && tar -xjf restinio-0.7.2.tar.bz2 \ + && cd restinio-0.7.2/dev \ + && cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off \ + -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system \ + -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system . \ + && make -j2 && make install \ && cd ../../ && rm -rf restinio* diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h index e49f7077..d6297edc 100644 --- a/include/opendht/dht_proxy_server.h +++ b/include/opendht/dht_proxy_server.h @@ -46,17 +46,17 @@ enum class PushType { } MSGPACK_ADD_ENUM(dht::PushType) -namespace http { -class Request; -struct ListenerSession; -} - namespace Json { class Value; } namespace dht { +namespace http { +class Request; +struct ListenerSession; +} + class DhtRunner; using RestRouter = restinio::router::express_router_t<>; diff --git a/include/opendht/http.h b/include/opendht/http.h index 9533d143..f379a0be 100644 --- a/include/opendht/http.h +++ b/include/opendht/http.h @@ -29,23 +29,20 @@ # define snprintf snprintf #endif +#include <asio/ip/tcp.hpp> +#include <asio/streambuf.hpp> #include <asio/ssl/context.hpp> -#include <restinio/http_headers.hpp> #include <restinio/message_builders.hpp> #include <memory> #include <queue> #include <mutex> +#include <future> namespace Json { class Value; } -extern "C" { -struct http_parser; -struct http_parser_settings; -} - namespace restinio { namespace impl { class tls_socket_t; @@ -156,17 +153,6 @@ private: bool checkOcsp_ {false}; }; -/** - * Session value associated with a connection_id_t key. - */ -struct ListenerSession -{ - ListenerSession() = default; - dht::InfoHash hash; - std::future<size_t> token; - std::shared_ptr<restinio::response_builder_t<restinio::chunked_output_t>> response; -}; - /* @class Resolver * @brief The purpose is to only resolve once to avoid mutliple dns requests per operation. */ @@ -338,7 +324,7 @@ private: void init_default_headers(); /** - * Initialized and wraps the http_parser callbacks with our user defined callbacks. + * Initialized and wraps the llhttp callbacks with our user defined callbacks. */ void init_parser(); @@ -380,8 +366,8 @@ private: Response response_ {}; std::string request_; std::atomic<bool> finishing_ {false}; - std::unique_ptr<http_parser> parser_; - std::unique_ptr<http_parser_settings> parser_s_; + std::unique_ptr<llhttp_t> parser_; + std::unique_ptr<llhttp_settings_t> parser_s_; // Next request in case of redirect following std::shared_ptr<Request> next_; diff --git a/meson.build b/meson.build index 40e2b674..c2f5f8ce 100644 --- a/meson.build +++ b/meson.build @@ -12,15 +12,9 @@ argon2 = dependency('libargon2') openssl = dependency('openssl', required: get_option('proxy_client')) jsoncpp = dependency('jsoncpp', required: get_option('proxy_client')) fmt = dependency('fmt') +llhttp = dependency('llhttp', 'libllhttp', required: get_option('proxy_client')) -dirs=[] -if host_machine.system() == 'freebsd' - dirs+='/usr/local/lib' -elif host_machine.system() == 'darwin' - dirs+='/opt/homebrew/lib' -endif -http_parser = meson.get_compiler('c').find_library('http_parser', dirs: dirs, required: get_option('proxy_client')) -deps = [fmt, gnutls, nettle, msgpack, argon2, openssl, jsoncpp, http_parser] +deps = [fmt, gnutls, nettle, msgpack, argon2, openssl, jsoncpp, llhttp] add_project_arguments('-DMSGPACK_NO_BOOST', language : 'cpp') add_project_arguments(['-Wno-return-type','-Wno-deprecated','-Wnon-virtual-dtor','-pedantic-errors','-fvisibility=hidden'], language : 'cpp') @@ -57,12 +51,12 @@ if jsoncpp.found() add_project_arguments('-DOPENDHT_JSONCPP', language : 'cpp') conf_data.set('jsoncpp_lib', ', jsoncpp') endif -if http_parser.found() +if llhttp.found() opendht_src += ['src/http.cpp', 'src/compat/os_cert.cpp'] if host_machine.system() == 'darwin' deps+=dependency('appleframeworks', modules : ['CoreFoundation', 'Security']) endif - conf_data.set('http_lib', '-lhttp_parser') + conf_data.set('http_lib', '-lllhttp') endif if openssl.found() conf_data.set('openssl_lib', ', openssl') @@ -158,7 +152,7 @@ if get_option('tools').enabled() link_with : opendht, dependencies : [readline, jsoncpp, msgpack, fmt, openssl], install : true) - if http_parser.found() + if llhttp.found() durl = executable('durl', 'tools/durl.cpp', include_directories : opendht_interface_inc, link_with : opendht, diff --git a/opendht.pc.in b/opendht.pc.in index b9c835ca..8526b697 100644 --- a/opendht.pc.in +++ b/opendht.pc.in @@ -6,7 +6,7 @@ Name: OpenDHT Description: C++17 Distributed Hash Table library Version: @VERSION@ Libs: -L${libdir} -lopendht -Libs.private: @http_parser_lib@ -pthread +Libs.private: @http_lib@ -pthread Requires: gnutls >= 3.3@jsoncpp_lib@@openssl_lib@ Requires.private: nettle >= 2.4@argon2_lib@ Cflags: -I${includedir} diff --git a/src/dht_proxy_client.cpp b/src/dht_proxy_client.cpp index 8188be86..314463ef 100644 --- a/src/dht_proxy_client.cpp +++ b/src/dht_proxy_client.cpp @@ -23,7 +23,7 @@ #include "op_cache.h" #include "utils.h" -#include <http_parser.h> +#include <llhttp.h> #include <deque> namespace dht { diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp index fc97e67c..9f51096a 100644 --- a/src/dht_proxy_server.cpp +++ b/src/dht_proxy_server.cpp @@ -58,6 +58,19 @@ constexpr const std::chrono::minutes PRINT_STATS_PERIOD {2}; using ResponseByParts = restinio::chunked_output_t; using ResponseByPartsBuilder = restinio::response_builder_t<ResponseByParts>; +namespace http { +/** + * Session value associated with a connection_id_t key. + */ +struct ListenerSession +{ + ListenerSession() = default; + dht::InfoHash hash; + std::future<size_t> token; + std::shared_ptr<restinio::response_builder_t<restinio::chunked_output_t>> response; +}; +} + class opendht_logger_t { public: @@ -116,7 +129,7 @@ private: void DhtProxyServer::ConnectionListener::state_changed(const restinio::connection_state::notice_t& notice) noexcept { - if (restinio::holds_alternative<restinio::connection_state::closed_t>(notice.cause())) { + if (std::holds_alternative<restinio::connection_state::closed_t>(notice.cause())) { onClosed_(notice.connection_id()); } } diff --git a/src/http.cpp b/src/http.cpp index 1d4f5a80..e3c6e14b 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -24,7 +24,7 @@ #include <asio.hpp> #include <restinio/impl/tls_socket.hpp> -#include <http_parser.h> +#include <llhttp.h> #include <json/json.h> #include <openssl/ocsp.h> @@ -1137,13 +1137,11 @@ Request::init_parser() response_.request = shared_from_this(); if (!parser_) - parser_ = std::make_unique<http_parser>(); - http_parser_init(parser_.get(), HTTP_RESPONSE); - parser_->data = static_cast<void*>(this); + parser_ = std::make_unique<llhttp_t>(); if (!parser_s_) - parser_s_ = std::make_unique<http_parser_settings>(); - http_parser_settings_init(parser_s_.get()); + parser_s_ = std::make_unique<llhttp_settings_t>(); + llhttp_settings_init(parser_s_.get()); cbs_.on_status = [this, statusCb = std::move(cbs_.on_status)](unsigned int status_code){ response_.status_code = status_code; @@ -1158,31 +1156,33 @@ Request::init_parser() response_.headers[*header_field] = std::string(at, length); }; - // http_parser raw c callback (note: no context can be passed into them) - parser_s_->on_status = [](http_parser* parser, const char* /*at*/, size_t /*length*/) -> int { + // llhttp raw c callback (note: no context can be passed into them) + parser_s_->on_status = [](llhttp_t* parser, const char* /*at*/, size_t /*length*/) -> int { static_cast<Request*>(parser->data)->cbs_.on_status(parser->status_code); return 0; }; - parser_s_->on_header_field = [](http_parser* parser, const char* at, size_t length) -> int { + parser_s_->on_header_field = [](llhttp_t* parser, const char* at, size_t length) -> int { static_cast<Request*>(parser->data)->cbs_.on_header_field(at, length); return 0; }; - parser_s_->on_header_value = [](http_parser* parser, const char* at, size_t length) -> int { + parser_s_->on_header_value = [](llhttp_t* parser, const char* at, size_t length) -> int { static_cast<Request*>(parser->data)->cbs_.on_header_value(at, length); return 0; }; - parser_s_->on_body = [](http_parser* parser, const char* at, size_t length) -> int { + parser_s_->on_body = [](llhttp_t* parser, const char* at, size_t length) -> int { static_cast<Request*>(parser->data)->onBody(at, length); return 0; }; - parser_s_->on_headers_complete = [](http_parser* parser) -> int { + parser_s_->on_headers_complete = [](llhttp_t* parser) -> int { static_cast<Request*>(parser->data)->onHeadersComplete(); return 0; }; - parser_s_->on_message_complete = [](http_parser* parser) -> int { + parser_s_->on_message_complete = [](llhttp_t* parser) -> int { static_cast<Request*>(parser->data)->onComplete(); return 0; }; + llhttp_init(parser_.get(), HTTP_RESPONSE, parser_s_.get()); + parser_->data = static_cast<void*>(this); } void @@ -1339,7 +1339,7 @@ Request::terminate(const asio::error_code& ec) logger_->debug("[http:request:{:d}] done with status code {:d}", id_, response_.status_code); } - if (!parser_ or !http_should_keep_alive(parser_.get())) + if (!parser_ or !llhttp_should_keep_alive(parser_.get())) if (auto c = conn_) c->close(); notify_state_change(State::DONE); @@ -1378,17 +1378,15 @@ Request::handle_response(const asio::error_code& ec, size_t /* n_bytes */) return; } auto request = (ec == asio::error::eof) ? std::string{} : conn_->read_bytes(); - size_t ret = http_parser_execute(parser_.get(), parser_s_.get(), request.c_str(), request.size()); - if (ret != request.size()) { + enum llhttp_errno ret = llhttp_execute(parser_.get(), request.c_str(), request.size()); + if (ret != HPE_OK && ret != HPE_PAUSED) { if (logger_) - logger_->e("Error parsing HTTP: %zu %s %s", ret, - http_errno_name(HTTP_PARSER_ERRNO(parser_)), - http_errno_description(HTTP_PARSER_ERRNO(parser_))); + logger_->e("Error parsing HTTP: %zu %s %d", (int)ret, llhttp_errno_name(ret), llhttp_get_error_reason(parser_.get())); terminate(asio::error::basic_errors::broken_pipe); return; } - if (state_ != State::DONE and parser_ and not http_body_is_final(parser_.get())) { + if (state_ != State::DONE and parser_ and not llhttp_message_needs_eof(parser_.get())) { auto toRead = parser_->content_length ? std::min<uint64_t>(parser_->content_length, 64 * 1024) : 64 * 1024; std::weak_ptr<Request> wthis = shared_from_this(); conn_->async_read_some(toRead, [wthis](const asio::error_code& ec, size_t bytes){ -- GitLab