Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion include/anyhttp/h2/session_details.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,13 @@ Task<void> ClientSession<Stream>::do_session(Buffer&& buffer)
// enforces it in neither direction. Header sections beyond max_header_size are rejected where
// they arrive, see on_header_callback().
//
// No server push, either: there is no way to hand out a pushed response. nghttp2 acts on this
// from here on, on its own: until the server has acknowledged it, a push is cancelled with
// RST_STREAM, and after that it is a connection error. See on_begin_frame_callback().
//
auto iv =
std::to_array<nghttp2_settings_entry>({{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
std::to_array<nghttp2_settings_entry>({{NGHTTP2_SETTINGS_ENABLE_PUSH, 0},
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, window_size}});
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), iv.size());
nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0, window_size);
Expand Down
28 changes: 16 additions & 12 deletions src/apps/client_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,44 @@ static awaitable<void> send(Request& request, std::string_view hello)
logd("send: sending string of {} bytes... done", hello.size());
}

static awaitable<void> do_request(Session& session, boost::urls::url url)
static awaitable<void> do_request(Session& session, boost::urls::url url, std::string_view method)
{
const std::string hello = "Hello, World!\r\n";
const std::string hello = method == "POST" ? "Hello, World!\r\n" : "";
#if 0
auto request = co_await session.async_submit(url, {{"Content-Length", std::format("{}", hello.size())}});
#else
Fields fields;
fields.set("X-Custom-Header", "value");
auto request = co_await session.async_submit(url, fields);
auto request = co_await session.async_submit(method, url, fields);
#endif
#if 0
size_t bytes = 64 * 1024 - 1;
auto result = co_await (send(request, bytes) && receive(request));
assert(bytes == result);
#else
co_await (send(request, hello) && count_response(request));
logi("do_request: done");
const size_t bytes = co_await (send(request, hello) && count_response(request));
logi("do_request: done, {} bytes of response body", bytes);
#endif
}

static awaitable<void> do_requests(any_io_executor executor, Session session, boost::urls::url url)
static awaitable<void> do_requests(any_io_executor executor, Session session, boost::urls::url url,
std::string_view method)
{
for (size_t i = 0; i < 833; ++i)
co_await do_request(session, url);
co_await do_request(session, url, method);
}

static awaitable<void> do_session(Client& client, boost::urls::url url)
static awaitable<void> do_session(Client& client, boost::urls::url url, std::string_view method)
{
auto session = co_await client.async_connect();

#if 1
for (size_t i = 0; i < 1; ++i)
co_await do_request(session, url);
co_await do_request(session, url, method);
#else
for (size_t i = 0; i < 3; ++i)
co_spawn(client.get_executor(), do_requests(client.get_executor(), session, url), detached);
co_spawn(client.get_executor(), do_requests(client.get_executor(), session, url, method),
detached);
#endif
logi("do_requests: done");
}
Expand All @@ -72,16 +74,18 @@ int main(int argc, char* argv[])
{
if (argc < 2)
{
std::println("Usage: {} URL", argv[0]);
std::println("Usage: {} URL [METHOD]", argv[0]);
return 1;
}

const std::string_view method = argc > 2 ? argv[2] : "POST";

io_context context;
Config config{.url = boost::urls::url(argv[1]), .protocol = Protocol::h2};
Client client(context.get_executor(), config);

for (size_t i = 0; i < 1; ++i)
co_spawn(context, do_session(client, config.url), detached);
co_spawn(context, do_session(client, config.url, method), detached);

context.run();
return 0;
Expand Down
26 changes: 25 additions & 1 deletion src/h2/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ static std::string_view frame_type(uint8_t type)

// =================================================================================================

/**
* The only place that gets to see a server push. The client asks for none, and then nghttp2 drops a
* PUSH_PROMISE on its own, without any of the other callbacks, see ClientSession::do_session(). So
* all that is known here is that there was one, not what it promised.
*/
static int on_begin_frame_callback(nghttp2_session*, const nghttp2_frame_hd* hd, void* user_data)
{
if (hd->type == NGHTTP2_PUSH_PROMISE)
logi("[{}] on_begin_frame_callback: PUSH_PROMISE, length={}, not asked for and dropped",
static_cast<NGHttp2Session*>(user_data)->log_prefix(hd->stream_id), hd->length);
return 0;
}

static int on_begin_headers_callback(nghttp2_session*, const nghttp2_frame* frame, void* user_data)
{
auto handler = static_cast<NGHttp2Session*>(user_data);
Expand All @@ -99,8 +112,18 @@ static int on_header_callback(nghttp2_session* session, const nghttp2_frame* fra
auto name = make_string_view(name_, namelen_);
auto value = make_string_view(value_, valuelen_);

//
// No frame of a peer may take down the process. This was an assert until a pushed response came
// here, which SETTINGS_ENABLE_PUSH=0 prevents now. Should another way lead here, there is just
// nothing to store the fields in.
//
auto stream = handler->find_stream(frame->hd.stream_id);
assert(stream);
if (!stream)
{
logd("[{}] ignoring header without a stream: {}: {}", handler->log_prefix(frame),
truncated(name), truncated(value));
return 0;
}

//
// Beyond the limit, fields are not stored any more, but nghttp2 still has to decode them: HPACK
Expand Down Expand Up @@ -434,6 +457,7 @@ nghttp2_unique_ptr<nghttp2_session_callbacks> NGHttp2Session::setup_callbacks()
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_frame_send_callback (cbs, on_frame_send_callback);
nghttp2_session_callbacks_set_on_stream_close_callback (cbs, on_stream_close_callback);
nghttp2_session_callbacks_set_on_begin_frame_callback (cbs, on_begin_frame_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback (cbs, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback (cbs, on_header_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback (cbs, on_frame_not_send_callback);
Expand Down
Loading