Skip to content

Commit 6a23394

Browse files
committed
quic: move HTTP/3 event emit logic into Http3Application
1 parent 3f2fd0d commit 6a23394

5 files changed

Lines changed: 102 additions & 77 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: 72 additions & 19 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,
@@ -323,9 +323,9 @@ using Http3Header = NgHeader<Http3HeaderTraits>;
323323

324324
// The Session::Application implementation for HTTP/3, which owns the nghttp3
325325
// connection and all HTTP/3 protocol logic and state.
326-
class Http3ApplicationImpl final : public Session::Application {
326+
class Http3Application final : public Session::Application {
327327
public:
328-
Http3ApplicationImpl(Session* session, const Http3Settings& settings)
328+
Http3Application(Session* session, const Http3Settings& settings)
329329
: Application(session),
330330
allocator_(BindingData::Get(session->env()).nghttp3_allocator()),
331331
options_(settings),
@@ -340,6 +340,59 @@ class Http3ApplicationImpl final : public Session::Application {
340340
session->set_priority_supported();
341341
}
342342

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

@@ -474,9 +527,9 @@ class Http3ApplicationImpl final : public Session::Application {
474527
stream_id emit_id = is_notice ? -1 : goaway_id;
475528

476529
if (!is_notice) {
477-
// Final GOAWAY: destroy client-initiated bidi streams with
478-
// IDs > goaway_id. These were not processed by the peer and
479-
// can be retried. Copy the map because Destroy modifies it.
530+
// Final GOAWAY: destroy client-initiated bidi streams with IDs >
531+
// goaway_id. These were not processed by the peer and can be retried.
532+
// Copy the map because Destroy() modifies it.
480533
auto streams = session().streams();
481534
for (auto& [id, stream] : streams) {
482535
if (session().is_destroyed()) return;
@@ -498,7 +551,7 @@ class Http3ApplicationImpl final : public Session::Application {
498551
// when the peer sends CONNECTION_CLOSE after all streams finish.
499552
// Calling Close(GRACEFUL) would send a GOAWAY back and trigger
500553
// BeginShutdown, which can interfere with in-progress streams.
501-
session().EmitGoaway(emit_id);
554+
EmitGoaway(emit_id);
502555
}
503556

504557
bool ReceiveStreamData(stream_id id,
@@ -638,9 +691,9 @@ class Http3ApplicationImpl final : public Session::Application {
638691
}
639692

640693
int rv = nghttp3_conn_close_stream(*this, stream->id(), code);
641-
// If the call is successful, the Http3ApplicationImpl::OnStreamClose callback will
642-
// be invoked when the stream is ready to be closed. We'll handle
643-
// destroying the actual Stream object there.
694+
// If the call is successful, the Http3Application::OnStreamClose
695+
// callback will be invoked when the stream is ready to be closed. We'll
696+
// handle destroying the actual Stream object there.
644697
if (rv == 0) return;
645698

646699
if (rv == NGHTTP3_ERR_STREAM_NOT_FOUND) {
@@ -880,8 +933,8 @@ class Http3ApplicationImpl final : public Session::Application {
880933
}
881934

882935
SET_NO_MEMORY_INFO()
883-
SET_MEMORY_INFO_NAME(Http3ApplicationImpl)
884-
SET_SELF_SIZE(Http3ApplicationImpl)
936+
SET_MEMORY_INFO_NAME(Http3Application)
937+
SET_SELF_SIZE(Http3Application)
885938

886939
private:
887940
inline operator nghttp3_conn*() const {
@@ -1168,8 +1221,8 @@ class Http3ApplicationImpl final : public Session::Application {
11681221
Debug(&session(),
11691222
"HTTP/3 application received updated settings: %s",
11701223
options_);
1171-
// Report the negotiated settings up to the JS application layer.
1172-
session().EmitApplication();
1224+
// Report the negotiated settings up to the session/JS layer.
1225+
EmitApplicationSettings();
11731226
}
11741227

11751228
// Inbound header-block accumulation, keyed by stream id. Entries are
@@ -1210,9 +1263,9 @@ class Http3ApplicationImpl final : public Session::Application {
12101263
// ==========================================================================
12111264
// Static callbacks
12121265

1213-
static Http3ApplicationImpl* From(nghttp3_conn* conn, void* user_data) {
1266+
static Http3Application* From(nghttp3_conn* conn, void* user_data) {
12141267
DCHECK_NOT_NULL(user_data);
1215-
auto app = static_cast<Http3ApplicationImpl*>(user_data);
1268+
auto app = static_cast<Http3Application*>(user_data);
12161269
DCHECK_EQ(conn, app->conn_.get());
12171270
return app;
12181271
}
@@ -1329,7 +1382,7 @@ class Http3ApplicationImpl final : public Session::Application {
13291382
void* conn_user_data,
13301383
void* stream_user_data) {
13311384
// This callback is invoked by nghttp3_conn_add_ack_offset() (called
1332-
// from Http3ApplicationImpl::AcknowledgeStreamData). We must NOT call
1385+
// from Http3Application::AcknowledgeStreamData). We must NOT call
13331386
// AcknowledgeStreamData here — that would re-enter nghttp3 via
13341387
// nghttp3_conn_add_ack_offset, triggering the NgHttp3CallbackScope
13351388
// re-entrancy assertion. Instead, directly notify the stream that data
@@ -1553,7 +1606,7 @@ class Http3ApplicationImpl final : public Session::Application {
15531606
static int on_end_origin(nghttp3_conn* conn, void* conn_user_data) {
15541607
NGHTTP3_CALLBACK_SCOPE(app);
15551608
if (!app.received_origins_.empty()) {
1556-
app.session().EmitOrigins(std::move(app.received_origins_));
1609+
app.EmitOrigins(std::move(app.received_origins_));
15571610
app.received_origins_.clear();
15581611
}
15591612
return NGTCP2_SUCCESS;
@@ -1598,7 +1651,7 @@ Http3Settings ResolveHttp3Settings(const Session::Options& options) {
15981651

15991652
std::unique_ptr<Session::Application> CreateHttp3Application(Session* session) {
16001653
Debug(session, "Installing HTTP/3 application");
1601-
return std::make_unique<Http3ApplicationImpl>(
1654+
return std::make_unique<Http3Application>(
16021655
session,
16031656
ResolveHttp3Settings(std::as_const(*session).config().options));
16041657
}

src/quic/session.cc

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3839,6 +3839,15 @@ void Session::ReplayDeferredEmits() {
38393839
}
38403840
}
38413841

3842+
void Session::QueueDeferredEmit(std::function<void()> fn) {
3843+
impl_->deferred_emits_.emplace_back(std::move(fn));
3844+
}
3845+
3846+
bool Session::has_origin_listener() const {
3847+
return HasListenerFlag(impl_->state()->listener_flags,
3848+
SessionListenerFlags::ORIGIN);
3849+
}
3850+
38423851
void Session::set_priority_supported(bool on) {
38433852
DCHECK(!is_destroyed());
38443853
impl_->state()->priority_supported = on ? 1 : 0;
@@ -4347,20 +4356,6 @@ void Session::set_max_datagram_size(uint16_t size) {
43474356
}
43484357
}
43494358

4350-
void Session::EmitGoaway(stream_id last_stream_id) {
4351-
if (is_destroyed()) return;
4352-
if (!env()->can_call_into_js()) return;
4353-
4354-
CallbackScope<Session> cb_scope(this);
4355-
4356-
Local<Value> argv[] = {
4357-
BigInt::New(env()->isolate(), last_stream_id),
4358-
};
4359-
4360-
MakeCallback(
4361-
BindingData::Get(env()).session_goaway_callback(), arraysize(argv), argv);
4362-
}
4363-
43644359
void Session::EmitDatagram(Store&& datagram, DatagramReceivedFlags flag) {
43654360
DCHECK(!is_destroyed());
43664361
if (!env()->can_call_into_js()) return;
@@ -4545,18 +4540,6 @@ void Session::EmitSessionTicket(Store&& ticket) {
45454540
}
45464541
}
45474542

4548-
void Session::EmitApplication() {
4549-
if (is_destroyed()) return;
4550-
if (!env()->can_call_into_js()) return;
4551-
4552-
// A bare notification that the installed application's negotiated options
4553-
// have been updated (e.g. an HTTP/3 SETTINGS frame arrived). The consumer
4554-
// reads the current values back through the application settings binding;
4555-
// the transport layer carries no protocol-specific data here.
4556-
CallbackScope<Session> cb_scope(this);
4557-
MakeCallback(
4558-
BindingData::Get(env()).session_application_callback(), 0, nullptr);
4559-
}
45604543

45614544
void Session::DestroyAllStreams(const QuicError& error) {
45624545
DCHECK(!is_destroyed());
@@ -4664,31 +4647,6 @@ void Session::EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
46644647
argv);
46654648
}
46664649

4667-
void Session::EmitOrigins(std::vector<std::string>&& origins) {
4668-
if (is_destroyed()) return;
4669-
if (!HasListenerFlag(impl_->state()->listener_flags,
4670-
SessionListenerFlags::ORIGIN))
4671-
return;
4672-
if (!env()->can_call_into_js()) return;
4673-
4674-
CallbackScope<Session> cb_scope(this);
4675-
4676-
auto isolate = env()->isolate();
4677-
4678-
LocalVector<Value> elements(env()->isolate(), origins.size());
4679-
for (size_t i = 0; i < origins.size(); i++) {
4680-
Local<Value> str;
4681-
if (!ToV8Value(env()->context(), origins[i]).ToLocal(&str)) [[unlikely]] {
4682-
return;
4683-
}
4684-
elements[i] = str;
4685-
}
4686-
4687-
Local<Value> argv[] = {Array::New(isolate, elements.data(), elements.size())};
4688-
MakeCallback(
4689-
BindingData::Get(env()).session_origin_callback(), arraysize(argv), argv);
4690-
}
4691-
46924650
void Session::EmitKeylog(const char* line) {
46934651
DCHECK(!is_destroyed());
46944652
if (!env()->can_call_into_js()) return;

src/quic/session.h

Lines changed: 19 additions & 5 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"
@@ -623,6 +625,21 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
623625
// Called synchronously right after the new-session emit.
624626
void ReplayDeferredEmits();
625627

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

670687
void EmitClose(const QuicError& error = QuicError());
671-
void EmitGoaway(stream_id last_stream_id);
672688

673689
// Sets the max datagram payload size in the shared state. Used by
674-
// Http3Conn to block datagram sends when the peer's
690+
// Http3Application to block datagram sends when the peer's
675691
// SETTINGS_H3_DATAGRAM=0 (RFC 9297 §3).
676692
void set_max_datagram_size(uint16_t size);
677693
void EmitDatagram(Store&& datagram, DatagramReceivedFlags flag);
678694
void EmitDatagramStatus(datagram_id id, DatagramStatus status);
679695
void EmitHandshakeComplete();
680696
void EmitKeylog(const char* line);
681-
void EmitOrigins(std::vector<std::string>&& origins);
682697

683698
struct ValidatedPath {
684699
std::shared_ptr<SocketAddress> local;
@@ -697,7 +712,6 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
697712
void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd,
698713
const uint32_t* sv,
699714
size_t nsv);
700-
void EmitApplication();
701715
void DatagramStatus(datagram_id datagramId, DatagramStatus status);
702716
void DatagramReceived(const uint8_t* data,
703717
size_t datalen,
@@ -750,7 +764,7 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
750764
friend struct NgHttp3CallbackScope;
751765
friend class Application;
752766
friend class BindingData;
753-
friend class Http3ApplicationImpl;
767+
friend class Http3Application;
754768
friend class Endpoint;
755769
friend class SessionManager;
756770
friend class Stream;

src/quic/streams.h

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

445445
friend struct Impl;
446446
friend class PendingStream;
447-
friend class Http3ApplicationImpl;
447+
friend class Http3Application;
448448
friend class Session;
449449

450450
public:

0 commit comments

Comments
 (0)