| #ifndef SRC_NODE_HTTP2_H_ |
| #define SRC_NODE_HTTP2_H_ |
| |
| #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
| |
| // clang-format off |
| #include "node.h" // nghttp2.h needs ssize_t |
| // clang-format on |
| #include "nghttp2/nghttp2.h" |
| |
| #include "env.h" |
| #include "aliased_struct.h" |
| #include "node_http2_state.h" |
| #include "node_http_common.h" |
| #include "node_mem.h" |
| #include "node_perf.h" |
| #include "stream_base.h" |
| #include "string_bytes.h" |
| |
| #include <algorithm> |
| #include <queue> |
| |
| namespace node { |
| namespace http2 { |
| |
| // Constants in all caps are exported as user-facing constants |
| // in JavaScript. Constants using the kName pattern are internal |
| // only. |
| |
| // 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. |
| constexpr size_t kDefaultMaxPings = 10; |
| |
| // Also strictly limit the number of outstanding SETTINGS frames a user sends |
| constexpr size_t kDefaultMaxSettings = 10; |
| |
| // Default maximum total memory cap for Http2Session. |
| constexpr uint64_t kDefaultMaxSessionMemory = 10000000; |
| |
| // These are the standard HTTP/2 defaults as specified by the RFC |
| constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096; |
| constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1; |
| constexpr uint32_t DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 0xffffffffu; |
| constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65535; |
| constexpr uint32_t DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16384; |
| constexpr uint32_t DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65535; |
| constexpr uint32_t DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0; |
| constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215; |
| constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE; |
| constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647; |
| |
| // Stream is not going to have any DATA frames |
| constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1; |
| |
| // Stream might have trailing headers |
| constexpr int STREAM_OPTION_GET_TRAILERS = 0x2; |
| |
| // Http2Stream internal states |
| constexpr int kStreamStateNone = 0x0; |
| constexpr int kStreamStateShut = 0x1; |
| constexpr int kStreamStateReadStart = 0x2; |
| constexpr int kStreamStateReadPaused = 0x4; |
| constexpr int kStreamStateClosed = 0x8; |
| constexpr int kStreamStateDestroyed = 0x10; |
| constexpr int kStreamStateTrailers = 0x20; |
| |
| // Http2Session internal states |
| constexpr int kSessionStateNone = 0x0; |
| constexpr int kSessionStateHasScope = 0x1; |
| constexpr int kSessionStateWriteScheduled = 0x2; |
| constexpr int kSessionStateClosed = 0x4; |
| constexpr int kSessionStateClosing = 0x8; |
| constexpr int kSessionStateSending = 0x10; |
| constexpr int kSessionStateWriteInProgress = 0x20; |
| constexpr int kSessionStateReadingStopped = 0x40; |
| constexpr int kSessionStateReceivePaused = 0x80; |
| |
| // 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 PaddingStrategy { |
| // 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, |
| // Removed and turned into an alias because it is unreasonably expensive for |
| // very little benefit. |
| PADDING_STRATEGY_CALLBACK = PADDING_STRATEGY_ALIGNED |
| }; |
| |
| enum SessionType { |
| NGHTTP2_SESSION_SERVER, |
| NGHTTP2_SESSION_CLIENT |
| }; |
| |
| template <typename T, void(*fn)(T*)> |
| struct Nghttp2Deleter { |
| void operator()(T* ptr) const noexcept { fn(ptr); } |
| }; |
| |
| using Nghttp2OptionPointer = |
| std::unique_ptr<nghttp2_option, |
| Nghttp2Deleter<nghttp2_option, nghttp2_option_del>>; |
| |
| using Nghttp2SessionPointer = |
| std::unique_ptr<nghttp2_session, |
| Nghttp2Deleter<nghttp2_session, nghttp2_session_del>>; |
| |
| using Nghttp2SessionCallbacksPointer = |
| std::unique_ptr<nghttp2_session_callbacks, |
| Nghttp2Deleter<nghttp2_session_callbacks, |
| nghttp2_session_callbacks_del>>; |
| |
| struct Http2HeadersTraits { |
| typedef nghttp2_nv nv_t; |
| }; |
| |
| struct Http2RcBufferPointerTraits { |
| typedef nghttp2_rcbuf rcbuf_t; |
| typedef nghttp2_vec vector_t; |
| |
| static void inc(rcbuf_t* buf) { |
| CHECK_NOT_NULL(buf); |
| nghttp2_rcbuf_incref(buf); |
| } |
| static void dec(rcbuf_t* buf) { |
| CHECK_NOT_NULL(buf); |
| nghttp2_rcbuf_decref(buf); |
| } |
| static vector_t get_vec(rcbuf_t* buf) { |
| CHECK_NOT_NULL(buf); |
| return nghttp2_rcbuf_get_buf(buf); |
| } |
| static bool is_static(const rcbuf_t* buf) { |
| CHECK_NOT_NULL(buf); |
| return nghttp2_rcbuf_is_static(buf); |
| } |
| }; |
| |
| using Http2Headers = NgHeaders<Http2HeadersTraits>; |
| using Http2RcBufferPointer = NgRcBufPointer<Http2RcBufferPointerTraits>; |
| |
| struct NgHttp2StreamWrite : public MemoryRetainer { |
| BaseObjectPtr<AsyncWrap> req_wrap; |
| uv_buf_t buf; |
| |
| inline explicit NgHttp2StreamWrite(uv_buf_t buf_) : buf(buf_) {} |
| inline NgHttp2StreamWrite(BaseObjectPtr<AsyncWrap> req_wrap, uv_buf_t buf_) : |
| req_wrap(std::move(req_wrap)), buf(buf_) {} |
| |
| void MemoryInfo(MemoryTracker* tracker) const override; |
| SET_MEMORY_INFO_NAME(NgHttp2StreamWrite) |
| SET_SELF_SIZE(NgHttp2StreamWrite) |
| }; |
| |
| typedef uint32_t(*get_setting)(nghttp2_session* session, |
| nghttp2_settings_id id); |
| |
| class Http2Ping; |
| class Http2Session; |
| class Http2Settings; |
| class Http2Stream; |
| class Origins; |
| |
| // 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: |
| BaseObjectPtr<Http2Session> session_; |
| }; |
| |
| // 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: |
| Http2Options(Http2State* http2_state, |
| SessionType type); |
| |
| ~Http2Options() = default; |
| |
| nghttp2_option* operator*() const { |
| return options_.get(); |
| } |
| |
| void set_max_header_pairs(uint32_t max) { |
| max_header_pairs_ = max; |
| } |
| |
| uint32_t max_header_pairs() const { |
| return max_header_pairs_; |
| } |
| |
| void set_padding_strategy(PaddingStrategy val) { |
| padding_strategy_ = val; |
| } |
| |
| PaddingStrategy padding_strategy() const { |
| return padding_strategy_; |
| } |
| |
| void set_max_outstanding_pings(size_t max) { |
| max_outstanding_pings_ = max; |
| } |
| |
| size_t max_outstanding_pings() const { |
| return max_outstanding_pings_; |
| } |
| |
| void set_max_outstanding_settings(size_t max) { |
| max_outstanding_settings_ = max; |
| } |
| |
| size_t max_outstanding_settings() const { |
| return max_outstanding_settings_; |
| } |
| |
| void set_max_session_memory(uint64_t max) { |
| max_session_memory_ = max; |
| } |
| |
| uint64_t max_session_memory() const { |
| return max_session_memory_; |
| } |
| |
| private: |
| Nghttp2OptionPointer options_; |
| uint64_t max_session_memory_ = kDefaultMaxSessionMemory; |
| uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; |
| PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE; |
| size_t max_outstanding_pings_ = kDefaultMaxPings; |
| size_t max_outstanding_settings_ = kDefaultMaxSettings; |
| }; |
| |
| struct Http2Priority : public nghttp2_priority_spec { |
| Http2Priority(Environment* env, |
| v8::Local<v8::Value> parent, |
| v8::Local<v8::Value> weight, |
| v8::Local<v8::Value> exclusive); |
| }; |
| |
| 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; |
| }; |
| |
| struct Http2HeaderTraits { |
| typedef Http2RcBufferPointer rcbufferpointer_t; |
| typedef Http2Session allocator_t; |
| |
| // HTTP/2 does not support identifying header names by token id. |
| // HTTP/3 will, however, so we prepare for that now. |
| static const char* ToHttpHeaderName(int32_t token) { return nullptr; } |
| }; |
| |
| using Http2Header = NgHeader<Http2HeaderTraits>; |
| |
| class Http2Stream : public AsyncWrap, |
| public StreamBase { |
| public: |
| static Http2Stream* New( |
| Http2Session* session, |
| int32_t id, |
| nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS, |
| int options = 0); |
| ~Http2Stream() override; |
| |
| nghttp2_stream* operator*() const; |
| |
| nghttp2_stream* stream() const; |
| |
| Http2Session* session() { return session_.get(); } |
| const Http2Session* session() const { return session_.get(); } |
| |
| // Required for StreamBase |
| int ReadStart() override; |
| |
| // Required for StreamBase |
| int ReadStop() override; |
| |
| // Required for StreamBase |
| ShutdownWrap* CreateShutdownWrap(v8::Local<v8::Object> object) override; |
| int DoShutdown(ShutdownWrap* req_wrap) override; |
| |
| bool HasWantsWrite() const override { return true; } |
| |
| // Initiate a response on this stream. |
| int SubmitResponse(const Http2Headers& headers, int options); |
| |
| // Submit informational headers for this stream |
| int SubmitInfo(const Http2Headers& headers); |
| |
| // Submit trailing headers for this stream |
| int SubmitTrailers(const Http2Headers& headers); |
| void OnTrailers(); |
| |
| // Submit a PRIORITY frame for this stream |
| int SubmitPriority(const Http2Priority& priority, bool silent = false); |
| |
| // Submits an RST_STREAM frame using the given code |
| void SubmitRstStream(const uint32_t code); |
| |
| void FlushRstStream(); |
| |
| // Submits a PUSH_PROMISE frame with this stream as the parent. |
| Http2Stream* SubmitPushPromise( |
| const Http2Headers& headers, |
| int32_t* ret, |
| int options = 0); |
| |
| |
| void Close(int32_t code); |
| |
| // Destroy this stream instance and free all held memory. |
| void Destroy(); |
| |
| bool is_destroyed() const { |
| return flags_ & kStreamStateDestroyed; |
| } |
| |
| bool is_writable() const { |
| return !(flags_ & kStreamStateShut); |
| } |
| |
| bool is_paused() const { |
| return flags_ & kStreamStateReadPaused; |
| } |
| |
| bool is_closed() const { |
| return flags_ & kStreamStateClosed; |
| } |
| |
| bool has_trailers() const { |
| return flags_ & kStreamStateTrailers; |
| } |
| |
| void set_has_trailers(bool on = true) { |
| if (on) |
| flags_ |= kStreamStateTrailers; |
| else |
| flags_ &= ~kStreamStateTrailers; |
| } |
| |
| void set_closed() { |
| flags_ |= kStreamStateClosed; |
| } |
| |
| void set_destroyed() { |
| flags_ |= kStreamStateDestroyed; |
| } |
| |
| void set_not_writable() { |
| flags_ |= kStreamStateShut; |
| } |
| |
| void set_reading(bool on = true) { |
| if (on) { |
| flags_ |= kStreamStateReadStart; |
| set_paused(false); |
| } else {} |
| } |
| |
| void set_paused(bool on = true) { |
| if (on) |
| flags_ |= kStreamStateReadPaused; |
| else |
| flags_ &= ~kStreamStateReadPaused; |
| } |
| |
| // Returns true if this stream is in the reading state, which occurs when |
| // the kStreamStateReadStart flag has been set and the |
| // kStreamStateReadPaused flag is *not* set. |
| bool is_reading() const { |
| return flags_ & kStreamStateReadStart && !is_paused(); |
| } |
| |
| // Returns the RST_STREAM code used to close this stream |
| int32_t code() const { return code_; } |
| |
| // Returns the stream identifier for this stream |
| int32_t id() const { return id_; } |
| |
| void IncrementAvailableOutboundLength(size_t amount); |
| void DecrementAvailableOutboundLength(size_t amount); |
| |
| bool AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags); |
| |
| template <typename Fn> |
| void TransferHeaders(Fn&& fn) { |
| size_t i = 0; |
| for (const auto& header : current_headers_ ) |
| fn(header, i++); |
| ClearHeaders(); |
| } |
| |
| void ClearHeaders() { |
| current_headers_.clear(); |
| } |
| |
| size_t headers_count() const { |
| return current_headers_.size(); |
| } |
| |
| nghttp2_headers_category headers_category() const { |
| return current_headers_category_; |
| } |
| |
| 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; |
| |
| void MemoryInfo(MemoryTracker* tracker) const override; |
| SET_MEMORY_INFO_NAME(Http2Stream) |
| SET_SELF_SIZE(Http2Stream) |
| |
| std::string diagnostic_name() const override; |
| |
| // JavaScript API |
| static void GetID(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Priority(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void PushPromise(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void RefreshState(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Info(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Trailers(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Respond(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void RstStream(const v8::FunctionCallbackInfo<v8::Value>& 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; |
| uint64_t id; |
| }; |
| |
| Statistics statistics_ = {}; |
| |
| private: |
| Http2Stream(Http2Session* session, |
| v8::Local<v8::Object> obj, |
| int32_t id, |
| nghttp2_headers_category category, |
| int options); |
| |
| void EmitStatistics(); |
| |
| BaseObjectWeakPtr<Http2Session> session_; // The Parent HTTP/2 Session |
| int32_t id_ = 0; // The Stream Identifier |
| int32_t code_ = NGHTTP2_NO_ERROR; // The RST_STREAM code (if any) |
| int flags_ = kStreamStateNone; // 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<Http2Header> 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<NgHttp2StreamWrite> queue_; |
| size_t available_outbound_length_ = 0; |
| |
| 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::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); |
| }; |
| |
| struct SessionJSFields { |
| uint8_t bitfield; |
| uint8_t priority_listener_count; |
| uint8_t frame_error_listener_count; |
| uint32_t max_invalid_frames = 1000; |
| uint32_t max_rejected_streams = 100; |
| }; |
| |
| // Indices for js_fields_, which serves as a way to communicate data with JS |
| // land fast. In particular, we store information about the number/presence |
| // of certain event listeners in JS, and skip calls from C++ into JS if they |
| // are missing. |
| enum SessionUint8Fields { |
| kBitfield = offsetof(SessionJSFields, bitfield), // See below |
| kSessionPriorityListenerCount = |
| offsetof(SessionJSFields, priority_listener_count), |
| kSessionFrameErrorListenerCount = |
| offsetof(SessionJSFields, frame_error_listener_count), |
| kSessionMaxInvalidFrames = offsetof(SessionJSFields, max_invalid_frames), |
| kSessionMaxRejectedStreams = offsetof(SessionJSFields, max_rejected_streams), |
| kSessionUint8FieldCount = sizeof(SessionJSFields) |
| }; |
| |
| enum SessionBitfieldFlags { |
| kSessionHasRemoteSettingsListeners, |
| kSessionRemoteSettingsIsUpToDate, |
| kSessionHasPingListeners, |
| kSessionHasAltsvcListeners |
| }; |
| |
| class Http2Session : public AsyncWrap, |
| public StreamListener, |
| public mem::NgLibMemoryManager<Http2Session, nghttp2_mem> { |
| public: |
| Http2Session(Http2State* http2_state, |
| v8::Local<v8::Object> wrap, |
| SessionType type = NGHTTP2_SESSION_SERVER); |
| ~Http2Session() override; |
| |
| StreamBase* underlying_stream() { |
| return static_cast<StreamBase*>(stream_); |
| } |
| |
| void Close(uint32_t code = NGHTTP2_NO_ERROR, |
| bool socket_closed = false); |
| |
| void Consume(v8::Local<v8::Object> stream); |
| |
| void Goaway(uint32_t code, int32_t lastStreamID, |
| const 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); |
| |
| void Origin(const Origins& origins); |
| |
| uint8_t 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. |
| Http2Stream* SubmitRequest( |
| const Http2Priority& priority, |
| const Http2Headers& headers, |
| int32_t* ret, |
| int options = 0); |
| |
| SessionType type() const { return session_type_; } |
| |
| nghttp2_session* session() const { return session_.get(); } |
| |
| nghttp2_session* operator*() { return session_.get(); } |
| |
| uint32_t max_header_pairs() const { return max_header_pairs_; } |
| |
| const char* TypeName() const; |
| |
| bool is_destroyed() { |
| return (flags_ & kSessionStateClosed) || session_ == nullptr; |
| } |
| |
| void set_destroyed() { |
| flags_ |= kSessionStateClosed; |
| } |
| |
| struct custom_settings_state { |
| size_t number; |
| nghttp2_settings_entry entries[MAX_ADDITIONAL_SETTINGS]; |
| }; |
| |
| custom_settings_state& custom_settings(bool local) { |
| return local ? local_custom_settings_ : remote_custom_settings_; |
| } |
| |
| #define IS_FLAG(name, flag) \ |
| bool is_##name() const { return flags_ & flag; } \ |
| void set_##name(bool on = true) { \ |
| if (on) \ |
| flags_ |= flag; \ |
| else \ |
| flags_ &= ~flag; \ |
| } |
| |
| IS_FLAG(in_scope, kSessionStateHasScope) |
| IS_FLAG(write_scheduled, kSessionStateWriteScheduled) |
| IS_FLAG(closing, kSessionStateClosing) |
| IS_FLAG(sending, kSessionStateSending) |
| IS_FLAG(write_in_progress, kSessionStateWriteInProgress) |
| IS_FLAG(reading_stopped, kSessionStateReadingStopped) |
| IS_FLAG(receive_paused, kSessionStateReceivePaused) |
| |
| #undef IS_FLAG |
| |
| // Schedule a write if nghttp2 indicates it wants to write to the socket. |
| void MaybeScheduleWrite(); |
| |
| // Stop reading if nghttp2 doesn't want to anymore. |
| void MaybeStopReading(); |
| |
| // Returns pointer to the stream, or nullptr if stream does not exist |
| BaseObjectPtr<Http2Stream> FindStream(int32_t id); |
| |
| bool CanAddStream(); |
| |
| // Adds a stream instance to this session |
| void AddStream(Http2Stream* stream); |
| |
| // Removes a stream instance from this session |
| BaseObjectPtr<Http2Stream> RemoveStream(int32_t id); |
| |
| // Indicates whether there currently exist outgoing buffers for this stream. |
| bool HasWritesOnSocketForStream(Http2Stream* stream); |
| |
| // Write data from stream_buf_ to the session. |
| // This will call the error callback if an error occurs. |
| void ConsumeHTTP2Data(); |
| |
| void MemoryInfo(MemoryTracker* tracker) const override; |
| SET_MEMORY_INFO_NAME(Http2Session) |
| SET_SELF_SIZE(Http2Session) |
| |
| std::string diagnostic_name() const override; |
| |
| // Schedule an RstStream for after the current write finishes. |
| void AddPendingRstStream(int32_t stream_id) { |
| pending_rst_streams_.emplace_back(stream_id); |
| } |
| |
| bool has_pending_rststream(int32_t stream_id) { |
| return pending_rst_streams_.end() != |
| std::ranges::find(pending_rst_streams_, stream_id); |
| } |
| |
| // Handle reads/writes from the underlying network transport. |
| uv_buf_t OnStreamAlloc(size_t suggested_size) override; |
| void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; |
| void OnStreamAfterWrite(WriteWrap* w, int status) override; |
| |
| // Implementation for mem::NgLibMemoryManager |
| void CheckAllocatedSize(size_t previous_size) const; |
| void IncreaseAllocatedSize(size_t size); |
| void DecreaseAllocatedSize(size_t size); |
| |
| // The JavaScript API |
| static void New(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Consume(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void HasPendingData(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Request(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void SetNextStreamID(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void SetLocalWindowSize( |
| const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Goaway(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void UpdateChunksSent(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void RefreshState(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Ping(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void AltSvc(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void Origin(const v8::FunctionCallbackInfo<v8::Value>& args); |
| static void SetGracefulClose(const v8::FunctionCallbackInfo<v8::Value>& args); |
| |
| template <get_setting fn, bool local> |
| static void RefreshSettings(const v8::FunctionCallbackInfo<v8::Value>& args); |
| |
| uv_loop_t* event_loop() const { |
| return env()->event_loop(); |
| } |
| |
| Http2State* http2_state() const { return http2_state_.get(); } |
| |
| BaseObjectPtr<Http2Ping> PopPing(); |
| bool AddPing(const uint8_t* data, v8::Local<v8::Function> callback); |
| bool HasPendingData() const; |
| |
| BaseObjectPtr<Http2Settings> PopSettings(); |
| bool AddSettings(v8::Local<v8::Function> callback); |
| |
| void IncrementCurrentSessionMemory(uint64_t amount) { |
| current_session_memory_ += amount; |
| } |
| |
| void DecrementCurrentSessionMemory(uint64_t amount) { |
| DCHECK_LE(amount, current_session_memory_); |
| current_session_memory_ -= amount; |
| } |
| |
| void UpdateLocalCustomSettings(size_t count_, |
| nghttp2_settings_entry* entries_); |
| |
| // Tell our custom memory allocator that this rcbuf is independent of |
| // this session now, and may outlive it. |
| void StopTrackingRcbuf(nghttp2_rcbuf* buf); |
| |
| // Returns the current session memory including memory allocated by nghttp2, |
| // the current outbound storage queue, and pending writes. |
| uint64_t current_session_memory() const { |
| uint64_t total = current_session_memory_ + sizeof(Http2Session); |
| total += current_nghttp2_memory_; |
| total += outgoing_storage_.size(); |
| return total; |
| } |
| |
| // Return true if current_session_memory + amount is less than the max |
| bool has_available_session_memory(uint64_t amount) const { |
| return current_session_memory() + 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; |
| SessionType session_type; |
| }; |
| |
| Statistics statistics_ = {}; |
| |
| bool IsGracefulCloseInitiated() const { |
| return graceful_close_initiated_; |
| } |
| void SetGracefulCloseInitiated(bool value) { |
| graceful_close_initiated_ = value; |
| } |
| |
| private: |
| void EmitStatistics(); |
| |
| void FetchAllowedRemoteCustomSettings(); |
| |
| // Frame Padding Strategies |
| ssize_t OnDWordAlignedPadding(size_t frameLength, |
| size_t maxPayloadLen); |
| ssize_t OnMaxFrameSizePadding(size_t frameLength, |
| size_t maxPayloadLen); |
| |
| // Frame Handler |
| int HandleDataFrame(const nghttp2_frame* frame); |
| void HandleGoawayFrame(const nghttp2_frame* frame); |
| void HandleHeadersFrame(const nghttp2_frame* frame); |
| void HandlePriorityFrame(const nghttp2_frame* frame); |
| void HandleSettingsFrame(const nghttp2_frame* frame); |
| void HandlePingFrame(const nghttp2_frame* frame); |
| void HandleAltSvcFrame(const nghttp2_frame* frame); |
| void HandleOriginFrame(const nghttp2_frame* frame); |
| |
| void DecrefHeaders(const nghttp2_frame* frame); |
| |
| // nghttp2 callbacks |
| 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 OnFrameSent( |
| nghttp2_session* session, |
| const nghttp2_frame* frame, |
| void* user_data); |
| static int OnStreamClose( |
| nghttp2_session* session, |
| int32_t id, |
| uint32_t code, |
| void* user_data); |
| static int OnInvalidHeader( |
| nghttp2_session* session, |
| const nghttp2_frame* frame, |
| nghttp2_rcbuf* name, |
| nghttp2_rcbuf* value, |
| uint8_t flags, |
| 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 OnSelectPadding( |
| nghttp2_session* session, |
| const nghttp2_frame* frame, |
| size_t maxPayloadLen, |
| void* user_data); |
| static int OnNghttpError(nghttp2_session* session, |
| int lib_error_code, |
| const char* message, |
| size_t len, |
| void* user_data); |
| static int OnSendData( |
| nghttp2_session* session, |
| nghttp2_frame* frame, |
| const uint8_t* framehd, |
| size_t length, |
| nghttp2_data_source* source, |
| void* user_data); |
| static int OnInvalidFrame( |
| nghttp2_session* session, |
| const nghttp2_frame* frame, |
| int lib_error_code, |
| void* user_data); |
| |
| struct Callbacks { |
| explicit Callbacks(bool kHasGetPaddingCallback); |
| |
| Nghttp2SessionCallbacksPointer callbacks; |
| }; |
| |
| /* Use callback_struct_saved[kHasGetPaddingCallback ? 1 : 0] */ |
| static const Callbacks callback_struct_saved[2]; |
| |
| // The underlying nghttp2_session handle |
| Nghttp2SessionPointer session_; |
| |
| // JS-accessible numeric fields, as indexed by SessionUint8Fields. |
| AliasedStruct<SessionJSFields> js_fields_; |
| |
| // The session type: client or server |
| SessionType 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_ = kDefaultMaxSessionMemory; |
| uint64_t current_session_memory_ = 0; |
| // The amount of memory allocated by nghttp2 internals |
| uint64_t current_nghttp2_memory_ = 0; |
| |
| // The collection of active Http2Streams associated with this session |
| std::unordered_map<int32_t, BaseObjectPtr<Http2Stream>> streams_; |
| |
| int flags_ = kSessionStateNone; |
| |
| // The StreamBase instance being used for i/o |
| PaddingStrategy 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); |
| // When processing input data, either stream_buf_ab_ or stream_buf_allocation_ |
| // will be set. stream_buf_ab_ is lazily created from stream_buf_allocation_. |
| v8::Global<v8::ArrayBuffer> stream_buf_ab_; |
| std::unique_ptr<v8::BackingStore> stream_buf_allocation_; |
| size_t stream_buf_offset_ = 0; |
| // Custom error code for errors that originated inside one of the callbacks |
| // called by nghttp2_session_mem_recv. |
| const char* custom_recv_error_code_ = nullptr; |
| |
| size_t max_outstanding_pings_ = kDefaultMaxPings; |
| std::queue<BaseObjectPtr<Http2Ping>> outstanding_pings_; |
| |
| size_t max_outstanding_settings_ = kDefaultMaxSettings; |
| std::queue<BaseObjectPtr<Http2Settings>> outstanding_settings_; |
| |
| struct custom_settings_state local_custom_settings_; |
| struct custom_settings_state remote_custom_settings_; |
| |
| std::vector<NgHttp2StreamWrite> outgoing_buffers_; |
| std::vector<uint8_t> outgoing_storage_; |
| size_t outgoing_length_ = 0; |
| std::vector<int32_t> pending_rst_streams_; |
| // Count streams that have been rejected while being opened. Exceeding a fixed |
| // limit will result in the session being destroyed, as an indication of a |
| // misbehaving peer. This counter is reset once new streams are being |
| // accepted again. |
| uint32_t rejected_stream_count_ = 0; |
| // Also use the invalid frame count as a measure for rejecting input frames. |
| uint32_t invalid_frame_count_ = 0; |
| |
| void PushOutgoingBuffer(NgHttp2StreamWrite&& write); |
| |
| BaseObjectPtr<Http2State> http2_state_; |
| |
| void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length); |
| void ClearOutgoing(int status); |
| |
| void MaybeNotifyGracefulCloseComplete(); |
| |
| friend class Http2Scope; |
| friend class Http2StreamListener; |
| |
| // Flag to indicate that JavaScript has initiated a graceful closure |
| bool graceful_close_initiated_ = false; |
| }; |
| |
| struct Http2SessionPerformanceEntryTraits { |
| static constexpr performance::PerformanceEntryType kType = |
| performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2; |
| |
| using Details = Http2Session::Statistics; |
| |
| static v8::MaybeLocal<v8::Object> GetDetails( |
| Environment* env, |
| const performance::PerformanceEntry<Http2SessionPerformanceEntryTraits>& |
| entry); |
| }; |
| |
| struct Http2StreamPerformanceEntryTraits { |
| static constexpr performance::PerformanceEntryType kType = |
| performance::NODE_PERFORMANCE_ENTRY_TYPE_HTTP2; |
| |
| using Details = Http2Stream::Statistics; |
| |
| static v8::MaybeLocal<v8::Object> GetDetails( |
| Environment* env, |
| const performance::PerformanceEntry<Http2StreamPerformanceEntryTraits>& |
| entry); |
| }; |
| |
| using Http2SessionPerformanceEntry = |
| performance::PerformanceEntry<Http2SessionPerformanceEntryTraits>; |
| using Http2StreamPerformanceEntry = |
| performance::PerformanceEntry<Http2StreamPerformanceEntryTraits>; |
| |
| class Http2Ping : public AsyncWrap { |
| public: |
| explicit Http2Ping( |
| Http2Session* session, |
| v8::Local<v8::Object> obj, |
| v8::Local<v8::Function> callback); |
| |
| void MemoryInfo(MemoryTracker* tracker) const override; |
| SET_MEMORY_INFO_NAME(Http2Ping) |
| SET_SELF_SIZE(Http2Ping) |
| |
| void Send(const uint8_t* payload); |
| void Done(bool ack, const uint8_t* payload = nullptr); |
| void DetachFromSession(); |
| |
| v8::Local<v8::Function> callback() const; |
| |
| private: |
| BaseObjectWeakPtr<Http2Session> session_; |
| v8::Global<v8::Function> callback_; |
| uint64_t startTime_; |
| }; |
| |
| // 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 Http2Settings : public AsyncWrap { |
| public: |
| Http2Settings(Http2Session* session, |
| v8::Local<v8::Object> obj, |
| v8::Local<v8::Function> callback, |
| uint64_t start_time = uv_hrtime()); |
| |
| void MemoryInfo(MemoryTracker* tracker) const override; |
| SET_MEMORY_INFO_NAME(Http2Settings) |
| SET_SELF_SIZE(Http2Settings) |
| |
| void Send(); |
| void Done(bool ack); |
| |
| v8::Local<v8::Function> callback() const; |
| |
| // Returns a Buffer instance with the serialized SETTINGS payload |
| v8::Local<v8::Value> Pack(); |
| |
| static v8::Local<v8::Value> Pack(Http2State* state); |
| |
| // Resets the default values in the settings buffer |
| static void RefreshDefaults(Http2State* http2_state); |
| |
| // Update the local or remote settings for the given session |
| static void Update(Http2Session* session, get_setting fn, bool local); |
| |
| private: |
| static size_t Init( |
| Http2State* http2_state, |
| nghttp2_settings_entry* entries); |
| |
| static v8::Local<v8::Value> Pack( |
| Environment* env, |
| size_t count, |
| const nghttp2_settings_entry* entries); |
| |
| BaseObjectWeakPtr<Http2Session> session_; |
| v8::Global<v8::Function> callback_; |
| uint64_t startTime_; |
| size_t count_ = 0; |
| nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT + MAX_ADDITIONAL_SETTINGS]; |
| }; |
| |
| class Origins { |
| public: |
| Origins(Environment* env, |
| v8::Local<v8::String> origin_string, |
| size_t origin_count); |
| ~Origins() = default; |
| |
| const nghttp2_origin_entry* operator*() const { |
| return static_cast<const nghttp2_origin_entry*>(bs_->Data()); |
| } |
| |
| size_t length() const { |
| return count_; |
| } |
| |
| private: |
| size_t count_; |
| std::unique_ptr<v8::BackingStore> bs_; |
| }; |
| |
| #define HTTP2_HIDDEN_CONSTANTS(V) \ |
| V(NGHTTP2_HCAT_REQUEST) \ |
| V(NGHTTP2_HCAT_RESPONSE) \ |
| V(NGHTTP2_HCAT_PUSH_RESPONSE) \ |
| V(NGHTTP2_HCAT_HEADERS) \ |
| V(NGHTTP2_NV_FLAG_NONE) \ |
| V(NGHTTP2_NV_FLAG_NO_INDEX) \ |
| V(NGHTTP2_ERR_DEFERRED) \ |
| V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \ |
| V(NGHTTP2_ERR_INVALID_ARGUMENT) \ |
| V(NGHTTP2_ERR_STREAM_CLOSED) \ |
| V(NGHTTP2_ERR_NOMEM) \ |
| V(STREAM_OPTION_EMPTY_PAYLOAD) \ |
| V(STREAM_OPTION_GET_TRAILERS) |
| |
| #define HTTP2_ERROR_CODES(V) \ |
| V(NGHTTP2_NO_ERROR) \ |
| V(NGHTTP2_PROTOCOL_ERROR) \ |
| V(NGHTTP2_INTERNAL_ERROR) \ |
| V(NGHTTP2_FLOW_CONTROL_ERROR) \ |
| V(NGHTTP2_SETTINGS_TIMEOUT) \ |
| V(NGHTTP2_STREAM_CLOSED) \ |
| V(NGHTTP2_FRAME_SIZE_ERROR) \ |
| V(NGHTTP2_REFUSED_STREAM) \ |
| V(NGHTTP2_CANCEL) \ |
| V(NGHTTP2_COMPRESSION_ERROR) \ |
| V(NGHTTP2_CONNECT_ERROR) \ |
| V(NGHTTP2_ENHANCE_YOUR_CALM) \ |
| V(NGHTTP2_INADEQUATE_SECURITY) \ |
| V(NGHTTP2_HTTP_1_1_REQUIRED) \ |
| |
| #define HTTP2_CONSTANTS(V) \ |
| V(NGHTTP2_ERR_FRAME_SIZE_ERROR) \ |
| V(NGHTTP2_SESSION_SERVER) \ |
| V(NGHTTP2_SESSION_CLIENT) \ |
| V(NGHTTP2_STREAM_STATE_IDLE) \ |
| V(NGHTTP2_STREAM_STATE_OPEN) \ |
| V(NGHTTP2_STREAM_STATE_RESERVED_LOCAL) \ |
| V(NGHTTP2_STREAM_STATE_RESERVED_REMOTE) \ |
| V(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) \ |
| V(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE) \ |
| V(NGHTTP2_STREAM_STATE_CLOSED) \ |
| V(NGHTTP2_FLAG_NONE) \ |
| V(NGHTTP2_FLAG_END_STREAM) \ |
| V(NGHTTP2_FLAG_END_HEADERS) \ |
| V(NGHTTP2_FLAG_ACK) \ |
| V(NGHTTP2_FLAG_PADDED) \ |
| V(NGHTTP2_FLAG_PRIORITY) \ |
| V(DEFAULT_SETTINGS_HEADER_TABLE_SIZE) \ |
| V(DEFAULT_SETTINGS_ENABLE_PUSH) \ |
| V(DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS) \ |
| V(DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE) \ |
| V(DEFAULT_SETTINGS_MAX_FRAME_SIZE) \ |
| V(DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE) \ |
| V(DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL) \ |
| V(MAX_MAX_FRAME_SIZE) \ |
| V(MIN_MAX_FRAME_SIZE) \ |
| V(MAX_INITIAL_WINDOW_SIZE) \ |
| V(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) \ |
| V(NGHTTP2_SETTINGS_ENABLE_PUSH) \ |
| V(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) \ |
| V(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) \ |
| V(NGHTTP2_SETTINGS_MAX_FRAME_SIZE) \ |
| V(NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE) \ |
| V(NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) \ |
| V(PADDING_STRATEGY_NONE) \ |
| V(PADDING_STRATEGY_ALIGNED) \ |
| V(PADDING_STRATEGY_MAX) \ |
| V(PADDING_STRATEGY_CALLBACK) \ |
| HTTP2_ERROR_CODES(V) |
| |
| #define HTTP2_SETTINGS(V) \ |
| V(HEADER_TABLE_SIZE) \ |
| V(ENABLE_PUSH) \ |
| V(MAX_CONCURRENT_STREAMS) \ |
| V(INITIAL_WINDOW_SIZE) \ |
| V(MAX_FRAME_SIZE) \ |
| V(MAX_HEADER_LIST_SIZE) \ |
| V(ENABLE_CONNECT_PROTOCOL) \ |
| |
| } // namespace http2 |
| } // namespace node |
| |
| #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
| |
| #endif // SRC_NODE_HTTP2_H_ |