#ifndef SRC_NODE_HTTP2_H_ #define SRC_NODE_HTTP2_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "nghttp2/nghttp2.h" #include "node_http2_state.h" #include "node_perf.h" #include "stream_base-inl.h" #include "string_bytes.h" #include namespace node { namespace http2 { using v8::Array; using v8::Context; using v8::EscapableHandleScope; using v8::Isolate; using v8::MaybeLocal; using performance::PerformanceEntry; #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__); #define DEBUG_HTTP2SESSION(session, message) \ do { \ if (session != nullptr) { \ DEBUG_HTTP2("Http2Session %s (%.0lf) " message "\n", \ session->TypeName(), \ session->get_async_id()); \ } \ } while (0) #define DEBUG_HTTP2SESSION2(session, message, ...) \ do { \ if (session != nullptr) { \ DEBUG_HTTP2("Http2Session %s (%.0lf) " message "\n", \ session->TypeName(), \ session->get_async_id(), \ __VA_ARGS__); \ } \ } while (0) #define DEBUG_HTTP2STREAM(stream, message) \ do { \ DEBUG_HTTP2("Http2Stream %d (%.0lf) [Http2Session %s (%.0lf)] " message \ "\n", stream->id(), stream->get_async_id(), \ stream->session()->TypeName(), \ stream->session()->get_async_id()); \ } while (0) #define DEBUG_HTTP2STREAM2(stream, message, ...) \ do { \ DEBUG_HTTP2("Http2Stream %d (%.0lf) [Http2Session %s (%.0lf)] " message \ "\n", stream->id(), stream->get_async_id(), \ stream->session()->TypeName(), \ stream->session()->get_async_id(), \ __VA_ARGS__); \ } while (0) #else #define DEBUG_HTTP2(...) do {} while (0) #define DEBUG_HTTP2SESSION(...) do {} while (0) #define DEBUG_HTTP2SESSION2(...) do {} while (0) #define DEBUG_HTTP2STREAM(...) do {} while (0) #define DEBUG_HTTP2STREAM2(...) do {} while (0) #endif // We strictly limit the number of outstanding unacknowledged PINGS a user // may send in order to prevent abuse. The current default cap is 10. The // user may set a different limit using a per Http2Session configuration // option. #define DEFAULT_MAX_PINGS 10 // Also strictly limit the number of outstanding SETTINGS frames a user sends #define DEFAULT_MAX_SETTINGS 10 // Default maximum total memory cap for Http2Session. #define DEFAULT_MAX_SESSION_MEMORY 1e7; // These are the standard HTTP/2 defaults as specified by the RFC #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 DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE 65535 #define MAX_MAX_FRAME_SIZE 16777215 #define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE #define MAX_INITIAL_WINDOW_SIZE 2147483647 #define MAX_MAX_HEADER_LIST_SIZE 16777215u #define DEFAULT_MAX_HEADER_LIST_PAIRS 128u #define MAX_BUFFER_COUNT 16 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_FLAG_READ_START = 0x2, // Reading is paused NGHTTP2_STREAM_FLAG_READ_PAUSED = 0x4, // Stream is closed NGHTTP2_STREAM_FLAG_CLOSED = 0x8, // Stream is destroyed NGHTTP2_STREAM_FLAG_DESTROYED = 0x10, // Stream has trailers NGHTTP2_STREAM_FLAG_TRAILERS = 0x20, // Stream has received all the data it can NGHTTP2_STREAM_FLAG_EOS = 0x40 }; enum nghttp2_stream_options { // Stream is not going to have any DATA frames STREAM_OPTION_EMPTY_PAYLOAD = 0x1, // Stream might have trailing headers STREAM_OPTION_GET_TRAILERS = 0x2, }; struct nghttp2_stream_write { WriteWrap* req_wrap = nullptr; uv_buf_t buf; inline explicit nghttp2_stream_write(uv_buf_t buf_) : buf(buf_) {} inline nghttp2_stream_write(WriteWrap* req, uv_buf_t buf_) : req_wrap(req), buf(buf_) {} }; struct nghttp2_header { nghttp2_rcbuf* name = nullptr; nghttp2_rcbuf* value = nullptr; uint8_t flags = 0; }; // Unlike the HTTP/1 implementation, the HTTP/2 implementation is not limited // to a fixed number of known supported HTTP methods. These constants, therefore // are provided strictly as a convenience to users and are exposed via the // require('http2').constants object. #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") // These are provided strictly as a convenience to users and are exposed via the // require('http2').constants objects #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_CREDENTIALS, "access-control-allow-credentials") \ V(ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers") \ V(ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods") \ V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \ V(ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers") \ V(ACCESS_CONTROL_MAX_AGE, "access-control-max-age") \ V(ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers") \ V(ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method") \ 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(DNT, "dnt") \ V(ETAG, "etag") \ V(EXPECT, "expect") \ V(EXPIRES, "expires") \ V(FORWARDED, "forwarded") \ 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(TRAILER, "trailer") \ V(TRANSFER_ENCODING, "transfer-encoding") \ V(TE, "te") \ V(TK, "tk") \ V(UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests") \ V(UPGRADE, "upgrade") \ V(USER_AGENT, "user-agent") \ V(VARY, "vary") \ V(VIA, "via") \ V(WARNING, "warning") \ V(WWW_AUTHENTICATE, "www-authenticate") \ V(X_CONTENT_TYPE_OPTIONS, "x-content-type-options") \ V(X_FRAME_OPTIONS, "x-frame-options") \ 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 }; // While some of these codes are used within the HTTP/2 implementation in // core, they are provided strictly as a convenience to users and are exposed // via the require('http2').constants object. #define HTTP_STATUS_CODES(V) \ V(CONTINUE, 100) \ V(SWITCHING_PROTOCOLS, 101) \ V(PROCESSING, 102) \ V(EARLY_HINTS, 103) \ 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 }; // The Padding Strategy determines the method by which extra padding is // selected for HEADERS and DATA frames. These are configurable via the // options passed in to a Http2Session object. enum padding_strategy_type { // No padding strategy. This is the default. PADDING_STRATEGY_NONE, // Attempts to ensure that the frame is 8-byte aligned PADDING_STRATEGY_ALIGNED, // Padding will ensure all data frames are maxFrameSize PADDING_STRATEGY_MAX, // Padding will be determined via a JS callback. Note that this can be // expensive because the callback is called once for every DATA and // HEADERS frame. For performance reasons, this strategy should be // avoided. PADDING_STRATEGY_CALLBACK }; // These are the error codes provided by the underlying nghttp2 implementation. #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"; } } enum session_state_flags { SESSION_STATE_NONE = 0x0, SESSION_STATE_HAS_SCOPE = 0x1, SESSION_STATE_WRITE_SCHEDULED = 0x2, SESSION_STATE_CLOSED = 0x4, SESSION_STATE_SENDING = 0x8, }; // This allows for 4 default-sized frames with their frame headers static const size_t kAllocBufferSize = 4 * (16384 + 9); typedef uint32_t(*get_setting)(nghttp2_session* session, nghttp2_settings_id id); class Http2Session; class Http2Stream; // This scope should be present when any call into nghttp2 that may schedule // data to be written to the underlying transport is made, and schedules // such a write automatically once the scope is exited. class Http2Scope { public: explicit Http2Scope(Http2Stream* stream); explicit Http2Scope(Http2Session* session); ~Http2Scope(); private: Http2Session* session_ = nullptr; Local session_handle_; }; // The Http2Options class is used to parse the options object passed in to // a Http2Session object and convert those into an appropriate nghttp2_option // struct. This is the primary mechanism by which the Http2Session object is // configured. class Http2Options { public: explicit Http2Options(Environment* env); ~Http2Options() { nghttp2_option_del(options_); } nghttp2_option* operator*() const { return options_; } void SetMaxHeaderPairs(uint32_t max) { max_header_pairs_ = max; } uint32_t GetMaxHeaderPairs() const { return max_header_pairs_; } void SetPaddingStrategy(padding_strategy_type val) { padding_strategy_ = static_cast(val); } padding_strategy_type GetPaddingStrategy() const { return padding_strategy_; } void SetMaxOutstandingPings(size_t max) { max_outstanding_pings_ = max; } size_t GetMaxOutstandingPings() { return max_outstanding_pings_; } void SetMaxOutstandingSettings(size_t max) { max_outstanding_settings_ = max; } size_t GetMaxOutstandingSettings() { return max_outstanding_settings_; } void SetMaxSessionMemory(uint64_t max) { max_session_memory_ = max; } uint64_t GetMaxSessionMemory() { return max_session_memory_; } private: nghttp2_option* options_; uint64_t max_session_memory_ = DEFAULT_MAX_SESSION_MEMORY; uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE; size_t max_outstanding_pings_ = DEFAULT_MAX_PINGS; size_t max_outstanding_settings_ = DEFAULT_MAX_SETTINGS; }; class Http2Priority { public: Http2Priority(Environment* env, Local parent, Local weight, Local exclusive); nghttp2_priority_spec* operator*() { return &spec; } private: nghttp2_priority_spec spec; }; class Http2StreamListener : public StreamListener { public: uv_buf_t OnStreamAlloc(size_t suggested_size) override; void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; }; class Http2Stream : public AsyncWrap, public StreamBase { public: Http2Stream(Http2Session* session, int32_t id, nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS, int options = 0); ~Http2Stream() override; nghttp2_stream* operator*(); Http2Session* session() { return session_; } inline void EmitStatistics(); // Process a Data Chunk void OnDataChunk(uv_buf_t* chunk); // Required for StreamBase int ReadStart() override; // Required for StreamBase int ReadStop() override; // Required for StreamBase int DoShutdown(ShutdownWrap* req_wrap) override; // Initiate a response on this stream. inline int SubmitResponse(nghttp2_nv* nva, size_t len, int options); // Send data read from a file descriptor as the response on this stream. inline int SubmitFile(int fd, nghttp2_nv* nva, size_t len, int64_t offset, int64_t length, int options); // 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 void SubmitRstStream(const uint32_t code); // Submits a PUSH_PROMISE frame with this stream as the parent. inline Http2Stream* SubmitPushPromise( nghttp2_nv* nva, size_t len, int32_t* ret, int options = 0); inline void Close(int32_t code); // Destroy this stream instance and free all held memory. inline void Destroy(); inline bool IsDestroyed() const { return flags_ & NGHTTP2_STREAM_FLAG_DESTROYED; } inline bool IsWritable() const { return !(flags_ & NGHTTP2_STREAM_FLAG_SHUT); } inline bool IsPaused() const { return flags_ & NGHTTP2_STREAM_FLAG_READ_PAUSED; } inline bool IsClosed() const { return flags_ & NGHTTP2_STREAM_FLAG_CLOSED; } inline bool HasTrailers() const { return flags_ & NGHTTP2_STREAM_FLAG_TRAILERS; } // Returns true if this stream is in the reading state, which occurs when // the NGHTTP2_STREAM_FLAG_READ_START flag has been set and the // NGHTTP2_STREAM_FLAG_READ_PAUSED flag is *not* set. inline bool IsReading() const { return flags_ & NGHTTP2_STREAM_FLAG_READ_START && !(flags_ & NGHTTP2_STREAM_FLAG_READ_PAUSED); } // 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 void IncrementAvailableOutboundLength(size_t amount); inline void DecrementAvailableOutboundLength(size_t amount); inline bool AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags); inline nghttp2_header* headers() { return current_headers_.data(); } inline nghttp2_headers_category headers_category() const { return current_headers_category_; } inline size_t headers_count() const { return current_headers_.size(); } void StartHeaders(nghttp2_headers_category category); // Required for StreamBase bool IsAlive() override { return true; } // Required for StreamBase bool IsClosing() override { return false; } AsyncWrap* GetAsyncWrap() override { return this; } int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count, uv_stream_t* send_handle) override; size_t self_size() const override { return sizeof(*this); } // Handling Trailer Headers class SubmitTrailers { public: inline void Submit(nghttp2_nv* trailers, size_t length) const; inline SubmitTrailers(Http2Session* sesion, Http2Stream* stream, uint32_t* flags); private: Http2Session* const session_; Http2Stream* const stream_; uint32_t* const flags_; friend class Http2Stream; }; void OnTrailers(const SubmitTrailers& submit_trailers); // JavaScript API static void GetID(const FunctionCallbackInfo& args); static void Destroy(const FunctionCallbackInfo& args); static void FlushData(const FunctionCallbackInfo& args); static void Priority(const FunctionCallbackInfo& args); static void PushPromise(const FunctionCallbackInfo& args); static void RefreshState(const FunctionCallbackInfo& args); static void Info(const FunctionCallbackInfo& args); static void RespondFD(const FunctionCallbackInfo& args); static void Respond(const FunctionCallbackInfo& args); static void RstStream(const FunctionCallbackInfo& args); class Provider; struct Statistics { uint64_t start_time; uint64_t end_time; uint64_t first_header; // Time first header was received uint64_t first_byte; // Time first DATA frame byte was received uint64_t first_byte_sent; // Time first DATA frame byte was sent uint64_t sent_bytes; uint64_t received_bytes; }; Statistics statistics_ = {}; private: Http2Session* session_; // The Parent HTTP/2 Session int32_t id_; // The Stream Identifier int32_t code_ = NGHTTP2_NO_ERROR; // The RST_STREAM code (if any) int flags_ = NGHTTP2_STREAM_FLAG_NONE; // Internal state flags uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; uint32_t max_header_length_ = DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE; // 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_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS; uint32_t current_headers_length_ = 0; // total number of octets std::vector current_headers_; // This keeps track of the amount of data read from the socket while the // socket was in paused mode. When `ReadStart()` is called (and not before // then), we tell nghttp2 that we consumed that data to get proper // backpressure handling. size_t inbound_consumed_data_while_paused_ = 0; // Outbound Data... This is the data written by the JS layer that is // waiting to be written out to the socket. std::queue queue_; size_t available_outbound_length_ = 0; int64_t fd_offset_ = 0; int64_t fd_length_ = -1; Http2StreamListener stream_listener_; friend class Http2Session; }; class Http2Stream::Provider { public: Provider(Http2Stream* stream, int options); explicit Provider(int options); virtual ~Provider(); nghttp2_data_provider* operator*() { return !empty_ ? &provider_ : nullptr; } class FD; class Stream; protected: nghttp2_data_provider provider_; private: bool empty_ = false; }; class Http2Stream::Provider::FD : public Http2Stream::Provider { public: FD(int options, int fd); FD(Http2Stream* stream, int options, int fd); static ssize_t OnRead(nghttp2_session* session, int32_t id, uint8_t* buf, size_t length, uint32_t* flags, nghttp2_data_source* source, void* user_data); }; class Http2Stream::Provider::Stream : public Http2Stream::Provider { public: Stream(Http2Stream* stream, int options); explicit Stream(int options); static ssize_t OnRead(nghttp2_session* session, int32_t id, uint8_t* buf, size_t length, uint32_t* flags, nghttp2_data_source* source, void* user_data); }; class Http2Session : public AsyncWrap, public StreamListener { public: Http2Session(Environment* env, Local wrap, nghttp2_session_type type = NGHTTP2_SESSION_SERVER); ~Http2Session() override; class Http2Ping; class Http2Settings; inline void EmitStatistics(); inline StreamBase* underlying_stream() { return static_cast(stream_); } void Start(); void Stop(); void Close(uint32_t code = NGHTTP2_NO_ERROR, bool socket_closed = false); void Consume(Local external); void Unconsume(); void Goaway(uint32_t code, int32_t lastStreamID, uint8_t* data, size_t len); void AltSvc(int32_t id, uint8_t* origin, size_t origin_len, uint8_t* value, size_t value_len); bool Ping(v8::Local function); inline void SendPendingData(); // Submits a new request. If the request is a success, assigned // will be a pointer to the Http2Stream instance assigned. // This only works if the session is a client session. inline Http2Stream* SubmitRequest( nghttp2_priority_spec* prispec, nghttp2_nv* nva, size_t len, int32_t* ret, int options = 0); nghttp2_session_type type() const { return session_type_; } inline nghttp2_session* session() const { return session_; } nghttp2_session* operator*() { return session_; } uint32_t GetMaxHeaderPairs() const { return max_header_pairs_; } inline const char* TypeName(); inline bool IsDestroyed() { return (flags_ & SESSION_STATE_CLOSED) || session_ == nullptr; } // Schedule a write if nghttp2 indicates it wants to write to the socket. void MaybeScheduleWrite(); // Returns pointer to the stream, or nullptr if stream does not exist inline Http2Stream* FindStream(int32_t id); inline bool CanAddStream(); // Adds a stream instance to this session inline void AddStream(Http2Stream* stream); // Removes a stream instance from this session inline void RemoveStream(Http2Stream* stream); // Indicates whether there currently exist outgoing buffers for this stream. bool HasWritesOnSocketForStream(Http2Stream* stream); // Write data to the session inline ssize_t Write(const uv_buf_t* bufs, size_t nbufs); inline void SetChunksSinceLastWrite(size_t n = 0); size_t self_size() const override { return sizeof(*this); } inline void GetTrailers(Http2Stream* stream, uint32_t* flags); // Handle reads/writes from the underlying network transport. void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; void OnStreamAfterWrite(WriteWrap* w, int status) override; // The JavaScript API static void New(const FunctionCallbackInfo& args); static void Consume(const FunctionCallbackInfo& args); static void Unconsume(const FunctionCallbackInfo& args); static void Destroying(const FunctionCallbackInfo& args); static void Destroy(const FunctionCallbackInfo& args); static void Settings(const FunctionCallbackInfo& args); static void Request(const FunctionCallbackInfo& args); static void SetNextStreamID(const FunctionCallbackInfo& args); static void Goaway(const FunctionCallbackInfo& args); static void UpdateChunksSent(const FunctionCallbackInfo& args); static void RefreshState(const FunctionCallbackInfo& args); static void Ping(const FunctionCallbackInfo& args); static void AltSvc(const FunctionCallbackInfo& args); template static void RefreshSettings(const FunctionCallbackInfo& args); template static void GetSettings(const FunctionCallbackInfo& args); uv_loop_t* event_loop() const { return env()->event_loop(); } Http2Ping* PopPing(); bool AddPing(Http2Ping* ping); Http2Settings* PopSettings(); bool AddSettings(Http2Settings* settings); void IncrementCurrentSessionMemory(uint64_t amount) { current_session_memory_ += amount; } void DecrementCurrentSessionMemory(uint64_t amount) { current_session_memory_ -= amount; } // Returns the current session memory including the current size of both // the inflate and deflate hpack headers, the current outbound storage // queue, and pending writes. uint64_t GetCurrentSessionMemory() { uint64_t total = current_session_memory_ + sizeof(Http2Session); total += nghttp2_session_get_hd_deflate_dynamic_table_size(session_); total += nghttp2_session_get_hd_inflate_dynamic_table_size(session_); total += outgoing_storage_.size(); return total; } // Return true if current_session_memory + amount is less than the max bool IsAvailableSessionMemory(uint64_t amount) { return GetCurrentSessionMemory() + amount <= max_session_memory_; } struct Statistics { uint64_t start_time; uint64_t end_time; uint64_t ping_rtt; uint64_t data_sent; uint64_t data_received; uint32_t frame_count; uint32_t frame_sent; int32_t stream_count; size_t max_concurrent_streams; double stream_average_duration; }; Statistics statistics_ = {}; private: // Frame Padding Strategies inline ssize_t OnDWordAlignedPadding(size_t frameLength, size_t maxPayloadLen); inline ssize_t OnMaxFrameSizePadding(size_t frameLength, size_t maxPayloadLen); inline ssize_t OnCallbackPadding(size_t frame, size_t maxPayloadLen); // Frame Handler inline void HandleDataFrame(const nghttp2_frame* frame); inline void HandleGoawayFrame(const nghttp2_frame* frame); inline void HandleHeadersFrame(const nghttp2_frame* frame); inline void HandlePriorityFrame(const nghttp2_frame* frame); inline void HandleSettingsFrame(const nghttp2_frame* frame); inline void HandlePingFrame(const nghttp2_frame* frame); inline void HandleAltSvcFrame(const nghttp2_frame* frame); // nghttp2 callbacks static inline int OnBeginHeadersCallback( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static inline int OnHeaderCallback( nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data); static inline int OnFrameReceive( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static inline int OnFrameNotSent( nghttp2_session* session, const nghttp2_frame* frame, int error_code, void* user_data); static inline int OnFrameSent( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static inline int OnStreamClose( nghttp2_session* session, int32_t id, uint32_t code, void* user_data); static inline int OnInvalidHeader( nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data); static inline int OnDataChunkReceived( nghttp2_session* session, uint8_t flags, int32_t id, const uint8_t* data, size_t len, void* user_data); static inline ssize_t OnSelectPadding( nghttp2_session* session, const nghttp2_frame* frame, size_t maxPayloadLen, void* user_data); static inline int OnNghttpError( nghttp2_session* session, const char* message, size_t len, void* user_data); static inline int OnSendData( nghttp2_session* session, nghttp2_frame* frame, const uint8_t* framehd, size_t length, nghttp2_data_source* source, void* user_data); static inline int OnInvalidFrame( nghttp2_session* session, const nghttp2_frame *frame, int lib_error_code, void* user_data); struct Callbacks { inline explicit Callbacks(bool kHasGetPaddingCallback); inline ~Callbacks(); nghttp2_session_callbacks* callbacks; }; /* Use callback_struct_saved[kHasGetPaddingCallback ? 1 : 0] */ static const Callbacks callback_struct_saved[2]; // The underlying nghttp2_session handle nghttp2_session* session_; // The session type: client or server nghttp2_session_type session_type_; // The maximum number of header pairs permitted for streams on this session uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; // The maximum amount of memory allocated for this session uint64_t max_session_memory_ = DEFAULT_MAX_SESSION_MEMORY; uint64_t current_session_memory_ = 0; // The collection of active Http2Streams associated with this session std::unordered_map streams_; int flags_ = SESSION_STATE_NONE; // The StreamBase instance being used for i/o padding_strategy_type padding_strategy_ = PADDING_STRATEGY_NONE; // use this to allow timeout tracking during long-lasting writes uint32_t chunks_sent_since_last_write_ = 0; uv_buf_t stream_buf_ = uv_buf_init(nullptr, 0); v8::Local stream_buf_ab_; size_t max_outstanding_pings_ = DEFAULT_MAX_PINGS; std::queue outstanding_pings_; size_t max_outstanding_settings_ = DEFAULT_MAX_SETTINGS; std::queue outstanding_settings_; std::vector outgoing_buffers_; std::vector outgoing_storage_; void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length); void ClearOutgoing(int status); friend class Http2Scope; friend class Http2StreamListener; }; class Http2SessionPerformanceEntry : public PerformanceEntry { public: Http2SessionPerformanceEntry( Environment* env, const Http2Session::Statistics& stats, nghttp2_session_type type) : PerformanceEntry(env, "Http2Session", "http2", stats.start_time, stats.end_time), ping_rtt_(stats.ping_rtt), data_sent_(stats.data_sent), data_received_(stats.data_received), frame_count_(stats.frame_count), frame_sent_(stats.frame_sent), stream_count_(stats.stream_count), max_concurrent_streams_(stats.max_concurrent_streams), stream_average_duration_(stats.stream_average_duration), session_type_(type) { } uint64_t ping_rtt() const { return ping_rtt_; } uint64_t data_sent() const { return data_sent_; } uint64_t data_received() const { return data_received_; } uint32_t frame_count() const { return frame_count_; } uint32_t frame_sent() const { return frame_sent_; } int32_t stream_count() const { return stream_count_; } size_t max_concurrent_streams() const { return max_concurrent_streams_; } double stream_average_duration() const { return stream_average_duration_; } nghttp2_session_type type() const { return session_type_; } void Notify(Local obj) { PerformanceEntry::Notify(env(), kind(), obj); } private: uint64_t ping_rtt_; uint64_t data_sent_; uint64_t data_received_; uint32_t frame_count_; uint32_t frame_sent_; int32_t stream_count_; size_t max_concurrent_streams_; double stream_average_duration_; nghttp2_session_type session_type_; }; class Http2StreamPerformanceEntry : public PerformanceEntry { public: Http2StreamPerformanceEntry( Environment* env, int32_t id, const Http2Stream::Statistics& stats) : PerformanceEntry(env, "Http2Stream", "http2", stats.start_time, stats.end_time), id_(id), first_header_(stats.first_header), first_byte_(stats.first_byte), first_byte_sent_(stats.first_byte_sent), sent_bytes_(stats.sent_bytes), received_bytes_(stats.received_bytes) { } int32_t id() const { return id_; } uint64_t first_header() const { return first_header_; } uint64_t first_byte() const { return first_byte_; } uint64_t first_byte_sent() const { return first_byte_sent_; } uint64_t sent_bytes() const { return sent_bytes_; } uint64_t received_bytes() const { return received_bytes_; } void Notify(Local obj) { PerformanceEntry::Notify(env(), kind(), obj); } private: int32_t id_; uint64_t first_header_; uint64_t first_byte_; uint64_t first_byte_sent_; uint64_t sent_bytes_; uint64_t received_bytes_; }; class Http2Session::Http2Ping : public AsyncWrap { public: explicit Http2Ping(Http2Session* session); ~Http2Ping(); size_t self_size() const override { return sizeof(*this); } void Send(uint8_t* payload); void Done(bool ack, const uint8_t* payload = nullptr); private: Http2Session* session_; uint64_t startTime_; friend class Http2Session; }; // The Http2Settings class is used to parse the settings passed in for // an Http2Session, converting those into an array of nghttp2_settings_entry // structs. class Http2Session::Http2Settings : public AsyncWrap { public: explicit Http2Settings(Environment* env); explicit Http2Settings(Http2Session* session); ~Http2Settings(); size_t self_size() const override { return sizeof(*this); } void Send(); void Done(bool ack); size_t length() const { return count_; } nghttp2_settings_entry* operator*() { return *entries_; } // Returns a Buffer instance with the serialized SETTINGS payload inline Local Pack(); // Resets the default values in the settings buffer static inline void RefreshDefaults(Environment* env); // Update the local or remote settings for the given session static inline void Update(Environment* env, Http2Session* session, get_setting fn); private: void Init(); Http2Session* session_; uint64_t startTime_; size_t count_ = 0; MaybeStackBuffer entries_; }; 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(reinterpret_cast(vec_.base)); } size_t length() const override { return vec_.len; } static inline MaybeLocal GetInternalizedString(Environment* env, const nghttp2_vec& vec) { return String::NewFromOneByte(env->isolate(), vec.base, v8::NewStringType::kInternalized, vec.len); } template static MaybeLocal New(Environment* env, nghttp2_rcbuf* buf) { if (nghttp2_rcbuf_is_static(buf)) { auto& static_str_map = env->isolate_data()->http2_static_strs; v8::Eternal& eternal = static_str_map[buf]; if (eternal.IsEmpty()) { Local str = GetInternalizedString(env, nghttp2_rcbuf_get_buf(buf)) .ToLocalChecked(); eternal.Set(env->isolate(), str); return str; } return eternal.Get(env->isolate()); } nghttp2_vec vec = nghttp2_rcbuf_get_buf(buf); if (vec.len == 0) { nghttp2_rcbuf_decref(buf); return String::Empty(env->isolate()); } if (may_internalize && vec.len < 64) { // This is a short header name, so there is a good chance V8 already has // it internalized. return GetInternalizedString(env, vec); } ExternalHeader* h_str = new ExternalHeader(buf); MaybeLocal str = String::NewExternalOneByte(env->isolate(), h_str); if (str.IsEmpty()) delete h_str; return str; } private: nghttp2_rcbuf* buf_; nghttp2_vec vec_; }; class Headers { public: Headers(Isolate* isolate, Local context, Local headers); ~Headers() {} nghttp2_nv* operator*() { return reinterpret_cast(*buf_); } size_t length() const { return count_; } private: size_t count_; MaybeStackBuffer buf_; }; } // namespace http2 } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_HTTP2_H_