summaryrefslogtreecommitdiff
path: root/src/node_http2.cc
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2017-07-17 10:17:16 -0700
committerJames M Snell <jasnell@gmail.com>2017-08-04 12:55:44 -0700
commite71e71b5138c3dfee080f4215dd957dc7a6cbdaf (patch)
treea46b77ae1bd423c0db3cf0f65ea370a721b67c24 /src/node_http2.cc
parent71a1876f6c3f2a7131c7019d63fea5c8fa740276 (diff)
downloadandroid-node-v8-e71e71b5138c3dfee080f4215dd957dc7a6cbdaf.tar.gz
android-node-v8-e71e71b5138c3dfee080f4215dd957dc7a6cbdaf.tar.bz2
android-node-v8-e71e71b5138c3dfee080f4215dd957dc7a6cbdaf.zip
http2: introducing HTTP/2
At long last: The initial *experimental* implementation of HTTP/2. This is an accumulation of the work that has been done in the nodejs/http2 repository, squashed down to a couple of commits. The original commit history has been preserved in the nodejs/http2 repository. This PR introduces the nghttp2 C library as a new dependency. This library provides the majority of the HTTP/2 protocol implementation, with the rest of the code here providing the mapping of the library into a usable JS API. Within src, a handful of new node_http2_*.c and node_http2_*.h files are introduced. These provide the internal mechanisms that interface with nghttp and define the `process.binding('http2')` interface. The JS API is defined within `internal/http2/*.js`. There are two APIs provided: Core and Compat. The Core API is HTTP/2 specific and is designed to be as minimal and as efficient as possible. The Compat API is intended to be as close to the existing HTTP/1 API as possible, with some exceptions. Tests, documentation and initial benchmarks are included. The `http2` module is gated by a new `--expose-http2` command line flag. When used, `require('http2')` will be exposed to users. Note that there is an existing `http2` module on npm that would be impacted by the introduction of this module, which is the main reason for gating this behind a flag. When using `require('http2')` the first time, a process warning will be emitted indicating that an experimental feature is being used. To run the benchmarks, the `h2load` tool (part of the nghttp project) is required: `./node benchmarks/http2/simple.js benchmarker=h2load`. Only two benchmarks are currently available. Additional configuration options to enable verbose debugging are provided: ``` $ ./configure --debug-http2 --debug-nghttp2 $ NODE_DEBUG=http2 ./node ``` The `--debug-http2` configuration option enables verbose debug statements from the `src/node_http2_*` files. The `--debug-nghttp2` enables the nghttp library's own verbose debug output. The `NODE_DEBUG=http2` enables JS-level debug output. The following illustrates as simple HTTP/2 server and client interaction: (The HTTP/2 client and server support both plain text and TLS connections) ```jt client = http2.connect('http://localhost:80'); const req = client.request({ ':path': '/some/path' }); req.on('data', (chunk) => { /* do something with the data */ }); req.on('end', () => { client.destroy(); }); // Plain text (non-TLS server) const server = http2.createServer(); server.on('stream', (stream, requestHeaders) => { stream.respond({ ':status': 200 }); stream.write('hello '); stream.end('world'); }); server.listen(80); ``` ```js const http2 = require('http2'); const client = http2.connect('http://localhost'); ``` Author: Anna Henningsen <anna@addaleax.net> Author: Colin Ihrig <cjihrig@gmail.com> Author: Daniel Bevenius <daniel.bevenius@gmail.com> Author: James M Snell <jasnell@gmail.com> Author: Jun Mukai Author: Kelvin Jin Author: Matteo Collina <matteo.collina@gmail.com> Author: Robert Kowalski <rok@kowalski.gd> Author: Santiago Gimeno <santiago.gimeno@gmail.com> Author: Sebastiaan Deckers <sebdeckers83@gmail.com> Author: Yosuke Furukawa <yosuke.furukawa@gmail.com> PR-URL: https://github.com/nodejs/node/pull/14239 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'src/node_http2.cc')
-rw-r--r--src/node_http2.cc1326
1 files changed, 1326 insertions, 0 deletions
diff --git a/src/node_http2.cc b/src/node_http2.cc
new file mode 100644
index 0000000000..5ad1352cc1
--- /dev/null
+++ b/src/node_http2.cc
@@ -0,0 +1,1326 @@
+#include "node.h"
+#include "node_buffer.h"
+#include "node_http2.h"
+
+namespace node {
+
+using v8::ArrayBuffer;
+using v8::Boolean;
+using v8::Context;
+using v8::Function;
+using v8::Integer;
+using v8::Undefined;
+
+namespace http2 {
+
+enum Http2SettingsIndex {
+ IDX_SETTINGS_HEADER_TABLE_SIZE,
+ IDX_SETTINGS_ENABLE_PUSH,
+ IDX_SETTINGS_INITIAL_WINDOW_SIZE,
+ IDX_SETTINGS_MAX_FRAME_SIZE,
+ IDX_SETTINGS_MAX_CONCURRENT_STREAMS,
+ IDX_SETTINGS_MAX_HEADER_LIST_SIZE,
+ IDX_SETTINGS_COUNT
+};
+
+enum Http2SessionStateIndex {
+ IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE,
+ IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH,
+ IDX_SESSION_STATE_NEXT_STREAM_ID,
+ IDX_SESSION_STATE_LOCAL_WINDOW_SIZE,
+ IDX_SESSION_STATE_LAST_PROC_STREAM_ID,
+ IDX_SESSION_STATE_REMOTE_WINDOW_SIZE,
+ IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE,
+ IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE,
+ IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE,
+ IDX_SESSION_STATE_COUNT
+};
+
+enum Http2StreamStateIndex {
+ IDX_STREAM_STATE,
+ IDX_STREAM_STATE_WEIGHT,
+ IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT,
+ IDX_STREAM_STATE_LOCAL_CLOSE,
+ IDX_STREAM_STATE_REMOTE_CLOSE,
+ IDX_STREAM_STATE_LOCAL_WINDOW_SIZE,
+ IDX_STREAM_STATE_COUNT
+};
+
+enum Http2OptionsIndex {
+ IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE,
+ IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS,
+ IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH,
+ IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS,
+ IDX_OPTIONS_PADDING_STRATEGY,
+ IDX_OPTIONS_FLAGS
+};
+
+Http2Options::Http2Options(Environment* env) {
+ nghttp2_option_new(&options_);
+
+ uint32_t* buffer = env->http2_options_buffer();
+ uint32_t flags = buffer[IDX_OPTIONS_FLAGS];
+
+ if ((flags & (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) ==
+ (1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE)) {
+ SetMaxDeflateDynamicTableSize(
+ buffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE]);
+ }
+
+ if ((flags & (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) ==
+ (1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS)) {
+ SetMaxReservedRemoteStreams(
+ buffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS]);
+ }
+
+ if ((flags & (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) ==
+ (1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH)) {
+ SetMaxSendHeaderBlockLength(
+ buffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH]);
+ }
+
+ SetPeerMaxConcurrentStreams(100); // Recommended default
+ if ((flags & (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) ==
+ (1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS)) {
+ SetPeerMaxConcurrentStreams(
+ buffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS]);
+ }
+
+ if ((flags & (1 << IDX_OPTIONS_PADDING_STRATEGY)) ==
+ (1 << IDX_OPTIONS_PADDING_STRATEGY)) {
+ SetPaddingStrategy(buffer[IDX_OPTIONS_PADDING_STRATEGY]);
+ }
+}
+
+inline void CopyHeaders(Isolate* isolate,
+ Local<Context> context,
+ MaybeStackBuffer<nghttp2_nv>* list,
+ Local<Array> headers) {
+ Local<Value> item;
+ Local<Array> header;
+
+ for (size_t n = 0; n < headers->Length(); n++) {
+ item = headers->Get(context, n).ToLocalChecked();
+ header = item.As<Array>();
+ Local<Value> key = header->Get(context, 0).ToLocalChecked();
+ Local<Value> value = header->Get(context, 1).ToLocalChecked();
+ CHECK(key->IsString());
+ CHECK(value->IsString());
+ size_t keylen = StringBytes::StorageSize(isolate, key, ASCII);
+ size_t valuelen = StringBytes::StorageSize(isolate, value, ASCII);
+ nghttp2_nv& nv = (*list)[n];
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+ Local<Value> flag = header->Get(context, 2).ToLocalChecked();
+ if (flag->BooleanValue(context).ToChecked())
+ nv.flags |= NGHTTP2_NV_FLAG_NO_INDEX;
+ nv.name = Malloc<uint8_t>(keylen);
+ nv.value = Malloc<uint8_t>(valuelen);
+ nv.namelen =
+ StringBytes::Write(isolate,
+ reinterpret_cast<char*>(nv.name),
+ keylen, key, ASCII);
+ nv.valuelen =
+ StringBytes::Write(isolate,
+ reinterpret_cast<char*>(nv.value),
+ valuelen, value, ASCII);
+ }
+}
+
+inline void FreeHeaders(MaybeStackBuffer<nghttp2_nv>* list) {
+ for (size_t n = 0; n < list->length(); n++) {
+ free((*list)[n].name);
+ free((*list)[n].value);
+ }
+}
+
+void Http2Session::OnFreeSession() {
+ ::delete this;
+}
+
+ssize_t Http2Session::OnMaxFrameSizePadding(size_t frameLen,
+ size_t maxPayloadLen) {
+ DEBUG_HTTP2("Http2Session: using max frame size padding\n");
+ return maxPayloadLen;
+}
+
+ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
+ size_t maxPayloadLen) {
+ DEBUG_HTTP2("Http2Session: using callback padding\n");
+ Isolate* isolate = env()->isolate();
+ Local<Context> context = env()->context();
+
+ HandleScope handle_scope(isolate);
+ Context::Scope context_scope(context);
+
+ if (object()->Has(context, env()->ongetpadding_string()).FromJust()) {
+ uint32_t* buffer = env()->http2_padding_buffer();
+ buffer[0] = frameLen;
+ buffer[1] = maxPayloadLen;
+ MakeCallback(env()->ongetpadding_string(), 0, nullptr);
+ uint32_t retval = buffer[2];
+ retval = retval <= maxPayloadLen ? retval : maxPayloadLen;
+ retval = retval >= frameLen ? retval : frameLen;
+ CHECK_GE(retval, frameLen);
+ CHECK_LE(retval, maxPayloadLen);
+ return retval;
+ }
+ return frameLen;
+}
+
+void Http2Session::SetNextStreamID(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ nghttp2_session* s = session->session();
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ DEBUG_HTTP2("Http2Session: setting next stream id to %d\n", id);
+ nghttp2_session_set_next_stream_id(s, id);
+}
+
+void HttpErrorString(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ uint32_t val = args[0]->Uint32Value(env->context()).ToChecked();
+ args.GetReturnValue().Set(
+ OneByteString(env->isolate(), nghttp2_strerror(val)));
+}
+
+// Serializes the settings object into a Buffer instance that
+// would be suitable, for instance, for creating the Base64
+// output for an HTTP2-Settings header field.
+void PackSettings(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ HandleScope scope(env->isolate());
+
+ std::vector<nghttp2_settings_entry> entries;
+ entries.reserve(6);
+
+ uint32_t* const buffer = env->http2_settings_buffer();
+ uint32_t flags = buffer[IDX_SETTINGS_COUNT];
+
+ if ((flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) ==
+ (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
+ DEBUG_HTTP2("Setting header table size: %d\n",
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_HEADER_TABLE_SIZE,
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) ==
+ (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
+ DEBUG_HTTP2("Setting max concurrent streams: %d\n",
+ buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+ buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) ==
+ (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
+ DEBUG_HTTP2("Setting max frame size: %d\n",
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_FRAME_SIZE,
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) ==
+ (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
+ DEBUG_HTTP2("Setting initial window size: %d\n",
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE,
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) ==
+ (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
+ DEBUG_HTTP2("Setting max header list size: %d\n",
+ buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
+ buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) ==
+ (1 << IDX_SETTINGS_ENABLE_PUSH)) {
+ DEBUG_HTTP2("Setting enable push: %d\n",
+ buffer[IDX_SETTINGS_ENABLE_PUSH]);
+ entries.push_back({NGHTTP2_SETTINGS_ENABLE_PUSH,
+ buffer[IDX_SETTINGS_ENABLE_PUSH]});
+ }
+
+ const size_t len = entries.size() * 6;
+ MaybeStackBuffer<char> buf(len);
+ ssize_t ret =
+ nghttp2_pack_settings_payload(
+ reinterpret_cast<uint8_t*>(*buf), len, &entries[0], entries.size());
+ if (ret >= 0) {
+ args.GetReturnValue().Set(
+ Buffer::Copy(env, *buf, len).ToLocalChecked());
+ }
+}
+
+// Used to fill in the spec defined initial values for each setting.
+void RefreshDefaultSettings(const FunctionCallbackInfo<Value>& args) {
+ DEBUG_HTTP2("Http2Session: refreshing default settings\n");
+ Environment* env = Environment::GetCurrent(args);
+ uint32_t* const buffer = env->http2_settings_buffer();
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
+ DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
+ buffer[IDX_SETTINGS_ENABLE_PUSH] =
+ DEFAULT_SETTINGS_ENABLE_PUSH;
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
+ DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE;
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
+ DEFAULT_SETTINGS_MAX_FRAME_SIZE;
+ buffer[IDX_SETTINGS_COUNT] =
+ (1 << IDX_SETTINGS_HEADER_TABLE_SIZE) |
+ (1 << IDX_SETTINGS_ENABLE_PUSH) |
+ (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE) |
+ (1 << IDX_SETTINGS_MAX_FRAME_SIZE);
+}
+
+template <get_setting fn>
+void RefreshSettings(const FunctionCallbackInfo<Value>& args) {
+ DEBUG_HTTP2("Http2Session: refreshing settings for session\n");
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsObject());
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
+ Environment* env = session->env();
+ nghttp2_session* s = session->session();
+
+ uint32_t* const buffer = env->http2_settings_buffer();
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE] =
+ fn(s, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
+ buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
+ fn(s, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
+ fn(s, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE] =
+ fn(s, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
+ buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
+ fn(s, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
+ buffer[IDX_SETTINGS_ENABLE_PUSH] =
+ fn(s, NGHTTP2_SETTINGS_ENABLE_PUSH);
+}
+
+// Used to fill in the spec defined initial values for each setting.
+void RefreshSessionState(const FunctionCallbackInfo<Value>& args) {
+ DEBUG_HTTP2("Http2Session: refreshing session state\n");
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsObject());
+ Environment* env = Environment::GetCurrent(args);
+ double* const buffer = env->http2_session_state_buffer();
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
+ nghttp2_session* s = session->session();
+
+ buffer[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE] =
+ nghttp2_session_get_effective_local_window_size(s);
+ buffer[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH] =
+ nghttp2_session_get_effective_recv_data_length(s);
+ buffer[IDX_SESSION_STATE_NEXT_STREAM_ID] =
+ nghttp2_session_get_next_stream_id(s);
+ buffer[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE] =
+ nghttp2_session_get_local_window_size(s);
+ buffer[IDX_SESSION_STATE_LAST_PROC_STREAM_ID] =
+ nghttp2_session_get_last_proc_stream_id(s);
+ buffer[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE] =
+ nghttp2_session_get_remote_window_size(s);
+ buffer[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE] =
+ nghttp2_session_get_outbound_queue_size(s);
+ buffer[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE] =
+ nghttp2_session_get_hd_deflate_dynamic_table_size(s);
+ buffer[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE] =
+ nghttp2_session_get_hd_inflate_dynamic_table_size(s);
+}
+
+void RefreshStreamState(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ CHECK_EQ(args.Length(), 2);
+ CHECK(args[0]->IsObject());
+ CHECK(args[1]->IsNumber());
+ int32_t id = args[1]->Int32Value(env->context()).ToChecked();
+ DEBUG_HTTP2("Http2Session: refreshing stream %d state\n", id);
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>());
+ nghttp2_session* s = session->session();
+ Nghttp2Stream* stream;
+
+ double* const buffer = env->http2_stream_state_buffer();
+
+ if ((stream = session->FindStream(id)) == nullptr) {
+ buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
+ buffer[IDX_STREAM_STATE_WEIGHT] =
+ buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
+ buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
+ buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
+ buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
+ return;
+ }
+ nghttp2_stream* str =
+ nghttp2_session_find_stream(s, stream->id());
+
+ if (str == nullptr) {
+ buffer[IDX_STREAM_STATE] = NGHTTP2_STREAM_STATE_IDLE;
+ buffer[IDX_STREAM_STATE_WEIGHT] =
+ buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
+ buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
+ buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
+ buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] = 0;
+ } else {
+ buffer[IDX_STREAM_STATE] =
+ nghttp2_stream_get_state(str);
+ buffer[IDX_STREAM_STATE_WEIGHT] =
+ nghttp2_stream_get_weight(str);
+ buffer[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT] =
+ nghttp2_stream_get_sum_dependency_weight(str);
+ buffer[IDX_STREAM_STATE_LOCAL_CLOSE] =
+ nghttp2_session_get_stream_local_close(s, id);
+ buffer[IDX_STREAM_STATE_REMOTE_CLOSE] =
+ nghttp2_session_get_stream_remote_close(s, id);
+ buffer[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE] =
+ nghttp2_session_get_stream_local_window_size(s, id);
+ }
+}
+
+void Http2Session::New(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ CHECK(args.IsConstructCall());
+
+ int val = args[0]->IntegerValue(env->context()).ToChecked();
+ nghttp2_session_type type = static_cast<nghttp2_session_type>(val);
+ DEBUG_HTTP2("Http2Session: creating a session of type: %d\n", type);
+ new Http2Session(env, args.This(), type);
+}
+
+
+// Capture the stream that this session will use to send and receive data
+void Http2Session::Consume(const FunctionCallbackInfo<Value>& args) {
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ CHECK(args[0]->IsExternal());
+ session->Consume(args[0].As<External>());
+}
+
+void Http2Session::Destroy(const FunctionCallbackInfo<Value>& args) {
+ DEBUG_HTTP2("Http2Session: destroying session\n");
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ session->Unconsume();
+ session->Free();
+}
+
+void Http2Session::SubmitPriority(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Local<Context> context = env->context();
+
+ nghttp2_priority_spec spec;
+ int32_t id = args[0]->Int32Value(context).ToChecked();
+ int32_t parent = args[1]->Int32Value(context).ToChecked();
+ int32_t weight = args[2]->Int32Value(context).ToChecked();
+ bool exclusive = args[3]->BooleanValue(context).ToChecked();
+ bool silent = args[4]->BooleanValue(context).ToChecked();
+ DEBUG_HTTP2("Http2Session: submitting priority for stream %d: "
+ "parent: %d, weight: %d, exclusive: %d, silent: %d\n",
+ id, parent, weight, exclusive, silent);
+ CHECK_GT(id, 0);
+ CHECK_GE(parent, 0);
+ CHECK_GE(weight, 0);
+
+ Nghttp2Stream* stream;
+ if (!(stream = session->FindStream(id))) {
+ // invalid stream
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ nghttp2_priority_spec_init(&spec, parent, weight, exclusive ? 1 : 0);
+
+ args.GetReturnValue().Set(stream->SubmitPriority(&spec, silent));
+}
+
+void Http2Session::SubmitSettings(const FunctionCallbackInfo<Value>& args) {
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+
+ Environment* env = session->env();
+
+ uint32_t* const buffer = env->http2_settings_buffer();
+ uint32_t flags = buffer[IDX_SETTINGS_COUNT];
+
+ std::vector<nghttp2_settings_entry> entries;
+ entries.reserve(6);
+
+ if ((flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) ==
+ (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) {
+ DEBUG_HTTP2("Setting header table size: %d\n",
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_HEADER_TABLE_SIZE,
+ buffer[IDX_SETTINGS_HEADER_TABLE_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) ==
+ (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) {
+ DEBUG_HTTP2("Setting max concurrent streams: %d\n",
+ buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+ buffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) ==
+ (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) {
+ DEBUG_HTTP2("Setting max frame size: %d\n",
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_FRAME_SIZE,
+ buffer[IDX_SETTINGS_MAX_FRAME_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) ==
+ (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) {
+ DEBUG_HTTP2("Setting initial window size: %d\n",
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE,
+ buffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) ==
+ (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) {
+ DEBUG_HTTP2("Setting max header list size: %d\n",
+ buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]);
+ entries.push_back({NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
+ buffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE]});
+ }
+
+ if ((flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) ==
+ (1 << IDX_SETTINGS_ENABLE_PUSH)) {
+ DEBUG_HTTP2("Setting enable push: %d\n",
+ buffer[IDX_SETTINGS_ENABLE_PUSH]);
+ entries.push_back({NGHTTP2_SETTINGS_ENABLE_PUSH,
+ buffer[IDX_SETTINGS_ENABLE_PUSH]});
+ }
+
+ if (entries.size() > 0) {
+ args.GetReturnValue().Set(
+ session->Nghttp2Session::SubmitSettings(&entries[0], entries.size()));
+ } else {
+ args.GetReturnValue().Set(
+ session->Nghttp2Session::SubmitSettings(nullptr, 0));
+ }
+}
+
+void Http2Session::SubmitRstStream(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Local<Context> context = env->context();
+ CHECK(args[0]->IsNumber());
+ CHECK(args[1]->IsNumber());
+
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+
+ int32_t id = args[0]->Int32Value(context).ToChecked();
+ uint32_t code = args[1]->Uint32Value(context).ToChecked();
+
+ Nghttp2Stream* stream;
+ if (!(stream = session->FindStream(id))) {
+ // invalid stream
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ DEBUG_HTTP2("Http2Session: sending rst_stream for stream %d, code: %d\n",
+ id, code);
+ args.GetReturnValue().Set(stream->SubmitRstStream(code));
+}
+
+void Http2Session::SubmitRequest(const FunctionCallbackInfo<Value>& args) {
+ // args[0] Array of headers
+ // args[1] endStream boolean
+ // args[2] parentStream ID (for priority spec)
+ // args[3] weight (for priority spec)
+ // args[4] exclusive boolean (for priority spec)
+ CHECK(args[0]->IsArray());
+
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Environment* env = session->env();
+ Local<Context> context = env->context();
+ Isolate* isolate = env->isolate();
+
+ Local<Array> headers = args[0].As<Array>();
+ bool endStream = args[1]->BooleanValue(context).ToChecked();
+ int32_t parent = args[2]->Int32Value(context).ToChecked();
+ int32_t weight = args[3]->Int32Value(context).ToChecked();
+ bool exclusive = args[4]->BooleanValue(context).ToChecked();
+
+ DEBUG_HTTP2("Http2Session: submitting request: headers: %d, end-stream: %d, "
+ "parent: %d, weight: %d, exclusive: %d\n", headers->Length(),
+ endStream, parent, weight, exclusive);
+
+ nghttp2_priority_spec prispec;
+ nghttp2_priority_spec_init(&prispec, parent, weight, exclusive ? 1 : 0);
+
+ Headers list(isolate, context, headers);
+
+ int32_t ret = session->Nghttp2Session::SubmitRequest(&prispec,
+ *list, list.length(),
+ nullptr, endStream);
+ DEBUG_HTTP2("Http2Session: request submitted, response: %d\n", ret);
+ args.GetReturnValue().Set(ret);
+}
+
+void Http2Session::SubmitResponse(const FunctionCallbackInfo<Value>& args) {
+ CHECK(args[0]->IsNumber());
+ CHECK(args[1]->IsArray());
+
+ Http2Session* session;
+ Nghttp2Stream* stream;
+
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Environment* env = session->env();
+ Local<Context> context = env->context();
+ Isolate* isolate = env->isolate();
+
+ int32_t id = args[0]->Int32Value(context).ToChecked();
+ Local<Array> headers = args[1].As<Array>();
+ bool endStream = args[2]->BooleanValue(context).ToChecked();
+
+ DEBUG_HTTP2("Http2Session: submitting response for stream %d: headers: %d, "
+ "end-stream: %d\n", id, headers->Length(), endStream);
+
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+
+ Headers list(isolate, context, headers);
+
+ args.GetReturnValue().Set(
+ stream->SubmitResponse(*list, list.length(), endStream));
+}
+
+void Http2Session::SubmitFile(const FunctionCallbackInfo<Value>& args) {
+ CHECK(args[0]->IsNumber()); // Stream ID
+ CHECK(args[1]->IsNumber()); // File Descriptor
+ CHECK(args[2]->IsArray()); // Headers
+
+ Http2Session* session;
+ Nghttp2Stream* stream;
+
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Environment* env = session->env();
+ Local<Context> context = env->context();
+ Isolate* isolate = env->isolate();
+
+ int32_t id = args[0]->Int32Value(context).ToChecked();
+ int fd = args[1]->Int32Value(context).ToChecked();
+ Local<Array> headers = args[2].As<Array>();
+
+ DEBUG_HTTP2("Http2Session: submitting file %d for stream %d: headers: %d, "
+ "end-stream: %d\n", fd, id, headers->Length());
+
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+
+ Headers list(isolate, context, headers);
+
+ args.GetReturnValue().Set(stream->SubmitFile(fd, *list, list.length()));
+}
+
+void Http2Session::SendHeaders(const FunctionCallbackInfo<Value>& args) {
+ CHECK(args[0]->IsNumber());
+ CHECK(args[1]->IsArray());
+
+ Http2Session* session;
+ Nghttp2Stream* stream;
+
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Environment* env = session->env();
+ Local<Context> context = env->context();
+ Isolate* isolate = env->isolate();
+
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ Local<Array> headers = args[1].As<Array>();
+
+ DEBUG_HTTP2("Http2Session: sending informational headers for stream %d, "
+ "count: %d\n", id, headers->Length());
+
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+
+ Headers list(isolate, context, headers);
+
+ args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length()));
+}
+
+void Http2Session::ShutdownStream(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ CHECK(args[0]->IsNumber());
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Nghttp2Stream* stream;
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ DEBUG_HTTP2("Http2Session: shutting down stream %d\n", id);
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ stream->Shutdown();
+}
+
+
+void Http2Session::StreamReadStart(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ CHECK(args[0]->IsNumber());
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Nghttp2Stream* stream;
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ stream->ReadStart();
+}
+
+
+void Http2Session::StreamReadStop(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ CHECK(args[0]->IsNumber());
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ Nghttp2Stream* stream;
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ stream->ReadStop();
+}
+
+void Http2Session::SendShutdownNotice(
+ const FunctionCallbackInfo<Value>& args) {
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+ session->SubmitShutdownNotice();
+}
+
+void Http2Session::SubmitGoaway(const FunctionCallbackInfo<Value>& args) {
+ Http2Session* session;
+ Environment* env = Environment::GetCurrent(args);
+ Local<Context> context = env->context();
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+
+ uint32_t errorCode = args[0]->Uint32Value(context).ToChecked();
+ int32_t lastStreamID = args[1]->Int32Value(context).ToChecked();
+ Local<Value> opaqueData = args[2];
+
+ uint8_t* data = NULL;
+ size_t length = 0;
+
+ if (opaqueData->BooleanValue(context).ToChecked()) {
+ THROW_AND_RETURN_UNLESS_BUFFER(env, opaqueData);
+ SPREAD_BUFFER_ARG(opaqueData, buf);
+ data = reinterpret_cast<uint8_t*>(buf_data);
+ length = buf_length;
+ }
+
+ DEBUG_HTTP2("Http2Session: initiating immediate shutdown. "
+ "last-stream-id: %d, code: %d, opaque-data: %d\n",
+ lastStreamID, errorCode, length);
+ int status = nghttp2_submit_goaway(session->session(),
+ NGHTTP2_FLAG_NONE,
+ lastStreamID,
+ errorCode,
+ data, length);
+ args.GetReturnValue().Set(status);
+}
+
+void Http2Session::DestroyStream(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Http2Session* session;
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+
+ CHECK_EQ(args.Length(), 1);
+ CHECK(args[0]->IsNumber());
+ int32_t id = args[0]->Int32Value(env->context()).ToChecked();
+ DEBUG_HTTP2("Http2Session: destroy stream %d\n", id);
+ Nghttp2Stream* stream;
+ if (!(stream = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+ stream->Destroy();
+}
+
+void Http2Session::SubmitPushPromise(const FunctionCallbackInfo<Value>& args) {
+ Http2Session* session;
+ Environment* env = Environment::GetCurrent(args);
+ Local<Context> context = env->context();
+ Isolate* isolate = env->isolate();
+ ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
+
+ CHECK(args[0]->IsNumber()); // parent stream ID
+ CHECK(args[1]->IsArray()); // headers array
+
+ Nghttp2Stream* parent;
+ int32_t id = args[0]->Int32Value(context).ToChecked();
+ Local<Array> headers = args[1].As<Array>();
+ bool endStream = args[2]->BooleanValue(context).ToChecked();
+
+ DEBUG_HTTP2("Http2Session: submitting push promise for stream %d: "
+ "end-stream: %d, headers: %d\n", id, endStream,
+ headers->Length());
+
+ if (!(parent = session->FindStream(id))) {
+ return args.GetReturnValue().Set(NGHTTP2_ERR_INVALID_STREAM_ID);
+ }
+
+ Headers list(isolate, context, headers);
+
+ int32_t ret = parent->SubmitPushPromise(*list, list.length(),
+ nullptr, endStream);
+ DEBUG_HTTP2("Http2Session: push promise submitted, ret: %d\n", ret);
+ args.GetReturnValue().Set(ret);
+}
+
+int Http2Session::DoWrite(WriteWrap* req_wrap,
+ uv_buf_t* bufs,
+ size_t count,
+ uv_stream_t* send_handle) {
+ Environment* env = req_wrap->env();
+ Local<Object> req_wrap_obj = req_wrap->object();
+ Local<Context> context = env->context();
+
+ Nghttp2Stream* stream;
+ {
+ Local<Value> val =
+ req_wrap_obj->Get(context, env->stream_string()).ToLocalChecked();
+ int32_t id = val->Int32Value(context).ToChecked();
+ if (!val->IsNumber() || !(stream = FindStream(id))) {
+ // invalid stream
+ req_wrap->Dispatched();
+ req_wrap->Done(0);
+ return NGHTTP2_ERR_INVALID_STREAM_ID;
+ }
+ }
+
+ nghttp2_stream_write_t* req = new nghttp2_stream_write_t;
+ req->data = req_wrap;
+
+ auto AfterWrite = [](nghttp2_stream_write_t* req, int status) {
+ WriteWrap* wrap = static_cast<WriteWrap*>(req->data);
+ wrap->Done(status);
+ delete req;
+ };
+ req_wrap->Dispatched();
+ stream->Write(req, bufs, count, AfterWrite);
+ return 0;
+}
+
+void Http2Session::AllocateSend(size_t recommended, uv_buf_t* buf) {
+ buf->base = stream_alloc();
+ buf->len = kAllocBufferSize;
+}
+
+void Http2Session::Send(uv_buf_t* buf, size_t length) {
+ if (stream_ == nullptr || !stream_->IsAlive() || stream_->IsClosing()) {
+ return;
+ }
+ HandleScope scope(env()->isolate());
+
+ auto AfterWrite = [](WriteWrap* req_wrap, int status) {
+ req_wrap->Dispose();
+ };
+ Local<Object> req_wrap_obj =
+ env()->write_wrap_constructor_function()
+ ->NewInstance(env()->context()).ToLocalChecked();
+ WriteWrap* write_req = WriteWrap::New(env(),
+ req_wrap_obj,
+ this,
+ AfterWrite);
+
+ uv_buf_t actual = uv_buf_init(buf->base, length);
+ if (stream_->DoWrite(write_req, &actual, 1, nullptr)) {
+ write_req->Dispose();
+ }
+}
+
+void Http2Session::OnTrailers(Nghttp2Stream* stream,
+ MaybeStackBuffer<nghttp2_nv>* trailers) {
+ DEBUG_HTTP2("Http2Session: prompting for trailers on stream %d\n",
+ stream->id());
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+
+ if (object()->Has(context, env()->ontrailers_string()).FromJust()) {
+ Local<Value> argv[1] = {
+ Integer::New(isolate, stream->id())
+ };
+
+ Local<Value> ret = MakeCallback(env()->ontrailers_string(),
+ arraysize(argv), argv);
+ if (!ret.IsEmpty()) {
+ if (ret->IsArray()) {
+ Local<Array> headers = ret.As<Array>();
+ if (headers->Length() > 0) {
+ trailers->AllocateSufficientStorage(headers->Length());
+ CopyHeaders(isolate, context, trailers, headers);
+ }
+ }
+ }
+ }
+}
+
+void Http2Session::OnHeaders(Nghttp2Stream* stream,
+ nghttp2_header_list* headers,
+ nghttp2_headers_category cat,
+ uint8_t flags) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ Context::Scope context_scope(context);
+ HandleScope scope(isolate);
+ Local<String> name_str;
+ Local<String> value_str;
+
+ Local<Array> holder = Array::New(isolate);
+ Local<Function> fn = env()->push_values_to_array_function();
+ Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2];
+
+ // The headers are passed in above as a linked list of nghttp2_header_list
+ // structs. The following converts that into a JS array with the structure:
+ // [name1, value1, name2, value2, name3, value3, name3, value4] and so on.
+ // That array is passed up to the JS layer and converted into an Object form
+ // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it
+ // this way for performance reasons (it's faster to generate and pass an
+ // array than it is to generate and pass the object).
+ do {
+ size_t j = 0;
+ while (headers != nullptr && j < arraysize(argv) / 2) {
+ nghttp2_header_list* item = headers;
+ // The header name and value are passed as external one-byte strings
+ name_str = ExternalHeader::New(isolate, item->name);
+ value_str = ExternalHeader::New(isolate, item->value);
+ argv[j * 2] = name_str;
+ argv[j * 2 + 1] = value_str;
+ headers = item->next;
+ j++;
+ }
+ // For performance, we pass name and value pairs to array.protototype.push
+ // in batches of size NODE_PUSH_VAL_TO_ARRAY_MAX * 2 until there are no
+ // more items to push.
+ if (j > 0) {
+ fn->Call(env()->context(), holder, j * 2, argv).ToLocalChecked();
+ }
+ } while (headers != nullptr);
+
+ if (object()->Has(context, env()->onheaders_string()).FromJust()) {
+ Local<Value> argv[4] = {
+ Integer::New(isolate, stream->id()),
+ Integer::New(isolate, cat),
+ Integer::New(isolate, flags),
+ holder
+ };
+ MakeCallback(env()->onheaders_string(), arraysize(argv), argv);
+ }
+}
+
+
+void Http2Session::OnStreamClose(int32_t id, uint32_t code) {
+ Isolate* isolate = env()->isolate();
+ Local<Context> context = env()->context();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+ if (object()->Has(context, env()->onstreamclose_string()).FromJust()) {
+ Local<Value> argv[2] = {
+ Integer::New(isolate, id),
+ Integer::NewFromUnsigned(isolate, code)
+ };
+ MakeCallback(env()->onstreamclose_string(), arraysize(argv), argv);
+ }
+}
+
+void FreeDataChunk(char* data, void* hint) {
+ nghttp2_data_chunk_t* item = reinterpret_cast<nghttp2_data_chunk_t*>(hint);
+ delete[] data;
+ data_chunk_free_list.push(item);
+}
+
+void Http2Session::OnDataChunk(
+ Nghttp2Stream* stream,
+ nghttp2_data_chunk_t* chunk) {
+ Isolate* isolate = env()->isolate();
+ Local<Context> context = env()->context();
+ HandleScope scope(isolate);
+ Local<Object> obj = Object::New(isolate);
+ obj->Set(context,
+ env()->id_string(),
+ Integer::New(isolate, stream->id())).FromJust();
+ ssize_t len = -1;
+ Local<Object> buf;
+ if (chunk != nullptr) {
+ len = chunk->buf.len;
+ buf = Buffer::New(isolate,
+ chunk->buf.base, len,
+ FreeDataChunk,
+ chunk).ToLocalChecked();
+ }
+ EmitData(len, buf, obj);
+}
+
+void Http2Session::OnSettings(bool ack) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+ if (object()->Has(context, env()->onsettings_string()).FromJust()) {
+ Local<Value> argv[1] = { Boolean::New(isolate, ack) };
+ MakeCallback(env()->onsettings_string(), arraysize(argv), argv);
+ }
+}
+
+void Http2Session::OnFrameError(int32_t id, uint8_t type, int error_code) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+ if (object()->Has(context, env()->onframeerror_string()).FromJust()) {
+ Local<Value> argv[3] = {
+ Integer::New(isolate, id),
+ Integer::New(isolate, type),
+ Integer::New(isolate, error_code)
+ };
+ MakeCallback(env()->onframeerror_string(), arraysize(argv), argv);
+ }
+}
+
+void Http2Session::OnPriority(int32_t stream,
+ int32_t parent,
+ int32_t weight,
+ int8_t exclusive) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+ if (object()->Has(context, env()->onpriority_string()).FromJust()) {
+ Local<Value> argv[4] = {
+ Integer::New(isolate, stream),
+ Integer::New(isolate, parent),
+ Integer::New(isolate, weight),
+ Boolean::New(isolate, exclusive)
+ };
+ MakeCallback(env()->onpriority_string(), arraysize(argv), argv);
+ }
+}
+
+void Http2Session::OnGoAway(int32_t lastStreamID,
+ uint32_t errorCode,
+ uint8_t* data,
+ size_t length) {
+ Local<Context> context = env()->context();
+ Isolate* isolate = env()->isolate();
+ HandleScope scope(isolate);
+ Context::Scope context_scope(context);
+ if (object()->Has(context, env()->ongoawaydata_string()).FromJust()) {
+ Local<Value> argv[3] = {
+ Integer::NewFromUnsigned(isolate, errorCode),
+ Integer::New(isolate, lastStreamID),
+ Undefined(isolate)
+ };
+
+ if (length > 0) {
+ argv[2] = Buffer::Copy(isolate,
+ reinterpret_cast<char*>(data),
+ length).ToLocalChecked();
+ }
+
+ MakeCallback(env()->ongoawaydata_string(), arraysize(argv), argv);
+ }
+}
+
+void Http2Session::OnStreamAllocImpl(size_t suggested_size,
+ uv_buf_t* buf,
+ void* ctx) {
+ Http2Session* session = static_cast<Http2Session*>(ctx);
+ buf->base = session->stream_alloc();
+ buf->len = kAllocBufferSize;
+}
+
+
+void Http2Session::OnStreamReadImpl(ssize_t nread,
+ const uv_buf_t* bufs,
+ uv_handle_type pending,
+ void* ctx) {
+ Http2Session* session = static_cast<Http2Session*>(ctx);
+ if (nread < 0) {
+ uv_buf_t tmp_buf;
+ tmp_buf.base = nullptr;
+ tmp_buf.len = 0;
+ session->prev_read_cb_.fn(nread,
+ &tmp_buf,
+ pending,
+ session->prev_read_cb_.ctx);
+ return;
+ }
+ if (nread > 0) {
+ // Only pass data on if nread > 0
+ uv_buf_t buf[] { uv_buf_init((*bufs).base, nread) };
+ ssize_t ret = session->Write(buf, 1);
+ if (ret < 0) {
+ DEBUG_HTTP2("Http2Session: fatal error receiving data: %d\n", ret);
+ nghttp2_session_terminate_session(session->session(),
+ NGHTTP2_PROTOCOL_ERROR);
+ }
+ }
+}
+
+
+void Http2Session::Consume(Local<External> external) {
+ DEBUG_HTTP2("Http2Session: consuming socket\n");
+ CHECK(prev_alloc_cb_.is_empty());
+ StreamBase* stream = static_cast<StreamBase*>(external->Value());
+ CHECK_NE(stream, nullptr);
+ stream->Consume();
+ stream_ = stream;
+ prev_alloc_cb_ = stream->alloc_cb();
+ prev_read_cb_ = stream->read_cb();
+ stream->set_alloc_cb({ Http2Session::OnStreamAllocImpl, this });
+ stream->set_read_cb({ Http2Session::OnStreamReadImpl, this });
+}
+
+
+void Http2Session::Unconsume() {
+ DEBUG_HTTP2("Http2Session: unconsuming socket\n");
+ if (prev_alloc_cb_.is_empty())
+ return;
+ stream_->set_alloc_cb(prev_alloc_cb_);
+ stream_->set_read_cb(prev_read_cb_);
+ prev_alloc_cb_.clear();
+ prev_read_cb_.clear();
+ stream_ = nullptr;
+}
+
+
+void Initialize(Local<Object> target,
+ Local<Value> unused,
+ Local<Context> context,
+ void* priv) {
+ Environment* env = Environment::GetCurrent(context);
+ Isolate* isolate = env->isolate();
+ HandleScope scope(isolate);
+
+ // Initialize the buffer used for padding callbacks
+ env->set_http2_padding_buffer(new uint32_t[3]);
+ const size_t http2_padding_buffer_byte_length =
+ sizeof(*env->http2_padding_buffer()) * 3;
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "paddingArrayBuffer"),
+ ArrayBuffer::New(env->isolate(),
+ env->http2_padding_buffer(),
+ http2_padding_buffer_byte_length))
+ .FromJust();
+
+ // Initialize the buffer used to store the session state
+ env->set_http2_session_state_buffer(
+ new double[IDX_SESSION_STATE_COUNT]);
+
+ const size_t http2_session_state_buffer_byte_length =
+ sizeof(*env->http2_session_state_buffer()) *
+ IDX_SESSION_STATE_COUNT;
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "sessionStateArrayBuffer"),
+ ArrayBuffer::New(env->isolate(),
+ env->http2_session_state_buffer(),
+ http2_session_state_buffer_byte_length))
+ .FromJust();
+
+ // Initialize the buffer used to store the stream state
+ env->set_http2_stream_state_buffer(
+ new double[IDX_STREAM_STATE_COUNT]);
+
+ const size_t http2_stream_state_buffer_byte_length =
+ sizeof(*env->http2_stream_state_buffer()) *
+ IDX_STREAM_STATE_COUNT;
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "streamStateArrayBuffer"),
+ ArrayBuffer::New(env->isolate(),
+ env->http2_stream_state_buffer(),
+ http2_stream_state_buffer_byte_length))
+ .FromJust();
+
+ // Initialize the buffer used to store the current settings
+ env->set_http2_settings_buffer(
+ new uint32_t[IDX_SETTINGS_COUNT + 1]);
+
+ const size_t http2_settings_buffer_byte_length =
+ sizeof(*env->http2_settings_buffer()) *
+ (IDX_SETTINGS_COUNT + 1);
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "settingsArrayBuffer"),
+ ArrayBuffer::New(env->isolate(),
+ env->http2_settings_buffer(),
+ http2_settings_buffer_byte_length))
+ .FromJust();
+
+ // Initialize the buffer used to store the options
+ env->set_http2_options_buffer(
+ new uint32_t[IDX_OPTIONS_FLAGS + 1]);
+
+ const size_t http2_options_buffer_byte_length =
+ sizeof(*env->http2_options_buffer()) *
+ (IDX_OPTIONS_FLAGS + 1);
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(env->isolate(), "optionsArrayBuffer"),
+ ArrayBuffer::New(env->isolate(),
+ env->http2_options_buffer(),
+ http2_options_buffer_byte_length))
+ .FromJust();
+
+ // Method to fetch the nghttp2 string description of an nghttp2 error code
+ env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
+
+ Local<String> http2SessionClassName =
+ String::NewFromUtf8(isolate, "Http2Session",
+ v8::NewStringType::kInternalized).ToLocalChecked();
+
+ Local<FunctionTemplate> session =
+ env->NewFunctionTemplate(Http2Session::New);
+ session->SetClassName(http2SessionClassName);
+ session->InstanceTemplate()->SetInternalFieldCount(1);
+ env->SetProtoMethod(session, "getAsyncId", AsyncWrap::GetAsyncId);
+ env->SetProtoMethod(session, "consume",
+ Http2Session::Consume);
+ env->SetProtoMethod(session, "destroy",
+ Http2Session::Destroy);
+ env->SetProtoMethod(session, "sendHeaders",
+ Http2Session::SendHeaders);
+ env->SetProtoMethod(session, "submitShutdownNotice",
+ Http2Session::SendShutdownNotice);
+ env->SetProtoMethod(session, "submitGoaway",
+ Http2Session::SubmitGoaway);
+ env->SetProtoMethod(session, "submitSettings",
+ Http2Session::SubmitSettings);
+ env->SetProtoMethod(session, "submitPushPromise",
+ Http2Session::SubmitPushPromise);
+ env->SetProtoMethod(session, "submitRstStream",
+ Http2Session::SubmitRstStream);
+ env->SetProtoMethod(session, "submitResponse",
+ Http2Session::SubmitResponse);
+ env->SetProtoMethod(session, "submitFile",
+ Http2Session::SubmitFile);
+ env->SetProtoMethod(session, "submitRequest",
+ Http2Session::SubmitRequest);
+ env->SetProtoMethod(session, "submitPriority",
+ Http2Session::SubmitPriority);
+ env->SetProtoMethod(session, "shutdownStream",
+ Http2Session::ShutdownStream);
+ env->SetProtoMethod(session, "streamReadStart",
+ Http2Session::StreamReadStart);
+ env->SetProtoMethod(session, "streamReadStop",
+ Http2Session::StreamReadStop);
+ env->SetProtoMethod(session, "setNextStreamID",
+ Http2Session::SetNextStreamID);
+ env->SetProtoMethod(session, "destroyStream",
+ Http2Session::DestroyStream);
+ StreamBase::AddMethods<Http2Session>(env, session,
+ StreamBase::kFlagHasWritev |
+ StreamBase::kFlagNoShutdown);
+ target->Set(context,
+ http2SessionClassName,
+ session->GetFunction()).FromJust();
+
+ Local<Object> constants = Object::New(isolate);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SESSION_SERVER);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SESSION_CLIENT);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_IDLE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_OPEN);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_RESERVED_LOCAL);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_RESERVED_REMOTE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_STATE_CLOSED);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_NO_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_PROTOCOL_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_INTERNAL_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLOW_CONTROL_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_TIMEOUT);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_STREAM_CLOSED);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FRAME_SIZE_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_REFUSED_STREAM);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_CANCEL);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_COMPRESSION_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_CONNECT_ERROR);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_ENHANCE_YOUR_CALM);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_INADEQUATE_SECURITY);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_HTTP_1_1_REQUIRED);
+
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_REQUEST);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_RESPONSE);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_PUSH_RESPONSE);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_HCAT_HEADERS);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NONE);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_NV_FLAG_NO_INDEX);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_DEFERRED);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_NOMEM);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_INVALID_ARGUMENT);
+ NODE_DEFINE_HIDDEN_CONSTANT(constants, NGHTTP2_ERR_STREAM_CLOSED);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_ERR_FRAME_SIZE_ERROR);
+
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_NONE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_STREAM);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_END_HEADERS);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_ACK);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PADDED);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_FLAG_PRIORITY);
+
+ NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_HEADER_TABLE_SIZE);
+ NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_ENABLE_PUSH);
+ NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE);
+ NODE_DEFINE_CONSTANT(constants, DEFAULT_SETTINGS_MAX_FRAME_SIZE);
+ NODE_DEFINE_CONSTANT(constants, MAX_MAX_FRAME_SIZE);
+ NODE_DEFINE_CONSTANT(constants, MIN_MAX_FRAME_SIZE);
+ NODE_DEFINE_CONSTANT(constants, MAX_INITIAL_WINDOW_SIZE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_DEFAULT_WEIGHT);
+
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_ENABLE_PUSH);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_FRAME_SIZE);
+ NODE_DEFINE_CONSTANT(constants, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE);
+
+ NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_NONE);
+ NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_MAX);
+ NODE_DEFINE_CONSTANT(constants, PADDING_STRATEGY_CALLBACK);
+
+#define STRING_CONSTANT(NAME, VALUE) \
+ NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_HEADER_" # NAME, VALUE);
+HTTP_KNOWN_HEADERS(STRING_CONSTANT)
+#undef STRING_CONSTANT
+
+#define STRING_CONSTANT(NAME, VALUE) \
+ NODE_DEFINE_STRING_CONSTANT(constants, "HTTP2_METHOD_" # NAME, VALUE);
+HTTP_KNOWN_METHODS(STRING_CONSTANT)
+#undef STRING_CONSTANT
+
+#define V(name, _) NODE_DEFINE_CONSTANT(constants, HTTP_STATUS_##name);
+HTTP_STATUS_CODES(V)
+#undef V
+
+ env->SetMethod(target, "refreshLocalSettings",
+ RefreshSettings<nghttp2_session_get_local_settings>);
+ env->SetMethod(target, "refreshRemoteSettings",
+ RefreshSettings<nghttp2_session_get_remote_settings>);
+ env->SetMethod(target, "refreshDefaultSettings", RefreshDefaultSettings);
+ env->SetMethod(target, "refreshSessionState", RefreshSessionState);
+ env->SetMethod(target, "refreshStreamState", RefreshStreamState);
+ env->SetMethod(target, "packSettings", PackSettings);
+
+ target->Set(context,
+ FIXED_ONE_BYTE_STRING(isolate, "constants"),
+ constants).FromJust();
+}
+} // namespace http2
+} // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_BUILTIN(http2, node::http2::Initialize)