Skip to content

Commit 58eae6b

Browse files
committed
quic: move HTTP/3 event emit logic into Http3Application
Signed-off-by: Tim Perry <pimterry@gmail.com>
1 parent 55450be commit 58eae6b

5 files changed

Lines changed: 93 additions & 87 deletions

File tree

‎src/quic/application.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Session::Application : public MemoryRetainer {
3636
// data owned by the matching application's ticket hooks.
3737
enum class Type : uint8_t {
3838
DEFAULT = 1, // Native opaque byte-match data (no application)
39-
HTTP3 = 2, // Http3Conn typed settings data
39+
HTTP3 = 2, // Http3Application typed settings data
4040
};
4141
virtual Type type() const = 0;
4242

‎src/quic/http3.cc‎

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Http3Settings::operator const nghttp3_settings() const {
151151
.enable_connect_protocol = enable_connect_protocol,
152152
.h3_datagram = enable_datagrams,
153153
// origin_list is nullptr here because it is set directly on the
154-
// nghttp3_settings in Http3ApplicationImpl::InitializeConnection()
154+
// nghttp3_settings in Http3Application::InitializeConnection()
155155
// from the SNI configuration.
156156
.origin_list = nullptr,
157157
.glitch_ratelim_burst = 1000,
@@ -320,9 +320,9 @@ using Http3Header = NgHeader<Http3HeaderTraits>;
320320

321321
// The Session::Application implementation for HTTP/3, which owns the nghttp3
322322
// connection and all HTTP/3 protocol logic and state.
323-
class Http3ApplicationImpl final : public Session::Application {
323+
class Http3Application final : public Session::Application {
324324
public:
325-
Http3ApplicationImpl(Session* session, const Http3Settings& settings)
325+
Http3Application(Session* session, const Http3Settings& settings)
326326
: Application(session),
327327
allocator_(BindingData::Get(session->env()).nghttp3_allocator()),
328328
options_(settings),
@@ -337,6 +337,58 @@ class Http3ApplicationImpl final : public Session::Application {
337337
session->set_priority_supported();
338338
}
339339

340+
// ==========================================================================
341+
// HTTP/3 events reported up to the session/JS layer. Each routes through
342+
// Session::DeferOrRun, so an event raised before the session is surfaced to
343+
// JS (in 0-RTT first-flight frames) is held and replayed after attach.
344+
345+
void EmitGoaway(stream_id last_stream_id) {
346+
session().DeferOrRun([this, last_stream_id]() {
347+
Session& s = session();
348+
if (s.is_destroyed() || !s.env()->can_call_into_js()) return;
349+
CallbackScope<Session> cb_scope(&s);
350+
Local<Value> argv[] = {BigInt::New(s.env()->isolate(), last_stream_id)};
351+
s.MakeCallback(BindingData::Get(s.env()).session_goaway_callback(),
352+
arraysize(argv),
353+
argv);
354+
});
355+
}
356+
357+
void EmitOrigins(std::vector<std::string>&& origins) {
358+
auto held = std::make_shared<std::vector<std::string>>(std::move(origins));
359+
session().DeferOrRun([this, held]() {
360+
Session& s = session();
361+
if (s.is_destroyed() || !s.env()->can_call_into_js()) return;
362+
if (!s.has_origin_listener()) return;
363+
CallbackScope<Session> cb_scope(&s);
364+
auto* isolate = s.env()->isolate();
365+
LocalVector<Value> elements(isolate, held->size());
366+
for (size_t i = 0; i < held->size(); i++) {
367+
Local<Value> str;
368+
if (!ToV8Value(s.env()->context(), (*held)[i]).ToLocal(&str))
369+
[[unlikely]] {
370+
return;
371+
}
372+
elements[i] = str;
373+
}
374+
Local<Value> argv[] = {
375+
Array::New(isolate, elements.data(), elements.size())};
376+
s.MakeCallback(BindingData::Get(s.env()).session_origin_callback(),
377+
arraysize(argv),
378+
argv);
379+
});
380+
}
381+
382+
void EmitApplicationSettings() {
383+
session().DeferOrRun([this]() {
384+
Session& s = session();
385+
if (s.is_destroyed() || !s.env()->can_call_into_js()) return;
386+
CallbackScope<Session> cb_scope(&s);
387+
s.MakeCallback(
388+
BindingData::Get(s.env()).session_application_callback(), 0, nullptr);
389+
});
390+
}
391+
340392
// ==========================================================================
341393
// Session::Application
342394

@@ -471,9 +523,9 @@ class Http3ApplicationImpl final : public Session::Application {
471523
stream_id emit_id = is_notice ? -1 : goaway_id;
472524

473525
if (!is_notice) {
474-
// Final GOAWAY: destroy client-initiated bidi streams with
475-
// IDs > goaway_id. These were not processed by the peer and
476-
// can be retried. Copy the map because Destroy modifies it.
526+
// Final GOAWAY: destroy client-initiated bidi streams with IDs >
527+
// goaway_id. These were not processed by the peer and can be retried.
528+
// Copy the map because Destroy() modifies it.
477529
auto streams = session().streams();
478530
for (auto& [id, stream] : streams) {
479531
if (session().is_destroyed()) return;
@@ -495,7 +547,7 @@ class Http3ApplicationImpl final : public Session::Application {
495547
// when the peer sends CONNECTION_CLOSE after all streams finish.
496548
// Calling Close(GRACEFUL) would send a GOAWAY back and trigger
497549
// BeginShutdown, which can interfere with in-progress streams.
498-
session().EmitGoaway(emit_id);
550+
EmitGoaway(emit_id);
499551
}
500552

501553
bool ReceiveStreamData(stream_id id,
@@ -635,7 +687,7 @@ class Http3ApplicationImpl final : public Session::Application {
635687
}
636688

637689
int rv = nghttp3_conn_close_stream(*this, stream->id(), code);
638-
// If the call is successful, the Http3ApplicationImpl::OnStreamClose
690+
// If the call is successful, the Http3Application::OnStreamClose
639691
// callback will be invoked when the stream is ready to be closed. We'll
640692
// handle destroying the actual Stream object there.
641693
if (rv == 0) return;
@@ -880,8 +932,8 @@ class Http3ApplicationImpl final : public Session::Application {
880932
}
881933

882934
SET_NO_MEMORY_INFO()
883-
SET_MEMORY_INFO_NAME(Http3ApplicationImpl)
884-
SET_SELF_SIZE(Http3ApplicationImpl)
935+
SET_MEMORY_INFO_NAME(Http3Application)
936+
SET_SELF_SIZE(Http3Application)
885937

886938
private:
887939
inline operator nghttp3_conn*() const {
@@ -1168,8 +1220,8 @@ class Http3ApplicationImpl final : public Session::Application {
11681220
Debug(&session(),
11691221
"HTTP/3 application received updated settings: %s",
11701222
options_);
1171-
// Report the negotiated settings up to the JS application layer.
1172-
session().EmitApplication();
1223+
// Report the negotiated settings up to the session/JS layer.
1224+
EmitApplicationSettings();
11731225
}
11741226

11751227
// Inbound header-block accumulation, keyed by stream id. Entries are
@@ -1210,9 +1262,9 @@ class Http3ApplicationImpl final : public Session::Application {
12101262
// ==========================================================================
12111263
// Static callbacks
12121264

1213-
static Http3ApplicationImpl* From(nghttp3_conn* conn, void* user_data) {
1265+
static Http3Application* From(nghttp3_conn* conn, void* user_data) {
12141266
DCHECK_NOT_NULL(user_data);
1215-
auto app = static_cast<Http3ApplicationImpl*>(user_data);
1267+
auto app = static_cast<Http3Application*>(user_data);
12161268
DCHECK_EQ(conn, app->conn_.get());
12171269
return app;
12181270
}
@@ -1329,7 +1381,7 @@ class Http3ApplicationImpl final : public Session::Application {
13291381
void* conn_user_data,
13301382
void* stream_user_data) {
13311383
// This callback is invoked by nghttp3_conn_add_ack_offset() (called
1332-
// from Http3ApplicationImpl::AcknowledgeStreamData). We must NOT call
1384+
// from Http3Application::AcknowledgeStreamData). We must NOT call
13331385
// AcknowledgeStreamData here — that would re-enter nghttp3 via
13341386
// nghttp3_conn_add_ack_offset, triggering the NgHttp3CallbackScope
13351387
// re-entrancy assertion. Instead, directly notify the stream that data
@@ -1555,7 +1607,7 @@ class Http3ApplicationImpl final : public Session::Application {
15551607
static int on_end_origin(nghttp3_conn* conn, void* conn_user_data) {
15561608
NGHTTP3_CALLBACK_SCOPE(app);
15571609
if (!app.received_origins_.empty()) {
1558-
app.session().EmitOrigins(std::move(app.received_origins_));
1610+
app.EmitOrigins(std::move(app.received_origins_));
15591611
app.received_origins_.clear();
15601612
}
15611613
return NGTCP2_SUCCESS;
@@ -1604,7 +1656,7 @@ Http3Settings ResolveHttp3Settings(const Session::Options& options) {
16041656

16051657
std::unique_ptr<Session::Application> CreateHttp3Application(Session* session) {
16061658
Debug(session, "Installing HTTP/3 application");
1607-
return std::make_unique<Http3ApplicationImpl>(
1659+
return std::make_unique<Http3Application>(
16081660
session, ResolveHttp3Settings(std::as_const(*session).config().options));
16091661
}
16101662

‎src/quic/session.cc‎

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3818,6 +3818,11 @@ void Session::ReplayDeferredEmits() {
38183818
}
38193819
}
38203820

3821+
bool Session::has_origin_listener() const {
3822+
return HasListenerFlag(impl_->state()->listener_flags,
3823+
SessionListenerFlags::ORIGIN);
3824+
}
3825+
38213826
void Session::set_priority_supported(bool on) {
38223827
DCHECK(!is_destroyed());
38233828
impl_->state()->priority_supported = on ? 1 : 0;
@@ -4327,21 +4332,6 @@ void Session::set_max_datagram_size(uint16_t size) {
43274332
}
43284333
}
43294334

4330-
void Session::EmitGoaway(stream_id last_stream_id) {
4331-
if (is_destroyed()) return;
4332-
if (DeferEmit([this, last_stream_id] { EmitGoaway(last_stream_id); })) return;
4333-
if (!env()->can_call_into_js()) return;
4334-
4335-
CallbackScope<Session> cb_scope(this);
4336-
4337-
Local<Value> argv[] = {
4338-
BigInt::New(env()->isolate(), last_stream_id),
4339-
};
4340-
4341-
MakeCallback(
4342-
BindingData::Get(env()).session_goaway_callback(), arraysize(argv), argv);
4343-
}
4344-
43454335
void Session::EmitDatagram(Store&& datagram, DatagramReceivedFlags flag) {
43464336
DCHECK(!is_destroyed());
43474337

@@ -4528,20 +4518,6 @@ void Session::EmitSessionTicket(Store&& ticket) {
45284518
}
45294519
}
45304520

4531-
void Session::EmitApplication() {
4532-
if (is_destroyed()) return;
4533-
if (DeferEmit([this] { EmitApplication(); })) return;
4534-
if (!env()->can_call_into_js()) return;
4535-
4536-
// A bare notification that the installed application's negotiated options
4537-
// have been updated (e.g. an HTTP/3 SETTINGS frame arrived). The consumer
4538-
// reads the current values back through the application settings binding;
4539-
// the transport layer carries no protocol-specific data here.
4540-
CallbackScope<Session> cb_scope(this);
4541-
MakeCallback(
4542-
BindingData::Get(env()).session_application_callback(), 0, nullptr);
4543-
}
4544-
45454521
void Session::DestroyAllStreams(const QuicError& error) {
45464522
DCHECK(!is_destroyed());
45474523
// Copy the streams map since streams remove themselves during
@@ -4644,39 +4620,6 @@ void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
46444620
argv);
46454621
}
46464622

4647-
void Session::EmitOrigins(std::vector<std::string>&& origins) {
4648-
if (is_destroyed()) return;
4649-
4650-
if (must_defer_emits()) {
4651-
QueueDeferredEmit([this, origins = std::move(origins)]() mutable {
4652-
EmitOrigins(std::move(origins));
4653-
});
4654-
return;
4655-
}
4656-
4657-
if (!HasListenerFlag(impl_->state()->listener_flags,
4658-
SessionListenerFlags::ORIGIN))
4659-
return;
4660-
if (!env()->can_call_into_js()) return;
4661-
4662-
CallbackScope<Session> cb_scope(this);
4663-
4664-
auto isolate = env()->isolate();
4665-
4666-
LocalVector<Value> elements(env()->isolate(), origins.size());
4667-
for (size_t i = 0; i < origins.size(); i++) {
4668-
Local<Value> str;
4669-
if (!ToV8Value(env()->context(), origins[i]).ToLocal(&str)) [[unlikely]] {
4670-
return;
4671-
}
4672-
elements[i] = str;
4673-
}
4674-
4675-
Local<Value> argv[] = {Array::New(isolate, elements.data(), elements.size())};
4676-
MakeCallback(
4677-
BindingData::Get(env()).session_origin_callback(), arraysize(argv), argv);
4678-
}
4679-
46804623
void Session::EmitKeylog(const char* line) {
46814624
DCHECK(!is_destroyed());
46824625

‎src/quic/session.h‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
#include <node_sockaddr.h>
1111
#include <timer_wrap.h>
1212
#include <util.h>
13+
#include <functional>
1314
#include <memory>
1415
#include <optional>
16+
#include <utility>
1517
#include "bindingdata.h"
1618
#include "cid.h"
1719
#include "data.h"
@@ -82,7 +84,6 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
8284
std::string ToString() const;
8385
};
8486

85-
8687
// Select the Application implementation: the factory registered under
8788
// options.application when set (see application.h), otherwise nullptr.
8889
// With no application requested the session runs the native raw-stream
@@ -628,6 +629,19 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
628629
return true;
629630
}
630631

632+
// Runs fn now, or queues it for replay if emits are currently being
633+
// deferred (must_defer_emits()) before the session is ready.
634+
template <typename F>
635+
void DeferOrRun(F&& fn) {
636+
if (must_defer_emits()) {
637+
QueueDeferredEmit(std::forward<F>(fn));
638+
} else {
639+
fn();
640+
}
641+
}
642+
643+
bool has_origin_listener() const;
644+
631645
enum class CloseMethod : uint8_t {
632646
// Immediate close with a roundtrip through JavaScript, causing all
633647
// currently opened streams to be closed. An attempt will be made to
@@ -673,17 +687,15 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
673687
// JavaScript callouts
674688

675689
void EmitClose(const QuicError& error = QuicError());
676-
void EmitGoaway(stream_id last_stream_id);
677690

678691
// Sets the max datagram payload size in the shared state. Used by
679-
// Http3Conn to block datagram sends when the peer's
692+
// Http3Application to block datagram sends when the peer's
680693
// SETTINGS_H3_DATAGRAM=0 (RFC 9297 §3).
681694
void set_max_datagram_size(uint16_t size);
682695
void EmitDatagram(Store&& datagram, DatagramReceivedFlags flag);
683696
void EmitDatagramStatus(datagram_id id, DatagramStatus status);
684697
void EmitHandshakeComplete();
685698
void EmitKeylog(const char* line);
686-
void EmitOrigins(std::vector<std::string>&& origins);
687699

688700
struct ValidatedPath {
689701
std::shared_ptr<SocketAddress> local;
@@ -702,7 +714,6 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
702714
void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
703715
const uint32_t* sv,
704716
size_t nsv);
705-
void EmitApplication();
706717
void DatagramStatus(datagram_id datagramId, DatagramStatus status);
707718
void DatagramReceived(const uint8_t* data,
708719
size_t datalen,
@@ -753,7 +764,7 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
753764
friend struct NgHttp3CallbackScope;
754765
friend class Application;
755766
friend class BindingData;
756-
friend class Http3ApplicationImpl;
767+
friend class Http3Application;
757768
friend class Endpoint;
758769
friend class SessionManager;
759770
friend class Stream;

‎src/quic/streams.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ class Stream final : public AsyncWrap,
462462

463463
friend struct Impl;
464464
friend class PendingStream;
465-
friend class Http3ApplicationImpl;
465+
friend class Http3Application;
466466
friend class Session;
467467

468468
public:

0 commit comments

Comments
 (0)