Branches the nghttp2_adapter interface from //net/http2. PiperOrigin-RevId: 361667825 Change-Id: I94cab47ce471f23eb872a39173efbc9c159eb76b
diff --git a/http2/adapter/http2_adapter.h b/http2/adapter/http2_adapter.h new file mode 100644 index 0000000..cccc269 --- /dev/null +++ b/http2/adapter/http2_adapter.h
@@ -0,0 +1,114 @@ +#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_ +#define QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_ + +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "http2/adapter/http2_protocol.h" +#include "http2/adapter/http2_session.h" +#include "http2/adapter/http2_visitor_interface.h" + +namespace http2 { +namespace adapter { + +// Http2Adapter is an HTTP/2-processing class that exposes an interface similar +// to the nghttp2 library for processing the HTTP/2 wire format. As nghttp2 +// parses HTTP/2 frames and invokes callbacks on Http2Adapter, Http2Adapter then +// invokes corresponding callbacks on its passed-in Http2VisitorInterface. +// Http2Adapter is a base class shared between client-side and server-side +// implementations. +class Http2Adapter { + public: + Http2Adapter(const Http2Adapter&) = delete; + Http2Adapter& operator=(const Http2Adapter&) = delete; + + // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the + // |visitor_| as appropriate. + ssize_t ProcessBytes(absl::string_view bytes); + + // Submits the |settings| to be written to the peer, e.g., as part of the + // HTTP/2 connection preface. + void SubmitSettings(absl::Span<const Http2Setting> settings); + + // Submits a PRIORITY frame for the given stream. + void SubmitPriorityForStream(Http2StreamId stream_id, + Http2StreamId parent_stream_id, int weight, + bool exclusive); + + // Submits a PING on the connection. Note that nghttp2 automatically submits + // PING acks upon receiving non-ack PINGs from the peer, so callers only use + // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack(). + void SubmitPing(Http2PingId ping_id); + + // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id| + // refers to stream IDs initiated by the peer. For client-side, this last + // stream ID must be even (or 0); for server-side, this last stream ID must be + // odd (or 0). To submit a GOAWAY with |last_accepted_stream_id| with the + // maximum stream ID, signaling imminent connection termination, call + // SubmitShutdownNotice() instead (though this is only possible server-side). + void SubmitGoAway(Http2StreamId last_accepted_stream_id, + Http2ErrorCode error_code, absl::string_view opaque_data); + + // Submits a WINDOW_UPDATE for the given stream (a |stream_id| of 0 indicates + // a connection-level WINDOW_UPDATE). + void SubmitWindowUpdate(Http2StreamId stream_id, int window_increment); + + // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates + // connection-level METADATA). If |fin|, the frame will also have the + // END_METADATA flag set. + void SubmitMetadata(Http2StreamId stream_id, bool fin); + + // Returns serialized bytes for writing to the wire. + // Writes should be submitted to Http2Adapter first, so that Http2Adapter + // has data to serialize and return in this method. + std::string GetBytesToWrite(); + + // Returns the connection-level flow control window for the peer. + int GetPeerConnectionWindow() const; + + // Marks the given amount of data as consumed for the given stream, which + // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate. + void MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes); + + protected: + // Subclasses should expose a public factory method for constructing and + // initializing (via Initialize()) adapter instances. + explicit Http2Adapter(Http2VisitorInterface& visitor) : visitor_(visitor) {} + virtual ~Http2Adapter(); + + // Initializes the underlying HTTP/2 session and submits initial SETTINGS. + // Users must call Initialize() before doing other work with Http2Adapter. + // Because Initialize() calls virtual methods, this method cannot be called in + // the constructor. Factory methods may instead construct and initialize + // Http2Adapter instances to hide this implementation detail from users. + void Initialize(); + + // Creates the callbacks that will be used to initialize the |session_|. + virtual std::unique_ptr<Http2SessionCallbacks> CreateCallbacks(); + + // Creates with the given |callbacks| and |visitor| as context. + virtual std::unique_ptr<Http2Session> CreateSession( + std::unique_ptr<Http2SessionCallbacks> callbacks, + std::unique_ptr<Http2Options> options, + std::unique_ptr<Http2VisitorInterface> visitor) = 0; + + // Accessors. Do not transfer ownership. + Http2VisitorInterface& visitor() { return visitor_; } + + private: + // Creates the connection-level configuration options for the |session_|. + std::unique_ptr<Http2Options> CreateOptions(); + + // Http2Adapter will invoke callbacks upon the |visitor_| while processing. + Http2VisitorInterface& visitor_; + + // Http2Adapter creates a |session_| for use with the underlying library. + std::unique_ptr<Http2Session> session_; + + // Http2Adapter creates the |options_| for use with the |session_|. + std::unique_ptr<Http2Options> options_; +}; + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_
diff --git a/http2/adapter/http2_protocol.cc b/http2/adapter/http2_protocol.cc new file mode 100644 index 0000000..18e8985 --- /dev/null +++ b/http2/adapter/http2_protocol.cc
@@ -0,0 +1,68 @@ +#include "http2/adapter/http2_protocol.h" + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace http2 { +namespace adapter { + +const char kHttp2MethodPseudoHeader[] = ":method"; +const char kHttp2SchemePseudoHeader[] = ":scheme"; +const char kHttp2AuthorityPseudoHeader[] = ":authority"; +const char kHttp2PathPseudoHeader[] = ":path"; +const char kHttp2StatusPseudoHeader[] = ":status"; + +absl::string_view Http2SettingsIdToString(uint16_t id) { + switch (id) { + case Http2KnownSettingsId::HEADER_TABLE_SIZE: + return "SETTINGS_HEADER_TABLE_SIZE"; + case Http2KnownSettingsId::ENABLE_PUSH: + return "SETTINGS_ENABLE_PUSH"; + case Http2KnownSettingsId::MAX_CONCURRENT_STREAMS: + return "SETTINGS_MAX_CONCURRENT_STREAMS"; + case Http2KnownSettingsId::INITIAL_WINDOW_SIZE: + return "SETTINGS_INITIAL_WINDOW_SIZE"; + case Http2KnownSettingsId::MAX_FRAME_SIZE: + return "SETTINGS_MAX_FRAME_SIZE"; + case Http2KnownSettingsId::MAX_HEADER_LIST_SIZE: + return "SETTINGS_MAX_HEADER_LIST_SIZE"; + } + return "SETTINGS_UNKNOWN"; +} + +absl::string_view Http2ErrorCodeToString(Http2ErrorCode error_code) { + switch (error_code) { + case Http2ErrorCode::NO_ERROR: + return "NO_ERROR"; + case Http2ErrorCode::PROTOCOL_ERROR: + return "PROTOCOL_ERROR"; + case Http2ErrorCode::INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case Http2ErrorCode::FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case Http2ErrorCode::SETTINGS_TIMEOUT: + return "SETTINGS_TIMEOUT"; + case Http2ErrorCode::STREAM_CLOSED: + return "STREAM_CLOSED"; + case Http2ErrorCode::FRAME_SIZE_ERROR: + return "FRAME_SIZE_ERROR"; + case Http2ErrorCode::REFUSED_STREAM: + return "REFUSED_STREAM"; + case Http2ErrorCode::CANCEL: + return "CANCEL"; + case Http2ErrorCode::COMPRESSION_ERROR: + return "COMPRESSION_ERROR"; + case Http2ErrorCode::CONNECT_ERROR: + return "CONNECT_ERROR"; + case Http2ErrorCode::ENHANCE_YOUR_CALM: + return "ENHANCE_YOUR_CALM"; + case Http2ErrorCode::INADEQUATE_SECURITY: + return "INADEQUATE_SECURITY"; + case Http2ErrorCode::HTTP_1_1_REQUIRED: + return "HTTP_1_1_REQUIRED"; + } + return "UNKNOWN_ERROR"; +} + +} // namespace adapter +} // namespace http2
diff --git a/http2/adapter/http2_protocol.h b/http2/adapter/http2_protocol.h new file mode 100644 index 0000000..49e5f16 --- /dev/null +++ b/http2/adapter/http2_protocol.h
@@ -0,0 +1,105 @@ +#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_ +#define QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_ + +#include <cstdint> +#include <string> +#include <utility> + +#include "base/integral_types.h" +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" + +namespace http2 { +namespace adapter { + +// Represents an HTTP/2 stream ID, consistent with nghttp2. +using Http2StreamId = int32_t; + +// Represents an HTTP/2 SETTINGS parameter as specified in RFC 7540 Section 6.5. +using Http2SettingsId = uint16_t; + +// Represents the payload of an HTTP/2 PING frame. +using Http2PingId = uint64_t; + +// Represents an HTTP/2 header field. A header field is a key-value pair with +// lowercase keys (as specified in RFC 7540 Section 8.1.2). +using Header = std::pair<std::string, std::string>; + +// Represents an HTTP/2 SETTINGS key-value parameter. +struct Http2Setting { + Http2SettingsId id; + uint32_t value; +}; + +// The maximum possible stream ID. +const Http2StreamId kMaxStreamId = 0x7FFFFFFF; + +// The stream ID that represents the connection (e.g., for connection-level flow +// control updates). +const Http2StreamId kConnectionStreamId = 0; + +// The default value for the size of the largest frame payload, according to RFC +// 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE). +const int kDefaultFramePayloadSizeLimit = 16 * 1024; + +// The default value for the initial stream flow control window size, according +// to RFC 7540 Section 6.9.2. +const int kDefaultInitialStreamWindowSize = 64 * 1024 - 1; + +// The pseudo-header fields as specified in RFC 7540 Section 8.1.2.3 (request) +// and Section 8.1.2.4 (response). +ABSL_CONST_INIT extern const char kHttp2MethodPseudoHeader[]; +ABSL_CONST_INIT extern const char kHttp2SchemePseudoHeader[]; +ABSL_CONST_INIT extern const char kHttp2AuthorityPseudoHeader[]; +ABSL_CONST_INIT extern const char kHttp2PathPseudoHeader[]; +ABSL_CONST_INIT extern const char kHttp2StatusPseudoHeader[]; + +// HTTP/2 error codes as specified in RFC 7540 Section 7. +enum class Http2ErrorCode { + NO_ERROR = 0x0, + PROTOCOL_ERROR = 0x1, + INTERNAL_ERROR = 0x2, + FLOW_CONTROL_ERROR = 0x3, + SETTINGS_TIMEOUT = 0x4, + STREAM_CLOSED = 0x5, + FRAME_SIZE_ERROR = 0x6, + REFUSED_STREAM = 0x7, + CANCEL = 0x8, + COMPRESSION_ERROR = 0x9, + CONNECT_ERROR = 0xA, + ENHANCE_YOUR_CALM = 0xB, + INADEQUATE_SECURITY = 0xC, + HTTP_1_1_REQUIRED = 0xD, + MAX_ERROR_CODE = HTTP_1_1_REQUIRED, +}; + +// The SETTINGS parameters defined in RFC 7540 Section 6.5.2. Endpoints may send +// SETTINGS parameters outside of these definitions as per RFC 7540 Section 5.5. +// This is explicitly an enum instead of an enum class for ease of implicit +// conversion to the underlying Http2SettingsId type and use with non-standard +// extension SETTINGS parameters. +enum Http2KnownSettingsId : Http2SettingsId { + HEADER_TABLE_SIZE = 0x1, + MIN_SETTING = HEADER_TABLE_SIZE, + ENABLE_PUSH = 0x2, + MAX_CONCURRENT_STREAMS = 0x3, + INITIAL_WINDOW_SIZE = 0x4, + MAX_FRAME_SIZE = 0x5, + MAX_HEADER_LIST_SIZE = 0x6, + MAX_SETTING = MAX_HEADER_LIST_SIZE +}; + +// Returns a human-readable string representation of the given SETTINGS |id| for +// logging/debugging. Returns "SETTINGS_UNKNOWN" for IDs outside of the RFC 7540 +// Section 6.5.2 definitions. +absl::string_view Http2SettingsIdToString(uint16_t id); + +// Returns a human-readable string representation of the given |error_code| for +// logging/debugging. Returns "UNKNOWN_ERROR" for errors outside of RFC 7540 +// Section 7 definitions. +absl::string_view Http2ErrorCodeToString(Http2ErrorCode error_code); + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_
diff --git a/http2/adapter/http2_session.h b/http2/adapter/http2_session.h new file mode 100644 index 0000000..831cf73 --- /dev/null +++ b/http2/adapter/http2_session.h
@@ -0,0 +1,41 @@ +#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_ +#define QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_ + +#include <cstdint> + +#include "absl/strings/string_view.h" +#include "http2/adapter/http2_protocol.h" + +namespace http2 { +namespace adapter { + +struct Http2SessionCallbacks {}; + +// A class to represent the state of a single HTTP/2 connection. +class Http2Session { + public: + Http2Session() = default; + virtual ~Http2Session() {} + + virtual ssize_t ProcessBytes(absl::string_view bytes) = 0; + + virtual int Consume(Http2StreamId stream_id, size_t num_bytes) = 0; + + virtual bool want_read() = 0; + virtual bool want_write() = 0; + virtual int GetRemoteWindowSize() = 0; +}; + +class Http2Options { + public: + Http2Options() = default; + virtual ~Http2Options() {} + + // This method returns an opaque reference to the underlying type. + virtual void* GetOptions() = 0; +}; + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_
diff --git a/http2/adapter/http2_visitor_interface.h b/http2/adapter/http2_visitor_interface.h new file mode 100644 index 0000000..765e9f4 --- /dev/null +++ b/http2/adapter/http2_visitor_interface.h
@@ -0,0 +1,178 @@ +#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_ +#define QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_ + +#include <vector> + +#include "absl/strings/string_view.h" +#include "http2/adapter/http2_protocol.h" + +namespace http2 { +namespace adapter { + +// Http2VisitorInterface contains callbacks for receiving HTTP/2-level events. A +// processor like NghttpAdapter parses HTTP/2 frames and invokes the callbacks +// on an instance of this interface. Prefer a void return type for these +// callbacks, instead setting output parameters as needed. +// +// Example sequences of calls/events: +// GET: +// - OnBeginHeadersForStream() +// - OnHeaderForStream() +// - OnEndHeadersForStream() +// - OnEndStream() +// +// POST: +// - OnBeginHeadersForStream() +// - OnHeaderForStream() +// - OnEndHeadersForStream() +// - OnBeginDataForStream() +// - OnDataForStream() +// - OnEndStream() +// +// Request canceled mid-stream, e.g, with error code CANCEL: +// - OnBeginHeadersForStream() +// - OnHeaderForStream() +// - OnEndHeadersForStream() +// - OnRstStream() +// - OnAbortStream() +// +// Request closed mid-stream, e.g., with error code NO_ERROR: +// - OnBeginHeadersForStream() +// - OnHeaderForStream() +// - OnEndHeadersForStream() +// - OnRstStream() +// - OnCloseStream() +// +// More details are at RFC 7540 (go/http2spec), and more examples are at +// http://google3/net/http2/server/lib/internal/h2/nghttp2/nghttp2_server_adapter_test.cc. +class Http2VisitorInterface { + public: + Http2VisitorInterface(const Http2VisitorInterface&) = delete; + Http2VisitorInterface& operator=(const Http2VisitorInterface&) = delete; + virtual ~Http2VisitorInterface() = default; + + // Called when a connection-level processing error has been encountered. + virtual void OnConnectionError() = 0; + + // Called when a non-ack SETTINGS frame is received. + virtual void OnSettingsStart() = 0; + + // Called for each SETTINGS id-value pair. + virtual void OnSetting(Http2Setting setting) = 0; + + // Called at the end of a non-ack SETTINGS frame. + virtual void OnSettingsEnd() = 0; + + // Called when a SETTINGS ack frame is received. + virtual void OnSettingsAck() = 0; + + // Called when the connection receives the header block for a HEADERS frame on + // a stream but has not yet parsed individual headers. + virtual void OnBeginHeadersForStream(Http2StreamId stream_id) = 0; + + // Called when the connection receives the header |key| and |value| for a + // stream. The HTTP/2 pseudo-headers defined in RFC 7540 Sections 8.1.2.3 and + // 8.1.2.4 are also conveyed in this callback. This method is called after + // OnBeginHeadersForStream(). + virtual void OnHeaderForStream(Http2StreamId stream_id, absl::string_view key, + absl::string_view value) = 0; + + // Called when the connection has received the complete header block for a + // logical HEADERS frame on a stream (which may contain CONTINUATION frames, + // transparent to the user). + virtual void OnEndHeadersForStream(Http2StreamId stream_id) = 0; + + // Called when the connection receives the beginning of a DATA frame. The data + // payload will be provided via subsequent calls to OnDataForStream(). + virtual void OnBeginDataForStream(Http2StreamId stream_id, + size_t payload_length) = 0; + + // Called when the connection receives some |data| (as part of a DATA frame + // payload) for a stream. + virtual void OnDataForStream(Http2StreamId stream_id, + absl::string_view data) = 0; + + // Called when the peer sends the END_STREAM flag on a stream, indicating that + // the peer will not send additional headers or data for that stream. + virtual void OnEndStream(Http2StreamId stream_id) = 0; + + // Called when the connection receives a RST_STREAM for a stream. This call + // will be followed by either OnCloseStream() or OnAbortStream(). + virtual void OnRstStream(Http2StreamId stream_id, + Http2ErrorCode error_code) = 0; + + // Called when a stream is closed with error code NO_ERROR. Compare with + // OnAbortStream(). + virtual void OnCloseStream(Http2StreamId stream_id) = 0; + + // Called when a stream is aborted, i.e., closed for the reason indicated by + // the given |error_code|, where error_code != NO_ERROR. Compare with + // OnCloseStream(). + virtual void OnAbortStream(Http2StreamId stream_id, + Http2ErrorCode error_code) = 0; + + // Called when the connection receives a PRIORITY frame. + virtual void OnPriorityForStream(Http2StreamId stream_id, + Http2StreamId parent_stream_id, int weight, + bool exclusive) = 0; + + // Called when the connection receives a PING frame. + virtual void OnPing(Http2PingId ping_id, bool is_ack) = 0; + + // Called when the connection receives a PUSH_PROMISE frame. The server push + // request headers follow in calls to OnHeaderForStream() with |stream_id|. + virtual void OnPushPromiseForStream(Http2StreamId stream_id, + Http2StreamId promised_stream_id) = 0; + + // Called when the connection receives a GOAWAY frame. + virtual void OnGoAway(Http2StreamId last_accepted_stream_id, + Http2ErrorCode error_code, + absl::string_view opaque_data) = 0; + + // Called when the connection receives a WINDOW_UPDATE frame. For + // connection-level window updates, the |stream_id| will be 0. + virtual void OnWindowUpdate(Http2StreamId stream_id, + int window_increment) = 0; + + // Called when the connection is ready to send data for a stream. The + // implementation should write at most |length| bytes of the data payload to + // the |destination_buffer| and set |end_stream| to true IFF there will be no + // more data sent on this stream. Sets |written| to the number of bytes + // written to the |destination_buffer| or a negative value if an error occurs. + virtual void OnReadyToSendDataForStream(Http2StreamId stream_id, + char* destination_buffer, + size_t length, + ssize_t* written, + bool* end_stream) = 0; + + // Called when the connection is ready to write metadata for |stream_id| to + // the wire. The implementation should write at most |length| bytes of the + // serialized metadata payload to the |buffer| and set |written| to the number + // of bytes written or a negative value if there was an error. + virtual void OnReadyToSendMetadataForStream(Http2StreamId stream_id, + char* buffer, size_t length, + ssize_t* written) = 0; + + // Called when the connection receives the beginning of a METADATA frame + // (which may itself be the middle of a logical metadata block). The metadata + // payload will be provided via subsequent calls to OnMetadataForStream(). + virtual void OnBeginMetadataForStream(Http2StreamId stream_id, + size_t payload_length) = 0; + + // Called when the connection receives |metadata| as part of a METADATA frame + // payload for a stream. + virtual void OnMetadataForStream(Http2StreamId stream_id, + absl::string_view metadata) = 0; + + // Called when the connection has finished receiving a logical metadata block + // for a stream. Note that there may be multiple metadata blocks for a stream. + virtual void OnMetadataEndForStream(Http2StreamId stream_id) = 0; + + protected: + Http2VisitorInterface() = default; +}; + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_