@@ -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
16051657std::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
0 commit comments