summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/async-wrap.h2
-rw-r--r--src/env-inl.h65
-rw-r--r--src/env.h41
-rw-r--r--src/freelist.h92
-rw-r--r--src/node.cc13
-rw-r--r--src/node.h19
-rw-r--r--src/node_config.cc3
-rw-r--r--src/node_crypto_bio.cc1
-rw-r--r--src/node_http2.cc1326
-rw-r--r--src/node_http2.h572
-rw-r--r--src/node_http2_core-inl.h590
-rw-r--r--src/node_http2_core.cc326
-rw-r--r--src/node_http2_core.h465
-rw-r--r--src/node_internals.h3
-rw-r--r--src/stream_base.cc1
-rw-r--r--src/stream_base.h11
16 files changed, 3528 insertions, 2 deletions
diff --git a/src/async-wrap.h b/src/async-wrap.h
index 79332d9a42..a02356e845 100644
--- a/src/async-wrap.h
+++ b/src/async-wrap.h
@@ -40,6 +40,8 @@ namespace node {
V(FSREQWRAP) \
V(GETADDRINFOREQWRAP) \
V(GETNAMEINFOREQWRAP) \
+ V(HTTP2SESSION) \
+ V(HTTP2SESSIONSHUTDOWNWRAP) \
V(HTTPPARSER) \
V(JSSTREAM) \
V(PIPECONNECTWRAP) \
diff --git a/src/env-inl.h b/src/env-inl.h
index 6753979ef5..021d50841d 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -302,6 +302,7 @@ inline Environment::Environment(IsolateData* isolate_data,
#endif
handle_cleanup_waiting_(0),
http_parser_buffer_(nullptr),
+ http2_socket_buffer_(nullptr),
fs_stats_field_array_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
@@ -328,6 +329,12 @@ inline Environment::~Environment() {
delete[] heap_statistics_buffer_;
delete[] heap_space_statistics_buffer_;
delete[] http_parser_buffer_;
+ delete[] http2_socket_buffer_;
+ delete[] http2_settings_buffer_;
+ delete[] http2_options_buffer_;
+ delete[] http2_session_state_buffer_;
+ delete[] http2_stream_state_buffer_;
+ delete[] http2_padding_buffer_;
}
inline v8::Isolate* Environment::isolate() const {
@@ -468,6 +475,55 @@ inline void Environment::set_heap_space_statistics_buffer(double* pointer) {
heap_space_statistics_buffer_ = pointer;
}
+inline uint32_t* Environment::http2_settings_buffer() const {
+ CHECK_NE(http2_settings_buffer_, nullptr);
+ return http2_settings_buffer_;
+}
+
+inline void Environment::set_http2_settings_buffer(uint32_t* pointer) {
+ CHECK_EQ(http2_settings_buffer_, nullptr); // Should be set only once
+ http2_settings_buffer_ = pointer;
+}
+
+inline uint32_t* Environment::http2_options_buffer() const {
+ CHECK_NE(http2_options_buffer_, nullptr);
+ return http2_options_buffer_;
+}
+
+inline void Environment::set_http2_options_buffer(uint32_t* pointer) {
+ CHECK_EQ(http2_options_buffer_, nullptr); // Should be set only once
+ http2_options_buffer_ = pointer;
+}
+
+inline double* Environment::http2_session_state_buffer() const {
+ CHECK_NE(http2_session_state_buffer_, nullptr);
+ return http2_session_state_buffer_;
+}
+
+inline void Environment::set_http2_session_state_buffer(double* pointer) {
+ CHECK_EQ(http2_session_state_buffer_, nullptr);
+ http2_session_state_buffer_ = pointer;
+}
+
+inline double* Environment::http2_stream_state_buffer() const {
+ CHECK_NE(http2_stream_state_buffer_, nullptr);
+ return http2_stream_state_buffer_;
+}
+
+inline void Environment::set_http2_stream_state_buffer(double* pointer) {
+ CHECK_EQ(http2_stream_state_buffer_, nullptr);
+ http2_stream_state_buffer_ = pointer;
+}
+
+inline uint32_t* Environment::http2_padding_buffer() const {
+ CHECK_NE(http2_padding_buffer_, nullptr);
+ return http2_padding_buffer_;
+}
+
+inline void Environment::set_http2_padding_buffer(uint32_t* pointer) {
+ CHECK_EQ(http2_padding_buffer_, nullptr);
+ http2_padding_buffer_ = pointer;
+}
inline char* Environment::http_parser_buffer() const {
return http_parser_buffer_;
@@ -487,6 +543,15 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
fs_stats_field_array_ = fields;
}
+inline char* Environment::http2_socket_buffer() const {
+ return http2_socket_buffer_;
+}
+
+inline void Environment::set_http2_socket_buffer(char* buffer) {
+ CHECK_EQ(http2_socket_buffer_, nullptr); // Should be set only once.
+ http2_socket_buffer_ = buffer;
+}
+
inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}
diff --git a/src/env.h b/src/env.h
index e1375080d1..59fe10448a 100644
--- a/src/env.h
+++ b/src/env.h
@@ -104,6 +104,7 @@ namespace node {
V(configurable_string, "configurable") \
V(cwd_string, "cwd") \
V(dest_string, "dest") \
+ V(destroy_string, "destroy") \
V(detached_string, "detached") \
V(disposed_string, "_disposed") \
V(dns_a_string, "A") \
@@ -117,11 +118,13 @@ namespace node {
V(dns_srv_string, "SRV") \
V(dns_txt_string, "TXT") \
V(domain_string, "domain") \
+ V(emit_string, "emit") \
V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \
V(exchange_string, "exchange") \
V(enumerable_string, "enumerable") \
V(idle_string, "idle") \
V(irq_string, "irq") \
+ V(enablepush_string, "enablePush") \
V(encoding_string, "encoding") \
V(enter_string, "enter") \
V(entries_string, "entries") \
@@ -148,8 +151,11 @@ namespace node {
V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \
V(gid_string, "gid") \
V(handle_string, "handle") \
+ V(heap_total_string, "heapTotal") \
+ V(heap_used_string, "heapUsed") \
V(homedir_string, "homedir") \
V(hostmaster_string, "hostmaster") \
+ V(id_string, "id") \
V(ignore_string, "ignore") \
V(immediate_callback_string, "_immediateCallback") \
V(infoaccess_string, "infoAccess") \
@@ -174,6 +180,7 @@ namespace node {
V(netmask_string, "netmask") \
V(nice_string, "nice") \
V(nsname_string, "nsname") \
+ V(nexttick_string, "nextTick") \
V(ocsp_request_string, "OCSPRequest") \
V(onchange_string, "onchange") \
V(onclienthello_string, "onclienthello") \
@@ -182,19 +189,27 @@ namespace node {
V(ondone_string, "ondone") \
V(onerror_string, "onerror") \
V(onexit_string, "onexit") \
+ V(onframeerror_string, "onframeerror") \
+ V(ongetpadding_string, "ongetpadding") \
V(onhandshakedone_string, "onhandshakedone") \
V(onhandshakestart_string, "onhandshakestart") \
+ V(onheaders_string, "onheaders") \
V(onmessage_string, "onmessage") \
V(onnewsession_string, "onnewsession") \
V(onnewsessiondone_string, "onnewsessiondone") \
V(onocspresponse_string, "onocspresponse") \
+ V(ongoawaydata_string, "ongoawaydata") \
+ V(onpriority_string, "onpriority") \
V(onread_string, "onread") \
V(onreadstart_string, "onreadstart") \
V(onreadstop_string, "onreadstop") \
V(onselect_string, "onselect") \
+ V(onsettings_string, "onsettings") \
V(onshutdown_string, "onshutdown") \
V(onsignal_string, "onsignal") \
V(onstop_string, "onstop") \
+ V(onstreamclose_string, "onstreamclose") \
+ V(ontrailers_string, "ontrailers") \
V(onwrite_string, "onwrite") \
V(output_string, "output") \
V(order_string, "order") \
@@ -234,6 +249,7 @@ namespace node {
V(stack_string, "stack") \
V(status_string, "status") \
V(stdio_string, "stdio") \
+ V(stream_string, "stream") \
V(subject_string, "subject") \
V(subjectaltname_string, "subjectaltname") \
V(sys_string, "sys") \
@@ -262,7 +278,7 @@ namespace node {
V(write_host_object_string, "_writeHostObject") \
V(write_queue_size_string, "writeQueueSize") \
V(x_forwarded_string, "x-forwarded-for") \
- V(zero_return_string, "ZERO_RETURN") \
+ V(zero_return_string, "ZERO_RETURN")
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
V(as_external, v8::External) \
@@ -579,8 +595,25 @@ class Environment {
inline double* heap_space_statistics_buffer() const;
inline void set_heap_space_statistics_buffer(double* pointer);
+ inline uint32_t* http2_settings_buffer() const;
+ inline void set_http2_settings_buffer(uint32_t* pointer);
+
+ inline uint32_t* http2_options_buffer() const;
+ inline void set_http2_options_buffer(uint32_t* pointer);
+
+ inline double* http2_session_state_buffer() const;
+ inline void set_http2_session_state_buffer(double* pointer);
+
+ inline double* http2_stream_state_buffer() const;
+ inline void set_http2_stream_state_buffer(double* pointer);
+
+ inline uint32_t* http2_padding_buffer() const;
+ inline void set_http2_padding_buffer(uint32_t* pointer);
+
inline char* http_parser_buffer() const;
inline void set_http_parser_buffer(char* buffer);
+ inline char* http2_socket_buffer() const;
+ inline void set_http2_socket_buffer(char* buffer);
inline double* fs_stats_field_array() const;
inline void set_fs_stats_field_array(double* fields);
@@ -686,8 +719,14 @@ class Environment {
double* heap_statistics_buffer_ = nullptr;
double* heap_space_statistics_buffer_ = nullptr;
+ uint32_t* http2_settings_buffer_ = nullptr;
+ uint32_t* http2_options_buffer_ = nullptr;
+ double* http2_session_state_buffer_ = nullptr;
+ double* http2_stream_state_buffer_ = nullptr;
+ uint32_t* http2_padding_buffer_ = nullptr;
char* http_parser_buffer_;
+ char* http2_socket_buffer_;
double* fs_stats_field_array_;
diff --git a/src/freelist.h b/src/freelist.h
new file mode 100644
index 0000000000..7dff56a35d
--- /dev/null
+++ b/src/freelist.h
@@ -0,0 +1,92 @@
+#ifndef SRC_FREELIST_H_
+#define SRC_FREELIST_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include "util.h"
+
+namespace node {
+
+struct DefaultFreelistTraits;
+
+template <typename T,
+ size_t kMaximumLength,
+ typename FreelistTraits = DefaultFreelistTraits>
+class Freelist {
+ public:
+ typedef struct list_item {
+ T* item = nullptr;
+ list_item* next = nullptr;
+ } list_item;
+
+ Freelist() {}
+ ~Freelist() {
+ while (head_ != nullptr) {
+ list_item* item = head_;
+ head_ = item->next;
+ FreelistTraits::Free(item->item);
+ free(item);
+ }
+ }
+
+ void push(T* item) {
+ if (size_ > kMaximumLength) {
+ FreelistTraits::Free(item);
+ } else {
+ size_++;
+ FreelistTraits::Reset(item);
+ list_item* li = Calloc<list_item>(1);
+ li->item = item;
+ if (head_ == nullptr) {
+ head_ = li;
+ tail_ = li;
+ } else {
+ tail_->next = li;
+ tail_ = li;
+ }
+ }
+ }
+
+ T* pop() {
+ if (head_ != nullptr) {
+ size_--;
+ list_item* cur = head_;
+ T* item = cur->item;
+ head_ = cur->next;
+ free(cur);
+ return item;
+ } else {
+ return FreelistTraits::template Alloc<T>();
+ }
+ }
+
+ private:
+ size_t size_ = 0;
+ list_item* head_ = nullptr;
+ list_item* tail_ = nullptr;
+};
+
+struct DefaultFreelistTraits {
+ template <typename T>
+ static T* Alloc() {
+ return ::new (Malloc<T>(1)) T();
+ }
+
+ template <typename T>
+ static void Free(T* item) {
+ item->~T();
+ free(item);
+ }
+
+ template <typename T>
+ static void Reset(T* item) {
+ item->~T();
+ ::new (item) T();
+ }
+};
+
+} // namespace node
+
+#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#endif // SRC_FREELIST_H_
diff --git a/src/node.cc b/src/node.cc
index 741b5dea82..5b1b9cadd9 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -59,6 +59,7 @@
#include "env-inl.h"
#include "handle_wrap.h"
#include "http_parser.h"
+#include "nghttp2/nghttp2ver.h"
#include "req-wrap.h"
#include "req-wrap-inl.h"
#include "string_bytes.h"
@@ -232,6 +233,9 @@ std::string config_warning_file; // NOLINT(runtime/string)
// that is used by lib/internal/bootstrap_node.js
bool config_expose_internals = false;
+// Set in node.cc by ParseArgs when --expose-http2 is used.
+bool config_expose_http2 = false;
+
bool v8_initialized = false;
bool linux_at_secure = false;
@@ -3210,6 +3214,10 @@ void SetupProcessObject(Environment* env,
"modules",
FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version));
+ READONLY_PROPERTY(versions,
+ "nghttp2",
+ FIXED_ONE_BYTE_STRING(env->isolate(), NGHTTP2_VERSION));
+
// process._promiseRejectEvent
Local<Object> promiseRejectEvent = Object::New(env->isolate());
READONLY_DONT_ENUM_PROPERTY(process,
@@ -3648,6 +3656,7 @@ static void PrintHelp() {
" --abort-on-uncaught-exception\n"
" aborting instead of exiting causes a\n"
" core file to be generated for analysis\n"
+ " --expose-http2 enable experimental HTTP2 support\n"
" --trace-warnings show stack traces on process warnings\n"
" --redirect-warnings=file\n"
" write warnings to file instead of\n"
@@ -3768,6 +3777,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
"--throw-deprecation",
"--no-warnings",
"--napi-modules",
+ "--expose-http2",
"--trace-warnings",
"--redirect-warnings",
"--trace-sync-io",
@@ -3965,6 +3975,9 @@ static void ParseArgs(int* argc,
} else if (strcmp(arg, "--expose-internals") == 0 ||
strcmp(arg, "--expose_internals") == 0) {
config_expose_internals = true;
+ } else if (strcmp(arg, "--expose-http2") == 0 ||
+ strcmp(arg, "--expose_http2") == 0) {
+ config_expose_http2 = true;
} else if (strcmp(arg, "-") == 0) {
break;
} else if (strcmp(arg, "--") == 0) {
diff --git a/src/node.h b/src/node.h
index b1d179bd16..59728685f7 100644
--- a/src/node.h
+++ b/src/node.h
@@ -253,6 +253,25 @@ NODE_EXTERN void RunAtExit(Environment* env);
} \
while (0)
+#define NODE_DEFINE_HIDDEN_CONSTANT(target, constant) \
+ do { \
+ v8::Isolate* isolate = target->GetIsolate(); \
+ v8::Local<v8::Context> context = isolate->GetCurrentContext(); \
+ v8::Local<v8::String> constant_name = \
+ v8::String::NewFromUtf8(isolate, #constant); \
+ v8::Local<v8::Number> constant_value = \
+ v8::Number::New(isolate, static_cast<double>(constant)); \
+ v8::PropertyAttribute constant_attributes = \
+ static_cast<v8::PropertyAttribute>(v8::ReadOnly | \
+ v8::DontDelete | \
+ v8::DontEnum); \
+ (target)->DefineOwnProperty(context, \
+ constant_name, \
+ constant_value, \
+ constant_attributes).FromJust(); \
+ } \
+ while (0)
+
// Used to be a macro, hence the uppercase name.
inline void NODE_SET_METHOD(v8::Local<v8::Template> recv,
const char* name,
diff --git a/src/node_config.cc b/src/node_config.cc
index b309171282..041e18f6b7 100644
--- a/src/node_config.cc
+++ b/src/node_config.cc
@@ -88,6 +88,9 @@ static void InitConfig(Local<Object> target,
if (config_expose_internals)
READONLY_BOOLEAN_PROPERTY("exposeInternals");
+
+ if (config_expose_http2)
+ READONLY_BOOLEAN_PROPERTY("exposeHTTP2");
} // InitConfig
} // namespace node
diff --git a/src/node_crypto_bio.cc b/src/node_crypto_bio.cc
index 00fd0b420c..4c84973f75 100644
--- a/src/node_crypto_bio.cc
+++ b/src/node_crypto_bio.cc
@@ -357,7 +357,6 @@ size_t NodeBIO::IndexOf(char delim, size_t limit) {
return max;
}
-
void NodeBIO::Write(const char* data, size_t size) {
size_t offset = 0;
size_t left = size;
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)
diff --git a/src/node_http2.h b/src/node_http2.h
new file mode 100644
index 0000000000..f6ccad2984
--- /dev/null
+++ b/src/node_http2.h
@@ -0,0 +1,572 @@
+#ifndef SRC_NODE_HTTP2_H_
+#define SRC_NODE_HTTP2_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include "node_http2_core-inl.h"
+#include "stream_base-inl.h"
+#include "string_bytes.h"
+
+namespace node {
+namespace http2 {
+
+using v8::Array;
+using v8::Context;
+using v8::EscapableHandleScope;
+using v8::Isolate;
+using v8::MaybeLocal;
+
+#define HTTP_KNOWN_METHODS(V) \
+ V(ACL, "ACL") \
+ V(BASELINE_CONTROL, "BASELINE-CONTROL") \
+ V(BIND, "BIND") \
+ V(CHECKIN, "CHECKIN") \
+ V(CHECKOUT, "CHECKOUT") \
+ V(CONNECT, "CONNECT") \
+ V(COPY, "COPY") \
+ V(DELETE, "DELETE") \
+ V(GET, "GET") \
+ V(HEAD, "HEAD") \
+ V(LABEL, "LABEL") \
+ V(LINK, "LINK") \
+ V(LOCK, "LOCK") \
+ V(MERGE, "MERGE") \
+ V(MKACTIVITY, "MKACTIVITY") \
+ V(MKCALENDAR, "MKCALENDAR") \
+ V(MKCOL, "MKCOL") \
+ V(MKREDIRECTREF, "MKREDIRECTREF") \
+ V(MKWORKSPACE, "MKWORKSPACE") \
+ V(MOVE, "MOVE") \
+ V(OPTIONS, "OPTIONS") \
+ V(ORDERPATCH, "ORDERPATCH") \
+ V(PATCH, "PATCH") \
+ V(POST, "POST") \
+ V(PRI, "PRI") \
+ V(PROPFIND, "PROPFIND") \
+ V(PROPPATCH, "PROPPATCH") \
+ V(PUT, "PUT") \
+ V(REBIND, "REBIND") \
+ V(REPORT, "REPORT") \
+ V(SEARCH, "SEARCH") \
+ V(TRACE, "TRACE") \
+ V(UNBIND, "UNBIND") \
+ V(UNCHECKOUT, "UNCHECKOUT") \
+ V(UNLINK, "UNLINK") \
+ V(UNLOCK, "UNLOCK") \
+ V(UPDATE, "UPDATE") \
+ V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \
+ V(VERSION_CONTROL, "VERSION-CONTROL")
+
+#define HTTP_KNOWN_HEADERS(V) \
+ V(STATUS, ":status") \
+ V(METHOD, ":method") \
+ V(AUTHORITY, ":authority") \
+ V(SCHEME, ":scheme") \
+ V(PATH, ":path") \
+ V(ACCEPT_CHARSET, "accept-charset") \
+ V(ACCEPT_ENCODING, "accept-encoding") \
+ V(ACCEPT_LANGUAGE, "accept-language") \
+ V(ACCEPT_RANGES, "accept-ranges") \
+ V(ACCEPT, "accept") \
+ V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \
+ V(AGE, "age") \
+ V(ALLOW, "allow") \
+ V(AUTHORIZATION, "authorization") \
+ V(CACHE_CONTROL, "cache-control") \
+ V(CONNECTION, "connection") \
+ V(CONTENT_DISPOSITION, "content-disposition") \
+ V(CONTENT_ENCODING, "content-encoding") \
+ V(CONTENT_LANGUAGE, "content-language") \
+ V(CONTENT_LENGTH, "content-length") \
+ V(CONTENT_LOCATION, "content-location") \
+ V(CONTENT_MD5, "content-md5") \
+ V(CONTENT_RANGE, "content-range") \
+ V(CONTENT_TYPE, "content-type") \
+ V(COOKIE, "cookie") \
+ V(DATE, "date") \
+ V(ETAG, "etag") \
+ V(EXPECT, "expect") \
+ V(EXPIRES, "expires") \
+ V(FROM, "from") \
+ V(HOST, "host") \
+ V(IF_MATCH, "if-match") \
+ V(IF_MODIFIED_SINCE, "if-modified-since") \
+ V(IF_NONE_MATCH, "if-none-match") \
+ V(IF_RANGE, "if-range") \
+ V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \
+ V(LAST_MODIFIED, "last-modified") \
+ V(LINK, "link") \
+ V(LOCATION, "location") \
+ V(MAX_FORWARDS, "max-forwards") \
+ V(PREFER, "prefer") \
+ V(PROXY_AUTHENTICATE, "proxy-authenticate") \
+ V(PROXY_AUTHORIZATION, "proxy-authorization") \
+ V(RANGE, "range") \
+ V(REFERER, "referer") \
+ V(REFRESH, "refresh") \
+ V(RETRY_AFTER, "retry-after") \
+ V(SERVER, "server") \
+ V(SET_COOKIE, "set-cookie") \
+ V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \
+ V(TRANSFER_ENCODING, "transfer-encoding") \
+ V(TE, "te") \
+ V(UPGRADE, "upgrade") \
+ V(USER_AGENT, "user-agent") \
+ V(VARY, "vary") \
+ V(VIA, "via") \
+ V(WWW_AUTHENTICATE, "www-authenticate") \
+ V(HTTP2_SETTINGS, "http2-settings") \
+ V(KEEP_ALIVE, "keep-alive") \
+ V(PROXY_CONNECTION, "proxy-connection")
+
+enum http_known_headers {
+HTTP_KNOWN_HEADER_MIN,
+#define V(name, value) HTTP_HEADER_##name,
+HTTP_KNOWN_HEADERS(V)
+#undef V
+HTTP_KNOWN_HEADER_MAX
+};
+
+#define HTTP_STATUS_CODES(V) \
+ V(CONTINUE, 100) \
+ V(SWITCHING_PROTOCOLS, 101) \
+ V(PROCESSING, 102) \
+ V(OK, 200) \
+ V(CREATED, 201) \
+ V(ACCEPTED, 202) \
+ V(NON_AUTHORITATIVE_INFORMATION, 203) \
+ V(NO_CONTENT, 204) \
+ V(RESET_CONTENT, 205) \
+ V(PARTIAL_CONTENT, 206) \
+ V(MULTI_STATUS, 207) \
+ V(ALREADY_REPORTED, 208) \
+ V(IM_USED, 226) \
+ V(MULTIPLE_CHOICES, 300) \
+ V(MOVED_PERMANENTLY, 301) \
+ V(FOUND, 302) \
+ V(SEE_OTHER, 303) \
+ V(NOT_MODIFIED, 304) \
+ V(USE_PROXY, 305) \
+ V(TEMPORARY_REDIRECT, 307) \
+ V(PERMANENT_REDIRECT, 308) \
+ V(BAD_REQUEST, 400) \
+ V(UNAUTHORIZED, 401) \
+ V(PAYMENT_REQUIRED, 402) \
+ V(FORBIDDEN, 403) \
+ V(NOT_FOUND, 404) \
+ V(METHOD_NOT_ALLOWED, 405) \
+ V(NOT_ACCEPTABLE, 406) \
+ V(PROXY_AUTHENTICATION_REQUIRED, 407) \
+ V(REQUEST_TIMEOUT, 408) \
+ V(CONFLICT, 409) \
+ V(GONE, 410) \
+ V(LENGTH_REQUIRED, 411) \
+ V(PRECONDITION_FAILED, 412) \
+ V(PAYLOAD_TOO_LARGE, 413) \
+ V(URI_TOO_LONG, 414) \
+ V(UNSUPPORTED_MEDIA_TYPE, 415) \
+ V(RANGE_NOT_SATISFIABLE, 416) \
+ V(EXPECTATION_FAILED, 417) \
+ V(TEAPOT, 418) \
+ V(MISDIRECTED_REQUEST, 421) \
+ V(UNPROCESSABLE_ENTITY, 422) \
+ V(LOCKED, 423) \
+ V(FAILED_DEPENDENCY, 424) \
+ V(UNORDERED_COLLECTION, 425) \
+ V(UPGRADE_REQUIRED, 426) \
+ V(PRECONDITION_REQUIRED, 428) \
+ V(TOO_MANY_REQUESTS, 429) \
+ V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \
+ V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \
+ V(INTERNAL_SERVER_ERROR, 500) \
+ V(NOT_IMPLEMENTED, 501) \
+ V(BAD_GATEWAY, 502) \
+ V(SERVICE_UNAVAILABLE, 503) \
+ V(GATEWAY_TIMEOUT, 504) \
+ V(HTTP_VERSION_NOT_SUPPORTED, 505) \
+ V(VARIANT_ALSO_NEGOTIATES, 506) \
+ V(INSUFFICIENT_STORAGE, 507) \
+ V(LOOP_DETECTED, 508) \
+ V(BANDWIDTH_LIMIT_EXCEEDED, 509) \
+ V(NOT_EXTENDED, 510) \
+ V(NETWORK_AUTHENTICATION_REQUIRED, 511)
+
+enum http_status_codes {
+#define V(name, code) HTTP_STATUS_##name = code,
+HTTP_STATUS_CODES(V)
+#undef V
+};
+
+enum padding_strategy_type {
+ // No padding strategy
+ PADDING_STRATEGY_NONE,
+ // Padding will ensure all data frames are maxFrameSize
+ PADDING_STRATEGY_MAX,
+ // Padding will be determined via JS callback
+ PADDING_STRATEGY_CALLBACK
+};
+
+#define NGHTTP2_ERROR_CODES(V) \
+ V(NGHTTP2_ERR_INVALID_ARGUMENT) \
+ V(NGHTTP2_ERR_BUFFER_ERROR) \
+ V(NGHTTP2_ERR_UNSUPPORTED_VERSION) \
+ V(NGHTTP2_ERR_WOULDBLOCK) \
+ V(NGHTTP2_ERR_PROTO) \
+ V(NGHTTP2_ERR_INVALID_FRAME) \
+ V(NGHTTP2_ERR_EOF) \
+ V(NGHTTP2_ERR_DEFERRED) \
+ V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \
+ V(NGHTTP2_ERR_STREAM_CLOSED) \
+ V(NGHTTP2_ERR_STREAM_CLOSING) \
+ V(NGHTTP2_ERR_STREAM_SHUT_WR) \
+ V(NGHTTP2_ERR_INVALID_STREAM_ID) \
+ V(NGHTTP2_ERR_INVALID_STREAM_STATE) \
+ V(NGHTTP2_ERR_DEFERRED_DATA_EXIST) \
+ V(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED) \
+ V(NGHTTP2_ERR_GOAWAY_ALREADY_SENT) \
+ V(NGHTTP2_ERR_INVALID_HEADER_BLOCK) \
+ V(NGHTTP2_ERR_INVALID_STATE) \
+ V(NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) \
+ V(NGHTTP2_ERR_FRAME_SIZE_ERROR) \
+ V(NGHTTP2_ERR_HEADER_COMP) \
+ V(NGHTTP2_ERR_FLOW_CONTROL) \
+ V(NGHTTP2_ERR_INSUFF_BUFSIZE) \
+ V(NGHTTP2_ERR_PAUSE) \
+ V(NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS) \
+ V(NGHTTP2_ERR_PUSH_DISABLED) \
+ V(NGHTTP2_ERR_DATA_EXIST) \
+ V(NGHTTP2_ERR_SESSION_CLOSING) \
+ V(NGHTTP2_ERR_HTTP_HEADER) \
+ V(NGHTTP2_ERR_HTTP_MESSAGING) \
+ V(NGHTTP2_ERR_REFUSED_STREAM) \
+ V(NGHTTP2_ERR_INTERNAL) \
+ V(NGHTTP2_ERR_CANCEL) \
+ V(NGHTTP2_ERR_FATAL) \
+ V(NGHTTP2_ERR_NOMEM) \
+ V(NGHTTP2_ERR_CALLBACK_FAILURE) \
+ V(NGHTTP2_ERR_BAD_CLIENT_MAGIC) \
+ V(NGHTTP2_ERR_FLOODED)
+
+const char* nghttp2_errname(int rv) {
+ switch (rv) {
+#define V(code) case code: return #code;
+ NGHTTP2_ERROR_CODES(V)
+#undef V
+ default:
+ return "NGHTTP2_UNKNOWN_ERROR";
+ }
+}
+
+#define DEFAULT_SETTINGS_HEADER_TABLE_SIZE 4096
+#define DEFAULT_SETTINGS_ENABLE_PUSH 1
+#define DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE 65535
+#define DEFAULT_SETTINGS_MAX_FRAME_SIZE 16384
+#define MAX_MAX_FRAME_SIZE 16777215
+#define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE
+#define MAX_INITIAL_WINDOW_SIZE 2147483647
+
+class Http2Options {
+ public:
+ explicit Http2Options(Environment* env);
+
+ ~Http2Options() {
+ nghttp2_option_del(options_);
+ }
+
+ nghttp2_option* operator*() {
+ return options_;
+ }
+
+ void SetPaddingStrategy(uint32_t val) {
+ CHECK_LE(val, PADDING_STRATEGY_CALLBACK);
+ padding_strategy_ = static_cast<padding_strategy_type>(val);
+ }
+
+ void SetMaxDeflateDynamicTableSize(size_t val) {
+ nghttp2_option_set_max_deflate_dynamic_table_size(options_, val);
+ }
+
+ void SetMaxReservedRemoteStreams(uint32_t val) {
+ nghttp2_option_set_max_reserved_remote_streams(options_, val);
+ }
+
+ void SetMaxSendHeaderBlockLength(size_t val) {
+ nghttp2_option_set_max_send_header_block_length(options_, val);
+ }
+
+ void SetPeerMaxConcurrentStreams(uint32_t val) {
+ nghttp2_option_set_peer_max_concurrent_streams(options_, val);
+ }
+
+ padding_strategy_type GetPaddingStrategy() {
+ return padding_strategy_;
+ }
+
+ private:
+ nghttp2_option* options_;
+ padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE;
+};
+
+static const size_t kAllocBufferSize = 64 * 1024;
+
+////
+typedef uint32_t(*get_setting)(nghttp2_session* session,
+ nghttp2_settings_id id);
+
+class Http2Session : public AsyncWrap,
+ public StreamBase,
+ public Nghttp2Session {
+ public:
+ Http2Session(Environment* env,
+ Local<Object> wrap,
+ nghttp2_session_type type) :
+ AsyncWrap(env, wrap, AsyncWrap::PROVIDER_HTTP2SESSION),
+ StreamBase(env) {
+ Wrap(object(), this);
+
+ Http2Options opts(env);
+
+ padding_strategy_ = opts.GetPaddingStrategy();
+
+ Init(env->event_loop(), type, *opts);
+ stream_buf_.AllocateSufficientStorage(kAllocBufferSize);
+ }
+
+ ~Http2Session() override {
+ CHECK_EQ(false, persistent().IsEmpty());
+ ClearWrap(object());
+ persistent().Reset();
+ CHECK_EQ(true, persistent().IsEmpty());
+ }
+
+ static void OnStreamAllocImpl(size_t suggested_size,
+ uv_buf_t* buf,
+ void* ctx);
+ static void OnStreamReadImpl(ssize_t nread,
+ const uv_buf_t* bufs,
+ uv_handle_type pending,
+ void* ctx);
+ protected:
+ void OnFreeSession() override;
+
+ ssize_t OnMaxFrameSizePadding(size_t frameLength,
+ size_t maxPayloadLen);
+
+ ssize_t OnCallbackPadding(size_t frame,
+ size_t maxPayloadLen);
+
+ bool HasGetPaddingCallback() override {
+ return padding_strategy_ == PADDING_STRATEGY_MAX ||
+ padding_strategy_ == PADDING_STRATEGY_CALLBACK;
+ }
+
+ ssize_t GetPadding(size_t frameLength, size_t maxPayloadLen) override {
+ if (padding_strategy_ == PADDING_STRATEGY_MAX) {
+ return OnMaxFrameSizePadding(frameLength, maxPayloadLen);
+ }
+
+ CHECK_EQ(padding_strategy_, PADDING_STRATEGY_CALLBACK);
+
+ return OnCallbackPadding(frameLength, maxPayloadLen);
+ }
+
+ void OnHeaders(Nghttp2Stream* stream,
+ nghttp2_header_list* headers,
+ nghttp2_headers_category cat,
+ uint8_t flags) override;
+ void OnStreamClose(int32_t id, uint32_t code) override;
+ void Send(uv_buf_t* bufs, size_t total) override;
+ void OnDataChunk(Nghttp2Stream* stream, nghttp2_data_chunk_t* chunk) override;
+ void OnSettings(bool ack) override;
+ void OnPriority(int32_t stream,
+ int32_t parent,
+ int32_t weight,
+ int8_t exclusive) override;
+ void OnGoAway(int32_t lastStreamID,
+ uint32_t errorCode,
+ uint8_t* data,
+ size_t length) override;
+ void OnFrameError(int32_t id, uint8_t type, int error_code) override;
+ void OnTrailers(Nghttp2Stream* stream,
+ MaybeStackBuffer<nghttp2_nv>* trailers) override;
+ void AllocateSend(size_t recommended, uv_buf_t* buf) override;
+
+ int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count,
+ uv_stream_t* send_handle) override;
+
+ AsyncWrap* GetAsyncWrap() override {
+ return static_cast<AsyncWrap*>(this);
+ }
+
+ void* Cast() override {
+ return reinterpret_cast<void*>(this);
+ }
+
+ // Required for StreamBase
+ bool IsAlive() override {
+ return true;
+ }
+
+ // Required for StreamBase
+ bool IsClosing() override {
+ return false;
+ }
+
+ // Required for StreamBase
+ int ReadStart() override { return 0; }
+
+ // Required for StreamBase
+ int ReadStop() override { return 0; }
+
+ // Required for StreamBase
+ int DoShutdown(ShutdownWrap* req_wrap) override {
+ return 0;
+ }
+
+ public:
+ void Consume(Local<External> external);
+ void Unconsume();
+
+ static void New(const FunctionCallbackInfo<Value>& args);
+ static void Consume(const FunctionCallbackInfo<Value>& args);
+ static void Unconsume(const FunctionCallbackInfo<Value>& args);
+ static void Destroy(const FunctionCallbackInfo<Value>& args);
+ static void SubmitSettings(const FunctionCallbackInfo<Value>& args);
+ static void SubmitRstStream(const FunctionCallbackInfo<Value>& args);
+ static void SubmitResponse(const FunctionCallbackInfo<Value>& args);
+ static void SubmitFile(const FunctionCallbackInfo<Value>& args);
+ static void SubmitRequest(const FunctionCallbackInfo<Value>& args);
+ static void SubmitPushPromise(const FunctionCallbackInfo<Value>& args);
+ static void SubmitPriority(const FunctionCallbackInfo<Value>& args);
+ static void SendHeaders(const FunctionCallbackInfo<Value>& args);
+ static void ShutdownStream(const FunctionCallbackInfo<Value>& args);
+ static void StreamWrite(const FunctionCallbackInfo<Value>& args);
+ static void StreamReadStart(const FunctionCallbackInfo<Value>& args);
+ static void StreamReadStop(const FunctionCallbackInfo<Value>& args);
+ static void SetNextStreamID(const FunctionCallbackInfo<Value>& args);
+ static void SendShutdownNotice(const FunctionCallbackInfo<Value>& args);
+ static void SubmitGoaway(const FunctionCallbackInfo<Value>& args);
+ static void DestroyStream(const FunctionCallbackInfo<Value>& args);
+
+ template <get_setting fn>
+ static void GetSettings(const FunctionCallbackInfo<Value>& args);
+
+ size_t self_size() const override {
+ return sizeof(*this);
+ }
+
+ char* stream_alloc() {
+ return *stream_buf_;
+ }
+
+ private:
+ StreamBase* stream_;
+ StreamResource::Callback<StreamResource::AllocCb> prev_alloc_cb_;
+ StreamResource::Callback<StreamResource::ReadCb> prev_read_cb_;
+ padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE;
+ MaybeStackBuffer<char, kAllocBufferSize> stream_buf_;
+};
+
+class ExternalHeader :
+ public String::ExternalOneByteStringResource {
+ public:
+ explicit ExternalHeader(nghttp2_rcbuf* buf)
+ : buf_(buf), vec_(nghttp2_rcbuf_get_buf(buf)) {
+ }
+
+ ~ExternalHeader() override {
+ nghttp2_rcbuf_decref(buf_);
+ buf_ = nullptr;
+ }
+
+ const char* data() const override {
+ return const_cast<const char*>(reinterpret_cast<char*>(vec_.base));
+ }
+
+ size_t length() const override {
+ return vec_.len;
+ }
+
+ static Local<String> New(Isolate* isolate, nghttp2_rcbuf* buf) {
+ EscapableHandleScope scope(isolate);
+ nghttp2_vec vec = nghttp2_rcbuf_get_buf(buf);
+ if (vec.len == 0) {
+ nghttp2_rcbuf_decref(buf);
+ return scope.Escape(String::Empty(isolate));
+ }
+
+ ExternalHeader* h_str = new ExternalHeader(buf);
+ MaybeLocal<String> str = String::NewExternalOneByte(isolate, h_str);
+ isolate->AdjustAmountOfExternalAllocatedMemory(vec.len);
+
+ if (str.IsEmpty()) {
+ delete h_str;
+ return scope.Escape(String::Empty(isolate));
+ }
+
+ return scope.Escape(str.ToLocalChecked());
+ }
+
+ private:
+ nghttp2_rcbuf* buf_;
+ nghttp2_vec vec_;
+};
+
+class Headers {
+ public:
+ Headers(Isolate* isolate, Local<Context> context, Local<Array> headers) {
+ headers_.AllocateSufficientStorage(headers->Length());
+ Local<Value> item;
+ Local<Array> header;
+
+ for (size_t n = 0; n < headers->Length(); n++) {
+ item = headers->Get(context, n).ToLocalChecked();
+ CHECK(item->IsArray());
+ 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);
+ headers_[n].flags = NGHTTP2_NV_FLAG_NONE;
+ Local<Value> flag = header->Get(context, 2).ToLocalChecked();
+ if (flag->BooleanValue(context).ToChecked())
+ headers_[n].flags |= NGHTTP2_NV_FLAG_NO_INDEX;
+ uint8_t* buf = Malloc<uint8_t>(keylen + valuelen);
+ headers_[n].name = buf;
+ headers_[n].value = buf + keylen;
+ headers_[n].namelen =
+ StringBytes::Write(isolate,
+ reinterpret_cast<char*>(headers_[n].name),
+ keylen, key, ASCII);
+ headers_[n].valuelen =
+ StringBytes::Write(isolate,
+ reinterpret_cast<char*>(headers_[n].value),
+ valuelen, value, ASCII);
+ }
+ }
+
+ ~Headers() {
+ for (size_t n = 0; n < headers_.length(); n++)
+ free(headers_[n].name);
+ }
+
+ nghttp2_nv* operator*() {
+ return *headers_;
+ }
+
+ size_t length() const {
+ return headers_.length();
+ }
+
+ private:
+ MaybeStackBuffer<nghttp2_nv> headers_;
+};
+
+} // namespace http2
+} // namespace node
+
+#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#endif // SRC_NODE_HTTP2_H_
diff --git a/src/node_http2_core-inl.h b/src/node_http2_core-inl.h
new file mode 100644
index 0000000000..49ec63b59b
--- /dev/null
+++ b/src/node_http2_core-inl.h
@@ -0,0 +1,590 @@
+#ifndef SRC_NODE_HTTP2_CORE_INL_H_
+#define SRC_NODE_HTTP2_CORE_INL_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include "node_http2_core.h"
+#include "node_internals.h" // arraysize
+#include "freelist.h"
+
+namespace node {
+namespace http2 {
+
+#define FREELIST_MAX 1024
+
+#define LINKED_LIST_ADD(list, item) \
+ do { \
+ if (list ## _tail_ == nullptr) { \
+ list ## _head_ = item; \
+ list ## _tail_ = item; \
+ } else { \
+ list ## _tail_->next = item; \
+ list ## _tail_ = item; \
+ } \
+ } while (0);
+
+extern Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
+ data_chunk_free_list;
+
+extern Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
+
+extern Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
+
+extern Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
+ data_chunks_free_list;
+
+// See: https://nghttp2.org/documentation/nghttp2_submit_shutdown_notice.html
+inline void Nghttp2Session::SubmitShutdownNotice() {
+ DEBUG_HTTP2("Nghttp2Session %d: submitting shutdown notice\n", session_type_);
+ nghttp2_submit_shutdown_notice(session_);
+}
+
+// Sends a SETTINGS frame on the current session
+// Note that this *should* send a SETTINGS frame even if niv == 0 and there
+// are no settings entries to send.
+inline int Nghttp2Session::SubmitSettings(const nghttp2_settings_entry iv[],
+ size_t niv) {
+ DEBUG_HTTP2("Nghttp2Session %d: submitting settings, count: %d\n",
+ session_type_, niv);
+ return nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv);
+}
+
+// Returns the Nghttp2Stream associated with the given id, or nullptr if none
+inline Nghttp2Stream* Nghttp2Session::FindStream(int32_t id) {
+ auto s = streams_.find(id);
+ if (s != streams_.end()) {
+ DEBUG_HTTP2("Nghttp2Session %d: stream %d found\n", session_type_, id);
+ return s->second;
+ } else {
+ DEBUG_HTTP2("Nghttp2Session %d: stream %d not found\n", session_type_, id);
+ return nullptr;
+ }
+}
+
+// Flushes any received queued chunks of data out to the JS layer
+inline void Nghttp2Stream::FlushDataChunks(bool done) {
+ while (data_chunks_head_ != nullptr) {
+ DEBUG_HTTP2("Nghttp2Stream %d: emitting data chunk\n", id_);
+ nghttp2_data_chunk_t* item = data_chunks_head_;
+ data_chunks_head_ = item->next;
+ // item will be passed to the Buffer instance and freed on gc
+ session_->OnDataChunk(this, item);
+ }
+ data_chunks_tail_ = nullptr;
+ if (done)
+ session_->OnDataChunk(this, nullptr);
+}
+
+// Passes all of the the chunks for a data frame out to the JS layer
+// The chunks are collected as the frame is being processed and sent out
+// to the JS side only when the frame is fully processed.
+inline void Nghttp2Session::HandleDataFrame(const nghttp2_frame* frame) {
+ int32_t id = frame->hd.stream_id;
+ DEBUG_HTTP2("Nghttp2Session %d: handling data frame for stream %d\n",
+ session_type_, id);
+ Nghttp2Stream* stream = this->FindStream(id);
+ // If the stream does not exist, something really bad happened
+ CHECK_NE(stream, nullptr);
+ bool done = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) ==
+ NGHTTP2_FLAG_END_STREAM;
+ stream->FlushDataChunks(done);
+}
+
+// Passes all of the collected headers for a HEADERS frame out to the JS layer.
+// The headers are collected as the frame is being processed and sent out
+// to the JS side only when the frame is fully processed.
+inline void Nghttp2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
+ int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
+ frame->push_promise.promised_stream_id : frame->hd.stream_id;
+ DEBUG_HTTP2("Nghttp2Session %d: handling headers frame for stream %d\n",
+ session_type_, id);
+ Nghttp2Stream* stream = FindStream(id);
+ // If the stream does not exist, something really bad happened
+ CHECK_NE(stream, nullptr);
+ OnHeaders(stream,
+ stream->headers(),
+ stream->headers_category(),
+ frame->hd.flags);
+ stream->FreeHeaders();
+}
+
+// Notifies the JS layer that a PRIORITY frame has been received
+inline void Nghttp2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
+ nghttp2_priority priority_frame = frame->priority;
+ int32_t id = frame->hd.stream_id;
+ DEBUG_HTTP2("Nghttp2Session %d: handling priority frame for stream %d\n",
+ session_type_, id);
+ // Ignore the priority frame if stream ID is <= 0
+ // This actually should never happen because nghttp2 should treat this as
+ // an error condition that terminates the session.
+ if (id > 0) {
+ nghttp2_priority_spec spec = priority_frame.pri_spec;
+ OnPriority(id, spec.stream_id, spec.weight, spec.exclusive);
+ }
+}
+
+// Notifies the JS layer that a GOAWAY frame has been received
+inline void Nghttp2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
+ nghttp2_goaway goaway_frame = frame->goaway;
+ DEBUG_HTTP2("Nghttp2Session %d: handling goaway frame\n", session_type_);
+
+ OnGoAway(goaway_frame.last_stream_id,
+ goaway_frame.error_code,
+ goaway_frame.opaque_data,
+ goaway_frame.opaque_data_len);
+}
+
+// Prompts nghttp2 to flush the queue of pending data frames
+inline void Nghttp2Session::SendPendingData() {
+ const uint8_t* data;
+ ssize_t len = 0;
+ size_t ncopy = 0;
+ uv_buf_t buf;
+ AllocateSend(SEND_BUFFER_RECOMMENDED_SIZE, &buf);
+ while (nghttp2_session_want_write(session_)) {
+ len = nghttp2_session_mem_send(session_, &data);
+ CHECK_GE(len, 0); // If this is less than zero, we're out of memory
+ // While len is greater than 0, send a chunk
+ while (len > 0) {
+ ncopy = len;
+ if (ncopy > buf.len)
+ ncopy = buf.len;
+ memcpy(buf.base, data, ncopy);
+ Send(&buf, ncopy);
+ len -= ncopy;
+ CHECK_GE(len, 0); // This should never be less than zero
+ }
+ }
+}
+
+// Initialize the Nghttp2Session handle by creating and
+// assigning the Nghttp2Session instance and associated
+// uv_loop_t.
+inline int Nghttp2Session::Init(uv_loop_t* loop,
+ const nghttp2_session_type type,
+ nghttp2_option* options,
+ nghttp2_mem* mem) {
+ DEBUG_HTTP2("Nghttp2Session %d: initializing session\n", type);
+ loop_ = loop;
+ session_type_ = type;
+ int ret = 0;
+
+ nghttp2_session_callbacks* callbacks
+ = callback_struct_saved[HasGetPaddingCallback() ? 1 : 0].callbacks;
+
+ nghttp2_option* opts;
+ if (options != nullptr) {
+ opts = options;
+ } else {
+ nghttp2_option_new(&opts);
+ }
+
+ switch (type) {
+ case NGHTTP2_SESSION_SERVER:
+ ret = nghttp2_session_server_new3(&session_,
+ callbacks,
+ this,
+ opts,
+ mem);
+ break;
+ case NGHTTP2_SESSION_CLIENT:
+ ret = nghttp2_session_client_new3(&session_,
+ callbacks,
+ this,
+ opts,
+ mem);
+ break;
+ }
+ if (opts != options) {
+ nghttp2_option_del(opts);
+ }
+
+ // For every node::Http2Session instance, there is a uv_prep_t handle
+ // whose callback is triggered on every tick of the event loop. When
+ // run, nghttp2 is prompted to send any queued data it may have stored.
+ uv_prepare_init(loop_, &prep_);
+ uv_prepare_start(&prep_, [](uv_prepare_t* t) {
+ Nghttp2Session* session = ContainerOf(&Nghttp2Session::prep_, t);
+ session->SendPendingData();
+ });
+// uv_unref(reinterpret_cast<uv_handle_t*>(&prep_));
+ return ret;
+}
+
+
+inline int Nghttp2Session::Free() {
+ assert(session_ != nullptr);
+ DEBUG_HTTP2("Nghttp2Session %d: freeing session\n", session_type_);
+ // Stop the loop
+ uv_prepare_stop(&prep_);
+ auto PrepClose = [](uv_handle_t* handle) {
+ Nghttp2Session* session =
+ ContainerOf(&Nghttp2Session::prep_,
+ reinterpret_cast<uv_prepare_t*>(handle));
+
+ session->OnFreeSession();
+ DEBUG_HTTP2("Nghttp2Session %d: session is free\n",
+ session->session_type_);
+ };
+ uv_close(reinterpret_cast<uv_handle_t*>(&prep_), PrepClose);
+
+ nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
+ nghttp2_session_del(session_);
+ session_ = nullptr;
+ loop_ = nullptr;
+ return 1;
+}
+
+// Write data received from the socket to the underlying nghttp2_session.
+inline ssize_t Nghttp2Session::Write(const uv_buf_t* bufs, unsigned int nbufs) {
+ size_t total = 0;
+ for (unsigned int n = 0; n < nbufs; n++) {
+ ssize_t ret =
+ nghttp2_session_mem_recv(session_,
+ reinterpret_cast<uint8_t*>(bufs[n].base),
+ bufs[n].len);
+ if (ret < 0) {
+ return ret;
+ } else {
+ total += ret;
+ }
+ }
+ SendPendingData();
+ return total;
+}
+
+inline void Nghttp2Session::AddStream(Nghttp2Stream* stream) {
+ streams_[stream->id()] = stream;
+}
+
+// Removes a stream instance from this session
+inline void Nghttp2Session::RemoveStream(int32_t id) {
+ streams_.erase(id);
+}
+
+// Implementation for Nghttp2Stream functions
+
+inline Nghttp2Stream* Nghttp2Stream::Init(
+ int32_t id,
+ Nghttp2Session* session,
+ nghttp2_headers_category category) {
+ DEBUG_HTTP2("Nghttp2Stream %d: initializing stream\n", id);
+ Nghttp2Stream* stream = stream_free_list.pop();
+ stream->ResetState(id, session, category);
+ session->AddStream(stream);
+ return stream;
+}
+
+
+// Resets the state of the stream instance to defaults
+inline void Nghttp2Stream::ResetState(
+ int32_t id,
+ Nghttp2Session* session,
+ nghttp2_headers_category category) {
+ DEBUG_HTTP2("Nghttp2Stream %d: resetting stream state\n", id);
+ session_ = session;
+ queue_head_ = nullptr;
+ queue_tail_ = nullptr;
+ data_chunks_head_ = nullptr;
+ data_chunks_tail_ = nullptr;
+ current_headers_head_ = nullptr;
+ current_headers_tail_ = nullptr;
+ current_headers_category_ = category;
+ flags_ = NGHTTP2_STREAM_FLAG_NONE;
+ id_ = id;
+ code_ = NGHTTP2_NO_ERROR;
+ prev_local_window_size_ = 65535;
+ queue_head_index_ = 0;
+ queue_head_offset_ = 0;
+}
+
+
+inline void Nghttp2Stream::Destroy() {
+ DEBUG_HTTP2("Nghttp2Stream %d: destroying stream\n", id_);
+ // Do nothing if this stream instance is already destroyed
+ if (IsDestroyed() || IsDestroying())
+ return;
+ flags_ |= NGHTTP2_STREAM_DESTROYING;
+ Nghttp2Session* session = this->session_;
+
+ if (session != nullptr) {
+ // Remove this stream from the associated session
+ session_->RemoveStream(this->id());
+ session_ = nullptr;
+ }
+
+ // Free any remaining incoming data chunks.
+ while (data_chunks_head_ != nullptr) {
+ nghttp2_data_chunk_t* chunk = data_chunks_head_;
+ data_chunks_head_ = chunk->next;
+ delete[] chunk->buf.base;
+ data_chunk_free_list.push(chunk);
+ }
+ data_chunks_tail_ = nullptr;
+
+ // Free any remaining outgoing data chunks.
+ while (queue_head_ != nullptr) {
+ nghttp2_stream_write_queue* head = queue_head_;
+ queue_head_ = head->next;
+ head->cb(head->req, UV_ECANCELED);
+ delete head;
+ }
+ queue_tail_ = nullptr;
+
+ // Free any remaining headers
+ FreeHeaders();
+
+ // Return this stream instance to the freelist
+ stream_free_list.push(this);
+}
+
+inline void Nghttp2Stream::FreeHeaders() {
+ DEBUG_HTTP2("Nghttp2Stream %d: freeing headers\n", id_);
+ while (current_headers_head_ != nullptr) {
+ DEBUG_HTTP2("Nghttp2Stream %d: freeing header item\n", id_);
+ nghttp2_header_list* item = current_headers_head_;
+ current_headers_head_ = item->next;
+ header_free_list.push(item);
+ }
+ current_headers_tail_ = nullptr;
+}
+
+// Submit informational headers for a stream.
+inline int Nghttp2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
+ DEBUG_HTTP2("Nghttp2Stream %d: sending informational headers, count: %d\n",
+ id_, len);
+ CHECK_GT(len, 0);
+ return nghttp2_submit_headers(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_, nullptr,
+ nva, len, nullptr);
+}
+
+inline int Nghttp2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
+ bool silent) {
+ DEBUG_HTTP2("Nghttp2Stream %d: sending priority spec\n", id_);
+ return silent ?
+ nghttp2_session_change_stream_priority(session_->session(),
+ id_, prispec) :
+ nghttp2_submit_priority(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_, prispec);
+}
+
+// Submit an RST_STREAM frame
+inline int Nghttp2Stream::SubmitRstStream(const uint32_t code) {
+ DEBUG_HTTP2("Nghttp2Stream %d: sending rst-stream, code: %d\n", id_, code);
+ session_->SendPendingData();
+ return nghttp2_submit_rst_stream(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_,
+ code);
+}
+
+// Submit a push promise.
+inline int32_t Nghttp2Stream::SubmitPushPromise(
+ nghttp2_nv* nva,
+ size_t len,
+ Nghttp2Stream** assigned,
+ bool emptyPayload) {
+ CHECK_GT(len, 0);
+ DEBUG_HTTP2("Nghttp2Stream %d: sending push promise\n", id_);
+ int32_t ret = nghttp2_submit_push_promise(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_, nva, len,
+ nullptr);
+ if (ret > 0) {
+ auto stream = Nghttp2Stream::Init(ret, session_);
+ if (emptyPayload) stream->Shutdown();
+ if (assigned != nullptr) *assigned = stream;
+ }
+ return ret;
+}
+
+// Initiate a response. If the nghttp2_stream is still writable by
+// the time this is called, then an nghttp2_data_provider will be
+// initialized, causing at least one (possibly empty) data frame to
+// be sent.
+inline int Nghttp2Stream::SubmitResponse(nghttp2_nv* nva,
+ size_t len,
+ bool emptyPayload) {
+ CHECK_GT(len, 0);
+ DEBUG_HTTP2("Nghttp2Stream %d: submitting response\n", id_);
+ nghttp2_data_provider* provider = nullptr;
+ nghttp2_data_provider prov;
+ prov.source.ptr = this;
+ prov.read_callback = Nghttp2Session::OnStreamRead;
+ if (!emptyPayload && IsWritable())
+ provider = &prov;
+
+ return nghttp2_submit_response(session_->session(), id_,
+ nva, len, provider);
+}
+
+// Initiate a response that contains data read from a file descriptor.
+inline int Nghttp2Stream::SubmitFile(int fd, nghttp2_nv* nva, size_t len) {
+ CHECK_GT(len, 0);
+ CHECK_GT(fd, 0);
+ DEBUG_HTTP2("Nghttp2Stream %d: submitting file\n", id_);
+ nghttp2_data_provider prov;
+ prov.source.ptr = this;
+ prov.source.fd = fd;
+ prov.read_callback = Nghttp2Session::OnStreamReadFD;
+
+ return nghttp2_submit_response(session_->session(), id_,
+ nva, len, &prov);
+}
+
+// Initiate a request. If writable is true (the default), then
+// an nghttp2_data_provider will be initialized, causing at
+// least one (possibly empty) data frame to to be sent.
+inline int32_t Nghttp2Session::SubmitRequest(
+ nghttp2_priority_spec* prispec,
+ nghttp2_nv* nva,
+ size_t len,
+ Nghttp2Stream** assigned,
+ bool emptyPayload) {
+ CHECK_GT(len, 0);
+ DEBUG_HTTP2("Nghttp2Session: submitting request\n");
+ nghttp2_data_provider* provider = nullptr;
+ nghttp2_data_provider prov;
+ prov.source.ptr = this;
+ prov.read_callback = OnStreamRead;
+ if (!emptyPayload)
+ provider = &prov;
+ int32_t ret = nghttp2_submit_request(session_,
+ prispec, nva, len,
+ provider, nullptr);
+ // Assign the Nghttp2Stream handle
+ if (ret > 0) {
+ Nghttp2Stream* stream = Nghttp2Stream::Init(ret, this);
+ if (emptyPayload) stream->Shutdown();
+ if (assigned != nullptr) *assigned = stream;
+ }
+ return ret;
+}
+
+// Queue the given set of uv_but_t handles for writing to an
+// nghttp2_stream. The callback will be invoked once the chunks
+// of data have been flushed to the underlying nghttp2_session.
+// Note that this does *not* mean that the data has been flushed
+// to the socket yet.
+inline int Nghttp2Stream::Write(nghttp2_stream_write_t* req,
+ const uv_buf_t bufs[],
+ unsigned int nbufs,
+ nghttp2_stream_write_cb cb) {
+ if (!IsWritable()) {
+ if (cb != nullptr)
+ cb(req, UV_EOF);
+ return 0;
+ }
+ DEBUG_HTTP2("Nghttp2Stream %d: queuing buffers to send, count: %d\n",
+ id_, nbufs);
+ nghttp2_stream_write_queue* item = new nghttp2_stream_write_queue;
+ item->cb = cb;
+ item->req = req;
+ item->nbufs = nbufs;
+ item->bufs.AllocateSufficientStorage(nbufs);
+ req->handle = this;
+ req->item = item;
+ memcpy(*(item->bufs), bufs, nbufs * sizeof(*bufs));
+
+ if (queue_head_ == nullptr) {
+ queue_head_ = item;
+ queue_tail_ = item;
+ } else {
+ queue_tail_->next = item;
+ queue_tail_ = item;
+ }
+ nghttp2_session_resume_data(session_->session(), id_);
+ return 0;
+}
+
+inline void Nghttp2Stream::ReadStart() {
+ // Has no effect if IsReading() is true.
+ if (IsReading())
+ return;
+ DEBUG_HTTP2("Nghttp2Stream %d: start reading\n", id_);
+ if (IsPaused()) {
+ // If handle->reading is less than zero, read_start had never previously
+ // been called. If handle->reading is zero, reading had started and read
+ // stop had been previously called, meaning that the flow control window
+ // has been explicitly set to zero. Reset the flow control window now to
+ // restart the flow of data.
+ nghttp2_session_set_local_window_size(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_,
+ prev_local_window_size_);
+ }
+ flags_ |= NGHTTP2_STREAM_READ_START;
+ flags_ &= ~NGHTTP2_STREAM_READ_PAUSED;
+
+ // Flush any queued data chunks immediately out to the JS layer
+ FlushDataChunks();
+}
+
+inline void Nghttp2Stream::ReadStop() {
+ DEBUG_HTTP2("Nghttp2Stream %d: stop reading\n", id_);
+ // Has no effect if IsReading() is false, which will happen if we either
+ // have not started reading yet at all (NGHTTP2_STREAM_READ_START is not
+ // set) or if we're already paused (NGHTTP2_STREAM_READ_PAUSED is set.
+ if (!IsReading())
+ return;
+ flags_ |= NGHTTP2_STREAM_READ_PAUSED;
+
+ // When not reading, explicitly set the local window size to 0 so that
+ // the peer does not keep sending data that has to be buffered
+ int32_t ret =
+ nghttp2_session_get_stream_local_window_size(session_->session(), id_);
+ if (ret >= 0)
+ prev_local_window_size_ = ret;
+ nghttp2_session_set_local_window_size(session_->session(),
+ NGHTTP2_FLAG_NONE,
+ id_, 0);
+}
+
+nghttp2_data_chunks_t::~nghttp2_data_chunks_t() {
+ for (unsigned int n = 0; n < nbufs; n++) {
+ free(buf[n].base);
+ }
+}
+
+Nghttp2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
+ nghttp2_session_callbacks_new(&callbacks);
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, OnBeginHeadersCallback);
+ nghttp2_session_callbacks_set_on_header_callback2(
+ callbacks, OnHeaderCallback);
+ nghttp2_session_callbacks_set_on_frame_recv_callback(
+ callbacks, OnFrameReceive);
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, OnStreamClose);
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, OnDataChunkReceived);
+ nghttp2_session_callbacks_set_on_frame_not_send_callback(
+ callbacks, OnFrameNotSent);
+
+ // nghttp2_session_callbacks_set_on_invalid_frame_recv(
+ // callbacks, OnInvalidFrameReceived);
+
+#ifdef NODE_DEBUG_HTTP2
+ nghttp2_session_callbacks_set_error_callback(
+ callbacks, OnNghttpError);
+#endif
+
+ if (kHasGetPaddingCallback) {
+ nghttp2_session_callbacks_set_select_padding_callback(
+ callbacks, OnSelectPadding);
+ }
+}
+
+Nghttp2Session::Callbacks::~Callbacks() {
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+} // namespace http2
+} // namespace node
+
+#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#endif // SRC_NODE_HTTP2_CORE_INL_H_
diff --git a/src/node_http2_core.cc b/src/node_http2_core.cc
new file mode 100644
index 0000000000..4d9ab4a4df
--- /dev/null
+++ b/src/node_http2_core.cc
@@ -0,0 +1,326 @@
+#include "node_http2_core-inl.h"
+
+namespace node {
+namespace http2 {
+
+#ifdef NODE_DEBUG_HTTP2
+int Nghttp2Session::OnNghttpError(nghttp2_session* session,
+ const char* message,
+ size_t len,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: Error '%.*s'\n",
+ handle->session_type_, len, message);
+ return 0;
+}
+#endif
+
+// nghttp2 calls this at the beginning a new HEADERS or PUSH_PROMISE frame.
+// We use it to ensure that an Nghttp2Stream instance is allocated to store
+// the state.
+int Nghttp2Session::OnBeginHeadersCallback(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
+ frame->push_promise.promised_stream_id :
+ frame->hd.stream_id;
+ DEBUG_HTTP2("Nghttp2Session %d: beginning headers for stream %d\n",
+ handle->session_type_, id);
+
+ Nghttp2Stream* stream = handle->FindStream(id);
+ if (stream == nullptr) {
+ Nghttp2Stream::Init(id, handle, frame->headers.cat);
+ } else {
+ stream->StartHeaders(frame->headers.cat);
+ }
+ return 0;
+}
+
+// nghttp2 calls this once for every header name-value pair in a HEADERS
+// or PUSH_PROMISE block. CONTINUATION frames are handled automatically
+// and transparently so we do not need to worry about those at all.
+int Nghttp2Session::OnHeaderCallback(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ nghttp2_rcbuf *name,
+ nghttp2_rcbuf *value,
+ uint8_t flags,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
+ frame->push_promise.promised_stream_id :
+ frame->hd.stream_id;
+ Nghttp2Stream* stream = handle->FindStream(id);
+ nghttp2_header_list* header = header_free_list.pop();
+ header->name = name;
+ header->value = value;
+ nghttp2_rcbuf_incref(name);
+ nghttp2_rcbuf_incref(value);
+ LINKED_LIST_ADD(stream->current_headers, header);
+ return 0;
+}
+
+// When nghttp2 has completely processed a frame, it calls OnFrameReceive.
+// It is our responsibility to delegate out from there. We can ignore most
+// control frames since nghttp2 will handle those for us.
+int Nghttp2Session::OnFrameReceive(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: complete frame received: type: %d\n",
+ handle->session_type_, frame->hd.type);
+ bool ack;
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ handle->HandleDataFrame(frame);
+ break;
+ case NGHTTP2_PUSH_PROMISE:
+ case NGHTTP2_HEADERS:
+ handle->HandleHeadersFrame(frame);
+ break;
+ case NGHTTP2_SETTINGS:
+ ack = (frame->hd.flags & NGHTTP2_FLAG_ACK) == NGHTTP2_FLAG_ACK;
+ handle->OnSettings(ack);
+ break;
+ case NGHTTP2_PRIORITY:
+ handle->HandlePriorityFrame(frame);
+ break;
+ case NGHTTP2_GOAWAY:
+ handle->HandleGoawayFrame(frame);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int Nghttp2Session::OnFrameNotSent(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ int error_code,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: frame type %d was not sent, code: %d\n",
+ handle->session_type_, frame->hd.type, error_code);
+ // Do not report if the frame was not sent due to the session closing
+ if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
+ error_code != NGHTTP2_ERR_STREAM_CLOSED &&
+ error_code != NGHTTP2_ERR_STREAM_CLOSING)
+ handle->OnFrameError(frame->hd.stream_id, frame->hd.type, error_code);
+ return 0;
+}
+
+// Called when nghttp2 closes a stream, either in response to an RST_STREAM
+// frame or the stream closing naturally on it's own
+int Nghttp2Session::OnStreamClose(nghttp2_session *session,
+ int32_t id,
+ uint32_t code,
+ void *user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: stream %d closed, code: %d\n",
+ handle->session_type_, id, code);
+ Nghttp2Stream* stream = handle->FindStream(id);
+ // Intentionally ignore the callback if the stream does not exist
+ if (stream != nullptr)
+ stream->Close(code);
+ return 0;
+}
+
+// Called by nghttp2 multiple times while processing a DATA frame
+int Nghttp2Session::OnDataChunkReceived(nghttp2_session *session,
+ uint8_t flags,
+ int32_t id,
+ const uint8_t *data,
+ size_t len,
+ void *user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: buffering data chunk for stream %d, size: "
+ "%d, flags: %d\n", handle->session_type_, id, len, flags);
+ Nghttp2Stream* stream = handle->FindStream(id);
+ nghttp2_data_chunk_t* chunk = data_chunk_free_list.pop();
+ chunk->buf = uv_buf_init(new char[len], len);
+ memcpy(chunk->buf.base, data, len);
+ if (stream->data_chunks_tail_ == nullptr) {
+ stream->data_chunks_head_ =
+ stream->data_chunks_tail_ = chunk;
+ } else {
+ stream->data_chunks_tail_->next = chunk;
+ stream->data_chunks_tail_ = chunk;
+ }
+ return 0;
+}
+
+// Called by nghttp2 when it needs to determine how much padding to apply
+// to a DATA or HEADERS frame
+ssize_t Nghttp2Session::OnSelectPadding(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ size_t maxPayloadLen,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ assert(handle->HasGetPaddingCallback());
+ ssize_t padding = handle->GetPadding(frame->hd.length, maxPayloadLen);
+ DEBUG_HTTP2("Nghttp2Session %d: using padding, size: %d\n",
+ handle->session_type_, padding);
+ return padding;
+}
+
+// Called by nghttp2 to collect the data while a file response is sent.
+// The buf is the DATA frame buffer that needs to be filled with at most
+// length bytes. flags is used to control what nghttp2 does next.
+ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session* session,
+ int32_t id,
+ uint8_t* buf,
+ size_t length,
+ uint32_t* flags,
+ nghttp2_data_source* source,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: reading outbound file data for stream %d\n",
+ handle->session_type_, id);
+ Nghttp2Stream* stream = handle->FindStream(id);
+
+ int fd = source->fd;
+ int64_t offset = stream->fd_offset_;
+ ssize_t numchars;
+
+ uv_buf_t data;
+ data.base = reinterpret_cast<char*>(buf);
+ data.len = length;
+
+ uv_fs_t read_req;
+ numchars = uv_fs_read(handle->loop_,
+ &read_req,
+ fd, &data, 1,
+ offset, nullptr);
+ uv_fs_req_cleanup(&read_req);
+
+ // Close the stream with an error if reading fails
+ if (numchars < 0)
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+
+ // Update the read offset for the next read
+ stream->fd_offset_ += numchars;
+
+ // if numchars < length, assume that we are done.
+ if (static_cast<size_t>(numchars) < length) {
+ DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
+ handle->session_type_, id);
+ *flags |= NGHTTP2_DATA_FLAG_EOF;
+ // Sending trailers is not permitted with this provider.
+ }
+
+ return numchars;
+}
+
+// Called by nghttp2 to collect the data to pack within a DATA frame.
+// The buf is the DATA frame buffer that needs to be filled with at most
+// length bytes. flags is used to control what nghttp2 does next.
+ssize_t Nghttp2Session::OnStreamRead(nghttp2_session* session,
+ int32_t id,
+ uint8_t* buf,
+ size_t length,
+ uint32_t* flags,
+ nghttp2_data_source* source,
+ void* user_data) {
+ Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
+ DEBUG_HTTP2("Nghttp2Session %d: reading outbound data for stream %d\n",
+ handle->session_type_, id);
+ Nghttp2Stream* stream = handle->FindStream(id);
+ size_t remaining = length;
+ size_t offset = 0;
+
+ // While there is data in the queue, copy data into buf until it is full.
+ // There may be data left over, which will be sent the next time nghttp
+ // calls this callback.
+ while (stream->queue_head_ != nullptr) {
+ DEBUG_HTTP2("Nghttp2Session %d: processing outbound data chunk\n",
+ handle->session_type_);
+ nghttp2_stream_write_queue* head = stream->queue_head_;
+ while (stream->queue_head_index_ < head->nbufs) {
+ if (remaining == 0) {
+ goto end;
+ }
+
+ unsigned int n = stream->queue_head_index_;
+ // len is the number of bytes in head->bufs[n] that are yet to be written
+ size_t len = head->bufs[n].len - stream->queue_head_offset_;
+ size_t bytes_to_write = len < remaining ? len : remaining;
+ memcpy(buf + offset,
+ head->bufs[n].base + stream->queue_head_offset_,
+ bytes_to_write);
+ offset += bytes_to_write;
+ remaining -= bytes_to_write;
+ if (bytes_to_write < len) {
+ stream->queue_head_offset_ += bytes_to_write;
+ } else {
+ stream->queue_head_index_++;
+ stream->queue_head_offset_ = 0;
+ }
+ }
+ stream->queue_head_offset_ = 0;
+ stream->queue_head_index_ = 0;
+ stream->queue_head_ = head->next;
+ head->cb(head->req, 0);
+ delete head;
+ }
+ stream->queue_tail_ = nullptr;
+
+ end:
+ // If we are no longer writable and there is no more data in the queue,
+ // then we need to set the NGHTTP2_DATA_FLAG_EOF flag.
+ // If we are still writable but there is not yet any data to send, set the
+ // NGHTTP2_ERR_DEFERRED flag. This will put the stream into a pending state
+ // that will wait for data to become available.
+ // If neither of these flags are set, then nghttp2 will call this callback
+ // again to get the data for the next DATA frame.
+ int writable = stream->queue_head_ != nullptr || stream->IsWritable();
+ if (offset == 0 && writable && stream->queue_head_ == nullptr) {
+ DEBUG_HTTP2("Nghttp2Session %d: deferring stream %d\n",
+ handle->session_type_, id);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ if (!writable) {
+ DEBUG_HTTP2("Nghttp2Session %d: no more data for stream %d\n",
+ handle->session_type_, id);
+ *flags |= NGHTTP2_DATA_FLAG_EOF;
+
+ // Only when we are done sending the last chunk of data do we check for
+ // any trailing headers that are to be sent. This is the only opportunity
+ // we have to make this check. If there are trailers, then the
+ // NGHTTP2_DATA_FLAG_NO_END_STREAM flag must be set.
+ MaybeStackBuffer<nghttp2_nv> trailers;
+ handle->OnTrailers(stream, &trailers);
+ if (trailers.length() > 0) {
+ DEBUG_HTTP2("Nghttp2Session %d: sending trailers for stream %d, "
+ "count: %d\n", handle->session_type_, id, trailers.length());
+ *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
+ nghttp2_submit_trailer(session,
+ stream->id(),
+ *trailers,
+ trailers.length());
+ }
+ for (size_t n = 0; n < trailers.length(); n++) {
+ free(trailers[n].name);
+ free(trailers[n].value);
+ }
+ }
+ assert(offset <= length);
+ return offset;
+}
+
+Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
+ data_chunk_free_list;
+
+Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
+
+Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
+
+Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
+ data_chunks_free_list;
+
+Nghttp2Session::Callbacks Nghttp2Session::callback_struct_saved[2] = {
+ Callbacks(false),
+ Callbacks(true)
+};
+
+} // namespace http2
+} // namespace node
diff --git a/src/node_http2_core.h b/src/node_http2_core.h
new file mode 100644
index 0000000000..10acd7736b
--- /dev/null
+++ b/src/node_http2_core.h
@@ -0,0 +1,465 @@
+#ifndef SRC_NODE_HTTP2_CORE_H_
+#define SRC_NODE_HTTP2_CORE_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include "util.h"
+#include "util-inl.h"
+#include "uv.h"
+#include "nghttp2/nghttp2.h"
+
+#include <stdio.h>
+#include <unordered_map>
+
+namespace node {
+namespace http2 {
+
+#ifdef NODE_DEBUG_HTTP2
+
+// Adapted from nghttp2 own debug printer
+static inline void _debug_vfprintf(const char *fmt, va_list args) {
+ vfprintf(stderr, fmt, args);
+}
+
+void inline debug_vfprintf(const char *format, ...) {
+ va_list args;
+ va_start(args, format);
+ _debug_vfprintf(format, args);
+ va_end(args);
+}
+
+#define DEBUG_HTTP2(...) debug_vfprintf(__VA_ARGS__);
+#else
+#define DEBUG_HTTP2(...) \
+ do { \
+ } while (0)
+#endif
+
+class Nghttp2Session;
+class Nghttp2Stream;
+
+struct nghttp2_stream_write_t;
+struct nghttp2_data_chunk_t;
+struct nghttp2_data_chunks_t;
+
+#define MAX_BUFFER_COUNT 10
+#define SEND_BUFFER_RECOMMENDED_SIZE 4096
+
+enum nghttp2_session_type {
+ NGHTTP2_SESSION_SERVER,
+ NGHTTP2_SESSION_CLIENT
+};
+
+enum nghttp2_shutdown_flags {
+ NGHTTP2_SHUTDOWN_FLAG_GRACEFUL
+};
+
+enum nghttp2_stream_flags {
+ NGHTTP2_STREAM_FLAG_NONE = 0x0,
+ // Writable side has ended
+ NGHTTP2_STREAM_FLAG_SHUT = 0x1,
+ // Reading has started
+ NGHTTP2_STREAM_READ_START = 0x2,
+ // Reading is paused
+ NGHTTP2_STREAM_READ_PAUSED = 0x4,
+ // Stream is closed
+ NGHTTP2_STREAM_CLOSED = 0x8,
+ // Stream is destroyed
+ NGHTTP2_STREAM_DESTROYED = 0x10,
+ // Stream is being destroyed
+ NGHTTP2_STREAM_DESTROYING = 0x20
+};
+
+
+// Callbacks
+typedef void (*nghttp2_stream_write_cb)(
+ nghttp2_stream_write_t* req,
+ int status);
+
+struct nghttp2_stream_write_queue {
+ unsigned int nbufs = 0;
+ nghttp2_stream_write_t* req = nullptr;
+ nghttp2_stream_write_cb cb = nullptr;
+ nghttp2_stream_write_queue* next = nullptr;
+ MaybeStackBuffer<uv_buf_t, MAX_BUFFER_COUNT> bufs;
+};
+
+struct nghttp2_header_list {
+ nghttp2_rcbuf* name = nullptr;
+ nghttp2_rcbuf* value = nullptr;
+ nghttp2_header_list* next = nullptr;
+};
+
+// Handle Types
+class Nghttp2Session {
+ public:
+ // Initializes the session instance
+ inline int Init(
+ uv_loop_t*,
+ const nghttp2_session_type type = NGHTTP2_SESSION_SERVER,
+ nghttp2_option* options = nullptr,
+ nghttp2_mem* mem = nullptr);
+
+ // Frees this session instance
+ inline int Free();
+
+ // Returns the pointer to the identified stream, or nullptr if
+ // the stream does not exist
+ inline Nghttp2Stream* FindStream(int32_t id);
+
+ // Submits a new request. If the request is a success, assigned
+ // will be a pointer to the Nghttp2Stream instance assigned.
+ // This only works if the session is a client session.
+ inline int32_t SubmitRequest(
+ nghttp2_priority_spec* prispec,
+ nghttp2_nv* nva,
+ size_t len,
+ Nghttp2Stream** assigned = nullptr,
+ bool emptyPayload = true);
+
+ // Submits a notice to the connected peer that the session is in the
+ // process of shutting down.
+ inline void SubmitShutdownNotice();
+
+ // Submits a SETTINGS frame to the connected peer.
+ inline int SubmitSettings(const nghttp2_settings_entry iv[], size_t niv);
+
+ // Write data to the session
+ inline ssize_t Write(const uv_buf_t* bufs, unsigned int nbufs);
+
+ // Returns the nghttp2 library session
+ inline nghttp2_session* session() { return session_; }
+
+ protected:
+ // Adds a stream instance to this session
+ inline void AddStream(Nghttp2Stream* stream);
+
+ // Removes a stream instance from this session
+ inline void RemoveStream(int32_t id);
+
+ virtual void Send(uv_buf_t* buf,
+ size_t length) {}
+ virtual void OnHeaders(Nghttp2Stream* stream,
+ nghttp2_header_list* headers,
+ nghttp2_headers_category cat,
+ uint8_t flags) {}
+ virtual void OnStreamClose(int32_t id, uint32_t code) {}
+ virtual void OnDataChunk(Nghttp2Stream* stream,
+ nghttp2_data_chunk_t* chunk) {}
+ virtual void OnSettings(bool ack) {}
+ virtual void OnPriority(int32_t id,
+ int32_t parent,
+ int32_t weight,
+ int8_t exclusive) {}
+ virtual void OnGoAway(int32_t lastStreamID,
+ uint32_t errorCode,
+ uint8_t* data,
+ size_t length) {}
+ virtual void OnFrameError(int32_t id,
+ uint8_t type,
+ int error_code) {}
+ virtual ssize_t GetPadding(size_t frameLength,
+ size_t maxFrameLength) { return 0; }
+ virtual void OnTrailers(Nghttp2Stream* stream,
+ MaybeStackBuffer<nghttp2_nv>* nva) {}
+ virtual void OnFreeSession() {}
+ virtual void AllocateSend(size_t suggested_size, uv_buf_t* buf) = 0;
+
+ virtual bool HasGetPaddingCallback() { return false; }
+
+ private:
+ inline void SendPendingData();
+ inline void HandleHeadersFrame(const nghttp2_frame* frame);
+ inline void HandlePriorityFrame(const nghttp2_frame* frame);
+ inline void HandleDataFrame(const nghttp2_frame* frame);
+ inline void HandleGoawayFrame(const nghttp2_frame* frame);
+
+ /* callbacks for nghttp2 */
+#ifdef NODE_DEBUG_HTTP2
+ static int OnNghttpError(nghttp2_session* session,
+ const char* message,
+ size_t len,
+ void* user_data);
+#endif
+
+ static int OnBeginHeadersCallback(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ void* user_data);
+ static int OnHeaderCallback(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ nghttp2_rcbuf* name,
+ nghttp2_rcbuf* value,
+ uint8_t flags,
+ void* user_data);
+ static int OnFrameReceive(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ void* user_data);
+ static int OnFrameNotSent(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ int error_code,
+ void* user_data);
+ static int OnStreamClose(nghttp2_session* session,
+ int32_t id,
+ uint32_t code,
+ void* user_data);
+ static int OnDataChunkReceived(nghttp2_session* session,
+ uint8_t flags,
+ int32_t id,
+ const uint8_t *data,
+ size_t len,
+ void* user_data);
+ static ssize_t OnStreamReadFD(nghttp2_session* session,
+ int32_t id,
+ uint8_t* buf,
+ size_t length,
+ uint32_t* flags,
+ nghttp2_data_source* source,
+ void* user_data);
+ static ssize_t OnStreamRead(nghttp2_session* session,
+ int32_t id,
+ uint8_t* buf,
+ size_t length,
+ uint32_t* flags,
+ nghttp2_data_source* source,
+ void* user_data);
+ static ssize_t OnSelectPadding(nghttp2_session* session,
+ const nghttp2_frame* frame,
+ size_t maxPayloadLen,
+ void* user_data);
+
+ struct Callbacks {
+ inline explicit Callbacks(bool kHasGetPaddingCallback);
+ inline ~Callbacks();
+
+ nghttp2_session_callbacks* callbacks;
+ };
+
+ /* Use callback_struct_saved[kHasGetPaddingCallback ? 1 : 0] */
+ static Callbacks callback_struct_saved[2];
+
+ nghttp2_session* session_;
+ uv_loop_t* loop_;
+ uv_prepare_t prep_;
+ nghttp2_session_type session_type_;
+ std::unordered_map<int32_t, Nghttp2Stream*> streams_;
+
+ friend class Nghttp2Stream;
+};
+
+
+
+class Nghttp2Stream {
+ public:
+ static inline Nghttp2Stream* Init(
+ int32_t id,
+ Nghttp2Session* session,
+ nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS);
+
+ inline ~Nghttp2Stream() {
+ CHECK_EQ(session_, nullptr);
+ CHECK_EQ(queue_head_, nullptr);
+ CHECK_EQ(queue_tail_, nullptr);
+ CHECK_EQ(data_chunks_head_, nullptr);
+ CHECK_EQ(data_chunks_tail_, nullptr);
+ CHECK_EQ(current_headers_head_, nullptr);
+ CHECK_EQ(current_headers_tail_, nullptr);
+ DEBUG_HTTP2("Nghttp2Stream %d: freed\n", id_);
+ }
+
+ inline void FlushDataChunks(bool done = false);
+
+ // Resets the state of the stream instance to defaults
+ inline void ResetState(
+ int32_t id,
+ Nghttp2Session* session,
+ nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS);
+
+ // Destroy this stream instance and free all held memory.
+ // Note that this will free queued outbound and inbound
+ // data chunks and inbound headers, so it's important not
+ // to call this until those are fully consumed.
+ //
+ // Also note: this does not actually destroy the instance.
+ // instead, it frees the held memory, removes the stream
+ // from the parent session, and returns the instance to
+ // the FreeList so that it can be reused.
+ inline void Destroy();
+
+ // Returns true if this stream has been destroyed
+ inline bool IsDestroyed() const {
+ return (flags_ & NGHTTP2_STREAM_DESTROYED) == NGHTTP2_STREAM_DESTROYED;
+ }
+
+ inline bool IsDestroying() const {
+ return (flags_ & NGHTTP2_STREAM_DESTROYING) == NGHTTP2_STREAM_DESTROYING;
+ }
+
+ // Queue outbound chunks of data to be sent on this stream
+ inline int Write(
+ nghttp2_stream_write_t* req,
+ const uv_buf_t bufs[],
+ unsigned int nbufs,
+ nghttp2_stream_write_cb cb);
+
+ // Initiate a response on this stream.
+ inline int SubmitResponse(nghttp2_nv* nva,
+ size_t len,
+ bool emptyPayload = false);
+
+ // Send data read from a file descriptor as the response on this stream.
+ inline int SubmitFile(int fd, nghttp2_nv* nva, size_t len);
+
+ // Submit informational headers for this stream
+ inline int SubmitInfo(nghttp2_nv* nva, size_t len);
+
+ // Submit a PRIORITY frame for this stream
+ inline int SubmitPriority(nghttp2_priority_spec* prispec,
+ bool silent = false);
+
+ // Submits an RST_STREAM frame using the given code
+ inline int SubmitRstStream(const uint32_t code);
+
+ // Submits a PUSH_PROMISE frame with this stream as the parent.
+ inline int SubmitPushPromise(
+ nghttp2_nv* nva,
+ size_t len,
+ Nghttp2Stream** assigned = nullptr,
+ bool writable = true);
+
+ // Marks the Writable side of the stream as being shutdown
+ inline void Shutdown() {
+ flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
+ nghttp2_session_resume_data(session_->session(), id_);
+ }
+
+ // Returns true if this stream is writable.
+ inline bool IsWritable() const {
+ return (flags_ & NGHTTP2_STREAM_FLAG_SHUT) == 0;
+ }
+
+ // Start Reading. If there are queued data chunks, they are pushed into
+ // the session to be emitted at the JS side
+ inline void ReadStart();
+
+ // Stop/Pause Reading.
+ inline void ReadStop();
+
+ // Returns true if reading is paused
+ inline bool IsPaused() const {
+ return (flags_ & NGHTTP2_STREAM_READ_PAUSED) == NGHTTP2_STREAM_READ_PAUSED;
+ }
+
+ // Returns true if this stream is in the reading state, which occurs when
+ // the NGHTTP2_STREAM_READ_START flag has been set and the
+ // NGHTTP2_STREAM_READ_PAUSED flag is *not* set.
+ inline bool IsReading() const {
+ return ((flags_ & NGHTTP2_STREAM_READ_START) == NGHTTP2_STREAM_READ_START)
+ && ((flags_ & NGHTTP2_STREAM_READ_PAUSED) == 0);
+ }
+
+ inline void Close(int32_t code) {
+ DEBUG_HTTP2("Nghttp2Stream %d: closing with code %d\n", id_, code);
+ flags_ |= NGHTTP2_STREAM_CLOSED;
+ code_ = code;
+ session_->OnStreamClose(id_, code);
+ DEBUG_HTTP2("Nghttp2Stream %d: closed\n", id_);
+ }
+
+ // Returns true if this stream has been closed either by receiving or
+ // sending an RST_STREAM frame.
+ inline bool IsClosed() const {
+ return (flags_ & NGHTTP2_STREAM_CLOSED) == NGHTTP2_STREAM_CLOSED;
+ }
+
+ // Returns the RST_STREAM code used to close this stream
+ inline int32_t code() const {
+ return code_;
+ }
+
+ // Returns the stream identifier for this stream
+ inline int32_t id() const {
+ return id_;
+ }
+
+ inline nghttp2_header_list* headers() const {
+ return current_headers_head_;
+ }
+
+ inline nghttp2_headers_category headers_category() const {
+ return current_headers_category_;
+ }
+
+ inline void FreeHeaders();
+
+ void StartHeaders(nghttp2_headers_category category) {
+ DEBUG_HTTP2("Nghttp2Stream %d: starting headers, category: %d\n",
+ id_, category);
+ // We shouldn't be in the middle of a headers block already.
+ // Something bad happened if this fails
+ CHECK_EQ(current_headers_head_, nullptr);
+ CHECK_EQ(current_headers_tail_, nullptr);
+ current_headers_category_ = category;
+ }
+
+ private:
+ // The Parent HTTP/2 Session
+ Nghttp2Session* session_ = nullptr;
+
+ // The Stream Identifier
+ int32_t id_ = 0;
+
+ // Internal state flags
+ int flags_ = 0;
+
+ // Outbound Data... This is the data written by the JS layer that is
+ // waiting to be written out to the socket.
+ nghttp2_stream_write_queue* queue_head_ = nullptr;
+ nghttp2_stream_write_queue* queue_tail_ = nullptr;
+ unsigned int queue_head_index_ = 0;
+ size_t queue_head_offset_ = 0;
+ size_t fd_offset_ = 0;
+
+ // The Current Headers block... As headers are received for this stream,
+ // they are temporarily stored here until the OnFrameReceived is called
+ // signalling the end of the HEADERS frame
+ nghttp2_header_list* current_headers_head_ = nullptr;
+ nghttp2_header_list* current_headers_tail_ = nullptr;
+ nghttp2_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS;
+
+ // Inbound Data... This is the data received via DATA frames for this stream.
+ nghttp2_data_chunk_t* data_chunks_head_ = nullptr;
+ nghttp2_data_chunk_t* data_chunks_tail_ = nullptr;
+
+ // The RST_STREAM code used to close this stream
+ int32_t code_ = NGHTTP2_NO_ERROR;
+
+ int32_t prev_local_window_size_ = 65535;
+
+ friend class Nghttp2Session;
+};
+
+struct nghttp2_stream_write_t {
+ void* data;
+ int status;
+ Nghttp2Stream* handle;
+ nghttp2_stream_write_queue* item;
+};
+
+struct nghttp2_data_chunk_t {
+ uv_buf_t buf;
+ nghttp2_data_chunk_t* next = nullptr;
+};
+
+struct nghttp2_data_chunks_t {
+ unsigned int nbufs = 0;
+ uv_buf_t buf[MAX_BUFFER_COUNT];
+
+ inline ~nghttp2_data_chunks_t();
+};
+
+} // namespace http2
+} // namespace node
+
+#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#endif // SRC_NODE_HTTP2_CORE_H_
diff --git a/src/node_internals.h b/src/node_internals.h
index d6bdf9b5ba..5d437fa302 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -83,6 +83,9 @@ extern std::string openssl_config;
// that is used by lib/module.js
extern bool config_preserve_symlinks;
+// Set in node.cc by ParseArgs when --expose-http2 is used.
+extern bool config_expose_http2;
+
// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is
// used.
// Used in node_config.cc to set a constant on process.binding('config')
diff --git a/src/stream_base.cc b/src/stream_base.cc
index 51bad94a4f..3e94054546 100644
--- a/src/stream_base.cc
+++ b/src/stream_base.cc
@@ -408,6 +408,7 @@ void StreamBase::AfterWrite(WriteWrap* req_wrap, int status) {
// Unref handle property
Local<Object> req_wrap_obj = req_wrap->object();
req_wrap_obj->Delete(env->context(), env->handle_string()).FromJust();
+
wrap->OnAfterWrite(req_wrap);
Local<Value> argv[] = {
diff --git a/src/stream_base.h b/src/stream_base.h
index 68c82d243f..1b486e61db 100644
--- a/src/stream_base.h
+++ b/src/stream_base.h
@@ -89,6 +89,17 @@ class WriteWrap: public ReqWrap<uv_write_t>,
static const size_t kAlignSize = 16;
+ WriteWrap(Environment* env,
+ v8::Local<v8::Object> obj,
+ StreamBase* wrap,
+ DoneCb cb)
+ : ReqWrap(env, obj, AsyncWrap::PROVIDER_WRITEWRAP),
+ StreamReq<WriteWrap>(cb),
+ wrap_(wrap),
+ storage_size_(0) {
+ Wrap(obj, this);
+ }
+
protected:
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,