1 #include "node_http2.h"
2 #include "aliased_buffer-inl.h"
3 #include "aliased_struct-inl.h"
4 #include "debug_utils-inl.h"
5 #include "histogram-inl.h"
6 #include "memory_tracker-inl.h"
7 #include "node.h"
8 #include "node_buffer.h"
9 #include "node_http_common-inl.h"
10 #include "node_mem-inl.h"
11 #include "node_perf.h"
12 #include "node_revert.h"
13 #include "stream_base-inl.h"
14 #include "util-inl.h"
15
16 #include <algorithm>
17 #include <memory>
18 #include <string>
19 #include <utility>
20 #include <vector>
21
22 namespace node {
23
24 using v8::Array;
25 using v8::ArrayBuffer;
26 using v8::ArrayBufferView;
27 using v8::BackingStore;
28 using v8::Boolean;
29 using v8::Context;
30 using v8::EscapableHandleScope;
31 using v8::Function;
32 using v8::FunctionCallbackInfo;
33 using v8::FunctionTemplate;
34 using v8::HandleScope;
35 using v8::Integer;
36 using v8::Isolate;
37 using v8::Local;
38 using v8::MaybeLocal;
39 using v8::NewStringType;
40 using v8::Number;
41 using v8::Object;
42 using v8::ObjectTemplate;
43 using v8::String;
44 using v8::Uint8Array;
45 using v8::Undefined;
46 using v8::Value;
47
48 namespace http2 {
49
50 namespace {
51
52 const char zero_bytes_256[256] = {};
53
HasHttp2Observer(Environment* env)54 bool HasHttp2Observer(Environment* env) {
55 AliasedUint32Array& observers = env->performance_state()->observers;
56 return observers[performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2] != 0;
57 }
58
59 } // anonymous namespace
60
61 // These configure the callbacks required by nghttp2 itself. There are
62 // two sets of callback functions, one that is used if a padding callback
63 // is set, and other that does not include the padding callback.
64 const Http2Session::Callbacks Http2Session::callback_struct_saved[2] = {
65 Callbacks(false),
66 Callbacks(true)};
67
68 // The Http2Scope object is used to queue a write to the i/o stream. It is
69 // used whenever any action is take on the underlying nghttp2 API that may
70 // push data into nghttp2 outbound data queue.
71 //
72 // For example:
73 //
74 // Http2Scope h2scope(session);
75 // nghttp2_submit_ping(session->session(), ... );
76 //
77 // When the Http2Scope passes out of scope and is deconstructed, it will
78 // call Http2Session::MaybeScheduleWrite().
Http2Scope(Http2Stream* stream)79 Http2Scope::Http2Scope(Http2Stream* stream) : Http2Scope(stream->session()) {}
80
Http2Scope(Http2Session* session)81 Http2Scope::Http2Scope(Http2Session* session) : session_(session) {
82 if (!session_) return;
83
84 // If there is another scope further below on the stack, or
85 // a write is already scheduled, there's nothing to do.
86 if (session_->is_in_scope() || session_->is_write_scheduled()) {
87 session_.reset();
88 return;
89 }
90 session_->set_in_scope();
91 }
92
~Http2Scope()93 Http2Scope::~Http2Scope() {
94 if (!session_) return;
95 session_->set_in_scope(false);
96 if (!session_->is_write_scheduled())
97 session_->MaybeScheduleWrite();
98 }
99
100 // The Http2Options object is used during the construction of Http2Session
101 // instances to configure an appropriate nghttp2_options struct. The class
102 // uses a single TypedArray instance that is shared with the JavaScript side
103 // to more efficiently pass values back and forth.
Http2Options(Http2State* http2_state, SessionType type)104 Http2Options::Http2Options(Http2State* http2_state, SessionType type) {
105 nghttp2_option* option;
106 CHECK_EQ(nghttp2_option_new(&option), 0);
107 CHECK_NOT_NULL(option);
108 options_.reset(option);
109
110 // Make sure closed connections aren't kept around, taking up memory.
111 // Note that this breaks the priority tree, which we don't use.
112 nghttp2_option_set_no_closed_streams(option, 1);
113
114 // We manually handle flow control within a session in order to
115 // implement backpressure -- that is, we only send WINDOW_UPDATE
116 // frames to the remote peer as data is actually consumed by user
117 // code. This ensures that the flow of data over the connection
118 // does not move too quickly and limits the amount of data we
119 // are required to buffer.
120 nghttp2_option_set_no_auto_window_update(option, 1);
121
122 // Enable built in support for receiving ALTSVC and ORIGIN frames (but
123 // only on client side sessions
124 if (type == NGHTTP2_SESSION_CLIENT) {
125 nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC);
126 nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN);
127 }
128
129 AliasedUint32Array& buffer = http2_state->options_buffer;
130 uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
131
132 if (flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
133 nghttp2_option_set_max_deflate_dynamic_table_size(
134 option,
135 buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
136 }
137
138 if (flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
139 nghttp2_option_set_max_reserved_remote_streams(
140 option,
141 buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
142 }
143
144 if (flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
145 nghttp2_option_set_max_send_header_block_length(
146 option,
147 buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
148 }
149
150 // Recommended default
151 nghttp2_option_set_peer_max_concurrent_streams(option, 100);
152 if (flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
153 nghttp2_option_set_peer_max_concurrent_streams(
154 option,
155 buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
156 }
157
158 // The padding strategy sets the mechanism by which we determine how much
159 // additional frame padding to apply to DATA and HEADERS frames. Currently
160 // this is set on a per-session basis, but eventually we may switch to
161 // a per-stream setting, giving users greater control
162 if (flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
163 PaddingStrategy strategy =
164 static_cast<PaddingStrategy>(
165 buffer.GetValue(IDX_OPTIONS_PADDING_STRATEGY));
166 set_padding_strategy(strategy);
167 }
168
169 // The max header list pairs option controls the maximum number of
170 // header pairs the session may accept. This is a hard limit.. that is,
171 // if the remote peer sends more than this amount, the stream will be
172 // automatically closed with an RST_STREAM.
173 if (flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS))
174 set_max_header_pairs(buffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS]);
175
176 // The HTTP2 specification places no limits on the number of HTTP2
177 // PING frames that can be sent. In order to prevent PINGS from being
178 // abused as an attack vector, however, we place a strict upper limit
179 // on the number of unacknowledged PINGS that can be sent at any given
180 // time.
181 if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS))
182 set_max_outstanding_pings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS]);
183
184 // The HTTP2 specification places no limits on the number of HTTP2
185 // SETTINGS frames that can be sent. In order to prevent PINGS from being
186 // abused as an attack vector, however, we place a strict upper limit
187 // on the number of unacknowledged SETTINGS that can be sent at any given
188 // time.
189 if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS))
190 set_max_outstanding_settings(buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
191
192 // The HTTP2 specification places no limits on the amount of memory
193 // that a session can consume. In order to prevent abuse, we place a
194 // cap on the amount of memory a session can consume at any given time.
195 // this is a credit based system. Existing streams may cause the limit
196 // to be temporarily exceeded but once over the limit, new streams cannot
197 // created.
198 // Important: The maxSessionMemory option in javascript is expressed in
199 // terms of MB increments (i.e. the value 1 == 1 MB)
200 if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY))
201 set_max_session_memory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] *
202 static_cast<uint64_t>(1000000));
203
204 if (flags & (1 << IDX_OPTIONS_MAX_SETTINGS)) {
205 nghttp2_option_set_max_settings(
206 option,
207 static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
208 }
209 }
210
211 #define GRABSETTING(entries, count, name) \
212 do { \
213 if (flags & (1 << IDX_SETTINGS_ ## name)) { \
214 uint32_t val = buffer[IDX_SETTINGS_ ## name]; \
215 entries[count++] = \
216 nghttp2_settings_entry {NGHTTP2_SETTINGS_ ## name, val}; \
217 } } while (0)
218
Init( Http2State* http2_state, nghttp2_settings_entry* entries)219 size_t Http2Settings::Init(
220 Http2State* http2_state,
221 nghttp2_settings_entry* entries) {
222 AliasedUint32Array& buffer = http2_state->settings_buffer;
223 uint32_t flags = buffer[IDX_SETTINGS_COUNT];
224
225 size_t count = 0;
226
227 #define V(name) GRABSETTING(entries, count, name);
228 HTTP2_SETTINGS(V)
229 #undef V
230
231 return count;
232 }
233 #undef GRABSETTING
234
235 // The Http2Settings class is used to configure a SETTINGS frame that is
236 // to be sent to the connected peer. The settings are set using a TypedArray
237 // that is shared with the JavaScript side.
Http2Settings(Http2Session* session, Local<Object> obj, Local<Function> callback, uint64_t start_time)238 Http2Settings::Http2Settings(Http2Session* session,
239 Local<Object> obj,
240 Local<Function> callback,
241 uint64_t start_time)
242 : AsyncWrap(session->env(), obj, PROVIDER_HTTP2SETTINGS),
243 session_(session),
244 startTime_(start_time) {
245 callback_.Reset(env()->isolate(), callback);
246 count_ = Init(session->http2_state(), entries_);
247 }
248
callback() const249 Local<Function> Http2Settings::callback() const {
250 return callback_.Get(env()->isolate());
251 }
252
MemoryInfo(MemoryTracker* tracker) const253 void Http2Settings::MemoryInfo(MemoryTracker* tracker) const {
254 tracker->TrackField("callback", callback_);
255 }
256
257 // Generates a Buffer that contains the serialized payload of a SETTINGS
258 // frame. This can be used, for instance, to create the Base64-encoded
259 // content of an Http2-Settings header field.
Pack()260 Local<Value> Http2Settings::Pack() {
261 return Pack(session_->env(), count_, entries_);
262 }
263
Pack(Http2State* state)264 Local<Value> Http2Settings::Pack(Http2State* state) {
265 nghttp2_settings_entry entries[IDX_SETTINGS_COUNT];
266 size_t count = Init(state, entries);
267 return Pack(state->env(), count, entries);
268 }
269
Pack( Environment* env, size_t count, const nghttp2_settings_entry* entries)270 Local<Value> Http2Settings::Pack(
271 Environment* env,
272 size_t count,
273 const nghttp2_settings_entry* entries) {
274 EscapableHandleScope scope(env->isolate());
275 std::unique_ptr<BackingStore> bs;
276 {
277 NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
278 bs = ArrayBuffer::NewBackingStore(env->isolate(), count * 6);
279 }
280 if (nghttp2_pack_settings_payload(static_cast<uint8_t*>(bs->Data()),
281 bs->ByteLength(),
282 entries,
283 count) < 0) {
284 return scope.Escape(Undefined(env->isolate()));
285 }
286 Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
287 return scope.Escape(Buffer::New(env, ab, 0, ab->ByteLength())
288 .FromMaybe(Local<Value>()));
289 }
290
291 // Updates the shared TypedArray with the current remote or local settings for
292 // the session.
Update(Http2Session* session, get_setting fn)293 void Http2Settings::Update(Http2Session* session, get_setting fn) {
294 AliasedUint32Array& buffer = session->http2_state()->settings_buffer;
295
296 #define V(name) \
297 buffer[IDX_SETTINGS_ ## name] = \
298 fn(session->session(), NGHTTP2_SETTINGS_ ## name);
299 HTTP2_SETTINGS(V)
300 #undef V
301 }
302
303 // Initializes the shared TypedArray with the default settings values.
RefreshDefaults(Http2State* http2_state)304 void Http2Settings::RefreshDefaults(Http2State* http2_state) {
305 AliasedUint32Array& buffer = http2_state->settings_buffer;
306 uint32_t flags = 0;
307
308 #define V(name) \
309 do { \
310 buffer[IDX_SETTINGS_ ## name] = DEFAULT_SETTINGS_ ## name; \
311 flags |= 1 << IDX_SETTINGS_ ## name; \
312 } while (0);
313 HTTP2_SETTINGS(V)
314 #undef V
315
316 buffer[IDX_SETTINGS_COUNT] = flags;
317 }
318
319
Send()320 void Http2Settings::Send() {
321 Http2Scope h2scope(session_.get());
322 CHECK_EQ(nghttp2_submit_settings(
323 session_->session(),
324 NGHTTP2_FLAG_NONE,
325 &entries_[0],
326 count_), 0);
327 }
328
Done(bool ack)329 void Http2Settings::Done(bool ack) {
330 uint64_t end = uv_hrtime();
331 double duration = (end - startTime_) / 1e6;
332
333 Local<Value> argv[] = {Boolean::New(env()->isolate(), ack),
334 Number::New(env()->isolate(), duration)};
335 MakeCallback(callback(), arraysize(argv), argv);
336 }
337
338 // The Http2Priority class initializes an appropriate nghttp2_priority_spec
339 // struct used when either creating a stream or updating its priority
340 // settings.
Http2Priority(Environment* env, Local<Value> parent, Local<Value> weight, Local<Value> exclusive)341 Http2Priority::Http2Priority(Environment* env,
342 Local<Value> parent,
343 Local<Value> weight,
344 Local<Value> exclusive) {
345 Local<Context> context = env->context();
346 int32_t parent_ = parent->Int32Value(context).ToChecked();
347 int32_t weight_ = weight->Int32Value(context).ToChecked();
348 bool exclusive_ = exclusive->IsTrue();
349 Debug(env, DebugCategory::HTTP2STREAM,
350 "Http2Priority: parent: %d, weight: %d, exclusive: %s\n",
351 parent_, weight_, exclusive_ ? "yes" : "no");
352 nghttp2_priority_spec_init(this, parent_, weight_, exclusive_ ? 1 : 0);
353 }
354
355
TypeName() const356 const char* Http2Session::TypeName() const {
357 switch (session_type_) {
358 case NGHTTP2_SESSION_SERVER: return "server";
359 case NGHTTP2_SESSION_CLIENT: return "client";
360 default:
361 // This should never happen
362 ABORT();
363 }
364 }
365
Origins( Environment* env, Local<String> origin_string, size_t origin_count)366 Origins::Origins(
367 Environment* env,
368 Local<String> origin_string,
369 size_t origin_count)
370 : count_(origin_count) {
371 int origin_string_len = origin_string->Length();
372 if (count_ == 0) {
373 CHECK_EQ(origin_string_len, 0);
374 return;
375 }
376
377 {
378 NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
379 bs_ = ArrayBuffer::NewBackingStore(env->isolate(),
380 alignof(nghttp2_origin_entry) - 1 +
381 count_ * sizeof(nghttp2_origin_entry) +
382 origin_string_len);
383 }
384
385 // Make sure the start address is aligned appropriately for an nghttp2_nv*.
386 char* start = AlignUp(static_cast<char*>(bs_->Data()),
387 alignof(nghttp2_origin_entry));
388 char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry));
389 nghttp2_origin_entry* const nva =
390 reinterpret_cast<nghttp2_origin_entry*>(start);
391
392 CHECK_LE(origin_contents + origin_string_len,
393 static_cast<char*>(bs_->Data()) + bs_->ByteLength());
394 CHECK_EQ(origin_string->WriteOneByte(
395 env->isolate(),
396 reinterpret_cast<uint8_t*>(origin_contents),
397 0,
398 origin_string_len,
399 String::NO_NULL_TERMINATION),
400 origin_string_len);
401
402 size_t n = 0;
403 char* p;
404 for (p = origin_contents; p < origin_contents + origin_string_len; n++) {
405 if (n >= count_) {
406 static uint8_t zero = '\0';
407 nva[0].origin = &zero;
408 nva[0].origin_len = 1;
409 count_ = 1;
410 return;
411 }
412
413 nva[n].origin = reinterpret_cast<uint8_t*>(p);
414 nva[n].origin_len = strlen(p);
415 p += nva[n].origin_len + 1;
416 }
417 }
418
419 // Sets the various callback functions that nghttp2 will use to notify us
420 // about significant events while processing http2 stuff.
Callbacks(bool kHasGetPaddingCallback)421 Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
422 nghttp2_session_callbacks* callbacks_;
423 CHECK_EQ(nghttp2_session_callbacks_new(&callbacks_), 0);
424 callbacks.reset(callbacks_);
425
426 nghttp2_session_callbacks_set_on_begin_headers_callback(
427 callbacks_, OnBeginHeadersCallback);
428 nghttp2_session_callbacks_set_on_header_callback2(
429 callbacks_, OnHeaderCallback);
430 nghttp2_session_callbacks_set_on_frame_recv_callback(
431 callbacks_, OnFrameReceive);
432 nghttp2_session_callbacks_set_on_stream_close_callback(
433 callbacks_, OnStreamClose);
434 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
435 callbacks_, OnDataChunkReceived);
436 nghttp2_session_callbacks_set_on_frame_not_send_callback(
437 callbacks_, OnFrameNotSent);
438 nghttp2_session_callbacks_set_on_invalid_header_callback2(
439 callbacks_, OnInvalidHeader);
440 nghttp2_session_callbacks_set_error_callback2(callbacks_, OnNghttpError);
441 nghttp2_session_callbacks_set_send_data_callback(
442 callbacks_, OnSendData);
443 nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
444 callbacks_, OnInvalidFrame);
445 nghttp2_session_callbacks_set_on_frame_send_callback(
446 callbacks_, OnFrameSent);
447
448 if (kHasGetPaddingCallback) {
449 nghttp2_session_callbacks_set_select_padding_callback(
450 callbacks_, OnSelectPadding);
451 }
452 }
453
StopTrackingRcbuf(nghttp2_rcbuf* buf)454 void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) {
455 StopTrackingMemory(buf);
456 }
457
CheckAllocatedSize(size_t previous_size) const458 void Http2Session::CheckAllocatedSize(size_t previous_size) const {
459 CHECK_GE(current_nghttp2_memory_, previous_size);
460 }
461
IncreaseAllocatedSize(size_t size)462 void Http2Session::IncreaseAllocatedSize(size_t size) {
463 current_nghttp2_memory_ += size;
464 }
465
DecreaseAllocatedSize(size_t size)466 void Http2Session::DecreaseAllocatedSize(size_t size) {
467 current_nghttp2_memory_ -= size;
468 }
469
Http2Session(Http2State* http2_state, Local<Object> wrap, SessionType type)470 Http2Session::Http2Session(Http2State* http2_state,
471 Local<Object> wrap,
472 SessionType type)
473 : AsyncWrap(http2_state->env(), wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
474 js_fields_(http2_state->env()->isolate()),
475 session_type_(type),
476 http2_state_(http2_state) {
477 MakeWeak();
478 statistics_.session_type = type;
479 statistics_.start_time = uv_hrtime();
480
481 // Capture the configuration options for this session
482 Http2Options opts(http2_state, type);
483
484 max_session_memory_ = opts.max_session_memory();
485
486 uint32_t maxHeaderPairs = opts.max_header_pairs();
487 max_header_pairs_ =
488 type == NGHTTP2_SESSION_SERVER
489 ? GetServerMaxHeaderPairs(maxHeaderPairs)
490 : GetClientMaxHeaderPairs(maxHeaderPairs);
491
492 max_outstanding_pings_ = opts.max_outstanding_pings();
493 max_outstanding_settings_ = opts.max_outstanding_settings();
494
495 padding_strategy_ = opts.padding_strategy();
496
497 bool hasGetPaddingCallback =
498 padding_strategy_ != PADDING_STRATEGY_NONE;
499
500 auto fn = type == NGHTTP2_SESSION_SERVER ?
501 nghttp2_session_server_new3 :
502 nghttp2_session_client_new3;
503
504 nghttp2_mem alloc_info = MakeAllocator();
505
506 // This should fail only if the system is out of memory, which
507 // is going to cause lots of other problems anyway, or if any
508 // of the options are out of acceptable range, which we should
509 // be catching before it gets this far. Either way, crash if this
510 // fails.
511 nghttp2_session* session;
512 CHECK_EQ(fn(
513 &session,
514 callback_struct_saved[hasGetPaddingCallback ? 1 : 0].callbacks.get(),
515 this,
516 *opts,
517 &alloc_info), 0);
518 session_.reset(session);
519
520 outgoing_storage_.reserve(1024);
521 outgoing_buffers_.reserve(32);
522
523 Local<Uint8Array> uint8_arr =
524 Uint8Array::New(js_fields_.GetArrayBuffer(), 0, kSessionUint8FieldCount);
525 USE(wrap->Set(env()->context(), env()->fields_string(), uint8_arr));
526 }
527
~Http2Session()528 Http2Session::~Http2Session() {
529 CHECK(!is_in_scope());
530 Debug(this, "freeing nghttp2 session");
531 // Ensure that all `Http2Stream` instances and the memory they hold
532 // on to are destroyed before the nghttp2 session is.
533 for (const auto& [id, stream] : streams_) {
534 stream->Detach();
535 }
536 streams_.clear();
537 // Explicitly reset session_ so the subsequent
538 // current_nghttp2_memory_ check passes.
539 session_.reset();
540 CHECK_EQ(current_nghttp2_memory_, 0);
541 }
542
MemoryInfo(MemoryTracker* tracker) const543 void Http2Session::MemoryInfo(MemoryTracker* tracker) const {
544 tracker->TrackField("streams", streams_);
545 tracker->TrackField("outstanding_pings", outstanding_pings_);
546 tracker->TrackField("outstanding_settings", outstanding_settings_);
547 tracker->TrackField("outgoing_buffers", outgoing_buffers_);
548 tracker->TrackFieldWithSize("stream_buf", stream_buf_.len);
549 tracker->TrackFieldWithSize("outgoing_storage", outgoing_storage_.size());
550 tracker->TrackFieldWithSize("pending_rst_streams",
551 pending_rst_streams_.size() * sizeof(int32_t));
552 tracker->TrackFieldWithSize("nghttp2_memory", current_nghttp2_memory_);
553 }
554
diagnostic_name() const555 std::string Http2Session::diagnostic_name() const {
556 return std::string("Http2Session ") + TypeName() + " (" +
557 std::to_string(static_cast<int64_t>(get_async_id())) + ")";
558 }
559
GetDetails( Environment* env, const Http2StreamPerformanceEntry& entry)560 MaybeLocal<Object> Http2StreamPerformanceEntryTraits::GetDetails(
561 Environment* env,
562 const Http2StreamPerformanceEntry& entry) {
563 Local<Object> obj = Object::New(env->isolate());
564
565 #define SET(name, val) \
566 if (!obj->Set( \
567 env->context(), \
568 env->name(), \
569 Number::New( \
570 env->isolate(), \
571 static_cast<double>(entry.details.val))).IsJust()) { \
572 return MaybeLocal<Object>(); \
573 }
574
575 SET(bytes_read_string, received_bytes)
576 SET(bytes_written_string, sent_bytes)
577 SET(id_string, id)
578 #undef SET
579
580 #define SET(name, val) \
581 if (!obj->Set( \
582 env->context(), \
583 env->name(), \
584 Number::New( \
585 env->isolate(), \
586 (entry.details.val - entry.details.start_time) / 1e6)) \
587 .IsJust()) { \
588 return MaybeLocal<Object>(); \
589 }
590
591 SET(time_to_first_byte_string, first_byte)
592 SET(time_to_first_byte_sent_string, first_byte_sent)
593 SET(time_to_first_header_string, first_header)
594 #undef SET
595
596 return obj;
597 }
598
GetDetails( Environment* env, const Http2SessionPerformanceEntry& entry)599 MaybeLocal<Object> Http2SessionPerformanceEntryTraits::GetDetails(
600 Environment* env,
601 const Http2SessionPerformanceEntry& entry) {
602 Local<Object> obj = Object::New(env->isolate());
603
604 #define SET(name, val) \
605 if (!obj->Set( \
606 env->context(), \
607 env->name(), \
608 Number::New( \
609 env->isolate(), \
610 static_cast<double>(entry.details.val))).IsJust()) { \
611 return MaybeLocal<Object>(); \
612 }
613
614 SET(bytes_written_string, data_sent)
615 SET(bytes_read_string, data_received)
616 SET(frames_received_string, frame_count)
617 SET(frames_sent_string, frame_sent)
618 SET(max_concurrent_streams_string, max_concurrent_streams)
619 SET(ping_rtt_string, ping_rtt)
620 SET(stream_average_duration_string, stream_average_duration)
621 SET(stream_count_string, stream_count)
622
623 if (!obj->Set(
624 env->context(),
625 env->type_string(),
626 OneByteString(
627 env->isolate(),
628 (entry.details.session_type == NGHTTP2_SESSION_SERVER)
629 ? "server" : "client")).IsJust()) {
630 return MaybeLocal<Object>();
631 }
632
633 #undef SET
634 return obj;
635 }
636
EmitStatistics()637 void Http2Stream::EmitStatistics() {
638 CHECK_NOT_NULL(session());
639 if (LIKELY(!HasHttp2Observer(env())))
640 return;
641
642 double start = statistics_.start_time / 1e6;
643 double duration = (PERFORMANCE_NOW() / 1e6) - start;
644
645 std::unique_ptr<Http2StreamPerformanceEntry> entry =
646 std::make_unique<Http2StreamPerformanceEntry>(
647 "Http2Stream",
648 start - (node::performance::timeOrigin / 1e6),
649 duration,
650 statistics_);
651
652 env()->SetImmediate([entry = std::move(entry)](Environment* env) {
653 if (HasHttp2Observer(env))
654 entry->Notify(env);
655 });
656 }
657
EmitStatistics()658 void Http2Session::EmitStatistics() {
659 if (LIKELY(!HasHttp2Observer(env())))
660 return;
661
662 double start = statistics_.start_time / 1e6;
663 double duration = (PERFORMANCE_NOW() / 1e6) - start;
664
665 std::unique_ptr<Http2SessionPerformanceEntry> entry =
666 std::make_unique<Http2SessionPerformanceEntry>(
667 "Http2Session",
668 start - (node::performance::timeOrigin / 1e6),
669 duration,
670 statistics_);
671
672 env()->SetImmediate([entry = std::move(entry)](Environment* env) {
673 if (HasHttp2Observer(env))
674 entry->Notify(env);
675 });
676 }
677
678 // Closes the session and frees the associated resources
Close(uint32_t code, bool socket_closed)679 void Http2Session::Close(uint32_t code, bool socket_closed) {
680 Debug(this, "closing session");
681
682 if (is_closing())
683 return;
684 set_closing();
685
686 // Stop reading on the i/o stream
687 if (stream_ != nullptr) {
688 set_reading_stopped();
689 stream_->ReadStop();
690 }
691
692 // If the socket is not closed, then attempt to send a closing GOAWAY
693 // frame. There is no guarantee that this GOAWAY will be received by
694 // the peer but the HTTP/2 spec recommends sending it anyway. We'll
695 // make a best effort.
696 if (!socket_closed) {
697 Debug(this, "terminating session with code %d", code);
698 CHECK_EQ(nghttp2_session_terminate_session(session_.get(), code), 0);
699 SendPendingData();
700 } else if (stream_ != nullptr) {
701 stream_->RemoveStreamListener(this);
702 }
703
704 set_destroyed();
705
706 // If we are writing we will get to make the callback in OnStreamAfterWrite.
707 if (!is_write_in_progress()) {
708 Debug(this, "make done session callback");
709 HandleScope scope(env()->isolate());
710 MakeCallback(env()->ondone_string(), 0, nullptr);
711 if (stream_ != nullptr) {
712 // Start reading again to detect the other end finishing.
713 set_reading_stopped(false);
714 stream_->ReadStart();
715 }
716 }
717
718 // If there are outstanding pings, those will need to be canceled, do
719 // so on the next iteration of the event loop to avoid calling out into
720 // javascript since this may be called during garbage collection.
721 while (BaseObjectPtr<Http2Ping> ping = PopPing()) {
722 ping->DetachFromSession();
723 env()->SetImmediate(
724 [ping = std::move(ping)](Environment* env) {
725 ping->Done(false);
726 });
727 }
728
729 statistics_.end_time = uv_hrtime();
730 EmitStatistics();
731 }
732
733 // Locates an existing known stream by ID. nghttp2 has a similar method
734 // but this is faster and does not fail if the stream is not found.
FindStream(int32_t id)735 BaseObjectPtr<Http2Stream> Http2Session::FindStream(int32_t id) {
736 auto s = streams_.find(id);
737 return s != streams_.end() ? s->second : BaseObjectPtr<Http2Stream>();
738 }
739
CanAddStream()740 bool Http2Session::CanAddStream() {
741 uint32_t maxConcurrentStreams =
742 nghttp2_session_get_local_settings(
743 session_.get(), NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
744 size_t maxSize =
745 std::min(streams_.max_size(), static_cast<size_t>(maxConcurrentStreams));
746 // We can add a new stream so long as we are less than the current
747 // maximum on concurrent streams and there's enough available memory
748 return streams_.size() < maxSize &&
749 has_available_session_memory(sizeof(Http2Stream));
750 }
751
AddStream(Http2Stream* stream)752 void Http2Session::AddStream(Http2Stream* stream) {
753 CHECK_GE(++statistics_.stream_count, 0);
754 streams_[stream->id()] = BaseObjectPtr<Http2Stream>(stream);
755 size_t size = streams_.size();
756 if (size > statistics_.max_concurrent_streams)
757 statistics_.max_concurrent_streams = size;
758 IncrementCurrentSessionMemory(sizeof(*stream));
759 }
760
761
RemoveStream(int32_t id)762 BaseObjectPtr<Http2Stream> Http2Session::RemoveStream(int32_t id) {
763 BaseObjectPtr<Http2Stream> stream;
764 if (streams_.empty())
765 return stream;
766 stream = FindStream(id);
767 if (stream) {
768 streams_.erase(id);
769 DecrementCurrentSessionMemory(sizeof(*stream));
770 }
771 return stream;
772 }
773
774 // Used as one of the Padding Strategy functions. Will attempt to ensure
775 // that the total frame size, including header bytes, are 8-byte aligned.
776 // If maxPayloadLen is smaller than the number of bytes necessary to align,
777 // will return maxPayloadLen instead.
OnDWordAlignedPadding(size_t frameLen, size_t maxPayloadLen)778 ssize_t Http2Session::OnDWordAlignedPadding(size_t frameLen,
779 size_t maxPayloadLen) {
780 size_t r = (frameLen + 9) % 8;
781 if (r == 0) return frameLen; // If already a multiple of 8, return.
782
783 size_t pad = frameLen + (8 - r);
784
785 // If maxPayloadLen happens to be less than the calculated pad length,
786 // use the max instead, even tho this means the frame will not be
787 // aligned.
788 pad = std::min(maxPayloadLen, pad);
789 Debug(this, "using frame size padding: %d", pad);
790 return pad;
791 }
792
793 // Used as one of the Padding Strategy functions. Uses the maximum amount
794 // of padding allowed for the current frame.
OnMaxFrameSizePadding(size_t frameLen, size_t maxPayloadLen)795 ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
796 size_t maxPayloadLen) {
797 Debug(this, "using max frame size padding: %d", maxPayloadLen);
798 return maxPayloadLen;
799 }
800
801 // Write data received from the i/o stream to the underlying nghttp2_session.
802 // On each call to nghttp2_session_mem_recv, nghttp2 will begin calling the
803 // various callback functions. Each of these will typically result in a call
804 // out to JavaScript so this particular function is rather hot and can be
805 // quite expensive. This is a potential performance optimization target later.
ConsumeHTTP2Data()806 void Http2Session::ConsumeHTTP2Data() {
807 CHECK_NOT_NULL(stream_buf_.base);
808 CHECK_LE(stream_buf_offset_, stream_buf_.len);
809 size_t read_len = stream_buf_.len - stream_buf_offset_;
810
811 // multiple side effects.
812 Debug(this, "receiving %d bytes [wants data? %d]",
813 read_len,
814 nghttp2_session_want_read(session_.get()));
815 set_receive_paused(false);
816 custom_recv_error_code_ = nullptr;
817 ssize_t ret =
818 nghttp2_session_mem_recv(session_.get(),
819 reinterpret_cast<uint8_t*>(stream_buf_.base) +
820 stream_buf_offset_,
821 read_len);
822 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
823 CHECK_IMPLIES(custom_recv_error_code_ != nullptr, ret < 0);
824
825 if (is_receive_paused()) {
826 CHECK(is_reading_stopped());
827
828 CHECK_GT(ret, 0);
829 CHECK_LE(static_cast<size_t>(ret), read_len);
830
831 // Mark the remainder of the data as available for later consumption.
832 // Even if all bytes were received, a paused stream may delay the
833 // nghttp2_on_frame_recv_callback which may have an END_STREAM flag.
834 stream_buf_offset_ += ret;
835 goto done;
836 }
837
838 // We are done processing the current input chunk.
839 DecrementCurrentSessionMemory(stream_buf_.len);
840 stream_buf_offset_ = 0;
841 stream_buf_ab_.Reset();
842 stream_buf_allocation_.reset();
843 stream_buf_ = uv_buf_init(nullptr, 0);
844
845 // Send any data that was queued up while processing the received data.
846 if (ret >= 0 && !is_destroyed()) {
847 SendPendingData();
848 }
849
850 done:
851 if (UNLIKELY(ret < 0)) {
852 Isolate* isolate = env()->isolate();
853 Debug(this,
854 "fatal error receiving data: %d (%s)",
855 ret,
856 custom_recv_error_code_ != nullptr ?
857 custom_recv_error_code_ : "(no custom error code)");
858 Local<Value> args[] = {
859 Integer::New(isolate, static_cast<int32_t>(ret)),
860 Null(isolate)
861 };
862 if (custom_recv_error_code_ != nullptr) {
863 args[1] = String::NewFromUtf8(
864 isolate,
865 custom_recv_error_code_,
866 NewStringType::kInternalized).ToLocalChecked();
867 }
868 MakeCallback(
869 env()->http2session_on_error_function(),
870 arraysize(args),
871 args);
872 }
873 }
874
875
GetFrameID(const nghttp2_frame* frame)876 int32_t GetFrameID(const nghttp2_frame* frame) {
877 // If this is a push promise, we want to grab the id of the promised stream
878 return (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
879 frame->push_promise.promised_stream_id :
880 frame->hd.stream_id;
881 }
882
883
884 // Called by nghttp2 at the start of receiving a HEADERS frame. We use this
885 // callback to determine if a new stream is being created or if we are simply
886 // adding a new block of headers to an existing stream. The header pairs
887 // themselves are set in the OnHeaderCallback
OnBeginHeadersCallback(nghttp2_session* handle, const nghttp2_frame* frame, void* user_data)888 int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
889 const nghttp2_frame* frame,
890 void* user_data) {
891 Http2Session* session = static_cast<Http2Session*>(user_data);
892 int32_t id = GetFrameID(frame);
893 Debug(session, "beginning headers for stream %d", id);
894
895 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
896 // The common case is that we're creating a new stream. The less likely
897 // case is that we're receiving a set of trailers
898 if (LIKELY(!stream)) {
899 if (UNLIKELY(!session->CanAddStream() ||
900 Http2Stream::New(session, id, frame->headers.cat) ==
901 nullptr)) {
902 if (session->rejected_stream_count_++ >
903 session->js_fields_->max_rejected_streams)
904 return NGHTTP2_ERR_CALLBACK_FAILURE;
905 // Too many concurrent streams being opened
906 nghttp2_submit_rst_stream(
907 session->session(),
908 NGHTTP2_FLAG_NONE,
909 id,
910 NGHTTP2_ENHANCE_YOUR_CALM);
911 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
912 }
913
914 session->rejected_stream_count_ = 0;
915 } else if (!stream->is_destroyed()) {
916 stream->StartHeaders(frame->headers.cat);
917 }
918 return 0;
919 }
920
921 // Called by nghttp2 for each header name/value pair in a HEADERS block.
922 // This had to have been preceded by a call to OnBeginHeadersCallback so
923 // the Http2Stream is guaranteed to already exist.
OnHeaderCallback(nghttp2_session* handle, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data)924 int Http2Session::OnHeaderCallback(nghttp2_session* handle,
925 const nghttp2_frame* frame,
926 nghttp2_rcbuf* name,
927 nghttp2_rcbuf* value,
928 uint8_t flags,
929 void* user_data) {
930 Http2Session* session = static_cast<Http2Session*>(user_data);
931 int32_t id = GetFrameID(frame);
932 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
933 // If stream is null at this point, either something odd has happened
934 // or the stream was closed locally while header processing was occurring.
935 // either way, do not proceed and close the stream.
936 if (UNLIKELY(!stream))
937 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
938
939 // If the stream has already been destroyed, ignore.
940 if (!stream->is_destroyed() && !stream->AddHeader(name, value, flags)) {
941 // This will only happen if the connected peer sends us more
942 // than the allowed number of header items at any given time
943 stream->SubmitRstStream(NGHTTP2_ENHANCE_YOUR_CALM);
944 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
945 }
946 return 0;
947 }
948
949
950 // Called by nghttp2 when a complete HTTP2 frame has been received. There are
951 // only a handful of frame types that we care about handling here.
OnFrameReceive(nghttp2_session* handle, const nghttp2_frame* frame, void* user_data)952 int Http2Session::OnFrameReceive(nghttp2_session* handle,
953 const nghttp2_frame* frame,
954 void* user_data) {
955 Http2Session* session = static_cast<Http2Session*>(user_data);
956 session->statistics_.frame_count++;
957 Debug(session, "complete frame received: type: %d",
958 frame->hd.type);
959 switch (frame->hd.type) {
960 case NGHTTP2_DATA:
961 return session->HandleDataFrame(frame);
962 case NGHTTP2_PUSH_PROMISE:
963 // Intentional fall-through, handled just like headers frames
964 case NGHTTP2_HEADERS:
965 session->HandleHeadersFrame(frame);
966 break;
967 case NGHTTP2_SETTINGS:
968 session->HandleSettingsFrame(frame);
969 break;
970 case NGHTTP2_PRIORITY:
971 session->HandlePriorityFrame(frame);
972 break;
973 case NGHTTP2_GOAWAY:
974 session->HandleGoawayFrame(frame);
975 break;
976 case NGHTTP2_PING:
977 session->HandlePingFrame(frame);
978 break;
979 case NGHTTP2_ALTSVC:
980 session->HandleAltSvcFrame(frame);
981 break;
982 case NGHTTP2_ORIGIN:
983 session->HandleOriginFrame(frame);
984 break;
985 default:
986 break;
987 }
988 return 0;
989 }
990
OnInvalidFrame(nghttp2_session* handle, const nghttp2_frame* frame, int lib_error_code, void* user_data)991 int Http2Session::OnInvalidFrame(nghttp2_session* handle,
992 const nghttp2_frame* frame,
993 int lib_error_code,
994 void* user_data) {
995 Http2Session* session = static_cast<Http2Session*>(user_data);
996 const uint32_t max_invalid_frames = session->js_fields_->max_invalid_frames;
997
998 Debug(session,
999 "invalid frame received (%u/%u), code: %d",
1000 session->invalid_frame_count_,
1001 max_invalid_frames,
1002 lib_error_code);
1003 if (session->invalid_frame_count_++ > max_invalid_frames) {
1004 session->custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";
1005 return 1;
1006 }
1007
1008 // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error
1009 if (nghttp2_is_fatal(lib_error_code) ||
1010 lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) {
1011 Environment* env = session->env();
1012 Isolate* isolate = env->isolate();
1013 HandleScope scope(isolate);
1014 Local<Context> context = env->context();
1015 Context::Scope context_scope(context);
1016 Local<Value> arg = Integer::New(isolate, lib_error_code);
1017 session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1018 }
1019 return 0;
1020 }
1021
1022 // Remove the headers reference.
1023 // Implicitly calls nghttp2_rcbuf_decref
DecrefHeaders(const nghttp2_frame* frame)1024 void Http2Session::DecrefHeaders(const nghttp2_frame* frame) {
1025 int32_t id = GetFrameID(frame);
1026 BaseObjectPtr<Http2Stream> stream = FindStream(id);
1027
1028 if (stream && !stream->is_destroyed() && stream->headers_count() > 0) {
1029 Debug(this, "freeing headers for stream %d", id);
1030 stream->ClearHeaders();
1031 CHECK_EQ(stream->headers_count(), 0);
1032 DecrementCurrentSessionMemory(stream->current_headers_length_);
1033 stream->current_headers_length_ = 0;
1034 }
1035 }
1036
TranslateNghttp2ErrorCode(const int libErrorCode)1037 uint32_t TranslateNghttp2ErrorCode(const int libErrorCode) {
1038 switch (libErrorCode) {
1039 case NGHTTP2_ERR_STREAM_CLOSED:
1040 return NGHTTP2_STREAM_CLOSED;
1041 case NGHTTP2_ERR_HEADER_COMP:
1042 return NGHTTP2_COMPRESSION_ERROR;
1043 case NGHTTP2_ERR_FRAME_SIZE_ERROR:
1044 return NGHTTP2_FRAME_SIZE_ERROR;
1045 case NGHTTP2_ERR_FLOW_CONTROL:
1046 return NGHTTP2_FLOW_CONTROL_ERROR;
1047 case NGHTTP2_ERR_REFUSED_STREAM:
1048 return NGHTTP2_REFUSED_STREAM;
1049 case NGHTTP2_ERR_PROTO:
1050 case NGHTTP2_ERR_HTTP_HEADER:
1051 case NGHTTP2_ERR_HTTP_MESSAGING:
1052 return NGHTTP2_PROTOCOL_ERROR;
1053 default:
1054 return NGHTTP2_INTERNAL_ERROR;
1055 }
1056 }
1057
1058 // If nghttp2 is unable to send a queued up frame, it will call this callback
1059 // to let us know. If the failure occurred because we are in the process of
1060 // closing down the session or stream, we go ahead and ignore it. We don't
1061 // really care about those and there's nothing we can reasonably do about it
1062 // anyway. Other types of failures are reported up to JavaScript. This should
1063 // be exceedingly rare.
OnFrameNotSent(nghttp2_session* handle, const nghttp2_frame* frame, int error_code, void* user_data)1064 int Http2Session::OnFrameNotSent(nghttp2_session* handle,
1065 const nghttp2_frame* frame,
1066 int error_code,
1067 void* user_data) {
1068 Http2Session* session = static_cast<Http2Session*>(user_data);
1069 Environment* env = session->env();
1070 Debug(session, "frame type %d was not sent, code: %d",
1071 frame->hd.type, error_code);
1072
1073 // Do not report if the frame was not sent due to the session closing
1074 if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
1075 error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1076 error_code == NGHTTP2_ERR_STREAM_CLOSING) {
1077 // Nghttp2 contains header limit of 65536. When this value is exceeded the
1078 // pipeline is stopped and we should remove the current headers reference
1079 // to destroy the session completely.
1080 // Further information see: https://github.com/nodejs/node/issues/35233
1081 session->DecrefHeaders(frame);
1082 return 0;
1083 }
1084
1085 Isolate* isolate = env->isolate();
1086 HandleScope scope(isolate);
1087 Local<Context> context = env->context();
1088 Context::Scope context_scope(context);
1089
1090 Local<Value> argv[3] = {
1091 Integer::New(isolate, frame->hd.stream_id),
1092 Integer::New(isolate, frame->hd.type),
1093 Integer::New(isolate, TranslateNghttp2ErrorCode(error_code))
1094 };
1095 session->MakeCallback(
1096 env->http2session_on_frame_error_function(),
1097 arraysize(argv), argv);
1098 return 0;
1099 }
1100
OnFrameSent(nghttp2_session* handle, const nghttp2_frame* frame, void* user_data)1101 int Http2Session::OnFrameSent(nghttp2_session* handle,
1102 const nghttp2_frame* frame,
1103 void* user_data) {
1104 Http2Session* session = static_cast<Http2Session*>(user_data);
1105 session->statistics_.frame_sent += 1;
1106 return 0;
1107 }
1108
1109 // Called by nghttp2 when a stream closes.
OnStreamClose(nghttp2_session* handle, int32_t id, uint32_t code, void* user_data)1110 int Http2Session::OnStreamClose(nghttp2_session* handle,
1111 int32_t id,
1112 uint32_t code,
1113 void* user_data) {
1114 Http2Session* session = static_cast<Http2Session*>(user_data);
1115 Environment* env = session->env();
1116 Isolate* isolate = env->isolate();
1117 HandleScope scope(isolate);
1118 Local<Context> context = env->context();
1119 Context::Scope context_scope(context);
1120 Debug(session, "stream %d closed with code: %d", id, code);
1121 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1122 // Intentionally ignore the callback if the stream does not exist or has
1123 // already been destroyed
1124 if (!stream || stream->is_destroyed())
1125 return 0;
1126
1127 stream->Close(code);
1128
1129 // It is possible for the stream close to occur before the stream is
1130 // ever passed on to the javascript side. If that happens, the callback
1131 // will return false.
1132 if (env->can_call_into_js()) {
1133 Local<Value> arg = Integer::NewFromUnsigned(isolate, code);
1134 MaybeLocal<Value> answer = stream->MakeCallback(
1135 env->http2session_on_stream_close_function(), 1, &arg);
1136 if (answer.IsEmpty() || answer.ToLocalChecked()->IsFalse()) {
1137 // Skip to destroy
1138 stream->Destroy();
1139 }
1140 }
1141 return 0;
1142 }
1143
1144 // Called by nghttp2 when an invalid header has been received. For now, we
1145 // ignore these. If this callback was not provided, nghttp2 would handle
1146 // invalid headers strictly and would shut down the stream. We are intentionally
1147 // being more lenient here although we may want to revisit this choice later.
OnInvalidHeader(nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data)1148 int Http2Session::OnInvalidHeader(nghttp2_session* session,
1149 const nghttp2_frame* frame,
1150 nghttp2_rcbuf* name,
1151 nghttp2_rcbuf* value,
1152 uint8_t flags,
1153 void* user_data) {
1154 // Ignore invalid header fields by default.
1155 return 0;
1156 }
1157
1158 // When nghttp2 receives a DATA frame, it will deliver the data payload to
1159 // us in discrete chunks. We push these into a linked list stored in the
1160 // Http2Sttream which is flushed out to JavaScript as quickly as possible.
1161 // This can be a particularly hot path.
OnDataChunkReceived(nghttp2_session* handle, uint8_t flags, int32_t id, const uint8_t* data, size_t len, void* user_data)1162 int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1163 uint8_t flags,
1164 int32_t id,
1165 const uint8_t* data,
1166 size_t len,
1167 void* user_data) {
1168 Http2Session* session = static_cast<Http2Session*>(user_data);
1169 Debug(session, "buffering data chunk for stream %d, size: "
1170 "%d, flags: %d", id, len, flags);
1171 Environment* env = session->env();
1172 HandleScope scope(env->isolate());
1173
1174 // We should never actually get a 0-length chunk so this check is
1175 // only a precaution at this point.
1176 if (len == 0)
1177 return 0;
1178
1179 // Notify nghttp2 that we've consumed a chunk of data on the connection
1180 // so that it can send a WINDOW_UPDATE frame. This is a critical part of
1181 // the flow control process in http2
1182 CHECK_EQ(nghttp2_session_consume_connection(handle, len), 0);
1183 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
1184
1185 // If the stream has been destroyed, ignore this chunk
1186 if (!stream || stream->is_destroyed())
1187 return 0;
1188
1189 stream->statistics_.received_bytes += len;
1190
1191 // Repeatedly ask the stream's owner for memory, and copy the read data
1192 // into those buffers.
1193 // The typical case is actually the exception here; Http2StreamListeners
1194 // know about the HTTP2 session associated with this stream, so they know
1195 // about the larger from-socket read buffer, so they do not require copying.
1196 do {
1197 uv_buf_t buf = stream->EmitAlloc(len);
1198 ssize_t avail = len;
1199 if (static_cast<ssize_t>(buf.len) < avail)
1200 avail = buf.len;
1201
1202 // `buf.base == nullptr` is the default Http2StreamListener's way
1203 // of saying that it wants a pointer to the raw original.
1204 // Since it has access to the original socket buffer from which the data
1205 // was read in the first place, it can use that to minimize ArrayBuffer
1206 // allocations.
1207 if (LIKELY(buf.base == nullptr))
1208 buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data));
1209 else
1210 memcpy(buf.base, data, avail);
1211 data += avail;
1212 len -= avail;
1213 stream->EmitRead(avail, buf);
1214
1215 // If the stream owner (e.g. the JS Http2Stream) wants more data, just
1216 // tell nghttp2 that all data has been consumed. Otherwise, defer until
1217 // more data is being requested.
1218 if (stream->is_reading())
1219 nghttp2_session_consume_stream(handle, id, avail);
1220 else
1221 stream->inbound_consumed_data_while_paused_ += avail;
1222
1223 // If we have a gathered a lot of data for output, try sending it now.
1224 if (session->outgoing_length_ > 4096 ||
1225 stream->available_outbound_length_ > 4096) {
1226 session->SendPendingData();
1227 }
1228 } while (len != 0);
1229
1230 // If we are currently waiting for a write operation to finish, we should
1231 // tell nghttp2 that we want to wait before we process more input data.
1232 if (session->is_write_in_progress()) {
1233 CHECK(session->is_reading_stopped());
1234 session->set_receive_paused();
1235 Debug(session, "receive paused");
1236 return NGHTTP2_ERR_PAUSE;
1237 }
1238
1239 return 0;
1240 }
1241
1242 // Called by nghttp2 when it needs to determine how much padding to use in
1243 // a DATA or HEADERS frame.
OnSelectPadding(nghttp2_session* handle, const nghttp2_frame* frame, size_t maxPayloadLen, void* user_data)1244 ssize_t Http2Session::OnSelectPadding(nghttp2_session* handle,
1245 const nghttp2_frame* frame,
1246 size_t maxPayloadLen,
1247 void* user_data) {
1248 Http2Session* session = static_cast<Http2Session*>(user_data);
1249 ssize_t padding = frame->hd.length;
1250
1251 switch (session->padding_strategy_) {
1252 case PADDING_STRATEGY_NONE:
1253 // Fall-through
1254 break;
1255 case PADDING_STRATEGY_MAX:
1256 padding = session->OnMaxFrameSizePadding(padding, maxPayloadLen);
1257 break;
1258 case PADDING_STRATEGY_ALIGNED:
1259 padding = session->OnDWordAlignedPadding(padding, maxPayloadLen);
1260 break;
1261 }
1262 return padding;
1263 }
1264
1265 // We use this currently to determine when an attempt is made to use the http2
1266 // protocol with a non-http2 peer.
OnNghttpError(nghttp2_session* handle, int lib_error_code, const char* message, size_t len, void* user_data)1267 int Http2Session::OnNghttpError(nghttp2_session* handle,
1268 int lib_error_code,
1269 const char* message,
1270 size_t len,
1271 void* user_data) {
1272 // Unfortunately, this is currently the only way for us to know if
1273 // the session errored because the peer is not an http2 peer.
1274 Http2Session* session = static_cast<Http2Session*>(user_data);
1275 Debug(session, "Error '%s'", message);
1276 if (lib_error_code == NGHTTP2_ERR_SETTINGS_EXPECTED) {
1277 Environment* env = session->env();
1278 Isolate* isolate = env->isolate();
1279 HandleScope scope(isolate);
1280 Local<Context> context = env->context();
1281 Context::Scope context_scope(context);
1282 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1283 session->MakeCallback(env->http2session_on_error_function(), 1, &arg);
1284 }
1285 return 0;
1286 }
1287
OnStreamAlloc(size_t size)1288 uv_buf_t Http2StreamListener::OnStreamAlloc(size_t size) {
1289 // See the comments in Http2Session::OnDataChunkReceived
1290 // (which is the only possible call site for this method).
1291 return uv_buf_init(nullptr, size);
1292 }
1293
OnStreamRead(ssize_t nread, const uv_buf_t& buf)1294 void Http2StreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
1295 Http2Stream* stream = static_cast<Http2Stream*>(stream_);
1296 Http2Session* session = stream->session();
1297 Environment* env = stream->env();
1298 HandleScope handle_scope(env->isolate());
1299 Context::Scope context_scope(env->context());
1300
1301 if (nread < 0) {
1302 PassReadErrorToPreviousListener(nread);
1303 return;
1304 }
1305
1306 Local<ArrayBuffer> ab;
1307 if (session->stream_buf_ab_.IsEmpty()) {
1308 ab = ArrayBuffer::New(env->isolate(),
1309 std::move(session->stream_buf_allocation_));
1310 session->stream_buf_ab_.Reset(env->isolate(), ab);
1311 } else {
1312 ab = PersistentToLocal::Strong(session->stream_buf_ab_);
1313 }
1314
1315 // There is a single large array buffer for the entire data read from the
1316 // network; create a slice of that array buffer and emit it as the
1317 // received data buffer.
1318 size_t offset = buf.base - session->stream_buf_.base;
1319
1320 // Verify that the data offset is inside the current read buffer.
1321 CHECK_GE(offset, session->stream_buf_offset_);
1322 CHECK_LE(offset, session->stream_buf_.len);
1323 CHECK_LE(offset + buf.len, session->stream_buf_.len);
1324
1325 stream->CallJSOnreadMethod(nread, ab, offset);
1326 }
1327
1328
1329 // Called by OnFrameReceived to notify JavaScript land that a complete
1330 // HEADERS frame has been received and processed. This method converts the
1331 // received headers into a JavaScript array and pushes those out to JS.
HandleHeadersFrame(const nghttp2_frame* frame)1332 void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
1333 Isolate* isolate = env()->isolate();
1334 HandleScope scope(isolate);
1335 Local<Context> context = env()->context();
1336 Context::Scope context_scope(context);
1337
1338 int32_t id = GetFrameID(frame);
1339 Debug(this, "handle headers frame for stream %d", id);
1340 BaseObjectPtr<Http2Stream> stream = FindStream(id);
1341
1342 // If the stream has already been destroyed, ignore.
1343 if (!stream || stream->is_destroyed())
1344 return;
1345
1346 // The headers are stored as a vector of Http2Header instances.
1347 // The following converts that into a JS array with the structure:
1348 // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
1349 // That array is passed up to the JS layer and converted into an Object form
1350 // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
1351 // this way for performance reasons (it's faster to generate and pass an
1352 // array than it is to generate and pass the object).
1353
1354 MaybeStackBuffer<Local<Value>, 64> headers_v(stream->headers_count() * 2);
1355 MaybeStackBuffer<Local<Value>, 32> sensitive_v(stream->headers_count());
1356 size_t sensitive_count = 0;
1357
1358 stream->TransferHeaders([&](const Http2Header& header, size_t i) {
1359 headers_v[i * 2] = header.GetName(this).ToLocalChecked();
1360 headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked();
1361 if (header.flags() & NGHTTP2_NV_FLAG_NO_INDEX)
1362 sensitive_v[sensitive_count++] = headers_v[i * 2];
1363 });
1364 CHECK_EQ(stream->headers_count(), 0);
1365
1366 DecrementCurrentSessionMemory(stream->current_headers_length_);
1367 stream->current_headers_length_ = 0;
1368
1369 Local<Value> args[] = {
1370 stream->object(),
1371 Integer::New(isolate, id),
1372 Integer::New(isolate, stream->headers_category()),
1373 Integer::New(isolate, frame->hd.flags),
1374 Array::New(isolate, headers_v.out(), headers_v.length()),
1375 Array::New(isolate, sensitive_v.out(), sensitive_count),
1376 };
1377 MakeCallback(env()->http2session_on_headers_function(),
1378 arraysize(args), args);
1379 }
1380
1381
1382 // Called by OnFrameReceived when a complete PRIORITY frame has been
1383 // received. Notifies JS land about the priority change. Note that priorities
1384 // are considered advisory only, so this has no real effect other than to
1385 // simply let user code know that the priority has changed.
HandlePriorityFrame(const nghttp2_frame* frame)1386 void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1387 if (js_fields_->priority_listener_count == 0) return;
1388 Isolate* isolate = env()->isolate();
1389 HandleScope scope(isolate);
1390 Local<Context> context = env()->context();
1391 Context::Scope context_scope(context);
1392
1393 nghttp2_priority priority_frame = frame->priority;
1394 int32_t id = GetFrameID(frame);
1395 Debug(this, "handle priority frame for stream %d", id);
1396 // Priority frame stream ID should never be <= 0. nghttp2 handles this for us
1397 nghttp2_priority_spec spec = priority_frame.pri_spec;
1398
1399 Local<Value> argv[4] = {
1400 Integer::New(isolate, id),
1401 Integer::New(isolate, spec.stream_id),
1402 Integer::New(isolate, spec.weight),
1403 Boolean::New(isolate, spec.exclusive)
1404 };
1405 MakeCallback(env()->http2session_on_priority_function(),
1406 arraysize(argv), argv);
1407 }
1408
1409
1410 // Called by OnFrameReceived when a complete DATA frame has been received.
1411 // If we know that this was the last DATA frame (because the END_STREAM flag
1412 // is set), then we'll terminate the readable side of the StreamBase.
HandleDataFrame(const nghttp2_frame* frame)1413 int Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1414 int32_t id = GetFrameID(frame);
1415 Debug(this, "handling data frame for stream %d", id);
1416 BaseObjectPtr<Http2Stream> stream = FindStream(id);
1417
1418 if (stream &&
1419 !stream->is_destroyed() &&
1420 frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1421 stream->EmitRead(UV_EOF);
1422 } else if (frame->hd.length == 0) {
1423 if (invalid_frame_count_++ > js_fields_->max_invalid_frames) {
1424 custom_recv_error_code_ = "ERR_HTTP2_TOO_MANY_INVALID_FRAMES";
1425 Debug(this, "rejecting empty-frame-without-END_STREAM flood\n");
1426 // Consider a flood of 0-length frames without END_STREAM an error.
1427 return 1;
1428 }
1429 }
1430 return 0;
1431 }
1432
1433
1434 // Called by OnFrameReceived when a complete GOAWAY frame has been received.
HandleGoawayFrame(const nghttp2_frame* frame)1435 void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
1436 Isolate* isolate = env()->isolate();
1437 HandleScope scope(isolate);
1438 Local<Context> context = env()->context();
1439 Context::Scope context_scope(context);
1440
1441 nghttp2_goaway goaway_frame = frame->goaway;
1442 Debug(this, "handling goaway frame");
1443
1444 Local<Value> argv[3] = {
1445 Integer::NewFromUnsigned(isolate, goaway_frame.error_code),
1446 Integer::New(isolate, goaway_frame.last_stream_id),
1447 Undefined(isolate)
1448 };
1449
1450 size_t length = goaway_frame.opaque_data_len;
1451 if (length > 0) {
1452 // If the copy fails for any reason here, we just ignore it.
1453 // The additional goaway data is completely optional and we
1454 // shouldn't fail if we're not able to process it.
1455 argv[2] = Buffer::Copy(isolate,
1456 reinterpret_cast<char*>(goaway_frame.opaque_data),
1457 length).ToLocalChecked();
1458 }
1459
1460 MakeCallback(env()->http2session_on_goaway_data_function(),
1461 arraysize(argv), argv);
1462 }
1463
1464 // Called by OnFrameReceived when a complete ALTSVC frame has been received.
HandleAltSvcFrame(const nghttp2_frame* frame)1465 void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1466 if (!(js_fields_->bitfield & (1 << kSessionHasAltsvcListeners))) return;
1467 Isolate* isolate = env()->isolate();
1468 HandleScope scope(isolate);
1469 Local<Context> context = env()->context();
1470 Context::Scope context_scope(context);
1471
1472 int32_t id = GetFrameID(frame);
1473
1474 nghttp2_extension ext = frame->ext;
1475 nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload);
1476 Debug(this, "handling altsvc frame");
1477
1478 Local<Value> argv[3] = {
1479 Integer::New(isolate, id),
1480 OneByteString(isolate, altsvc->origin, altsvc->origin_len),
1481 OneByteString(isolate, altsvc->field_value, altsvc->field_value_len)
1482 };
1483
1484 MakeCallback(env()->http2session_on_altsvc_function(),
1485 arraysize(argv), argv);
1486 }
1487
HandleOriginFrame(const nghttp2_frame* frame)1488 void Http2Session::HandleOriginFrame(const nghttp2_frame* frame) {
1489 Isolate* isolate = env()->isolate();
1490 HandleScope scope(isolate);
1491 Local<Context> context = env()->context();
1492 Context::Scope context_scope(context);
1493
1494 Debug(this, "handling origin frame");
1495
1496 nghttp2_extension ext = frame->ext;
1497 nghttp2_ext_origin* origin = static_cast<nghttp2_ext_origin*>(ext.payload);
1498
1499 size_t nov = origin->nov;
1500 std::vector<Local<Value>> origin_v(nov);
1501
1502 for (size_t i = 0; i < nov; ++i) {
1503 const nghttp2_origin_entry& entry = origin->ov[i];
1504 origin_v[i] = OneByteString(isolate, entry.origin, entry.origin_len);
1505 }
1506 Local<Value> holder = Array::New(isolate, origin_v.data(), origin_v.size());
1507 MakeCallback(env()->http2session_on_origin_function(), 1, &holder);
1508 }
1509
1510 // Called by OnFrameReceived when a complete PING frame has been received.
HandlePingFrame(const nghttp2_frame* frame)1511 void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
1512 Isolate* isolate = env()->isolate();
1513 HandleScope scope(isolate);
1514 Local<Context> context = env()->context();
1515 Context::Scope context_scope(context);
1516 Local<Value> arg;
1517 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1518 if (ack) {
1519 BaseObjectPtr<Http2Ping> ping = PopPing();
1520
1521 if (!ping) {
1522 // PING Ack is unsolicited. Treat as a connection error. The HTTP/2
1523 // spec does not require this, but there is no legitimate reason to
1524 // receive an unsolicited PING ack on a connection. Either the peer
1525 // is buggy or malicious, and we're not going to tolerate such
1526 // nonsense.
1527 arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1528 MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1529 return;
1530 }
1531
1532 ping->Done(true, frame->ping.opaque_data);
1533 return;
1534 }
1535
1536 if (!(js_fields_->bitfield & (1 << kSessionHasPingListeners))) return;
1537 // Notify the session that a ping occurred
1538 arg = Buffer::Copy(
1539 env(),
1540 reinterpret_cast<const char*>(frame->ping.opaque_data),
1541 8).ToLocalChecked();
1542 MakeCallback(env()->http2session_on_ping_function(), 1, &arg);
1543 }
1544
1545 // Called by OnFrameReceived when a complete SETTINGS frame has been received.
HandleSettingsFrame(const nghttp2_frame* frame)1546 void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
1547 bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
1548 if (!ack) {
1549 js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1550 if (!(js_fields_->bitfield & (1 << kSessionHasRemoteSettingsListeners)))
1551 return;
1552 // This is not a SETTINGS acknowledgement, notify and return
1553 MakeCallback(env()->http2session_on_settings_function(), 0, nullptr);
1554 return;
1555 }
1556
1557 // If this is an acknowledgement, we should have an Http2Settings
1558 // object for it.
1559 BaseObjectPtr<Http2Settings> settings = PopSettings();
1560 if (settings) {
1561 settings->Done(true);
1562 return;
1563 }
1564 // SETTINGS Ack is unsolicited. Treat as a connection error. The HTTP/2
1565 // spec does not require this, but there is no legitimate reason to
1566 // receive an unsolicited SETTINGS ack on a connection. Either the peer
1567 // is buggy or malicious, and we're not going to tolerate such
1568 // nonsense.
1569 // Note that nghttp2 currently prevents this from happening for SETTINGS
1570 // frames, so this block is purely defensive just in case that behavior
1571 // changes. Specifically, unlike unsolicited PING acks, unsolicited
1572 // SETTINGS acks should *never* make it this far.
1573 Isolate* isolate = env()->isolate();
1574 HandleScope scope(isolate);
1575 Local<Context> context = env()->context();
1576 Context::Scope context_scope(context);
1577 Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
1578 MakeCallback(env()->http2session_on_error_function(), 1, &arg);
1579 }
1580
1581 // Callback used when data has been written to the stream.
OnStreamAfterWrite(WriteWrap* w, int status)1582 void Http2Session::OnStreamAfterWrite(WriteWrap* w, int status) {
1583 Debug(this, "write finished with status %d", status);
1584
1585 CHECK(is_write_in_progress());
1586 set_write_in_progress(false);
1587
1588 // Inform all pending writes about their completion.
1589 ClearOutgoing(status);
1590
1591 if (is_reading_stopped() &&
1592 !is_write_in_progress() &&
1593 nghttp2_session_want_read(session_.get())) {
1594 set_reading_stopped(false);
1595 stream_->ReadStart();
1596 }
1597
1598 if (is_destroyed()) {
1599 HandleScope scope(env()->isolate());
1600 MakeCallback(env()->ondone_string(), 0, nullptr);
1601 if (stream_ != nullptr) {
1602 // Start reading again to detect the other end finishing.
1603 set_reading_stopped(false);
1604 stream_->ReadStart();
1605 }
1606 return;
1607 }
1608
1609 // If there is more incoming data queued up, consume it.
1610 if (stream_buf_offset_ > 0) {
1611 ConsumeHTTP2Data();
1612 }
1613
1614 if (!is_write_scheduled() && !is_destroyed()) {
1615 // Schedule a new write if nghttp2 wants to send data.
1616 MaybeScheduleWrite();
1617 }
1618 }
1619
1620 // If the underlying nghttp2_session struct has data pending in its outbound
1621 // queue, MaybeScheduleWrite will schedule a SendPendingData() call to occur
1622 // on the next iteration of the Node.js event loop (using the SetImmediate
1623 // queue), but only if a write has not already been scheduled.
MaybeScheduleWrite()1624 void Http2Session::MaybeScheduleWrite() {
1625 CHECK(!is_write_scheduled());
1626 if (UNLIKELY(!session_))
1627 return;
1628
1629 if (nghttp2_session_want_write(session_.get())) {
1630 HandleScope handle_scope(env()->isolate());
1631 Debug(this, "scheduling write");
1632 set_write_scheduled();
1633 BaseObjectPtr<Http2Session> strong_ref{this};
1634 env()->SetImmediate([this, strong_ref](Environment* env) {
1635 if (!session_ || !is_write_scheduled()) {
1636 // This can happen e.g. when a stream was reset before this turn
1637 // of the event loop, in which case SendPendingData() is called early,
1638 // or the session was destroyed in the meantime.
1639 return;
1640 }
1641
1642 // Sending data may call arbitrary JS code, so keep track of
1643 // async context.
1644 if (env->can_call_into_js()) {
1645 HandleScope handle_scope(env->isolate());
1646 InternalCallbackScope callback_scope(this);
1647 SendPendingData();
1648 }
1649 });
1650 }
1651 }
1652
MaybeStopReading()1653 void Http2Session::MaybeStopReading() {
1654 // If the session is already closing we don't want to stop reading as we want
1655 // to detect when the other peer is actually closed.
1656 if (is_reading_stopped() || is_closing()) return;
1657 int want_read = nghttp2_session_want_read(session_.get());
1658 Debug(this, "wants read? %d", want_read);
1659 if (want_read == 0 || is_write_in_progress()) {
1660 set_reading_stopped();
1661 stream_->ReadStop();
1662 }
1663 }
1664
1665 // Unset the sending state, finish up all current writes, and reset
1666 // storage for data and metadata that was associated with these writes.
ClearOutgoing(int status)1667 void Http2Session::ClearOutgoing(int status) {
1668 CHECK(is_sending());
1669
1670 set_sending(false);
1671
1672 if (!outgoing_buffers_.empty()) {
1673 outgoing_storage_.clear();
1674 outgoing_length_ = 0;
1675
1676 std::vector<NgHttp2StreamWrite> current_outgoing_buffers_;
1677 current_outgoing_buffers_.swap(outgoing_buffers_);
1678 for (const NgHttp2StreamWrite& wr : current_outgoing_buffers_) {
1679 BaseObjectPtr<AsyncWrap> wrap = std::move(wr.req_wrap);
1680 if (wrap) {
1681 // TODO(addaleax): Pass `status` instead of 0, so that we actually error
1682 // out with the error from the write to the underlying protocol,
1683 // if one occurred.
1684 WriteWrap::FromObject(wrap)->Done(0);
1685 }
1686 }
1687 }
1688
1689 // Now that we've finished sending queued data, if there are any pending
1690 // RstStreams we should try sending again and then flush them one by one.
1691 if (!pending_rst_streams_.empty()) {
1692 std::vector<int32_t> current_pending_rst_streams;
1693 pending_rst_streams_.swap(current_pending_rst_streams);
1694
1695 SendPendingData();
1696
1697 for (int32_t stream_id : current_pending_rst_streams) {
1698 BaseObjectPtr<Http2Stream> stream = FindStream(stream_id);
1699 if (LIKELY(stream))
1700 stream->FlushRstStream();
1701 }
1702 }
1703 }
1704
PushOutgoingBuffer(NgHttp2StreamWrite&& write)1705 void Http2Session::PushOutgoingBuffer(NgHttp2StreamWrite&& write) {
1706 outgoing_length_ += write.buf.len;
1707 outgoing_buffers_.emplace_back(std::move(write));
1708 }
1709
1710 // Queue a given block of data for sending. This always creates a copy,
1711 // so it is used for the cases in which nghttp2 requests sending of a
1712 // small chunk of data.
CopyDataIntoOutgoing(const uint8_t* src, size_t src_length)1713 void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1714 size_t offset = outgoing_storage_.size();
1715 outgoing_storage_.resize(offset + src_length);
1716 memcpy(&outgoing_storage_[offset], src, src_length);
1717
1718 // Store with a base of `nullptr` initially, since future resizes
1719 // of the outgoing_buffers_ vector may invalidate the pointer.
1720 // The correct base pointers will be set later, before writing to the
1721 // underlying socket.
1722 PushOutgoingBuffer(NgHttp2StreamWrite {
1723 uv_buf_init(nullptr, src_length)
1724 });
1725 }
1726
1727 // Prompts nghttp2 to begin serializing it's pending data and pushes each
1728 // chunk out to the i/o socket to be sent. This is a particularly hot method
1729 // that will generally be called at least twice be event loop iteration.
1730 // This is a potential performance optimization target later.
1731 // Returns non-zero value if a write is already in progress.
SendPendingData()1732 uint8_t Http2Session::SendPendingData() {
1733 Debug(this, "sending pending data");
1734 // Do not attempt to send data on the socket if the destroying flag has
1735 // been set. That means everything is shutting down and the socket
1736 // will not be usable.
1737 if (is_destroyed())
1738 return 0;
1739 set_write_scheduled(false);
1740
1741 // SendPendingData should not be called recursively.
1742 if (is_sending())
1743 return 1;
1744 // This is cleared by ClearOutgoing().
1745 set_sending();
1746
1747 ssize_t src_length;
1748 const uint8_t* src;
1749
1750 CHECK(outgoing_buffers_.empty());
1751 CHECK(outgoing_storage_.empty());
1752
1753 // Part One: Gather data from nghttp2
1754
1755 while ((src_length = nghttp2_session_mem_send(session_.get(), &src)) > 0) {
1756 Debug(this, "nghttp2 has %d bytes to send", src_length);
1757 CopyDataIntoOutgoing(src, src_length);
1758 }
1759
1760 CHECK_NE(src_length, NGHTTP2_ERR_NOMEM);
1761
1762 if (stream_ == nullptr) {
1763 // It would seem nice to bail out earlier, but `nghttp2_session_mem_send()`
1764 // does take care of things like closing the individual streams after
1765 // a socket has been torn down, so we still need to call it.
1766 ClearOutgoing(UV_ECANCELED);
1767 return 0;
1768 }
1769
1770 // Part Two: Pass Data to the underlying stream
1771
1772 size_t count = outgoing_buffers_.size();
1773 if (count == 0) {
1774 ClearOutgoing(0);
1775 return 0;
1776 }
1777 MaybeStackBuffer<uv_buf_t, 32> bufs;
1778 bufs.AllocateSufficientStorage(count);
1779
1780 // Set the buffer base pointers for copied data that ended up in the
1781 // sessions's own storage since it might have shifted around during gathering.
1782 // (Those are marked by having .base == nullptr.)
1783 size_t offset = 0;
1784 size_t i = 0;
1785 for (const NgHttp2StreamWrite& write : outgoing_buffers_) {
1786 statistics_.data_sent += write.buf.len;
1787 if (write.buf.base == nullptr) {
1788 bufs[i++] = uv_buf_init(
1789 reinterpret_cast<char*>(outgoing_storage_.data() + offset),
1790 write.buf.len);
1791 offset += write.buf.len;
1792 } else {
1793 bufs[i++] = write.buf;
1794 }
1795 }
1796
1797 chunks_sent_since_last_write_++;
1798
1799 CHECK(!is_write_in_progress());
1800 set_write_in_progress();
1801 StreamWriteResult res = underlying_stream()->Write(*bufs, count);
1802 if (!res.async) {
1803 set_write_in_progress(false);
1804 ClearOutgoing(res.err);
1805 }
1806
1807 MaybeStopReading();
1808
1809 return 0;
1810 }
1811
1812
1813 // This callback is called from nghttp2 when it wants to send DATA frames for a
1814 // given Http2Stream, when we set the `NGHTTP2_DATA_FLAG_NO_COPY` flag earlier
1815 // in the Http2Stream::Provider::Stream::OnRead callback.
1816 // We take the write information directly out of the stream's data queue.
OnSendData( nghttp2_session* session_, nghttp2_frame* frame, const uint8_t* framehd, size_t length, nghttp2_data_source* source, void* user_data)1817 int Http2Session::OnSendData(
1818 nghttp2_session* session_,
1819 nghttp2_frame* frame,
1820 const uint8_t* framehd,
1821 size_t length,
1822 nghttp2_data_source* source,
1823 void* user_data) {
1824 Http2Session* session = static_cast<Http2Session*>(user_data);
1825 BaseObjectPtr<Http2Stream> stream = session->FindStream(frame->hd.stream_id);
1826 if (!stream) return 0;
1827
1828 // Send the frame header + a byte that indicates padding length.
1829 session->CopyDataIntoOutgoing(framehd, 9);
1830 if (frame->data.padlen > 0) {
1831 uint8_t padding_byte = frame->data.padlen - 1;
1832 CHECK_EQ(padding_byte, frame->data.padlen - 1);
1833 session->CopyDataIntoOutgoing(&padding_byte, 1);
1834 }
1835
1836 Debug(session, "nghttp2 has %d bytes to send directly", length);
1837 while (length > 0) {
1838 // nghttp2 thinks that there is data available (length > 0), which means
1839 // we told it so, which means that we *should* have data available.
1840 CHECK(!stream->queue_.empty());
1841
1842 NgHttp2StreamWrite& write = stream->queue_.front();
1843 if (write.buf.len <= length) {
1844 // This write does not suffice by itself, so we can consume it completely.
1845 length -= write.buf.len;
1846 session->PushOutgoingBuffer(std::move(write));
1847 stream->queue_.pop();
1848 continue;
1849 }
1850
1851 // Slice off `length` bytes of the first write in the queue.
1852 session->PushOutgoingBuffer(NgHttp2StreamWrite {
1853 uv_buf_init(write.buf.base, length)
1854 });
1855 write.buf.base += length;
1856 write.buf.len -= length;
1857 break;
1858 }
1859
1860 if (frame->data.padlen > 0) {
1861 // Send padding if that was requested.
1862 session->PushOutgoingBuffer(NgHttp2StreamWrite {
1863 uv_buf_init(const_cast<char*>(zero_bytes_256), frame->data.padlen - 1)
1864 });
1865 }
1866
1867 return 0;
1868 }
1869
1870 // Creates a new Http2Stream and submits a new http2 request.
SubmitRequest( const Http2Priority& priority, const Http2Headers& headers, int32_t* ret, int options)1871 Http2Stream* Http2Session::SubmitRequest(
1872 const Http2Priority& priority,
1873 const Http2Headers& headers,
1874 int32_t* ret,
1875 int options) {
1876 Debug(this, "submitting request");
1877 Http2Scope h2scope(this);
1878 Http2Stream* stream = nullptr;
1879 Http2Stream::Provider::Stream prov(options);
1880 *ret = nghttp2_submit_request(
1881 session_.get(),
1882 &priority,
1883 headers.data(),
1884 headers.length(),
1885 *prov,
1886 nullptr);
1887 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
1888 if (LIKELY(*ret > 0))
1889 stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options);
1890 return stream;
1891 }
1892
OnStreamAlloc(size_t suggested_size)1893 uv_buf_t Http2Session::OnStreamAlloc(size_t suggested_size) {
1894 return env()->allocate_managed_buffer(suggested_size);
1895 }
1896
1897 // Callback used to receive inbound data from the i/o stream
OnStreamRead(ssize_t nread, const uv_buf_t& buf_)1898 void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
1899 HandleScope handle_scope(env()->isolate());
1900 Context::Scope context_scope(env()->context());
1901 Http2Scope h2scope(this);
1902 CHECK_NOT_NULL(stream_);
1903 Debug(this, "receiving %d bytes, offset %d", nread, stream_buf_offset_);
1904 std::unique_ptr<BackingStore> bs = env()->release_managed_buffer(buf_);
1905
1906 // Only pass data on if nread > 0
1907 if (nread <= 0) {
1908 if (nread < 0) {
1909 PassReadErrorToPreviousListener(nread);
1910 }
1911 return;
1912 }
1913
1914 CHECK_LE(static_cast<size_t>(nread), bs->ByteLength());
1915
1916 statistics_.data_received += nread;
1917
1918 if (LIKELY(stream_buf_offset_ == 0)) {
1919 // Shrink to the actual amount of used data.
1920 bs = BackingStore::Reallocate(env()->isolate(), std::move(bs), nread);
1921 } else {
1922 // This is a very unlikely case, and should only happen if the ReadStart()
1923 // call in OnStreamAfterWrite() immediately provides data. If that does
1924 // happen, we concatenate the data we received with the already-stored
1925 // pending input data, slicing off the already processed part.
1926 size_t pending_len = stream_buf_.len - stream_buf_offset_;
1927 std::unique_ptr<BackingStore> new_bs;
1928 {
1929 NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
1930 new_bs = ArrayBuffer::NewBackingStore(env()->isolate(),
1931 pending_len + nread);
1932 }
1933 memcpy(static_cast<char*>(new_bs->Data()),
1934 stream_buf_.base + stream_buf_offset_,
1935 pending_len);
1936 memcpy(static_cast<char*>(new_bs->Data()) + pending_len,
1937 bs->Data(),
1938 nread);
1939
1940 bs = std::move(new_bs);
1941 nread = bs->ByteLength();
1942 stream_buf_offset_ = 0;
1943 stream_buf_ab_.Reset();
1944
1945 // We have now fully processed the stream_buf_ input chunk (by moving the
1946 // remaining part into buf, which will be accounted for below).
1947 DecrementCurrentSessionMemory(stream_buf_.len);
1948 }
1949
1950 IncrementCurrentSessionMemory(nread);
1951
1952 // Remember the current buffer, so that OnDataChunkReceived knows the
1953 // offset of a DATA frame's data into the socket read buffer.
1954 stream_buf_ = uv_buf_init(static_cast<char*>(bs->Data()),
1955 static_cast<unsigned int>(nread));
1956
1957 // Store this so we can create an ArrayBuffer for read data from it.
1958 // DATA frames will be emitted as slices of that ArrayBuffer to avoid having
1959 // to copy memory.
1960 stream_buf_allocation_ = std::move(bs);
1961
1962 ConsumeHTTP2Data();
1963
1964 MaybeStopReading();
1965 }
1966
HasWritesOnSocketForStream(Http2Stream* stream)1967 bool Http2Session::HasWritesOnSocketForStream(Http2Stream* stream) {
1968 for (const NgHttp2StreamWrite& wr : outgoing_buffers_) {
1969 if (wr.req_wrap && WriteWrap::FromObject(wr.req_wrap)->stream() == stream)
1970 return true;
1971 }
1972 return false;
1973 }
1974
1975 // Every Http2Session session is tightly bound to a single i/o StreamBase
1976 // (typically a net.Socket or tls.TLSSocket). The lifecycle of the two is
1977 // tightly coupled with all data transfer between the two happening at the
1978 // C++ layer via the StreamBase API.
Consume(Local<Object> stream_obj)1979 void Http2Session::Consume(Local<Object> stream_obj) {
1980 StreamBase* stream = StreamBase::FromObject(stream_obj);
1981 stream->PushStreamListener(this);
1982 Debug(this, "i/o stream consumed");
1983 }
1984
1985 // Allow injecting of data from JS
1986 // This is used when the socket has already some data received
1987 // before our listener was attached
1988 // https://github.com/nodejs/node/issues/35475
Receive(const FunctionCallbackInfo<Value>& args)1989 void Http2Session::Receive(const FunctionCallbackInfo<Value>& args) {
1990 Http2Session* session;
1991 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1992 CHECK(args[0]->IsObject());
1993
1994 ArrayBufferViewContents<char> buffer(args[0]);
1995 const char* data = buffer.data();
1996 size_t len = buffer.length();
1997 Debug(session, "Receiving %zu bytes injected from JS", len);
1998
1999 // Copy given buffer
2000 while (len > 0) {
2001 uv_buf_t buf = session->OnStreamAlloc(len);
2002 size_t copy = buf.len > len ? len : buf.len;
2003 memcpy(buf.base, data, copy);
2004 buf.len = copy;
2005 session->OnStreamRead(copy, buf);
2006
2007 data += copy;
2008 len -= copy;
2009 }
2010 }
2011
New(Http2Session* session, int32_t id, nghttp2_headers_category category, int options)2012 Http2Stream* Http2Stream::New(Http2Session* session,
2013 int32_t id,
2014 nghttp2_headers_category category,
2015 int options) {
2016 Local<Object> obj;
2017 if (!session->env()
2018 ->http2stream_constructor_template()
2019 ->NewInstance(session->env()->context())
2020 .ToLocal(&obj)) {
2021 return nullptr;
2022 }
2023 return new Http2Stream(session, obj, id, category, options);
2024 }
2025
Http2Stream(Http2Session* session, Local<Object> obj, int32_t id, nghttp2_headers_category category, int options)2026 Http2Stream::Http2Stream(Http2Session* session,
2027 Local<Object> obj,
2028 int32_t id,
2029 nghttp2_headers_category category,
2030 int options)
2031 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2STREAM),
2032 StreamBase(session->env()),
2033 session_(session),
2034 id_(id),
2035 current_headers_category_(category) {
2036 MakeWeak();
2037 StreamBase::AttachToObject(GetObject());
2038 statistics_.id = id;
2039 statistics_.start_time = uv_hrtime();
2040
2041 // Limit the number of header pairs
2042 max_header_pairs_ = session->max_header_pairs();
2043 if (max_header_pairs_ == 0) {
2044 max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS;
2045 }
2046 current_headers_.reserve(std::min(max_header_pairs_, 12u));
2047
2048 // Limit the number of header octets
2049 max_header_length_ =
2050 std::min(
2051 nghttp2_session_get_local_settings(
2052 session->session(),
2053 NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE),
2054 MAX_MAX_HEADER_LIST_SIZE);
2055
2056 if (options & STREAM_OPTION_GET_TRAILERS)
2057 set_has_trailers();
2058
2059 PushStreamListener(&stream_listener_);
2060
2061 if (options & STREAM_OPTION_EMPTY_PAYLOAD)
2062 Shutdown();
2063 session->AddStream(this);
2064 }
2065
~Http2Stream()2066 Http2Stream::~Http2Stream() {
2067 Debug(this, "tearing down stream");
2068 }
2069
MemoryInfo(MemoryTracker* tracker) const2070 void Http2Stream::MemoryInfo(MemoryTracker* tracker) const {
2071 tracker->TrackField("current_headers", current_headers_);
2072 tracker->TrackField("queue", queue_);
2073 }
2074
diagnostic_name() const2075 std::string Http2Stream::diagnostic_name() const {
2076 const Http2Session* sess = session();
2077 const std::string sname =
2078 sess ? sess->diagnostic_name() : "session already destroyed";
2079 return "HttpStream " + std::to_string(id()) + " (" +
2080 std::to_string(static_cast<int64_t>(get_async_id())) + ") [" + sname +
2081 "]";
2082 }
2083
2084 // Notify the Http2Stream that a new block of HEADERS is being processed.
StartHeaders(nghttp2_headers_category category)2085 void Http2Stream::StartHeaders(nghttp2_headers_category category) {
2086 Debug(this, "starting headers, category: %d", category);
2087 CHECK(!this->is_destroyed());
2088 session_->DecrementCurrentSessionMemory(current_headers_length_);
2089 current_headers_length_ = 0;
2090 current_headers_.clear();
2091 current_headers_category_ = category;
2092 }
2093
2094
operator *() const2095 nghttp2_stream* Http2Stream::operator*() const { return stream(); }
2096
stream() const2097 nghttp2_stream* Http2Stream::stream() const {
2098 return nghttp2_session_find_stream(session_->session(), id_);
2099 }
2100
Close(int32_t code)2101 void Http2Stream::Close(int32_t code) {
2102 CHECK(!this->is_destroyed());
2103 set_closed();
2104 code_ = code;
2105 Debug(this, "closed with code %d", code);
2106 }
2107
CreateShutdownWrap(Local<Object> object)2108 ShutdownWrap* Http2Stream::CreateShutdownWrap(Local<Object> object) {
2109 // DoShutdown() always finishes synchronously, so there's no need to create
2110 // a structure to store asynchronous context.
2111 return nullptr;
2112 }
2113
DoShutdown(ShutdownWrap* req_wrap)2114 int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
2115 if (is_destroyed())
2116 return UV_EPIPE;
2117
2118 {
2119 Http2Scope h2scope(this);
2120 set_not_writable();
2121 CHECK_NE(nghttp2_session_resume_data(
2122 session_->session(), id_),
2123 NGHTTP2_ERR_NOMEM);
2124 Debug(this, "writable side shutdown");
2125 }
2126 return 1;
2127 }
2128
2129 // Destroy the Http2Stream and render it unusable. Actual resources for the
2130 // Stream will not be freed until the next tick of the Node.js event loop
2131 // using the SetImmediate queue.
Destroy()2132 void Http2Stream::Destroy() {
2133 // Do nothing if this stream instance is already destroyed
2134 if (is_destroyed())
2135 return;
2136 if (session_->has_pending_rststream(id_))
2137 FlushRstStream();
2138 set_destroyed();
2139
2140 Debug(this, "destroying stream");
2141
2142 // Wait until the start of the next loop to delete because there
2143 // may still be some pending operations queued for this stream.
2144 BaseObjectPtr<Http2Stream> strong_ref = session_->RemoveStream(id_);
2145 if (strong_ref) {
2146 env()->SetImmediate([this, strong_ref = std::move(strong_ref)](
2147 Environment* env) {
2148 // Free any remaining outgoing data chunks here. This should be done
2149 // here because it's possible for destroy to have been called while
2150 // we still have queued outbound writes.
2151 while (!queue_.empty()) {
2152 NgHttp2StreamWrite& head = queue_.front();
2153 if (head.req_wrap)
2154 WriteWrap::FromObject(head.req_wrap)->Done(UV_ECANCELED);
2155 queue_.pop();
2156 }
2157
2158 // We can destroy the stream now if there are no writes for it
2159 // already on the socket. Otherwise, we'll wait for the garbage collector
2160 // to take care of cleaning up.
2161 if (session() == nullptr ||
2162 !session()->HasWritesOnSocketForStream(this)) {
2163 // Delete once strong_ref goes out of scope.
2164 Detach();
2165 }
2166 });
2167 }
2168
2169 statistics_.end_time = uv_hrtime();
2170 session_->statistics_.stream_average_duration =
2171 ((statistics_.end_time - statistics_.start_time) /
2172 session_->statistics_.stream_count) / 1e6;
2173 EmitStatistics();
2174 }
2175
2176
2177 // Initiates a response on the Http2Stream using data provided via the
2178 // StreamBase Streams API.
SubmitResponse(const Http2Headers& headers, int options)2179 int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) {
2180 CHECK(!this->is_destroyed());
2181 Http2Scope h2scope(this);
2182 Debug(this, "submitting response");
2183 if (options & STREAM_OPTION_GET_TRAILERS)
2184 set_has_trailers();
2185
2186 if (!is_writable())
2187 options |= STREAM_OPTION_EMPTY_PAYLOAD;
2188
2189 Http2Stream::Provider::Stream prov(this, options);
2190 int ret = nghttp2_submit_response(
2191 session_->session(),
2192 id_,
2193 headers.data(),
2194 headers.length(),
2195 *prov);
2196 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2197 return ret;
2198 }
2199
2200
2201 // Submit informational headers for a stream.
SubmitInfo(const Http2Headers& headers)2202 int Http2Stream::SubmitInfo(const Http2Headers& headers) {
2203 CHECK(!this->is_destroyed());
2204 Http2Scope h2scope(this);
2205 Debug(this, "sending %d informational headers", headers.length());
2206 int ret = nghttp2_submit_headers(
2207 session_->session(),
2208 NGHTTP2_FLAG_NONE,
2209 id_,
2210 nullptr,
2211 headers.data(),
2212 headers.length(),
2213 nullptr);
2214 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2215 return ret;
2216 }
2217
OnTrailers()2218 void Http2Stream::OnTrailers() {
2219 Debug(this, "let javascript know we are ready for trailers");
2220 CHECK(!this->is_destroyed());
2221 Isolate* isolate = env()->isolate();
2222 HandleScope scope(isolate);
2223 Local<Context> context = env()->context();
2224 Context::Scope context_scope(context);
2225 set_has_trailers(false);
2226 MakeCallback(env()->http2session_on_stream_trailers_function(), 0, nullptr);
2227 }
2228
2229 // Submit informational headers for a stream.
SubmitTrailers(const Http2Headers& headers)2230 int Http2Stream::SubmitTrailers(const Http2Headers& headers) {
2231 CHECK(!this->is_destroyed());
2232 Http2Scope h2scope(this);
2233 Debug(this, "sending %d trailers", headers.length());
2234 int ret;
2235 // Sending an empty trailers frame poses problems in Safari, Edge & IE.
2236 // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM
2237 // to indicate that the stream is ready to be closed.
2238 if (headers.length() == 0) {
2239 Http2Stream::Provider::Stream prov(this, 0);
2240 ret = nghttp2_submit_data(
2241 session_->session(),
2242 NGHTTP2_FLAG_END_STREAM,
2243 id_,
2244 *prov);
2245 } else {
2246 ret = nghttp2_submit_trailer(
2247 session_->session(),
2248 id_,
2249 headers.data(),
2250 headers.length());
2251 }
2252 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2253 return ret;
2254 }
2255
2256 // Submit a PRIORITY frame to the connected peer.
SubmitPriority(const Http2Priority& priority, bool silent)2257 int Http2Stream::SubmitPriority(const Http2Priority& priority,
2258 bool silent) {
2259 CHECK(!this->is_destroyed());
2260 Http2Scope h2scope(this);
2261 Debug(this, "sending priority spec");
2262 int ret = silent ?
2263 nghttp2_session_change_stream_priority(
2264 session_->session(),
2265 id_,
2266 &priority) :
2267 nghttp2_submit_priority(
2268 session_->session(),
2269 NGHTTP2_FLAG_NONE,
2270 id_, &priority);
2271 CHECK_NE(ret, NGHTTP2_ERR_NOMEM);
2272 return ret;
2273 }
2274
2275 // Closes the Http2Stream by submitting an RST_STREAM frame to the connected
2276 // peer.
SubmitRstStream(const uint32_t code)2277 void Http2Stream::SubmitRstStream(const uint32_t code) {
2278 CHECK(!this->is_destroyed());
2279 code_ = code;
2280
2281 auto is_stream_cancel = [](const uint32_t code) {
2282 return code == NGHTTP2_CANCEL;
2283 };
2284
2285 // If RST_STREAM frame is received with error code NGHTTP2_CANCEL,
2286 // add it to the pending list and don't force purge the data. It is
2287 // to avoids the double free error due to unwanted behavior of nghttp2.
2288
2289 // Add stream to the pending list only if it is received with scope
2290 // below in the stack. The pending list may not get processed
2291 // if RST_STREAM received is not in scope and added to the list
2292 // causing endpoint to hang.
2293 if (session_->is_in_scope() && is_stream_cancel(code)) {
2294 session_->AddPendingRstStream(id_);
2295 return;
2296 }
2297
2298
2299 // If possible, force a purge of any currently pending data here to make sure
2300 // it is sent before closing the stream. If it returns non-zero then we need
2301 // to wait until the current write finishes and try again to avoid nghttp2
2302 // behaviour where it prioritizes RstStream over everything else.
2303 if (session_->SendPendingData() != 0) {
2304 session_->AddPendingRstStream(id_);
2305 return;
2306 }
2307
2308 FlushRstStream();
2309 }
2310
FlushRstStream()2311 void Http2Stream::FlushRstStream() {
2312 if (is_destroyed())
2313 return;
2314 Http2Scope h2scope(this);
2315 CHECK_EQ(nghttp2_submit_rst_stream(
2316 session_->session(),
2317 NGHTTP2_FLAG_NONE,
2318 id_,
2319 code_), 0);
2320 }
2321
2322
2323 // Submit a push promise and create the associated Http2Stream if successful.
SubmitPushPromise(const Http2Headers& headers, int32_t* ret, int options)2324 Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers,
2325 int32_t* ret,
2326 int options) {
2327 CHECK(!this->is_destroyed());
2328 Http2Scope h2scope(this);
2329 Debug(this, "sending push promise");
2330 *ret = nghttp2_submit_push_promise(
2331 session_->session(),
2332 NGHTTP2_FLAG_NONE,
2333 id_,
2334 headers.data(),
2335 headers.length(),
2336 nullptr);
2337 CHECK_NE(*ret, NGHTTP2_ERR_NOMEM);
2338 Http2Stream* stream = nullptr;
2339 if (*ret > 0) {
2340 stream = Http2Stream::New(
2341 session_.get(), *ret, NGHTTP2_HCAT_HEADERS, options);
2342 }
2343
2344 return stream;
2345 }
2346
2347 // Switch the StreamBase into flowing mode to begin pushing chunks of data
2348 // out to JS land.
ReadStart()2349 int Http2Stream::ReadStart() {
2350 Http2Scope h2scope(this);
2351 CHECK(!this->is_destroyed());
2352 set_reading();
2353
2354 Debug(this, "reading starting");
2355
2356 // Tell nghttp2 about our consumption of the data that was handed
2357 // off to JS land.
2358 nghttp2_session_consume_stream(
2359 session_->session(),
2360 id_,
2361 inbound_consumed_data_while_paused_);
2362 inbound_consumed_data_while_paused_ = 0;
2363
2364 return 0;
2365 }
2366
2367 // Switch the StreamBase into paused mode.
ReadStop()2368 int Http2Stream::ReadStop() {
2369 CHECK(!this->is_destroyed());
2370 if (!is_reading())
2371 return 0;
2372 set_paused();
2373 Debug(this, "reading stopped");
2374 return 0;
2375 }
2376
2377 // The Http2Stream class is a subclass of StreamBase. The DoWrite method
2378 // receives outbound chunks of data to send as outbound DATA frames. These
2379 // are queued in an internal linked list of uv_buf_t structs that are sent
2380 // when nghttp2 is ready to serialize the data frame.
2381 //
2382 // Queue the given set of uv_but_t handles for writing to an
2383 // nghttp2_stream. The WriteWrap's Done callback will be invoked once the
2384 // chunks of data have been flushed to the underlying nghttp2_session.
2385 // Note that this does *not* mean that the data has been flushed
2386 // to the socket yet.
DoWrite(WriteWrap* req_wrap, uv_buf_t* bufs, size_t nbufs, uv_stream_t* send_handle)2387 int Http2Stream::DoWrite(WriteWrap* req_wrap,
2388 uv_buf_t* bufs,
2389 size_t nbufs,
2390 uv_stream_t* send_handle) {
2391 CHECK_NULL(send_handle);
2392 Http2Scope h2scope(this);
2393 if (!is_writable() || is_destroyed()) {
2394 return UV_EOF;
2395 }
2396 Debug(this, "queuing %d buffers to send", nbufs);
2397 for (size_t i = 0; i < nbufs; ++i) {
2398 // Store the req_wrap on the last write info in the queue, so that it is
2399 // only marked as finished once all buffers associated with it are finished.
2400 queue_.emplace(NgHttp2StreamWrite {
2401 BaseObjectPtr<AsyncWrap>(
2402 i == nbufs - 1 ? req_wrap->GetAsyncWrap() : nullptr),
2403 bufs[i]
2404 });
2405 IncrementAvailableOutboundLength(bufs[i].len);
2406 }
2407 CHECK_NE(nghttp2_session_resume_data(
2408 session_->session(),
2409 id_), NGHTTP2_ERR_NOMEM);
2410 return 0;
2411 }
2412
2413 // Ads a header to the Http2Stream. Note that the header name and value are
2414 // provided using a buffer structure provided by nghttp2 that allows us to
2415 // avoid unnecessary memcpy's. Those buffers are ref counted. The ref count
2416 // is incremented here and are decremented when the header name and values
2417 // are garbage collected later.
AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags)2418 bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2419 nghttp2_rcbuf* value,
2420 uint8_t flags) {
2421 CHECK(!this->is_destroyed());
2422
2423 if (Http2RcBufferPointer::IsZeroLength(name))
2424 return true; // Ignore empty headers.
2425
2426 Http2Header header(env(), name, value, flags);
2427 size_t length = header.length() + 32;
2428 // A header can only be added if we have not exceeded the maximum number
2429 // of headers and the session has memory available for it.
2430 if (!session_->has_available_session_memory(length) ||
2431 current_headers_.size() == max_header_pairs_ ||
2432 current_headers_length_ + length > max_header_length_) {
2433 return false;
2434 }
2435
2436 if (statistics_.first_header == 0)
2437 statistics_.first_header = uv_hrtime();
2438
2439 current_headers_.push_back(std::move(header));
2440
2441 current_headers_length_ += length;
2442 session_->IncrementCurrentSessionMemory(length);
2443 return true;
2444 }
2445
2446 // A Provider is the thing that provides outbound DATA frame data.
Provider(Http2Stream* stream, int options)2447 Http2Stream::Provider::Provider(Http2Stream* stream, int options) {
2448 CHECK(!stream->is_destroyed());
2449 provider_.source.ptr = stream;
2450 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2451 }
2452
Provider(int options)2453 Http2Stream::Provider::Provider(int options) {
2454 provider_.source.ptr = nullptr;
2455 empty_ = options & STREAM_OPTION_EMPTY_PAYLOAD;
2456 }
2457
~Provider()2458 Http2Stream::Provider::~Provider() {
2459 provider_.source.ptr = nullptr;
2460 }
2461
2462 // The Stream Provider pulls data from a linked list of uv_buf_t structs
2463 // built via the StreamBase API and the Streams js API.
Stream(int options)2464 Http2Stream::Provider::Stream::Stream(int options)
2465 : Http2Stream::Provider(options) {
2466 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2467 }
2468
Stream(Http2Stream* stream, int options)2469 Http2Stream::Provider::Stream::Stream(Http2Stream* stream, int options)
2470 : Http2Stream::Provider(stream, options) {
2471 provider_.read_callback = Http2Stream::Provider::Stream::OnRead;
2472 }
2473
OnRead(nghttp2_session* handle, int32_t id, uint8_t* buf, size_t length, uint32_t* flags, nghttp2_data_source* source, void* user_data)2474 ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2475 int32_t id,
2476 uint8_t* buf,
2477 size_t length,
2478 uint32_t* flags,
2479 nghttp2_data_source* source,
2480 void* user_data) {
2481 Http2Session* session = static_cast<Http2Session*>(user_data);
2482 Debug(session, "reading outbound data for stream %d", id);
2483 BaseObjectPtr<Http2Stream> stream = session->FindStream(id);
2484 if (!stream) return 0;
2485 if (stream->statistics_.first_byte_sent == 0)
2486 stream->statistics_.first_byte_sent = uv_hrtime();
2487 CHECK_EQ(id, stream->id());
2488
2489 size_t amount = 0; // amount of data being sent in this data frame.
2490
2491 // Remove all empty chunks from the head of the queue.
2492 // This is done here so that .write('', cb) is still a meaningful way to
2493 // find out when the HTTP2 stream wants to consume data, and because the
2494 // StreamBase API allows empty input chunks.
2495 while (!stream->queue_.empty() && stream->queue_.front().buf.len == 0) {
2496 BaseObjectPtr<AsyncWrap> finished =
2497 std::move(stream->queue_.front().req_wrap);
2498 stream->queue_.pop();
2499 if (finished)
2500 WriteWrap::FromObject(finished)->Done(0);
2501 }
2502
2503 if (!stream->queue_.empty()) {
2504 Debug(session, "stream %d has pending outbound data", id);
2505 amount = std::min(stream->available_outbound_length_, length);
2506 Debug(session, "sending %d bytes for data frame on stream %d", amount, id);
2507 if (amount > 0) {
2508 // Just return the length, let Http2Session::OnSendData take care of
2509 // actually taking the buffers out of the queue.
2510 *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2511 stream->DecrementAvailableOutboundLength(amount);
2512 }
2513 }
2514
2515 if (amount == 0 && stream->is_writable()) {
2516 CHECK(stream->queue_.empty());
2517 Debug(session, "deferring stream %d", id);
2518 stream->EmitWantsWrite(length);
2519 if (stream->available_outbound_length_ > 0 || !stream->is_writable()) {
2520 // EmitWantsWrite() did something interesting synchronously, restart:
2521 return OnRead(handle, id, buf, length, flags, source, user_data);
2522 }
2523 return NGHTTP2_ERR_DEFERRED;
2524 }
2525
2526 if (stream->available_outbound_length_ == 0 && !stream->is_writable()) {
2527 Debug(session, "no more data for stream %d", id);
2528 *flags |= NGHTTP2_DATA_FLAG_EOF;
2529 if (stream->has_trailers()) {
2530 *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
2531 stream->OnTrailers();
2532 }
2533 }
2534
2535 stream->statistics_.sent_bytes += amount;
2536 return amount;
2537 }
2538
IncrementAvailableOutboundLength(size_t amount)2539 void Http2Stream::IncrementAvailableOutboundLength(size_t amount) {
2540 available_outbound_length_ += amount;
2541 session_->IncrementCurrentSessionMemory(amount);
2542 }
2543
DecrementAvailableOutboundLength(size_t amount)2544 void Http2Stream::DecrementAvailableOutboundLength(size_t amount) {
2545 available_outbound_length_ -= amount;
2546 session_->DecrementCurrentSessionMemory(amount);
2547 }
2548
2549
2550 // Implementation of the JavaScript API
2551
2552 // Fetches the string description of a nghttp2 error code and passes that
2553 // back to JS land
HttpErrorString(const FunctionCallbackInfo<Value>& args)2554 void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
2555 Environment* env = Environment::GetCurrent(args);
2556 uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
2557 args.GetReturnValue().Set(
2558 OneByteString(
2559 env->isolate(),
2560 reinterpret_cast<const uint8_t*>(nghttp2_strerror(val))));
2561 }
2562
2563
2564 // Serializes the settings object into a Buffer instance that
2565 // would be suitable, for instance, for creating the Base64
2566 // output for an HTTP2-Settings header field.
PackSettings(const FunctionCallbackInfo<Value>& args)2567 void PackSettings(const FunctionCallbackInfo<Value>& args) {
2568 Http2State* state = Realm::GetBindingData<Http2State>(args);
2569 args.GetReturnValue().Set(Http2Settings::Pack(state));
2570 }
2571
2572 // A TypedArray instance is shared between C++ and JS land to contain the
2573 // default SETTINGS. RefreshDefaultSettings updates that TypedArray with the
2574 // default values.
RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args)2575 void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
2576 Http2State* state = Realm::GetBindingData<Http2State>(args);
2577 Http2Settings::RefreshDefaults(state);
2578 }
2579
2580 // Sets the next stream ID the Http2Session. If successful, returns true.
SetNextStreamID(const FunctionCallbackInfo<Value>& args)2581 void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
2582 Environment* env = Environment::GetCurrent(args);
2583 Http2Session* session;
2584 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2585 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2586 if (nghttp2_session_set_next_stream_id(session->session(), id) < 0) {
2587 Debug(session, "failed to set next stream id to %d", id);
2588 return args.GetReturnValue().Set(false);
2589 }
2590 args.GetReturnValue().Set(true);
2591 Debug(session, "set next stream id to %d", id);
2592 }
2593
2594 // Set local window size (local endpoints's window size) to the given
2595 // window_size for the stream denoted by 0.
2596 // This function returns 0 if it succeeds, or one of a negative codes
SetLocalWindowSize( const FunctionCallbackInfo<Value>& args)2597 void Http2Session::SetLocalWindowSize(
2598 const FunctionCallbackInfo<Value>& args) {
2599 Environment* env = Environment::GetCurrent(args);
2600 Http2Session* session;
2601 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2602
2603 int32_t window_size = args[0]->Int32Value(env->context()).ToChecked();
2604
2605 int result = nghttp2_session_set_local_window_size(
2606 session->session(), NGHTTP2_FLAG_NONE, 0, window_size);
2607
2608 args.GetReturnValue().Set(result);
2609
2610 Debug(session, "set local window size to %d", window_size);
2611 }
2612
2613 // A TypedArray instance is shared between C++ and JS land to contain the
2614 // SETTINGS (either remote or local). RefreshSettings updates the current
2615 // values established for each of the settings so those can be read in JS land.
2616 template <get_setting fn>
RefreshSettings(const FunctionCallbackInfo<Value>& args)2617 void Http2Session::RefreshSettings(const FunctionCallbackInfo<Value>& args) {
2618 Http2Session* session;
2619 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2620 Http2Settings::Update(session, fn);
2621 Debug(session, "settings refreshed for session");
2622 }
2623
2624 // A TypedArray instance is shared between C++ and JS land to contain state
2625 // information of the current Http2Session. This updates the values in the
2626 // TypedArray so those can be read in JS land.
RefreshState(const FunctionCallbackInfo<Value>& args)2627 void Http2Session::RefreshState(const FunctionCallbackInfo<Value>& args) {
2628 Http2Session* session;
2629 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2630 Debug(session, "refreshing state");
2631
2632 AliasedFloat64Array& buffer = session->http2_state()->session_state_buffer;
2633
2634 nghttp2_session* s = session->session();
2635
2636 buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
2637 nghttp2_session_get_effective_local_window_size(s);
2638 buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
2639 nghttp2_session_get_effective_recv_data_length(s);
2640 buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
2641 nghttp2_session_get_next_stream_id(s);
2642 buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
2643 nghttp2_session_get_local_window_size(s);
2644 buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
2645 nghttp2_session_get_last_proc_stream_id(s);
2646 buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
2647 nghttp2_session_get_remote_window_size(s);
2648 buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
2649 static_cast<double>(nghttp2_session_get_outbound_queue_size(s));
2650 buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
2651 static_cast<double>(nghttp2_session_get_hd_deflate_dynamic_table_size(s));
2652 buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
2653 static_cast<double>(nghttp2_session_get_hd_inflate_dynamic_table_size(s));
2654 }
2655
2656
2657 // Constructor for new Http2Session instances.
New(const FunctionCallbackInfo<Value>& args)2658 void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
2659 Http2State* state = Realm::GetBindingData<Http2State>(args);
2660 Environment* env = state->env();
2661 CHECK(args.IsConstructCall());
2662 SessionType type =
2663 static_cast<SessionType>(
2664 args[0]->Int32Value(env->context()).ToChecked());
2665 Http2Session* session = new Http2Session(state, args.This(), type);
2666 Debug(session, "session created");
2667 }
2668
2669
2670 // Binds the Http2Session with a StreamBase used for i/o
Consume(const FunctionCallbackInfo<Value>& args)2671 void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
2672 Http2Session* session;
2673 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2674 CHECK(args[0]->IsObject());
2675 session->Consume(args[0].As<Object>());
2676 }
2677
2678 // Destroys the Http2Session instance and renders it unusable
Destroy(const FunctionCallbackInfo<Value>& args)2679 void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
2680 Http2Session* session;
2681 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2682 Debug(session, "destroying session");
2683 Environment* env = Environment::GetCurrent(args);
2684 Local<Context> context = env->context();
2685
2686 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2687 session->Close(code, args[1]->IsTrue());
2688 }
2689
2690 // Submits a new request on the Http2Session and returns either an error code
2691 // or the Http2Stream object.
Request(const FunctionCallbackInfo<Value>& args)2692 void Http2Session::Request(const FunctionCallbackInfo<Value>& args) {
2693 Http2Session* session;
2694 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2695 Environment* env = session->env();
2696
2697 Local<Array> headers = args[0].As<Array>();
2698 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2699
2700 Debug(session, "request submitted");
2701
2702 int32_t ret = 0;
2703 Http2Stream* stream =
2704 session->Http2Session::SubmitRequest(
2705 Http2Priority(env, args[2], args[3], args[4]),
2706 Http2Headers(env, headers),
2707 &ret,
2708 static_cast<int>(options));
2709
2710 if (ret <= 0 || stream == nullptr) {
2711 Debug(session, "could not submit request: %s", nghttp2_strerror(ret));
2712 return args.GetReturnValue().Set(ret);
2713 }
2714
2715 Debug(session, "request submitted, new stream id %d", stream->id());
2716 args.GetReturnValue().Set(stream->object());
2717 }
2718
2719 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2720 // of shutting down. Note that this function does not actually alter the
2721 // state of the Http2Session, it's simply a notification.
Goaway(uint32_t code, int32_t lastStreamID, const uint8_t* data, size_t len)2722 void Http2Session::Goaway(uint32_t code,
2723 int32_t lastStreamID,
2724 const uint8_t* data,
2725 size_t len) {
2726 if (is_destroyed())
2727 return;
2728
2729 Http2Scope h2scope(this);
2730 // the last proc stream id is the most recently created Http2Stream.
2731 if (lastStreamID <= 0)
2732 lastStreamID = nghttp2_session_get_last_proc_stream_id(session_.get());
2733 Debug(this, "submitting goaway");
2734 nghttp2_submit_goaway(session_.get(), NGHTTP2_FLAG_NONE,
2735 lastStreamID, code, data, len);
2736 }
2737
2738 // Submits a GOAWAY frame to signal that the Http2Session is in the process
2739 // of shutting down. The opaque data argument is an optional TypedArray that
2740 // can be used to send debugging data to the connected peer.
Goaway(const FunctionCallbackInfo<Value>& args)2741 void Http2Session::Goaway(const FunctionCallbackInfo<Value>& args) {
2742 Environment* env = Environment::GetCurrent(args);
2743 Local<Context> context = env->context();
2744 Http2Session* session;
2745 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2746
2747 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2748 int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
2749 ArrayBufferViewContents<uint8_t> opaque_data;
2750
2751 if (args[2]->IsArrayBufferView()) {
2752 opaque_data.Read(args[2].As<ArrayBufferView>());
2753 }
2754
2755 session->Goaway(code, lastStreamID, opaque_data.data(), opaque_data.length());
2756 }
2757
2758 // Update accounting of data chunks. This is used primarily to manage timeout
2759 // logic when using the FD Provider.
UpdateChunksSent(const FunctionCallbackInfo<Value>& args)2760 void Http2Session::UpdateChunksSent(const FunctionCallbackInfo<Value>& args) {
2761 Environment* env = Environment::GetCurrent(args);
2762 Isolate* isolate = env->isolate();
2763 HandleScope scope(isolate);
2764 Http2Session* session;
2765 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2766
2767 uint32_t length = session->chunks_sent_since_last_write_;
2768
2769 session->object()->Set(env->context(),
2770 env->chunks_sent_since_last_write_string(),
2771 Integer::NewFromUnsigned(isolate, length)).Check();
2772
2773 args.GetReturnValue().Set(length);
2774 }
2775
2776 // Submits an RST_STREAM frame effectively closing the Http2Stream. Note that
2777 // this *WILL* alter the state of the stream, causing the OnStreamClose
2778 // callback to the triggered.
RstStream(const FunctionCallbackInfo<Value>& args)2779 void Http2Stream::RstStream(const FunctionCallbackInfo<Value>& args) {
2780 Environment* env = Environment::GetCurrent(args);
2781 Local<Context> context = env->context();
2782 Http2Stream* stream;
2783 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2784 uint32_t code = args[0]->Uint32Value(context).ToChecked();
2785 Debug(stream, "sending rst_stream with code %d", code);
2786 stream->SubmitRstStream(code);
2787 }
2788
2789 // Initiates a response on the Http2Stream using the StreamBase API to provide
2790 // outbound DATA frames.
Respond(const FunctionCallbackInfo<Value>& args)2791 void Http2Stream::Respond(const FunctionCallbackInfo<Value>& args) {
2792 Environment* env = Environment::GetCurrent(args);
2793 Http2Stream* stream;
2794 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2795
2796 Local<Array> headers = args[0].As<Array>();
2797 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2798
2799 args.GetReturnValue().Set(
2800 stream->SubmitResponse(
2801 Http2Headers(env, headers),
2802 static_cast<int>(options)));
2803 Debug(stream, "response submitted");
2804 }
2805
2806
2807 // Submits informational headers on the Http2Stream
Info(const FunctionCallbackInfo<Value>& args)2808 void Http2Stream::Info(const FunctionCallbackInfo<Value>& args) {
2809 Environment* env = Environment::GetCurrent(args);
2810 Http2Stream* stream;
2811 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2812
2813 Local<Array> headers = args[0].As<Array>();
2814
2815 args.GetReturnValue().Set(stream->SubmitInfo(Http2Headers(env, headers)));
2816 }
2817
2818 // Submits trailing headers on the Http2Stream
Trailers(const FunctionCallbackInfo<Value>& args)2819 void Http2Stream::Trailers(const FunctionCallbackInfo<Value>& args) {
2820 Environment* env = Environment::GetCurrent(args);
2821 Http2Stream* stream;
2822 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2823
2824 Local<Array> headers = args[0].As<Array>();
2825
2826 args.GetReturnValue().Set(
2827 stream->SubmitTrailers(Http2Headers(env, headers)));
2828 }
2829
2830 // Grab the numeric id of the Http2Stream
GetID(const FunctionCallbackInfo<Value>& args)2831 void Http2Stream::GetID(const FunctionCallbackInfo<Value>& args) {
2832 Http2Stream* stream;
2833 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2834 args.GetReturnValue().Set(stream->id());
2835 }
2836
2837 // Destroy the Http2Stream, rendering it no longer usable
Destroy(const FunctionCallbackInfo<Value>& args)2838 void Http2Stream::Destroy(const FunctionCallbackInfo<Value>& args) {
2839 Http2Stream* stream;
2840 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2841 Debug(stream, "destroying stream");
2842 stream->Destroy();
2843 }
2844
2845 // Initiate a Push Promise and create the associated Http2Stream
PushPromise(const FunctionCallbackInfo<Value>& args)2846 void Http2Stream::PushPromise(const FunctionCallbackInfo<Value>& args) {
2847 Environment* env = Environment::GetCurrent(args);
2848 Http2Stream* parent;
2849 ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder());
2850
2851 Local<Array> headers = args[0].As<Array>();
2852 int32_t options = args[1]->Int32Value(env->context()).ToChecked();
2853
2854 Debug(parent, "creating push promise");
2855
2856 int32_t ret = 0;
2857 Http2Stream* stream =
2858 parent->SubmitPushPromise(
2859 Http2Headers(env, headers),
2860 &ret,
2861 static_cast<int>(options));
2862
2863 if (ret <= 0 || stream == nullptr) {
2864 Debug(parent, "failed to create push stream: %d", ret);
2865 return args.GetReturnValue().Set(ret);
2866 }
2867 Debug(parent, "push stream %d created", stream->id());
2868 args.GetReturnValue().Set(stream->object());
2869 }
2870
2871 // Send a PRIORITY frame
Priority(const FunctionCallbackInfo<Value>& args)2872 void Http2Stream::Priority(const FunctionCallbackInfo<Value>& args) {
2873 Environment* env = Environment::GetCurrent(args);
2874 Http2Stream* stream;
2875 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2876
2877 CHECK_EQ(stream->SubmitPriority(
2878 Http2Priority(env, args[0], args[1], args[2]),
2879 args[3]->IsTrue()), 0);
2880 Debug(stream, "priority submitted");
2881 }
2882
2883 // A TypedArray shared by C++ and JS land is used to communicate state
2884 // information about the Http2Stream. This updates the values in that
2885 // TypedArray so that the state can be read by JS.
RefreshState(const FunctionCallbackInfo<Value>& args)2886 void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) {
2887 Http2Stream* stream;
2888 ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder());
2889
2890 Debug(stream, "refreshing state");
2891
2892 CHECK_NOT_NULL(stream->session());
2893 AliasedFloat64Array& buffer =
2894 stream->session()->http2_state()->stream_state_buffer;
2895
2896 nghttp2_stream* str = stream->stream();
2897 nghttp2_session* s = stream->session()->session();
2898
2899 if (str == nullptr) {
2900 buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
2901 buffer[IDX_STREAM_STATE_WEIGHT] =
2902 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2903 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2904 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2905 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
2906 } else {
2907 buffer[IDX_STREAM_STATE] =
2908 nghttp2_stream_get_state(str);
2909 buffer[IDX_STREAM_STATE_WEIGHT] =
2910 nghttp2_stream_get_weight(str);
2911 buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
2912 nghttp2_stream_get_sum_dependency_weight(str);
2913 buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
2914 nghttp2_session_get_stream_local_close(s, stream->id());
2915 buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
2916 nghttp2_session_get_stream_remote_close(s, stream->id());
2917 buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
2918 nghttp2_session_get_stream_local_window_size(s, stream->id());
2919 }
2920 }
2921
AltSvc(int32_t id, uint8_t* origin, size_t origin_len, uint8_t* value, size_t value_len)2922 void Http2Session::AltSvc(int32_t id,
2923 uint8_t* origin,
2924 size_t origin_len,
2925 uint8_t* value,
2926 size_t value_len) {
2927 Http2Scope h2scope(this);
2928 CHECK_EQ(nghttp2_submit_altsvc(session_.get(), NGHTTP2_FLAG_NONE, id,
2929 origin, origin_len, value, value_len), 0);
2930 }
2931
Origin(const Origins& origins)2932 void Http2Session::Origin(const Origins& origins) {
2933 Http2Scope h2scope(this);
2934 CHECK_EQ(nghttp2_submit_origin(
2935 session_.get(),
2936 NGHTTP2_FLAG_NONE,
2937 *origins,
2938 origins.length()), 0);
2939 }
2940
2941 // Submits an AltSvc frame to be sent to the connected peer.
AltSvc(const FunctionCallbackInfo<Value>& args)2942 void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) {
2943 Environment* env = Environment::GetCurrent(args);
2944 Http2Session* session;
2945 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2946
2947 int32_t id = args[0]->Int32Value(env->context()).ToChecked();
2948
2949 // origin and value are both required to be ASCII, handle them as such.
2950 Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked();
2951 Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked();
2952
2953 if (origin_str.IsEmpty() || value_str.IsEmpty())
2954 return;
2955
2956 size_t origin_len = origin_str->Length();
2957 size_t value_len = value_str->Length();
2958
2959 CHECK_LE(origin_len + value_len, 16382); // Max permitted for ALTSVC
2960 // Verify that origin len != 0 if stream id == 0, or
2961 // that origin len == 0 if stream id != 0
2962 CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0));
2963
2964 MaybeStackBuffer<uint8_t> origin(origin_len);
2965 MaybeStackBuffer<uint8_t> value(value_len);
2966 origin_str->WriteOneByte(env->isolate(), *origin);
2967 value_str->WriteOneByte(env->isolate(), *value);
2968
2969 session->AltSvc(id, *origin, origin_len, *value, value_len);
2970 }
2971
Origin(const FunctionCallbackInfo<Value>& args)2972 void Http2Session::Origin(const FunctionCallbackInfo<Value>& args) {
2973 Environment* env = Environment::GetCurrent(args);
2974 Local<Context> context = env->context();
2975 Http2Session* session;
2976 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2977
2978 Local<String> origin_string = args[0].As<String>();
2979 size_t count = args[1]->Int32Value(context).ToChecked();
2980
2981 session->Origin(Origins(env, origin_string, count));
2982 }
2983
2984 // Submits a PING frame to be sent to the connected peer.
Ping(const FunctionCallbackInfo<Value>& args)2985 void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) {
2986 Http2Session* session;
2987 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
2988
2989 // A PING frame may have exactly 8 bytes of payload data. If not provided,
2990 // then the current hrtime will be used as the payload.
2991 ArrayBufferViewContents<uint8_t, 8> payload;
2992 if (args[0]->IsArrayBufferView()) {
2993 payload.Read(args[0].As<ArrayBufferView>());
2994 CHECK_EQ(payload.length(), 8);
2995 }
2996
2997 CHECK(args[1]->IsFunction());
2998 args.GetReturnValue().Set(
2999 session->AddPing(payload.data(), args[1].As<Function>()));
3000 }
3001
3002 // Submits a SETTINGS frame for the Http2Session
Settings(const FunctionCallbackInfo<Value>& args)3003 void Http2Session::Settings(const FunctionCallbackInfo<Value>& args) {
3004 Http2Session* session;
3005 ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
3006 CHECK(args[0]->IsFunction());
3007 args.GetReturnValue().Set(session->AddSettings(args[0].As<Function>()));
3008 }
3009
PopPing()3010 BaseObjectPtr<Http2Ping> Http2Session::PopPing() {
3011 BaseObjectPtr<Http2Ping> ping;
3012 if (!outstanding_pings_.empty()) {
3013 ping = std::move(outstanding_pings_.front());
3014 outstanding_pings_.pop();
3015 DecrementCurrentSessionMemory(sizeof(*ping));
3016 }
3017 return ping;
3018 }
3019
AddPing(const uint8_t* payload, Local<Function> callback)3020 bool Http2Session::AddPing(const uint8_t* payload, Local<Function> callback) {
3021 Local<Object> obj;
3022 if (!env()->http2ping_constructor_template()
3023 ->NewInstance(env()->context())
3024 .ToLocal(&obj)) {
3025 return false;
3026 }
3027
3028 BaseObjectPtr<Http2Ping> ping =
3029 MakeDetachedBaseObject<Http2Ping>(this, obj, callback);
3030 if (!ping)
3031 return false;
3032
3033 if (outstanding_pings_.size() == max_outstanding_pings_) {
3034 ping->Done(false);
3035 return false;
3036 }
3037
3038 IncrementCurrentSessionMemory(sizeof(*ping));
3039 // The Ping itself is an Async resource. When the acknowledgement is received,
3040 // the callback will be invoked and a notification sent out to JS land. The
3041 // notification will include the duration of the ping, allowing the round
3042 // trip to be measured.
3043 ping->Send(payload);
3044
3045 outstanding_pings_.emplace(std::move(ping));
3046 return true;
3047 }
3048
PopSettings()3049 BaseObjectPtr<Http2Settings> Http2Session::PopSettings() {
3050 BaseObjectPtr<Http2Settings> settings;
3051 if (!outstanding_settings_.empty()) {
3052 settings = std::move(outstanding_settings_.front());
3053 outstanding_settings_.pop();
3054 DecrementCurrentSessionMemory(sizeof(*settings));
3055 }
3056 return settings;
3057 }
3058
AddSettings(Local<Function> callback)3059 bool Http2Session::AddSettings(Local<Function> callback) {
3060 Local<Object> obj;
3061 if (!env()->http2settings_constructor_template()
3062 ->NewInstance(env()->context())
3063 .ToLocal(&obj)) {
3064 return false;
3065 }
3066
3067 BaseObjectPtr<Http2Settings> settings =
3068 MakeDetachedBaseObject<Http2Settings>(this, obj, callback, 0);
3069 if (!settings)
3070 return false;
3071
3072 if (outstanding_settings_.size() == max_outstanding_settings_) {
3073 settings->Done(false);
3074 return false;
3075 }
3076
3077 IncrementCurrentSessionMemory(sizeof(*settings));
3078 settings->Send();
3079 outstanding_settings_.emplace(std::move(settings));
3080 return true;
3081 }
3082
Http2Ping( Http2Session* session, Local<Object> obj, Local<Function> callback)3083 Http2Ping::Http2Ping(
3084 Http2Session* session,
3085 Local<Object> obj,
3086 Local<Function> callback)
3087 : AsyncWrap(session->env(), obj, AsyncWrap::PROVIDER_HTTP2PING),
3088 session_(session),
3089 startTime_(uv_hrtime()) {
3090 callback_.Reset(env()->isolate(), callback);
3091 }
3092
MemoryInfo(MemoryTracker* tracker) const3093 void Http2Ping::MemoryInfo(MemoryTracker* tracker) const {
3094 tracker->TrackField("callback", callback_);
3095 }
3096
callback() const3097 Local<Function> Http2Ping::callback() const {
3098 return callback_.Get(env()->isolate());
3099 }
3100
Send(const uint8_t* payload)3101 void Http2Ping::Send(const uint8_t* payload) {
3102 CHECK(session_);
3103 uint8_t data[8];
3104 if (payload == nullptr) {
3105 memcpy(&data, &startTime_, arraysize(data));
3106 payload = data;
3107 }
3108 Http2Scope h2scope(session_.get());
3109 CHECK_EQ(nghttp2_submit_ping(
3110 session_->session(),
3111 NGHTTP2_FLAG_NONE,
3112 payload), 0);
3113 }
3114
Done(bool ack, const uint8_t* payload)3115 void Http2Ping::Done(bool ack, const uint8_t* payload) {
3116 uint64_t duration_ns = uv_hrtime() - startTime_;
3117 double duration_ms = duration_ns / 1e6;
3118 if (session_) session_->statistics_.ping_rtt = duration_ns;
3119
3120 Isolate* isolate = env()->isolate();
3121 HandleScope handle_scope(isolate);
3122 Context::Scope context_scope(env()->context());
3123
3124 Local<Value> buf = Undefined(isolate);
3125 if (payload != nullptr) {
3126 buf = Buffer::Copy(isolate,
3127 reinterpret_cast<const char*>(payload),
3128 8).ToLocalChecked();
3129 }
3130
3131 Local<Value> argv[] = {
3132 Boolean::New(isolate, ack), Number::New(isolate, duration_ms), buf};
3133 MakeCallback(callback(), arraysize(argv), argv);
3134 }
3135
DetachFromSession()3136 void Http2Ping::DetachFromSession() {
3137 session_.reset();
3138 }
3139
MemoryInfo(MemoryTracker* tracker) const3140 void NgHttp2StreamWrite::MemoryInfo(MemoryTracker* tracker) const {
3141 if (req_wrap)
3142 tracker->TrackField("req_wrap", req_wrap);
3143 tracker->TrackField("buf", buf);
3144 }
3145
SetCallbackFunctions(const FunctionCallbackInfo<Value>& args)3146 void SetCallbackFunctions(const FunctionCallbackInfo<Value>& args) {
3147 Environment* env = Environment::GetCurrent(args);
3148 CHECK_EQ(args.Length(), 11);
3149
3150 #define SET_FUNCTION(arg, name) \
3151 CHECK(args[arg]->IsFunction()); \
3152 env->set_http2session_on_ ## name ## _function(args[arg].As<Function>());
3153
3154 SET_FUNCTION(0, error)
3155 SET_FUNCTION(1, priority)
3156 SET_FUNCTION(2, settings)
3157 SET_FUNCTION(3, ping)
3158 SET_FUNCTION(4, headers)
3159 SET_FUNCTION(5, frame_error)
3160 SET_FUNCTION(6, goaway_data)
3161 SET_FUNCTION(7, altsvc)
3162 SET_FUNCTION(8, origin)
3163 SET_FUNCTION(9, stream_trailers)
3164 SET_FUNCTION(10, stream_close)
3165
3166 #undef SET_FUNCTION
3167 }
3168
3169 #ifdef NODE_DEBUG_NGHTTP2
NgHttp2Debug(const char* format, va_list args)3170 void NgHttp2Debug(const char* format, va_list args) {
3171 vfprintf(stderr, format, args);
3172 }
3173 #endif
3174
MemoryInfo(MemoryTracker* tracker) const3175 void Http2State::MemoryInfo(MemoryTracker* tracker) const {
3176 tracker->TrackField("root_buffer", root_buffer);
3177 }
3178
3179 // Set up the process.binding('http2') binding.
Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv)3180 void Initialize(Local<Object> target,
3181 Local<Value> unused,
3182 Local<Context> context,
3183 void* priv) {
3184 Realm* realm = Realm::GetCurrent(context);
3185 Environment* env = realm->env();
3186 Isolate* isolate = env->isolate();
3187 HandleScope handle_scope(isolate);
3188
3189 Http2State* const state = realm->AddBindingData<Http2State>(context, target);
3190 if (state == nullptr) return;
3191
3192 #define SET_STATE_TYPEDARRAY(name, field) \
3193 target->Set(context, \
3194 FIXED_ONE_BYTE_STRING(isolate, (name)), \
3195 (field)).FromJust()
3196
3197 // Initialize the buffer used to store the session state
3198 SET_STATE_TYPEDARRAY(
3199 "sessionState", state->session_state_buffer.GetJSArray());
3200 // Initialize the buffer used to store the stream state
3201 SET_STATE_TYPEDARRAY(
3202 "streamState", state->stream_state_buffer.GetJSArray());
3203 SET_STATE_TYPEDARRAY(
3204 "settingsBuffer", state->settings_buffer.GetJSArray());
3205 SET_STATE_TYPEDARRAY(
3206 "optionsBuffer", state->options_buffer.GetJSArray());
3207 SET_STATE_TYPEDARRAY(
3208 "streamStats", state->stream_stats_buffer.GetJSArray());
3209 SET_STATE_TYPEDARRAY(
3210 "sessionStats", state->session_stats_buffer.GetJSArray());
3211 #undef SET_STATE_TYPEDARRAY
3212
3213 NODE_DEFINE_CONSTANT(target, kBitfield);
3214 NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3215 NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3216 NODE_DEFINE_CONSTANT(target, kSessionMaxInvalidFrames);
3217 NODE_DEFINE_CONSTANT(target, kSessionMaxRejectedStreams);
3218 NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3219
3220 NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3221 NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3222 NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3223 NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3224
3225 // Method to fetch the nghttp2 string description of an nghttp2 error code
3226 SetMethod(context, target, "nghttp2ErrorString", HttpErrorString);
3227 SetMethod(context, target, "refreshDefaultSettings", RefreshDefaultSettings);
3228 SetMethod(context, target, "packSettings", PackSettings);
3229 SetMethod(context, target, "setCallbackFunctions", SetCallbackFunctions);
3230
3231 Local<FunctionTemplate> ping = FunctionTemplate::New(env->isolate());
3232 ping->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Http2Ping"));
3233 ping->Inherit(AsyncWrap::GetConstructorTemplate(env));
3234 Local<ObjectTemplate> pingt = ping->InstanceTemplate();
3235 pingt->SetInternalFieldCount(Http2Ping::kInternalFieldCount);
3236 env->set_http2ping_constructor_template(pingt);
3237
3238 Local<FunctionTemplate> setting = FunctionTemplate::New(env->isolate());
3239 setting->Inherit(AsyncWrap::GetConstructorTemplate(env));
3240 Local<ObjectTemplate> settingt = setting->InstanceTemplate();
3241 settingt->SetInternalFieldCount(AsyncWrap::kInternalFieldCount);
3242 env->set_http2settings_constructor_template(settingt);
3243
3244 Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate());
3245 SetProtoMethod(isolate, stream, "id", Http2Stream::GetID);
3246 SetProtoMethod(isolate, stream, "destroy", Http2Stream::Destroy);
3247 SetProtoMethod(isolate, stream, "priority", Http2Stream::Priority);
3248 SetProtoMethod(isolate, stream, "pushPromise", Http2Stream::PushPromise);
3249 SetProtoMethod(isolate, stream, "info", Http2Stream::Info);
3250 SetProtoMethod(isolate, stream, "trailers", Http2Stream::Trailers);
3251 SetProtoMethod(isolate, stream, "respond", Http2Stream::Respond);
3252 SetProtoMethod(isolate, stream, "rstStream", Http2Stream::RstStream);
3253 SetProtoMethod(isolate, stream, "refreshState", Http2Stream::RefreshState);
3254 stream->Inherit(AsyncWrap::GetConstructorTemplate(env));
3255 StreamBase::AddMethods(env, stream);
3256 Local<ObjectTemplate> streamt = stream->InstanceTemplate();
3257 streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount);
3258 env->set_http2stream_constructor_template(streamt);
3259 SetConstructorFunction(context, target, "Http2Stream", stream);
3260
3261 Local<FunctionTemplate> session =
3262 NewFunctionTemplate(isolate, Http2Session::New);
3263 session->InstanceTemplate()->SetInternalFieldCount(
3264 Http2Session::kInternalFieldCount);
3265 session->Inherit(AsyncWrap::GetConstructorTemplate(env));
3266 SetProtoMethod(isolate, session, "origin", Http2Session::Origin);
3267 SetProtoMethod(isolate, session, "altsvc", Http2Session::AltSvc);
3268 SetProtoMethod(isolate, session, "ping", Http2Session::Ping);
3269 SetProtoMethod(isolate, session, "consume", Http2Session::Consume);
3270 SetProtoMethod(isolate, session, "receive", Http2Session::Receive);
3271 SetProtoMethod(isolate, session, "destroy", Http2Session::Destroy);
3272 SetProtoMethod(isolate, session, "goaway", Http2Session::Goaway);
3273 SetProtoMethod(isolate, session, "settings", Http2Session::Settings);
3274 SetProtoMethod(isolate, session, "request", Http2Session::Request);
3275 SetProtoMethod(
3276 isolate, session, "setNextStreamID", Http2Session::SetNextStreamID);
3277 SetProtoMethod(
3278 isolate, session, "setLocalWindowSize", Http2Session::SetLocalWindowSize);
3279 SetProtoMethod(
3280 isolate, session, "updateChunksSent", Http2Session::UpdateChunksSent);
3281 SetProtoMethod(isolate, session, "refreshState", Http2Session::RefreshState);
3282 SetProtoMethod(
3283 isolate,
3284 session,
3285 "localSettings",
3286 Http2Session::RefreshSettings<nghttp2_session_get_local_settings>);
3287 SetProtoMethod(
3288 isolate,
3289 session,
3290 "remoteSettings",
3291 Http2Session::RefreshSettings<nghttp2_session_get_remote_settings>);
3292 SetConstructorFunction(context, target, "Http2Session", session);
3293
3294 Local<Object> constants = Object::New(isolate);
3295
3296 // This does allocate one more slot than needed but it's not used.
3297 #define V(name) FIXED_ONE_BYTE_STRING(isolate, #name),
3298 Local<Value> error_code_names[] = {
3299 HTTP2_ERROR_CODES(V)
3300 };
3301 #undef V
3302
3303 Local<Array> name_for_error_code =
3304 Array::New(
3305 isolate,
3306 error_code_names,
3307 arraysize(error_code_names));
3308
3309 target->Set(context,
3310 FIXED_ONE_BYTE_STRING(isolate, "nameForErrorCode"),
3311 name_for_error_code).Check();
3312
3313 #define V(constant) NODE_DEFINE_HIDDEN_CONSTANT(constants, constant);
3314 HTTP2_HIDDEN_CONSTANTS(V)
3315 #undef V
3316
3317 #define V(constant) NODE_DEFINE_CONSTANT(constants, constant);
3318 HTTP2_CONSTANTS(V)
3319 #undef V
3320
3321 // NGHTTP2_DEFAULT_WEIGHT is a macro and not a regular define
3322 // it won't be set properly on the constants object if included
3323 // in the HTTP2_CONSTANTS macro.
3324 NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
3325
3326 #define V(NAME, VALUE) \
3327 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
3328 HTTP_KNOWN_HEADERS(V)
3329 #undef V
3330
3331 #define V(NAME, VALUE) \
3332 NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
3333 HTTP_KNOWN_METHODS(V)
3334 #undef V
3335
3336 #define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
3337 HTTP_STATUS_CODES(V)
3338 #undef V
3339
3340 target->Set(context, env->constants_string(), constants).Check();
3341
3342 #ifdef NODE_DEBUG_NGHTTP2
3343 nghttp2_set_debug_vprintf_callback(NgHttp2Debug);
3344 #endif
3345 }
3346 } // namespace http2
3347 } // namespace node
3348
3349 NODE_BINDING_CONTEXT_AWARE_INTERNAL(http2, node::http2::Initialize)
3350