diff --git a/CMakeLists.txt b/CMakeLists.txt index 899b01629..e46606de0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) set(LIBHV_FIND_DEPENDENCY_OPENSSL FALSE) +set(LIBHV_FIND_DEPENDENCY_NGHTTP2 FALSE) set(LIBHV_FIND_DEPENDENCY_QUICKJS FALSE) option(BUILD_SHARED "build shared library" ON) @@ -160,7 +161,13 @@ endif() if(WITH_NGHTTP2) add_definitions(-DWITH_NGHTTP2) - set(LIBS ${LIBS} nghttp2) + find_package(nghttp2 CONFIG QUIET) + if(TARGET nghttp2::nghttp2) + set(LIBS ${LIBS} nghttp2::nghttp2) + set(LIBHV_FIND_DEPENDENCY_NGHTTP2 TRUE) + else() + set(LIBS ${LIBS} nghttp2) + endif() endif() if(WITH_OPENSSL) diff --git a/cmake/libhvConfig.cmake.in b/cmake/libhvConfig.cmake.in index 719e08eba..b8252f99a 100644 --- a/cmake/libhvConfig.cmake.in +++ b/cmake/libhvConfig.cmake.in @@ -2,6 +2,10 @@ include(CMakeFindDependencyMacro) +if(@LIBHV_FIND_DEPENDENCY_NGHTTP2@) + find_dependency(nghttp2 CONFIG) +endif() + if(@LIBHV_FIND_DEPENDENCY_OPENSSL@) find_dependency(OpenSSL) endif() diff --git a/http/Http2Parser.cpp b/http/Http2Parser.cpp index 55598cb4b..a1987e058 100644 --- a/http/Http2Parser.cpp +++ b/http/Http2Parser.cpp @@ -6,6 +6,22 @@ #include #include +#if NGHTTP2_VERSION_NUM >= 0x013c00 +using Http2SSize = nghttp2_ssize; +using Http2DataProvider = nghttp2_data_provider2; +#define HV_NGHTTP2_MEM_SEND nghttp2_session_mem_send2 +#define HV_NGHTTP2_MEM_RECV nghttp2_session_mem_recv2 +#define HV_NGHTTP2_SUBMIT_REQUEST nghttp2_submit_request2 +#define HV_NGHTTP2_SUBMIT_RESPONSE nghttp2_submit_response2 +#else +using Http2SSize = decltype(nghttp2_session_mem_send(nullptr, nullptr)); +using Http2DataProvider = nghttp2_data_provider; +#define HV_NGHTTP2_MEM_SEND nghttp2_session_mem_send +#define HV_NGHTTP2_MEM_RECV nghttp2_session_mem_recv +#define HV_NGHTTP2_SUBMIT_REQUEST nghttp2_submit_request +#define HV_NGHTTP2_SUBMIT_RESPONSE nghttp2_submit_response +#endif + static nghttp2_nv make_nv(const char* name, const char* value) { nghttp2_nv nv; nv.name = (uint8_t*)name; @@ -53,9 +69,8 @@ static int on_frame_recv_callback(nghttp2_session *session, // the DATA framing, splitting to SETTINGS_MAX_FRAME_SIZE and flow control. // (One copy into nghttp2's buffer; the safe/simple path vs. NO_COPY framing.) // For gRPC, send_body holds the 5-byte-length-prefixed payload. -static ssize_t data_source_read_callback(nghttp2_session *session, - int32_t stream_id, uint8_t *buf, size_t length, - uint32_t *data_flags, nghttp2_data_source *source, void *userdata) { +static Http2SSize data_source_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *userdata) { Http2Parser* hp = (Http2Parser*)userdata; size_t remain = hp->send_len - hp->send_off; size_t n = remain < length ? remain : length; @@ -89,7 +104,7 @@ static ssize_t data_source_read_callback(nghttp2_session *session, nghttp2_submit_trailer(session, stream_id, nvs, n); } } - return (ssize_t)n; + return (Http2SSize)n; } @@ -131,21 +146,21 @@ int Http2Parser::GetSendData(char** data, size_t* len) { // nghttp2 produces everything: magic, SETTINGS, HEADERS, DATA (framed and // flow-controlled via the data_provider), SETTINGS/PING ACK, WINDOW_UPDATE, // trailers. Just drain it. - ssize_t ret = nghttp2_session_mem_send(session, (const uint8_t**)data); + Http2SSize ret = HV_NGHTTP2_MEM_SEND(session, (const uint8_t**)data); if (ret < 0) { error = (int)ret; *len = 0; return 0; } *len = (size_t)ret; - printd("nghttp2_session_mem_send %zu\n", *len); + printd("nghttp2 memory send %zu\n", *len); return (int)*len; } int Http2Parser::FeedRecvData(const char* data, size_t len) { - printd("nghttp2_session_mem_recv %zu\n", len); + printd("nghttp2 memory receive %zu\n", len); state = H2_WANT_RECV; - ssize_t ret = nghttp2_session_mem_recv(session, (const uint8_t*)data, len); + Http2SSize ret = HV_NGHTTP2_MEM_RECV(session, (const uint8_t*)data, len); if (ret < 0) { // nghttp2 error (e.g. NGHTTP2_ERR_*): surface it, caller closes. error = (int)ret; @@ -208,11 +223,10 @@ int Http2Parser::SubmitRequest(HttpRequest* req) { // 5-byte length-prefix into an owned buffer. is_grpc = (req->ContentType() == APPLICATION_GRPC); prepareSendBody(); - nghttp2_data_provider data_prd; + Http2DataProvider data_prd; data_prd.source.ptr = this; data_prd.read_callback = data_source_read_callback; - stream_id = nghttp2_submit_request(session, NULL, &nvs[0], nvs.size(), - send_len > 0 ? &data_prd : NULL, NULL); + stream_id = HV_NGHTTP2_SUBMIT_REQUEST(session, NULL, &nvs[0], nvs.size(), send_len > 0 ? &data_prd : NULL, NULL); if (stream_id < 0) { error = stream_id; return -1; @@ -287,18 +301,21 @@ int Http2Parser::SubmitResponse(HttpResponse* res) { } if (stream_id == -1) { // h2c upgrade: the HTTP/1.1 request maps to stream 1. +#if NGHTTP2_VERSION_NUM >= 0x010500 + nghttp2_session_upgrade2(session, NULL, 0, parsed && ((HttpRequest*)parsed)->method == HTTP_HEAD, NULL); +#else nghttp2_session_upgrade(session, NULL, 0, NULL); +#endif stream_id = 1; } // Set up the outgoing body as an nghttp2 data_provider (framing + splitting // + flow control handled by nghttp2). For gRPC, prepend the length-prefix. is_grpc = (parsed && parsed->ContentType() == APPLICATION_GRPC); prepareSendBody(); - nghttp2_data_provider data_prd; + Http2DataProvider data_prd; data_prd.source.ptr = this; data_prd.read_callback = data_source_read_callback; - int ret = nghttp2_submit_response(session, stream_id, &nvs[0], nvs.size(), - send_len > 0 ? &data_prd : NULL); + int ret = HV_NGHTTP2_SUBMIT_RESPONSE(session, stream_id, &nvs[0], nvs.size(), send_len > 0 ? &data_prd : NULL); if (ret != 0) { error = ret; return -1; diff --git a/http/Http2Parser.h b/http/Http2Parser.h index 1409c9405..45da2cae9 100644 --- a/http/Http2Parser.h +++ b/http/Http2Parser.h @@ -6,7 +6,22 @@ #include "http2def.h" #include "grpcdef.h" +#include "nghttp2/nghttp2ver.h" +#if NGHTTP2_VERSION_NUM >= 0x013c00 && defined(_MSC_VER) +#ifndef NGHTTP2_NO_SSIZE_T +#define NGHTTP2_NO_SSIZE_T +#endif +#elif defined(_MSC_VER) +#pragma push_macro("ssize_t") +#ifndef ssize_t +// Older nghttp2 CMake builds use int for ssize_t on MSVC. +#define ssize_t int +#endif +#endif #include "nghttp2/nghttp2.h" +#if NGHTTP2_VERSION_NUM < 0x013c00 && defined(_MSC_VER) +#pragma pop_macro("ssize_t") +#endif enum http2_session_state { H2_WANT_RECV, diff --git a/http/httpdef.h b/http/httpdef.h index e5d9ae67f..3d870a049 100644 --- a/http/httpdef.h +++ b/http/httpdef.h @@ -75,6 +75,7 @@ enum http_parser_state { XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ + XX(499, REQUEST_CANCELLED, Request Cancelled) \ XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ XX(501, NOT_IMPLEMENTED, Not Implemented) \ XX(502, BAD_GATEWAY, Bad Gateway) \