diff --git a/.gitignore b/.gitignore index 767cb1e17ad..1e8da8b85d3 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ proxy/hdrs/test_mime proxy/hdrs/test_Huffmancode proxy/hdrs/test_XPACK proxy/http/test_ForwardedConfig +proxy/http2/test_libhttp2 proxy/http2/test_Http2DependencyTree proxy/http2/test_Http2FrequencyCounter proxy/http2/test_HPACK diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst index 3f15da66df0..2c512cc6ed8 100644 --- a/doc/admin-guide/logging/formatting.en.rst +++ b/doc/admin-guide/logging/formatting.en.rst @@ -185,6 +185,8 @@ Connections and Transactions .. _sstc: .. _ccid: .. _ctid: +.. _ctpw: +.. _ctpd: The following log fields are used to list various details of connections and transactions between |TS| proxies and origin servers. @@ -204,6 +206,10 @@ ctid Client Request Client Transaction ID, a non-negative number for a transact which is different for all currently-active transactions on the same client connection. For client HTTP/2 transactions, this value is the stream ID for the transaction. +ctpw Client Request Client Transaction Priority Weight, the priority weight for the + underlying HTTP/2 protocol. +ctpd Client Request Client Transaction Priority Dependence, the transaction ID that + the current transaction depends on for HTTP/2 priority logic. ===== ============== ================================================================== .. _admin-logging-fields-content-type: diff --git a/iocore/eventsystem/I_IOBuffer.h b/iocore/eventsystem/I_IOBuffer.h index ac652e36aab..852ccee1e4c 100644 --- a/iocore/eventsystem/I_IOBuffer.h +++ b/iocore/eventsystem/I_IOBuffer.h @@ -1184,13 +1184,13 @@ class MIOBuffer */ struct MIOBufferAccessor { IOBufferReader * - reader() + reader() const { return entry; } MIOBuffer * - writer() + writer() const { return mbuf; } diff --git a/iocore/eventsystem/I_VIO.h b/iocore/eventsystem/I_VIO.h index eacbc35e5f0..69f4890e10b 100644 --- a/iocore/eventsystem/I_VIO.h +++ b/iocore/eventsystem/I_VIO.h @@ -25,17 +25,12 @@ #pragma once #define I_VIO_h -#include "tscore/ink_platform.h" -#include "I_EventSystem.h" #if !defined(I_IOBuffer_h) #error "include I_IOBuffer.h" ----include I_IOBuffer.h #endif -#include "tscore/ink_apidefs.h" - class Continuation; + +class Continuation; class VConnection; -class IOVConnection; -class MIOBuffer; class ProxyMutex; /** @@ -73,9 +68,12 @@ class ProxyMutex; class VIO { public: + explicit VIO(int aop); + VIO(); ~VIO() {} + /** Interface for the VConnection that owns this handle. */ - Continuation *get_continuation(); + Continuation *get_continuation() const; void set_continuation(Continuation *cont); /** @@ -102,8 +100,8 @@ class VIO ///////////////////// void set_writer(MIOBuffer *writer); void set_reader(IOBufferReader *reader); - MIOBuffer *get_writer(); - IOBufferReader *get_reader(); + MIOBuffer *get_writer() const; + IOBufferReader *get_reader() const; /** Reenable the IO operation. @@ -140,10 +138,7 @@ class VIO inkcoreapi void reenable_re(); void disable(); - bool is_disabled(); - - VIO(int aop); - VIO(); + bool is_disabled() const; enum { NONE = 0, @@ -160,7 +155,6 @@ class VIO STAT, }; -public: /** Continuation to callback. @@ -225,5 +219,3 @@ class VIO private: bool _disabled = false; }; - -#include "I_VConnection.h" diff --git a/iocore/eventsystem/P_VIO.h b/iocore/eventsystem/P_VIO.h index 5c0d0572f8e..b09a1903267 100644 --- a/iocore/eventsystem/P_VIO.h +++ b/iocore/eventsystem/P_VIO.h @@ -27,44 +27,45 @@ TS_INLINE VIO::VIO(int aop) : cont(nullptr), nbytes(0), ndone(0), op(aop), buffer(), vc_server(nullptr), mutex(nullptr) {} -///////////////////////////////////////////////////////////// -// -// VIO::VIO() -// -///////////////////////////////////////////////////////////// TS_INLINE VIO::VIO() : cont(nullptr), nbytes(0), ndone(0), op(VIO::NONE), buffer(), vc_server(nullptr), mutex(nullptr) {} TS_INLINE Continuation * -VIO::get_continuation() +VIO::get_continuation() const { return cont; } + TS_INLINE void VIO::set_writer(MIOBuffer *writer) { buffer.writer_for(writer); } + TS_INLINE void VIO::set_reader(IOBufferReader *reader) { buffer.reader_for(reader); } + TS_INLINE MIOBuffer * -VIO::get_writer() +VIO::get_writer() const { return buffer.writer(); } + TS_INLINE IOBufferReader * -VIO::get_reader() +VIO::get_reader() const { return (buffer.reader()); } + TS_INLINE int64_t VIO::ntodo() const { return nbytes - ndone; } + TS_INLINE void VIO::done() { @@ -75,11 +76,6 @@ VIO::done() } } -///////////////////////////////////////////////////////////// -// -// VIO::set_continuation() -// -///////////////////////////////////////////////////////////// TS_INLINE void VIO::set_continuation(Continuation *acont) { @@ -96,11 +92,6 @@ VIO::set_continuation(Continuation *acont) return; } -///////////////////////////////////////////////////////////// -// -// VIO::reenable() -// -///////////////////////////////////////////////////////////// TS_INLINE void VIO::reenable() { @@ -110,11 +101,6 @@ VIO::reenable() } } -///////////////////////////////////////////////////////////// -// -// VIO::reenable_re() -// -///////////////////////////////////////////////////////////// TS_INLINE void VIO::reenable_re() { @@ -131,7 +117,7 @@ VIO::disable() } TS_INLINE bool -VIO::is_disabled() +VIO::is_disabled() const { return this->_disabled; } diff --git a/proxy/ProxyClientTransaction.cc b/proxy/ProxyClientTransaction.cc index 17c1c93f5e4..dcbdf5f09db 100644 --- a/proxy/ProxyClientTransaction.cc +++ b/proxy/ProxyClientTransaction.cc @@ -115,3 +115,15 @@ ProxyClientTransaction::set_tx_error_code(ProxyError e) this->current_reader->t_state.client_info.tx_error_code = e; } } + +int +ProxyClientTransaction::get_transaction_priority_weight() const +{ + return 0; +} + +int +ProxyClientTransaction::get_transaction_priority_dependence() const +{ + return 0; +} diff --git a/proxy/ProxyClientTransaction.h b/proxy/ProxyClientTransaction.h index 12fcf30cade..560c5c6f2e4 100644 --- a/proxy/ProxyClientTransaction.h +++ b/proxy/ProxyClientTransaction.h @@ -235,6 +235,8 @@ class ProxyClientTransaction : public VConnection return current_reader; } + virtual int get_transaction_priority_weight() const; + virtual int get_transaction_priority_dependence() const; virtual bool allow_half_open() const = 0; virtual const char * diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h index 9e01142ad8a..d23a0b7efed 100644 --- a/proxy/hdrs/HTTP.h +++ b/proxy/hdrs/HTTP.h @@ -618,7 +618,7 @@ class HTTPHdr : public MIMEHdr /// header internals, they must be able to do this. void mark_target_dirty() const; - HTTPStatus status_get(); + HTTPStatus status_get() const; void status_set(HTTPStatus status); const char *reason_get(int *length); @@ -635,6 +635,7 @@ class HTTPHdr : public MIMEHdr bool is_cache_control_set(const char *cc_directive_wks); bool is_pragma_no_cache_set(); bool is_keep_alive_set() const; + bool expect_final_response() const; HTTPKeepAlive keep_alive_get() const; protected: @@ -1004,6 +1005,24 @@ HTTPHdr::is_keep_alive_set() const return this->keep_alive_get() == HTTP_KEEPALIVE; } +/** + Check the status code is informational and expecting final response + - e.g. "100 Continue", "103 Early Hints" + + Please note that "101 Switching Protocol" is not included. + */ +inline bool +HTTPHdr::expect_final_response() const +{ + switch (this->status_get()) { + case HTTP_STATUS_CONTINUE: + case HTTP_STATUS_EARLY_HINTS: + return true; + default: + return false; + } +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ @@ -1154,7 +1173,7 @@ http_hdr_status_get(HTTPHdrImpl *hh) -------------------------------------------------------------------------*/ inline HTTPStatus -HTTPHdr::status_get() +HTTPHdr::status_get() const { ink_assert(valid()); diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index e23ab53a9c1..e431c40d823 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -432,7 +432,9 @@ HttpSM::attach_client_session(ProxyClientTransaction *client_vc, IOBufferReader // It seems to be possible that the ua_txn pointer will go stale before log entries for this HTTP transaction are // generated. Therefore, collect information that may be needed for logging from the ua_txn object at this point. // - _client_transaction_id = ua_txn->get_transaction_id(); + _client_transaction_id = ua_txn->get_transaction_id(); + _client_transaction_priority_weight = ua_txn->get_transaction_priority_weight(); + _client_transaction_priority_dependence = ua_txn->get_transaction_priority_dependence(); { auto p = ua_txn->get_parent(); @@ -2694,6 +2696,8 @@ HttpSM::tunnel_handler_post(int event, void *data) handle_post_failure(); break; case HTTP_SM_POST_UA_FAIL: + // Client side failed. Shutdown and go home. No need to communicate back to UA + terminate_sm = true; break; case HTTP_SM_POST_SUCCESS: // It's time to start reading the response @@ -3439,9 +3443,7 @@ HttpSM::tunnel_handler_post_ua(int event, HttpTunnelProducer *p) hsm_release_assert(ua_entry->in_tunnel == true); if (p->consumer_list.head && p->consumer_list.head->vc_type == HT_TRANSFORM) { hsm_release_assert(post_transform_info.entry->in_tunnel == true); - } else if (server_entry != nullptr) { - hsm_release_assert(server_entry->in_tunnel == true); - } + } // server side may have completed before the user agent side, so it may no longer be in tunnel break; case VC_EVENT_READ_COMPLETE: @@ -3578,7 +3580,8 @@ HttpSM::tunnel_handler_post_server(int event, HttpTunnelConsumer *c) case VC_EVENT_WRITE_COMPLETE: // Completed successfully - c->write_success = true; + c->write_success = true; + server_entry->in_tunnel = false; break; default: ink_release_assert(0); diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h index b49e203ee3f..ebfdeba0ece 100644 --- a/proxy/http/HttpSM.h +++ b/proxy/http/HttpSM.h @@ -617,9 +617,22 @@ class HttpSM : public Continuation return _client_transaction_id; } + int + client_transaction_priority_weight() const + { + return _client_transaction_priority_weight; + } + + int + client_transaction_priority_dependence() const + { + return _client_transaction_priority_dependence; + } + private: PostDataBuffers _postbuf; int _client_connection_id = -1, _client_transaction_id = -1; + int _client_transaction_priority_weight = -1, _client_transaction_priority_dependence = -1; }; // Function to get the cache_sm object - YTS Team, yamsat diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 72680ae96c3..cebef72a85d 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -6833,10 +6833,10 @@ HttpTransact::handle_response_keep_alive_headers(State *s, HTTPVersion ver, HTTP // to the client to keep the connection alive. // Insert a Transfer-Encoding header in the response if necessary. - // check that the client is HTTP 1.1 and the conf allows chunking or the client - // protocol unchunks before returning to the user agent (i.e. is http/2) - if (s->client_info.http_version == HTTPVersion(1, 1) && s->txn_conf->chunking_enabled == 1 && - s->state_machine->ua_txn->is_chunked_encoding_supported() && + // check that the client protocol is HTTP/1.1 and the conf allows chunking or + // the client protocol doesn't support chunked transfer coding (i.e. HTTP/1.0, HTTP/2) + if (s->state_machine->ua_txn && s->state_machine->ua_txn->is_chunked_encoding_supported() && + s->client_info.http_version == HTTPVersion(1, 1) && s->txn_conf->chunking_enabled == 1 && // if we're not sending a body, don't set a chunked header regardless of server response !is_response_body_precluded(s->hdr_info.client_response.status_get(), s->method) && // we do not need chunked encoding for internal error messages @@ -6868,12 +6868,13 @@ HttpTransact::handle_response_keep_alive_headers(State *s, HTTPVersion ver, HTTP } // Close the connection if client_info is not keep-alive. - // Otherwise, if we cannot trust the content length and the client process chunked encoding, we will close the connection - // unless we are going to use chunked encoding or the client issued - // a PUSH request + // Otherwise, if we cannot trust the content length, we will close the connection + // unless we are going to use chunked encoding on HTTP/1.1 or the client issued a PUSH request if (s->client_info.keep_alive != HTTP_KEEPALIVE) { ka_action = KA_DISABLED; - } else if (s->hdr_info.trust_response_cl == false && s->state_machine->ua_txn->is_chunked_encoding_supported() && + } else if (s->state_machine->client_protocol && (strncmp(s->state_machine->client_protocol, "http/2", 6) == 0)) { + ka_action = KA_CONNECTION; + } else if (s->hdr_info.trust_response_cl == false && !(s->client_info.receive_chunked_response == true || (s->method == HTTP_WKSIDX_PUSH && s->client_info.keep_alive == HTTP_KEEPALIVE))) { ka_action = KA_CLOSE; diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 5be510856bd..7b40df7efac 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -241,24 +241,6 @@ http2_write_frame_header(const Http2FrameHeader &hdr, IOVec iov) return true; } -bool -http2_write_data(const uint8_t *src, size_t length, const IOVec &iov) -{ - byte_pointer ptr(iov.iov_base); - write_and_advance(ptr, src, length); - - return true; -} - -bool -http2_write_headers(const uint8_t *src, size_t length, const IOVec &iov) -{ - byte_pointer ptr(iov.iov_base); - write_and_advance(ptr, src, length); - - return true; -} - bool http2_write_rst_stream(uint32_t error_code, IOVec iov) { diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 660ecbb4bf0..6b193e7dae6 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -33,6 +33,9 @@ class HTTPHdr; typedef unsigned Http2StreamId; +constexpr Http2StreamId HTTP2_CONNECTION_CONTROL_STRTEAM = 0; +constexpr uint8_t HTTP2_FRAME_NO_FLAG = 0; + // [RFC 7540] 6.9.2. Initial Flow Control Window Size // the flow control window can be come negative so we need to track it with a signed type. typedef int32_t Http2WindowSize; @@ -302,9 +305,8 @@ struct Http2RstStream { // [RFC 7540] 6.6 PUSH_PROMISE Format struct Http2PushPromise { - Http2PushPromise() : pad_length(0), promised_streamid(0) {} - uint8_t pad_length; - Http2StreamId promised_streamid; + uint8_t pad_length = 0; + Http2StreamId promised_streamid = 0; }; static inline bool @@ -323,10 +325,6 @@ bool http2_parse_frame_header(IOVec, Http2FrameHeader &); bool http2_write_frame_header(const Http2FrameHeader &, IOVec); -bool http2_write_data(const uint8_t *, size_t, const IOVec &); - -bool http2_write_headers(const uint8_t *, size_t, const IOVec &); - bool http2_write_rst_stream(uint32_t, IOVec); bool http2_write_settings(const Http2SettingsParameter &, const IOVec &); diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index 422be4f5d3e..b014e0ee810 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -216,7 +216,8 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB this->h2_pushed_urls = ink_hash_table_create(InkHashTableKeyType_String); this->h2_pushed_urls_size = 0; - this->write_buffer = new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); + // Set write buffer size to max size of TLS record (16KB) + this->write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_16K); this->sm_writer = this->write_buffer->alloc_reader(); do_api_callout(TS_HTTP_SSN_START_HOOK); @@ -325,6 +326,19 @@ Http2ClientSession::set_half_close_local_flag(bool flag) half_close_local = flag; } +int64_t +Http2ClientSession::xmit(const Http2TxFrame &frame) +{ + int64_t len = frame.write_to(this->write_buffer); + + if (len > 0) { + total_write_len += len; + write_reenable(); + } + + return len; +} + int Http2ClientSession::main_event_handler(int event, void *edata) { @@ -349,16 +363,6 @@ Http2ClientSession::main_event_handler(int event, void *edata) break; } - case HTTP2_SESSION_EVENT_XMIT: { - Http2Frame *frame = (Http2Frame *)edata; - total_write_len += frame->size(); - write_vio->nbytes = total_write_len; - frame->xmit(this->write_buffer); - write_reenable(); - retval = 0; - break; - } - case HTTP2_SESSION_EVENT_REENABLE: // VIO will be reenableed in this handler retval = (this->*session_handler)(VC_EVENT_READ_READY, static_cast(e->cookie)); @@ -384,6 +388,7 @@ Http2ClientSession::main_event_handler(int event, void *edata) retval = 0; break; + case HTTP2_SESSION_EVENT_XMIT: default: Http2SsnDebug("unexpected event=%d edata=%p", event, edata); ink_release_assert(0); diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index d2b44cc2e65..c1e3bb301a3 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -27,6 +27,7 @@ #include "Plugin.h" #include "ProxyClientSession.h" #include "Http2ConnectionState.h" +#include "Http2Frame.h" #include #include "tscore/ink_inet.h" #include "tscore/History.h" @@ -77,95 +78,6 @@ struct Http2UpgradeContext { Http2ConnectionSettings client_settings; }; -class Http2Frame -{ -public: - Http2Frame(const Http2FrameHeader &h, IOBufferReader *r) - { - this->hdr = h; - this->ioreader = r; - } - - Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags) - { - this->hdr = {0, (uint8_t)type, flags, streamid}; - this->ioreader = nullptr; - } - - IOBufferReader * - reader() const - { - return ioreader; - } - - const Http2FrameHeader & - header() const - { - return this->hdr; - } - - // Allocate an IOBufferBlock for payload of this frame. - void - alloc(int index) - { - this->ioblock = new_IOBufferBlock(); - this->ioblock->alloc(index); - } - - // Return the writeable buffer space for frame payload - IOVec - write() - { - return make_iovec(this->ioblock->end(), this->ioblock->write_avail()); - } - - // Once the frame has been serialized, update the payload length of frame header. - void - finalize(size_t nbytes) - { - if (this->ioblock) { - ink_assert((int64_t)nbytes <= this->ioblock->write_avail()); - this->ioblock->fill(nbytes); - - this->hdr.length = this->ioblock->size(); - } - } - - void - xmit(MIOBuffer *iobuffer) - { - // Write frame header - uint8_t buf[HTTP2_FRAME_HEADER_LEN]; - http2_write_frame_header(hdr, make_iovec(buf)); - iobuffer->write(buf, sizeof(buf)); - - // Write frame payload - // It could be empty (e.g. SETTINGS frame with ACK flag) - if (ioblock && ioblock->read_avail() > 0) { - iobuffer->append_block(this->ioblock.get()); - } - } - - int64_t - size() - { - if (ioblock) { - return HTTP2_FRAME_HEADER_LEN + ioblock->size(); - } else { - return HTTP2_FRAME_HEADER_LEN; - } - } - - // noncopyable - Http2Frame(Http2Frame &) = delete; - Http2Frame &operator=(const Http2Frame &) = delete; - -private: - Http2FrameHeader hdr; // frame header - Ptr ioblock; // frame payload - IOBufferReader *ioreader; -}; - class Http2ClientSession : public ProxyClientSession { public: @@ -194,6 +106,7 @@ class Http2ClientSession : public ProxyClientSession // more methods void write_reenable(); + int64_t xmit(const Http2TxFrame &frame); //////////////////// // Accessors diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 869e0ee200a..1a3872f6f00 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -25,6 +25,7 @@ #include "Http2ConnectionState.h" #include "Http2ClientSession.h" #include "Http2Stream.h" +#include "Http2Frame.h" #include "Http2DebugNames.h" #include "HttpDebugNames.h" #include @@ -49,12 +50,12 @@ static const int buffer_size_index[HTTP2_FRAME_TYPE_MAX] = { BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_DATA BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_HEADERS -1, // HTTP2_FRAME_TYPE_PRIORITY - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_RST_STREAM - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_SETTINGS + -1, // HTTP2_FRAME_TYPE_RST_STREAM + -1, // HTTP2_FRAME_TYPE_SETTINGS BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_PUSH_PROMISE - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_PING - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_GOAWAY - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_WINDOW_UPDATE + -1, // HTTP2_FRAME_TYPE_PING + -1, // HTTP2_FRAME_TYPE_GOAWAY + -1, // HTTP2_FRAME_TYPE_WINDOW_UPDATE BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_CONTINUATION }; @@ -138,11 +139,17 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR, "recv data bad payload length"); } - } - // If Data length is 0, do nothing. - if (payload_length == 0) { - return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + // Pure END_STREAM + if (payload_length == 0) { + stream->signal_read_event(VC_EVENT_READ_COMPLETE); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + } + } else { + // If payload length is 0 without END_STREAM flag, do nothing + if (payload_length == 0) { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); + } } // Check whether Window Size is acceptable @@ -160,6 +167,11 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->decrement_server_rwnd(payload_length); const uint32_t unpadded_length = payload_length - pad_length; + MIOBuffer *writer = stream->read_vio_writer(); + if (writer == nullptr) { + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); + } + // If we call write() multiple times, we must keep the same reader, so we can // update its offset via consume. Otherwise, we will read the same data on the // second time through @@ -168,18 +180,28 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) if (frame.header().flags & HTTP2_FLAGS_DATA_PADDED) { myreader->consume(HTTP2_DATA_PADLEN_LEN); } - while (nbytes < payload_length - pad_length) { + + if (nbytes < unpadded_length) { size_t read_len = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_DATA]); if (nbytes + read_len > unpadded_length) { read_len -= nbytes + read_len - unpadded_length; } - nbytes += stream->request_buffer.write(myreader, read_len); - myreader->consume(nbytes); - // If there is an outstanding read, update the buffer - stream->update_read_request(INT64_MAX, true); + unsigned int num_written = writer->write(myreader, read_len); + if (num_written != read_len) { + myreader->writer()->dealloc_reader(myreader); + return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); + } + myreader->consume(num_written); } myreader->writer()->dealloc_reader(myreader); + if (frame.header().flags & HTTP2_FLAGS_DATA_END_STREAM) { + // TODO: set total written size to read_vio.nbytes + stream->signal_read_event(VC_EVENT_READ_COMPLETE); + } else { + stream->signal_read_event(VC_EVENT_READ_READY); + } + uint32_t initial_rwnd = cstate.server_settings.get(HTTP2_SETTINGS_INITIAL_WINDOW_SIZE); uint32_t min_rwnd = std::min(initial_rwnd, cstate.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)); // Connection level WINDOW UPDATE @@ -361,6 +383,9 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->new_transaction(); // Send request header to SM stream->send_request(cstate); + } else { + // Signal VC_EVENT_READ_COMPLETE becasue received trailing header fields with END_STREAM flag + stream->signal_read_event(VC_EVENT_READ_COMPLETE); } } else { // NOTE: Expect CONTINUATION Frame. Do NOT change state of stream or decode @@ -628,8 +653,8 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST // immediately emit a SETTINGS frame with the ACK flag set. - Http2Frame ackFrame(HTTP2_FRAME_TYPE_SETTINGS, 0, HTTP2_FLAGS_SETTINGS_ACK); - cstate.ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ackFrame); + Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK); + cstate.ua_session->xmit(ack_frame); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } @@ -1415,7 +1440,7 @@ Http2ConnectionState::send_data_frames_depends_on_priority() switch (result) { case Http2SendDataFrameResult::NO_ERROR: { // No response body to send - if (len == 0 && !stream->is_body_done()) { + if (len == 0 && !stream->is_write_vio_done()) { dependency_tree->deactivate(node, len); } else { dependency_tree->update(node, len); @@ -1446,32 +1471,31 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len const ssize_t window_size = std::min(this->client_rwnd(), stream->client_rwnd()); const size_t buf_len = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_DATA]); const size_t write_available_size = std::min(buf_len, static_cast(window_size)); - size_t read_available_size = 0; payload_length = 0; - uint8_t flags = 0x00; - uint8_t payload_buffer[buf_len]; - IOBufferReader *current_reader = stream->response_get_data_reader(); + uint8_t flags = 0x00; + IOBufferReader *resp_reader = stream->response_get_data_reader(); SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); - if (current_reader) { - read_available_size = static_cast(current_reader->read_avail()); - } else { + if (!resp_reader) { Http2StreamDebug(this->ua_session, stream->get_id(), "couldn't get data reader"); return Http2SendDataFrameResult::ERROR; } // Select appropriate payload length - if (read_available_size > 0) { + if (resp_reader->is_read_avail_more_than(0)) { // We only need to check for window size when there is a payload if (window_size <= 0) { Http2StreamDebug(this->ua_session, stream->get_id(), "No window"); return Http2SendDataFrameResult::NO_WINDOW; } - // Copy into the payload buffer. Seems like we should be able to skip this copy step - payload_length = std::min(read_available_size, write_available_size); - current_reader->memcpy(payload_buffer, static_cast(payload_length)); + + if (resp_reader->is_read_avail_more_than(write_available_size)) { + payload_length = write_available_size; + } else { + payload_length = resp_reader->read_avail(); + } } else { payload_length = 0; } @@ -1479,12 +1503,12 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len // Are we at the end? // If we return here, we never send the END_STREAM in the case of a early terminating OS. // OK if there is no body yet. Otherwise continue on to send a DATA frame and delete the stream - if (!stream->is_body_done() && payload_length == 0) { + if (!stream->is_write_vio_done() && payload_length == 0) { Http2StreamDebug(this->ua_session, stream->get_id(), "No payload"); return Http2SendDataFrameResult::NO_PAYLOAD; } - if (stream->is_body_done() && read_available_size <= write_available_size) { + if (stream->is_write_vio_done() && !resp_reader->is_read_avail_more_than(0)) { flags |= HTTP2_FLAGS_DATA_END_STREAM; } @@ -1496,23 +1520,16 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len Http2StreamDebug(ua_session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd", _client_rwnd, stream->client_rwnd(), payload_length); - Http2Frame data(HTTP2_FRAME_TYPE_DATA, stream->get_id(), flags); - data.alloc(buffer_size_index[HTTP2_FRAME_TYPE_DATA]); - http2_write_data(payload_buffer, payload_length, data.write()); - data.finalize(payload_length); + Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); + this->ua_session->xmit(data); stream->update_sent_count(payload_length); - current_reader->consume(payload_length); - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &data); if (flags & HTTP2_FLAGS_DATA_END_STREAM) { - Http2StreamDebug(ua_session, stream->get_id(), "End of DATA frame"); + Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM"); stream->send_end_stream = true; // Setting to the same state shouldn't be erroneous - stream->change_state(data.header().type, data.header().flags); + stream->change_state(HTTP2_FRAME_TYPE_DATA, flags); return Http2SendDataFrameResult::DONE; } @@ -1557,10 +1574,8 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) uint32_t buf_len = 0; uint32_t header_blocks_size = 0; int payload_length = 0; - uint64_t sent = 0; uint8_t flags = 0x00; - - HTTPHdr *resp_header = &stream->response_header; + HTTPHdr *resp_header = &stream->response_header; Http2StreamDebug(ua_session, stream->get_id(), "Send HEADERS frame"); @@ -1587,7 +1602,9 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) if (header_blocks_size <= static_cast(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]))) { payload_length = header_blocks_size; flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; - if (h2_hdr.presence(MIME_PRESENCE_CONTENT_LENGTH) && h2_hdr.get_content_length() == 0) { + if ((h2_hdr.presence(MIME_PRESENCE_CONTENT_LENGTH) && h2_hdr.get_content_length() == 0) || + (!resp_header->expect_final_response() && stream->is_write_vio_done())) { + Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM"); flags |= HTTP2_FLAGS_HEADERS_END_STREAM; stream->send_end_stream = true; } @@ -1595,10 +1612,6 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) } else { payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); } - Http2Frame headers(HTTP2_FRAME_TYPE_HEADERS, stream->get_id(), flags); - headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); - http2_write_headers(buf, payload_length, headers.write()); - headers.finalize(payload_length); // Change stream state if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, flags)) { @@ -1613,10 +1626,9 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) return; } - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); - sent += payload_length; + Http2HeadersFrame headers(stream->get_id(), flags, buf, payload_length); + this->ua_session->xmit(headers); + uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; @@ -1627,14 +1639,10 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) if (sent + payload_length == header_blocks_size) { flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS; } - Http2Frame headers(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags); - headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]); - http2_write_headers(buf + sent, payload_length, headers.write()); - headers.finalize(payload_length); - stream->change_state(headers.header().type, headers.header().flags); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); + stream->change_state(HTTP2_FRAME_TYPE_CONTINUATION, flags); + + Http2ContinuationFrame continuation_frame(stream->get_id(), flags, buf + sent, payload_length); + this->ua_session->xmit(continuation_frame); sent += payload_length; } @@ -1650,7 +1658,6 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con uint32_t buf_len = 0; uint32_t header_blocks_size = 0; int payload_length = 0; - uint64_t sent = 0; uint8_t flags = 0x00; if (client_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) { @@ -1704,16 +1711,13 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]) - sizeof(push_promise.promised_streamid); } - Http2Frame headers(HTTP2_FRAME_TYPE_PUSH_PROMISE, stream->get_id(), flags); - headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]); + Http2StreamId id = this->get_latest_stream_id_out() + 2; push_promise.promised_streamid = id; - http2_write_push_promise(push_promise, buf, payload_length, headers.write()); - headers.finalize(sizeof(push_promise.promised_streamid) + payload_length); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); - sent += payload_length; + + Http2PushPromiseFrame push_promise_frame(stream->get_id(), flags, push_promise, buf, payload_length); + this->ua_session->xmit(push_promise_frame); + uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; @@ -1724,13 +1728,9 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con if (sent + payload_length == header_blocks_size) { flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS; } - Http2Frame headers(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags); - headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]); - http2_write_headers(buf + sent, payload_length, headers.write()); - headers.finalize(payload_length); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); + + Http2ContinuationFrame continuation(stream->get_id(), flags, buf + sent, payload_length); + this->ua_session->xmit(continuation); sent += payload_length; } ats_free(buf); @@ -1774,12 +1774,6 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) ++stream_error_count; } - Http2Frame rst_stream(HTTP2_FRAME_TYPE_RST_STREAM, id, 0); - - rst_stream.alloc(buffer_size_index[HTTP2_FRAME_TYPE_RST_STREAM]); - http2_write_rst_stream(static_cast(ec), rst_stream.write()); - rst_stream.finalize(HTTP2_RST_STREAM_LEN); - // change state to closed Http2Stream *stream = find_stream(id); if (stream != nullptr) { @@ -1795,9 +1789,8 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) } } - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &rst_stream); + Http2RstStreamFrame rst_stream(id, static_cast(ec)); + this->ua_session->xmit(rst_stream); } void @@ -1807,11 +1800,8 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set Http2StreamDebug(ua_session, stream_id, "Send SETTINGS frame"); - Http2Frame settings(HTTP2_FRAME_TYPE_SETTINGS, stream_id, 0); - settings.alloc(buffer_size_index[HTTP2_FRAME_TYPE_SETTINGS]); - - IOVec iov = settings.write(); - uint32_t settings_length = 0; + Http2SettingsParameter params[HTTP2_SETTINGS_MAX]; + size_t params_size = 0; for (int i = HTTP2_SETTINGS_HEADER_TABLE_SIZE; i < HTTP2_SETTINGS_MAX; ++i) { Http2SettingsIdentifier id = static_cast(i); @@ -1819,32 +1809,17 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set // Send only difference if (settings_value != server_settings.get(id)) { - const Http2SettingsParameter param = {static_cast(id), settings_value}; + Http2StreamDebug(ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value); - // Write settings to send buffer - if (!http2_write_settings(param, iov)) { - this->send_goaway_frame(this->latest_streamid_in, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); - this->ua_session->set_half_close_local_flag(true); - if (fini_event == nullptr) { - fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); - } - - return; - } - iov.iov_base = reinterpret_cast(iov.iov_base) + HTTP2_SETTINGS_PARAMETER_LEN; - iov.iov_len -= HTTP2_SETTINGS_PARAMETER_LEN; - settings_length += HTTP2_SETTINGS_PARAMETER_LEN; + params[params_size++] = {static_cast(id), settings_value}; // Update current settings server_settings.set(id, new_settings.get(id)); - - Http2StreamDebug(ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(param.id), param.value); } } - settings.finalize(settings_length); - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &settings); + Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size); + this->ua_session->xmit(settings); } void @@ -1852,15 +1827,8 @@ Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint { Http2StreamDebug(ua_session, id, "Send PING frame"); - Http2Frame ping(HTTP2_FRAME_TYPE_PING, id, flag); - - ping.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PING]); - http2_write_ping(opaque_data, ping.write()); - ping.finalize(HTTP2_PING_LEN); - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ping); + Http2PingFrame ping(id, flag, opaque_data); + this->ua_session->xmit(ping); } // As for gracefull shutdown, TS should process outstanding stream as long as possible. @@ -1876,21 +1844,14 @@ Http2ConnectionState::send_goaway_frame(Http2StreamId id, Http2ErrorCode ec) HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CONNECTION_ERRORS_COUNT, this_ethread()); } - Http2Frame frame(HTTP2_FRAME_TYPE_GOAWAY, 0, 0); - Http2Goaway goaway; + this->tx_error_code = {ProxyErrorClass::SSN, static_cast(ec)}; + Http2Goaway goaway; goaway.last_streamid = id; goaway.error_code = ec; - frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_GOAWAY]); - http2_write_goaway(goaway, frame.write()); - frame.finalize(HTTP2_GOAWAY_LEN); - - this->tx_error_code = {ProxyErrorClass::SSN, static_cast(ec)}; - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &frame); + Http2GoawayFrame frame(goaway); + this->ua_session->xmit(frame); } void @@ -1899,14 +1860,8 @@ Http2ConnectionState::send_window_update_frame(Http2StreamId id, uint32_t size) Http2StreamDebug(ua_session, id, "Send WINDOW_UPDATE frame"); // Create WINDOW_UPDATE frame - Http2Frame window_update(HTTP2_FRAME_TYPE_WINDOW_UPDATE, id, 0x0); - window_update.alloc(buffer_size_index[HTTP2_FRAME_TYPE_WINDOW_UPDATE]); - http2_write_window_update(static_cast(size), window_update.write()); - window_update.finalize(sizeof(uint32_t)); - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &window_update); + Http2WindowUpdateFrame window_update(id, size); + this->ua_session->xmit(window_update); } void diff --git a/proxy/http2/Http2Frame.cc b/proxy/http2/Http2Frame.cc new file mode 100644 index 00000000000..a731f09e2a8 --- /dev/null +++ b/proxy/http2/Http2Frame.cc @@ -0,0 +1,253 @@ +/** @file + + Http2Frame + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "Http2Frame.h" + +// +// Http2Frame +// +IOBufferReader * +Http2Frame::reader() const +{ + return this->_ioreader; +} + +const Http2FrameHeader & +Http2Frame::header() const +{ + return this->_hdr; +} + +bool +Http2Frame::is_from_early_data() const +{ + return this->_from_early_data; +} + +// +// DATA Frame +// +int64_t +Http2DataFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_reader && this->_payload_len > 0) { + int64_t written = 0; + // Fill current IOBufferBlock as much as possible to reduce SSL_write() calls + while (written < this->_payload_len) { + int64_t read_len = std::min(this->_payload_len - written, this->_reader->block_read_avail()); + written += iobuffer->write(this->_reader->start(), read_len); + this->_reader->consume(read_len); + } + len += written; + } + + return len; +} + +// +// HEADERS Frame +// +int64_t +Http2HeadersFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_hdr_block && this->_hdr_block_len > 0) { + len += iobuffer->write(this->_hdr_block, this->_hdr_block_len); + } + + return len; +} + +// +// PRIORITY Frame +// +int64_t +Http2PriorityFrame::write_to(MIOBuffer *iobuffer) const +{ + ink_abort("not supported yet"); + + return 0; +} + +// +// RST_STREM Frame +// +int64_t +Http2RstStreamFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_RST_STREAM_LEN]; + http2_write_rst_stream(this->_error_code, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// SETTINGS Frame +// +int64_t +Http2SettingsFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + for (uint32_t i = 0; i < this->_psize; ++i) { + Http2SettingsParameter *p = this->_params + i; + + uint8_t p_buf[HTTP2_SETTINGS_PARAMETER_LEN]; + http2_write_settings(*p, make_iovec(p_buf)); + len += iobuffer->write(p_buf, sizeof(p_buf)); + } + + return len; +} + +// +// PUSH_PROMISE Frame +// +int64_t +Http2PushPromiseFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t p_buf[HTTP2_MAX_FRAME_SIZE]; + http2_write_push_promise(this->_params, this->_hdr_block, this->_hdr_block_len, make_iovec(p_buf)); + len += iobuffer->write(p_buf, sizeof(Http2StreamId) + this->_hdr_block_len); + + return len; +} + +// +// PING Frame +// +int64_t +Http2PingFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_PING_LEN] = {0}; + http2_write_ping(this->_opaque_data, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// GOAWAY Frame +// +int64_t +Http2GoawayFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_GOAWAY_LEN]; + http2_write_goaway(this->_params, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// WINDOW_UPDATE Frame +// +int64_t +Http2WindowUpdateFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_WINDOW_UPDATE_LEN]; + http2_write_window_update(this->_window, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// CONTINUATION Frame +// +int64_t +Http2ContinuationFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_hdr_block && this->_hdr_block_len > 0) { + len += iobuffer->write(this->_hdr_block, this->_hdr_block_len); + } + + return len; +} diff --git a/proxy/http2/Http2Frame.h b/proxy/http2/Http2Frame.h new file mode 100644 index 00000000000..b0bcd35d022 --- /dev/null +++ b/proxy/http2/Http2Frame.h @@ -0,0 +1,256 @@ +/** @file + + Http2Frame + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "P_Net.h" + +#include "HTTP2.h" + +/** + Incoming HTTP/2 Frame + */ +class Http2Frame +{ +public: + Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : _hdr(h), _ioreader(r), _from_early_data(e) {} + + // Accessor + IOBufferReader *reader() const; + const Http2FrameHeader &header() const; + bool is_from_early_data() const; + +private: + Http2FrameHeader _hdr; + IOBufferReader *_ioreader = nullptr; + bool _from_early_data = false; +}; + +/** + Outgoing HTTP/2 Frame + */ +class Http2TxFrame +{ +public: + Http2TxFrame(const Http2FrameHeader &h) : _hdr(h) {} + virtual ~Http2TxFrame() {} + + // Don't allocate on heap + void *operator new(std::size_t) = delete; + void *operator new[](std::size_t) = delete; + + virtual int64_t write_to(MIOBuffer *iobuffer) const = 0; + +protected: + Http2FrameHeader _hdr; +}; + +/** + DATA Frame + */ +class Http2DataFrame : public Http2TxFrame +{ +public: + Http2DataFrame(Http2StreamId stream_id, uint8_t flags, IOBufferReader *r, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_DATA, flags, stream_id}), _reader(r), _payload_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + IOBufferReader *_reader = nullptr; + uint32_t _payload_len = 0; +}; + +/** + HEADERS Frame + + TODO: support priority info & padding using Http2HeadersParameter + */ +class Http2HeadersFrame : public Http2TxFrame +{ +public: + Http2HeadersFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_HEADERS, flags, stream_id}), _hdr_block(h), _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; + +/** + PRIORITY Frame + + TODO: implement xmit function + */ +class Http2PriorityFrame : public Http2TxFrame +{ +public: + Http2PriorityFrame(Http2StreamId stream_id, uint8_t flags, Http2Priority p) + : Http2TxFrame({HTTP2_PRIORITY_LEN, HTTP2_FRAME_TYPE_PRIORITY, flags, stream_id}), _params(p) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2Priority _params; +}; + +/** + RST_STREAM Frame + */ +class Http2RstStreamFrame : public Http2TxFrame +{ +public: + Http2RstStreamFrame(Http2StreamId stream_id, uint32_t e) + : Http2TxFrame({HTTP2_RST_STREAM_LEN, HTTP2_FRAME_TYPE_RST_STREAM, HTTP2_FRAME_NO_FLAG, stream_id}), _error_code(e) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint32_t _error_code; +}; + +/** + SETTINGS Frame + */ +class Http2SettingsFrame : public Http2TxFrame +{ +public: + Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags) : Http2TxFrame({0, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}) {} + Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags, Http2SettingsParameter *p, uint32_t s) + : Http2TxFrame({static_cast(HTTP2_SETTINGS_PARAMETER_LEN) * s, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}), + _params(p), + _psize(s) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2SettingsParameter *_params = nullptr; + uint32_t _psize = 0; +}; + +/** + PUSH_PROMISE Frame + + TODO: support padding + */ +class Http2PushPromiseFrame : public Http2TxFrame +{ +public: + Http2PushPromiseFrame(Http2StreamId stream_id, uint8_t flags, Http2PushPromise p, uint8_t *h, uint32_t l) + : Http2TxFrame({l + static_cast(sizeof(Http2StreamId)), HTTP2_FRAME_TYPE_PUSH_PROMISE, flags, stream_id}), + _params(p), + _hdr_block(h), + _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2PushPromise _params; + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; + +/** + PING Frame + */ +class Http2PingFrame : public Http2TxFrame +{ +public: + Http2PingFrame(Http2StreamId stream_id, uint8_t flags, const uint8_t *data) + : Http2TxFrame({HTTP2_PING_LEN, HTTP2_FRAME_TYPE_PING, flags, stream_id}), _opaque_data(data) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + const uint8_t *_opaque_data; +}; + +/** + GOAWAY Frame + + TODO: support Additional Debug Data + */ +class Http2GoawayFrame : public Http2TxFrame +{ +public: + Http2GoawayFrame(Http2Goaway p) + : Http2TxFrame({HTTP2_GOAWAY_LEN, HTTP2_FRAME_TYPE_GOAWAY, HTTP2_FRAME_NO_FLAG, HTTP2_CONNECTION_CONTROL_STRTEAM}), _params(p) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2Goaway _params; +}; + +/** + WINDOW_UPDATE Frame + */ +class Http2WindowUpdateFrame : public Http2TxFrame +{ +public: + Http2WindowUpdateFrame(Http2StreamId stream_id, uint32_t w) + : Http2TxFrame({HTTP2_WINDOW_UPDATE_LEN, HTTP2_FRAME_TYPE_WINDOW_UPDATE, HTTP2_FRAME_NO_FLAG, stream_id}), _window(w) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint32_t _window = 0; +}; + +/** + CONTINUATION Frame + */ +class Http2ContinuationFrame : public Http2TxFrame +{ +public: + Http2ContinuationFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_CONTINUATION, flags, stream_id}), _hdr_block(h), _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; diff --git a/proxy/http2/Http2FrequencyCounter.h b/proxy/http2/Http2FrequencyCounter.h index 0b6de174afc..bcd3dbba561 100644 --- a/proxy/http2/Http2FrequencyCounter.h +++ b/proxy/http2/Http2FrequencyCounter.h @@ -31,6 +31,7 @@ class Http2FrequencyCounter public: void increment(uint16_t amount = 1); uint32_t get_count(); + virtual ~Http2FrequencyCounter() {} protected: uint16_t _count[2] = {0}; diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index 076ca4840da..5107a3d36ac 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -55,11 +55,11 @@ Http2Stream::init(Http2StreamId sid, ssize_t initial_rwnd) HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CURRENT_CLIENT_STREAM_COUNT, _thread); HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_TOTAL_CLIENT_STREAM_COUNT, _thread); - sm_reader = request_reader = request_buffer.alloc_reader(); - http_parser_init(&http_parser); + sm_reader = this->_request_buffer.alloc_reader(); // FIXME: Are you sure? every "stream" needs request_header? _req_header.create(HTTP_TYPE_REQUEST); response_header.create(HTTP_TYPE_RESPONSE); + http_parser_init(&http_parser); } int @@ -76,7 +76,13 @@ Http2Stream::main_event_handler(int event, void *edata) Event *e = static_cast(edata); reentrancy_count++; - if (e == cross_thread_event) { + if (e == _read_vio_event) { + this->signal_read_event(e->callback_event); + return 0; + } else if (e == _write_vio_event) { + this->signal_write_event(e->callback_event); + return 0; + } else if (e == cross_thread_event) { cross_thread_event = nullptr; } else if (e == active_event) { event = VC_EVENT_ACTIVE_TIMEOUT; @@ -98,32 +104,17 @@ Http2Stream::main_event_handler(int event, void *edata) case VC_EVENT_ACTIVE_TIMEOUT: case VC_EVENT_INACTIVITY_TIMEOUT: if (current_reader && read_vio.ntodo() > 0) { - MUTEX_TRY_LOCK(lock, read_vio.mutex, this_ethread()); - if (lock.is_locked()) { - read_vio.cont->handleEvent(event, &read_vio); - } else { - this_ethread()->schedule_imm(read_vio.cont, event, &read_vio); - } + this->signal_read_event(event); } else if (current_reader && write_vio.ntodo() > 0) { - MUTEX_TRY_LOCK(lock, write_vio.mutex, this_ethread()); - if (lock.is_locked()) { - write_vio.cont->handleEvent(event, &write_vio); - } else { - this_ethread()->schedule_imm(write_vio.cont, event, &write_vio); - } + this->signal_write_event(event); } break; case VC_EVENT_WRITE_READY: case VC_EVENT_WRITE_COMPLETE: inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; if (e->cookie == &write_vio) { - if (write_vio.mutex) { - MUTEX_TRY_LOCK(lock, write_vio.mutex, this_ethread()); - if (lock.is_locked() && write_vio.cont && this->current_reader) { - write_vio.cont->handleEvent(event, &write_vio); - } else { - this_ethread()->schedule_imm(write_vio.cont, event, &write_vio); - } + if (write_vio.mutex && write_vio.cont && this->current_reader) { + this->signal_write_event(event); } } else { update_write_request(write_vio.get_reader(), INT64_MAX, true); @@ -133,13 +124,8 @@ Http2Stream::main_event_handler(int event, void *edata) case VC_EVENT_READ_READY: inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; if (e->cookie == &read_vio) { - if (read_vio.mutex) { - MUTEX_TRY_LOCK(lock, read_vio.mutex, this_ethread()); - if (lock.is_locked() && read_vio.cont && this->current_reader) { - read_vio.cont->handleEvent(event, &read_vio); - } else { - this_ethread()->schedule_imm(read_vio.cont, event, &read_vio); - } + if (read_vio.mutex && read_vio.cont && this->current_reader) { + signal_read_event(event); } } else { this->update_read_request(INT64_MAX, true); @@ -183,25 +169,33 @@ Http2Stream::send_request(Http2ConnectionState &cstate) int bufindex; int dumpoffset = 0; int done, tmp; - IOBufferBlock *block; do { - bufindex = 0; - tmp = dumpoffset; - block = request_buffer.get_current_block(); + bufindex = 0; + tmp = dumpoffset; + IOBufferBlock *block = this->_request_buffer.get_current_block(); if (!block) { - request_buffer.add_block(); - block = request_buffer.get_current_block(); + this->_request_buffer.add_block(); + block = this->_request_buffer.get_current_block(); } done = _req_header.print(block->start(), block->write_avail(), &bufindex, &tmp); dumpoffset += bufindex; - request_buffer.fill(bufindex); + this->_request_buffer.fill(bufindex); if (!done) { - request_buffer.add_block(); + this->_request_buffer.add_block(); } } while (!done); - // Is there a read_vio request waiting? - this->update_read_request(INT64_MAX, true); + if (bufindex == 0) { + // No data to signal read event + return; + } + + if (this->recv_end_stream) { + this->read_vio.nbytes = bufindex; + this->signal_read_event(VC_EVENT_READ_COMPLETE); + } else { + this->signal_read_event(VC_EVENT_READ_READY); + } } bool @@ -322,9 +316,7 @@ Http2Stream::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) read_vio.vc_server = this; read_vio.op = VIO::READ; - // Is there already data in the request_buffer? If so, copy it over and then - // schedule a READ_READY or READ_COMPLETE event after we return. - update_read_request(nbytes, false, true); + // TODO: re-enable read_vio return &read_vio; } @@ -369,7 +361,9 @@ Http2Stream::do_io_close(int /* flags */) if (parent && this->is_client_state_writeable()) { // Make sure any trailing end of stream frames are sent // Wee will be removed at send_data_frames or closing connection phase - static_cast(parent)->connection_state.send_data_frames(this); + Http2ClientSession *h2_proxy_ssn = static_cast(parent); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.send_data_frames(this); } clear_timers(); @@ -411,10 +405,9 @@ Http2Stream::terminate_if_possible() { if (terminate_stream && reentrancy_count == 0) { REMEMBER(NO_EVENT, this->reentrancy_count); - - Http2ClientSession *h2_parent = static_cast(parent); - SCOPED_MUTEX_LOCK(lock, h2_parent->connection_state.mutex, this_ethread()); - h2_parent->connection_state.delete_stream(this); + Http2ClientSession *h2_proxy_ssn = static_cast(parent); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.delete_stream(this); destroy(); } } @@ -444,7 +437,6 @@ Http2Stream::initiating_close() // This should result in do_io_close or release being called. That will schedule the final // kill yourself signal - // Send the SM the EOS signal if there are no active VIO's to signal // We are sending signals rather than calling the handlers directly to avoid the case where // the HttpTunnel handler causes the HttpSM to be deleted on the stack. bool sent_write_complete = false; @@ -471,9 +463,6 @@ Http2Stream::initiating_close() Http2StreamDebug("send EOS to read cont"); read_event = send_tracked_event(read_event, VC_EVENT_EOS, &read_vio); } - } else if (current_reader) { - SCOPED_MUTEX_LOCK(lock, current_reader->mutex, this_ethread()); - current_reader->handleEvent(VC_EVENT_ERROR); } else if (!sent_write_complete) { // Transaction is already gone or not started. Kill yourself do_io_close(); @@ -515,44 +504,26 @@ Http2Stream::update_read_request(int64_t read_len, bool call_update, bool check_ ink_release_assert(this->_thread == this_ethread()); SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); - if (read_vio.nbytes > 0 && read_vio.ndone <= read_vio.nbytes) { - // If this vio has a different buffer, we must copy - ink_release_assert(this_ethread() == this->_thread); - if (read_vio.buffer.writer() != (&request_buffer)) { - int64_t num_to_read = read_vio.nbytes - read_vio.ndone; - if (num_to_read > read_len) { - num_to_read = read_len; - } - if (num_to_read > 0) { - int bytes_added = read_vio.buffer.writer()->write(request_reader, num_to_read); - if (bytes_added > 0 || (check_eos && recv_end_stream)) { - request_reader->consume(bytes_added); - read_vio.ndone += bytes_added; - int send_event = (read_vio.nbytes == read_vio.ndone || recv_end_stream) ? VC_EVENT_READ_COMPLETE : VC_EVENT_READ_READY; - if (call_update) { // Safe to call vio handler directly - inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; - if (read_vio.cont && this->current_reader) { - read_vio.cont->handleEvent(send_event, &read_vio); - } - } else { // Called from do_io_read. Still setting things up. Send event to handle this after the dust settles - read_event = send_tracked_event(read_event, send_event, &read_vio); - } - } - } - } else { - // Try to be smart and only signal if there was additional data - int send_event = (read_vio.nbytes == read_vio.ndone) ? VC_EVENT_READ_COMPLETE : VC_EVENT_READ_READY; - if (request_reader->read_avail() > 0 || send_event == VC_EVENT_READ_COMPLETE) { - if (call_update) { // Safe to call vio handler directly - inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; - if (read_vio.cont && this->current_reader) { - read_vio.cont->handleEvent(send_event, &read_vio); - } - } else { // Called from do_io_read. Still setting things up. Send event - // to handle this after the dust settles - read_event = send_tracked_event(read_event, send_event, &read_vio); - } + if (read_vio.nbytes == 0) { + return; + } + + // Try to be smart and only signal if there was additional data + int send_event = VC_EVENT_READ_READY; + if (read_vio.ntodo() == 0 || (this->recv_end_stream && this->read_vio.nbytes != INT64_MAX)) { + send_event = VC_EVENT_READ_COMPLETE; + } + + int64_t read_avail = this->read_vio.buffer.writer()->max_read_avail(); + if (read_avail > 0 || send_event == VC_EVENT_READ_COMPLETE) { + if (call_update) { // Safe to call vio handler directly + inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; + if (read_vio.cont && this->current_reader) { + read_vio.cont->handleEvent(send_event, &read_vio); } + } else { // Called from do_io_read. Still setting things up. Send event + // to handle this after the dust settles + read_event = send_tracked_event(read_event, send_event, &read_vio); } } } @@ -567,7 +538,7 @@ void Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, bool call_update) { if (!this->is_client_state_writeable() || closed || parent == nullptr || write_vio.mutex == nullptr || - (buf_reader == nullptr && write_len == 0)) { + (buf_reader == nullptr && write_len == 0) || this->response_reader == nullptr) { return; } @@ -576,27 +547,11 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, return; } ink_release_assert(this->_thread == this_ethread()); - - Http2ClientSession *parent = static_cast(this->get_parent()); + Http2ClientSession *h2_proxy_ssn = static_cast(this->get_parent()); SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); - // if response is chunked, limit the dechunked_buffer size. - bool is_done = false; - if (this->chunked) { - if (chunked_handler.dechunked_buffer && chunked_handler.dechunked_buffer->max_read_avail() > HTTP2_MAX_BUFFER_USAGE) { - if (buffer_full_write_event == nullptr) { - buffer_full_write_event = _thread->schedule_imm(this, VC_EVENT_WRITE_READY); - } - } else { - this->response_process_data(is_done); - } - } - - if (this->response_get_data_reader() == nullptr) { - return; - } - int64_t bytes_avail = this->response_get_data_reader()->read_avail(); + int64_t bytes_avail = this->response_reader->read_avail(); if (write_vio.nbytes > 0 && write_vio.ntodo() > 0) { int64_t num_to_write = write_vio.ntodo(); if (num_to_write > write_len) { @@ -611,7 +566,7 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, ", reader.read_avail=%" PRId64, write_vio.nbytes, write_vio.ndone, write_vio.get_writer()->write_avail(), bytes_avail); - if (bytes_avail <= 0 && !is_done) { + if (bytes_avail <= 0) { return; } @@ -633,27 +588,31 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, int len; const char *value = field->value_get(&len); if (memcmp(HTTP_VALUE_CLOSE, value, HTTP_LEN_CLOSE) == 0) { - SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex, this_ethread()); - if (parent->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { - parent->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + if (h2_proxy_ssn->connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { + h2_proxy_ssn->connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); } } } { - SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex, this_ethread()); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); // Send the response header back - parent->connection_state.send_headers_frame(this); + h2_proxy_ssn->connection_state.send_headers_frame(this); + } + + // Roll back states of response header to read final response + if (this->response_header.expect_final_response()) { + this->response_header_done = false; + response_header.destroy(); + response_header.create(HTTP_TYPE_RESPONSE); + http_parser_clear(&http_parser); + http_parser_init(&http_parser); } - // See if the response is chunked. Set up the dechunking logic if it is - // Make sure to check if the chunk is complete and signal appropriately - this->response_initialize_data_handling(is_done); + this->signal_write_event(call_update); - // If there is additional data, send it along in a data frame. Or if this was header only - // make sure to send the end of stream - is_done |= (write_vio.ntodo() + this->response_header.length_get()) == bytes_avail; - if (this->response_is_data_available() || is_done) { + if (this->response_reader->is_read_avail_more_than(0)) { this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); this->send_response_body(call_update); } @@ -673,6 +632,44 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, return; } +void +Http2Stream::signal_read_event(int event) +{ + if (this->read_vio.cont == nullptr || this->read_vio.cont->mutex == nullptr || this->read_vio.op == VIO::NONE) { + return; + } + + MUTEX_TRY_LOCK(lock, read_vio.cont->mutex, this_ethread()); + if (lock.is_locked()) { + inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; + this->read_vio.cont->handleEvent(event, &this->read_vio); + } else { + if (this->_read_vio_event) { + this->_read_vio_event->cancel(); + } + this->_read_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &read_vio); + } +} + +void +Http2Stream::signal_write_event(int event) +{ + if (this->write_vio.cont == nullptr || this->write_vio.cont->mutex == nullptr || this->write_vio.op == VIO::NONE) { + return; + } + + MUTEX_TRY_LOCK(lock, write_vio.cont->mutex, this_ethread()); + if (lock.is_locked()) { + inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; + this->write_vio.cont->handleEvent(event, &this->write_vio); + } else { + if (this->_write_vio_event) { + this->_write_vio_event->cancel(); + } + this->_write_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &write_vio); + } +} + void Http2Stream::signal_write_event(bool call_update) { @@ -700,25 +697,25 @@ Http2Stream::signal_write_event(bool call_update) void Http2Stream::push_promise(URL &url, const MIMEField *accept_encoding) { - Http2ClientSession *parent = static_cast(this->get_parent()); - SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex, this_ethread()); - parent->connection_state.send_push_promise_frame(this, url, accept_encoding); + Http2ClientSession *h2_proxy_ssn = static_cast(this->get_parent()); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.send_push_promise_frame(this, url, accept_encoding); } void Http2Stream::send_response_body(bool call_update) { - Http2ClientSession *parent = static_cast(this->get_parent()); - inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; + Http2ClientSession *h2_proxy_ssn = static_cast(this->get_parent()); + inactive_timeout_at = Thread::get_hrtime() + inactive_timeout; if (Http2::stream_priority_enabled) { - SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex, this_ethread()); - parent->connection_state.schedule_stream(this); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.schedule_stream(this); // signal_write_event() will be called from `Http2ConnectionState::send_data_frames_depends_on_priority()` // when write_vio is consumed } else { - SCOPED_MUTEX_LOCK(lock, parent->connection_state.mutex, this_ethread()); - parent->connection_state.send_data_frames(this); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.send_data_frames(this); this->signal_write_event(call_update); // XXX The call to signal_write_event can destroy/free the Http2Stream. // Don't modify the Http2Stream after calling this method. @@ -753,15 +750,18 @@ Http2Stream::destroy() // Safe to initiate SSN_CLOSE if this is the last stream if (parent) { - Http2ClientSession *h2_parent = static_cast(parent); - SCOPED_MUTEX_LOCK(lock, h2_parent->connection_state.mutex, this_ethread()); + cid = parent->connection_id(); + + Http2ClientSession *h2_proxy_ssn = static_cast(parent); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); // Make sure the stream is removed from the stream list and priority tree // In many cases, this has been called earlier, so this call is a no-op - h2_parent->connection_state.delete_stream(this); + h2_proxy_ssn->connection_state.delete_stream(this); // Update session's stream counts, so it accurately goes into keep-alive state - h2_parent->connection_state.release_stream(this); - cid = parent->connection_id(); + h2_proxy_ssn->connection_state.release_stream(this); + + // Do not access `_proxy_ssn` in below. It might be freed by `release_stream`. } // Clean up the write VIO in case of inactivity timeout @@ -798,7 +798,7 @@ Http2Stream::destroy() response_header.destroy(); // Drop references to all buffer data - request_buffer.clear(); + this->_request_buffer.clear(); // Free the mutexes in the VIO read_vio.mutex.clear(); @@ -807,61 +807,18 @@ Http2Stream::destroy() if (header_blocks) { ats_free(header_blocks); } - chunked_handler.clear(); clear_timers(); clear_io_events(); + http_parser_clear(&http_parser); super::destroy(); THREAD_FREE(this, http2StreamAllocator, this_ethread()); } -void -Http2Stream::response_initialize_data_handling(bool &is_done) -{ - is_done = false; - int chunked_index = response_header.value_get_index(TS_MIME_FIELD_TRANSFER_ENCODING, TS_MIME_LEN_TRANSFER_ENCODING, - TS_HTTP_VALUE_CHUNKED, TS_HTTP_LEN_CHUNKED); - // -1 means this value was not found for this field - if (chunked_index >= 0) { - Http2StreamDebug("Response is chunked"); - chunked = true; - this->chunked_handler.init_by_action(this->response_reader, ChunkedHandler::ACTION_DECHUNK); - this->chunked_handler.state = ChunkedHandler::CHUNK_READ_SIZE; - this->chunked_handler.dechunked_reader = this->chunked_handler.dechunked_buffer->alloc_reader(); - this->response_reader->dealloc(); - this->response_reader = nullptr; - // Get things going if there is already data waiting - if (this->chunked_handler.chunked_reader->is_read_avail_more_than(0)) { - response_process_data(is_done); - } - } -} - -void -Http2Stream::response_process_data(bool &done) -{ - done = false; - if (chunked) { - do { - if (chunked_handler.state == ChunkedHandler::CHUNK_FLOW_CONTROL) { - chunked_handler.state = ChunkedHandler::CHUNK_READ_SIZE_START; - } - done = this->chunked_handler.process_chunked_content(); - } while (chunked_handler.state == ChunkedHandler::CHUNK_FLOW_CONTROL); - } -} - -bool -Http2Stream::response_is_data_available() const -{ - IOBufferReader *reader = this->response_get_data_reader(); - return reader ? reader->is_read_avail_more_than(0) : false; -} - IOBufferReader * Http2Stream::response_get_data_reader() const { - return (chunked) ? chunked_handler.dechunked_reader : response_reader; + return this->response_reader; } void @@ -935,6 +892,16 @@ Http2Stream::clear_io_events() buffer_full_write_event->cancel(); buffer_full_write_event = nullptr; } + + if (this->_read_vio_event) { + this->_read_vio_event->cancel(); + this->_read_vio_event = nullptr; + } + + if (this->_write_vio_event) { + this->_write_vio_event->cancel(); + this->_write_vio_event = nullptr; + } } void @@ -1015,3 +982,19 @@ Http2Stream::_switch_thread_if_not_on_right_thread(int event, void *edata) } return true; } + +int +Http2Stream::get_transaction_priority_weight() const +{ + return priority_node ? priority_node->weight : 0; +} + +int +Http2Stream::get_transaction_priority_dependence() const +{ + if (!priority_node) { + return -1; + } else { + return priority_node->parent ? priority_node->parent->id : 0; + } +} diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index 16d879058e7..fc52cf3329d 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -24,9 +24,8 @@ #pragma once #include "HTTP2.h" -#include "../ProxyClientTransaction.h" +#include "ProxyClientTransaction.h" #include "Http2DebugNames.h" -#include "../http/HttpTunnel.h" // To get ChunkedHandler #include "Http2DependencyTree.h" #include "tscore/History.h" #include "Milestones.h" @@ -50,7 +49,8 @@ enum class Http2StreamMilestone { class Http2Stream : public ProxyClientTransaction { public: - using super = ProxyClientTransaction; ///< Parent type. + const int retry_delay = HRTIME_MSECONDS(10); + using super = ProxyClientTransaction; ///< Parent type. Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd = Http2::initial_window_size); @@ -74,7 +74,11 @@ class Http2Stream : public ProxyClientTransaction void terminate_if_possible(); void update_read_request(int64_t read_len, bool send_update, bool check_eos = false); void update_write_request(IOBufferReader *buf_reader, int64_t write_len, bool send_update); + + void signal_read_event(int event); + void signal_write_event(int event); void signal_write_event(bool call_update); + void restart_sending(); void push_promise(URL &url, const MIMEField *accept_encoding); @@ -95,6 +99,8 @@ class Http2Stream : public ProxyClientTransaction bool allow_half_open() const override; bool is_first_transaction() const override; int get_transaction_id() const override; + int get_transaction_priority_weight() const override; + int get_transaction_priority_dependence() const override; bool ignore_keep_alive() override; void clear_inactive_timer(); @@ -104,14 +110,13 @@ class Http2Stream : public ProxyClientTransaction bool is_client_state_writeable() const; bool is_closed() const; - bool response_is_chunked() const; IOBufferReader *response_get_data_reader() const; void mark_milestone(Http2StreamMilestone type); void increment_data_length(uint64_t length); bool payload_length_is_valid() const; - bool is_body_done() const; + bool is_write_vio_done() const; void update_sent_count(unsigned num_bytes); Http2StreamId get_id() const; Http2StreamState get_state() const; @@ -119,6 +124,7 @@ class Http2Stream : public ProxyClientTransaction void update_initial_rwnd(Http2WindowSize new_size); bool has_trailing_header() const; void set_request_headers(HTTPHdr &h2_headers); + MIOBuffer *read_vio_writer() const; ////////////////// // Variables @@ -137,13 +143,9 @@ class Http2Stream : public ProxyClientTransaction HTTPHdr response_header; IOBufferReader *response_reader = nullptr; - IOBufferReader *request_reader = nullptr; - MIOBuffer request_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; Http2DependencyTree::Node *priority_node = nullptr; private: - void response_initialize_data_handling(bool &is_done); - void response_process_data(bool &is_done); bool response_is_data_available() const; Event *send_tracked_event(Event *event, int send_event, VIO *vio); void send_response_body(bool call_update); @@ -162,6 +164,8 @@ class Http2Stream : public ProxyClientTransaction int64_t _http_sm_id = -1; HTTPHdr _req_header; + MIOBuffer _request_buffer = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; + int64_t read_vio_nbytes; VIO read_vio; VIO write_vio; @@ -169,7 +173,6 @@ class Http2Stream : public ProxyClientTransaction Milestones(Http2StreamMilestone::LAST_ENTRY)> _milestones; bool trailing_header = false; - bool chunked = false; // A brief disucssion of similar flags and state variables: _state, closed, terminate_stream // @@ -203,7 +206,6 @@ class Http2Stream : public ProxyClientTransaction std::vector _recent_rwnd_increment = {SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX, SIZE_MAX}; int _recent_rwnd_increment_index = 0; - ChunkedHandler chunked_handler; Event *cross_thread_event = nullptr; Event *buffer_full_write_event = nullptr; @@ -215,8 +217,10 @@ class Http2Stream : public ProxyClientTransaction ink_hrtime inactive_timeout_at = 0; Event *inactive_event = nullptr; - Event *read_event = nullptr; - Event *write_event = nullptr; + Event *read_event = nullptr; + Event *write_event = nullptr; + Event *_read_vio_event = nullptr; + Event *_write_vio_event = nullptr; }; extern ClassAllocator http2StreamAllocator; @@ -231,7 +235,7 @@ Http2Stream::mark_milestone(Http2StreamMilestone type) } inline bool -Http2Stream::is_body_done() const +Http2Stream::is_write_vio_done() const { return this->write_vio.ntodo() == 0; } @@ -301,12 +305,6 @@ Http2Stream::ignore_keep_alive() return false; } -inline bool -Http2Stream::response_is_chunked() const -{ - return chunked; -} - inline bool Http2Stream::allow_half_open() const { @@ -331,3 +329,9 @@ Http2Stream::is_first_transaction() const { return is_first_transaction_flag; } + +inline MIOBuffer * +Http2Stream::read_vio_writer() const +{ + return this->read_vio.get_writer(); +} diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am index c0ae3abae05..beb8692474a 100644 --- a/proxy/http2/Makefile.am +++ b/proxy/http2/Makefile.am @@ -38,6 +38,8 @@ libhttp2_a_SOURCES = \ HPACK.h \ HTTP2.cc \ HTTP2.h \ + Http2Frame.cc \ + Http2Frame.h \ Http2ClientSession.cc \ Http2ClientSession.h \ Http2ConnectionState.cc \ @@ -58,14 +60,31 @@ libhttp2_a_SOURCES += \ endif check_PROGRAMS = \ + test_libhttp2 \ test_Http2DependencyTree \ test_Http2FrequencyCounter \ test_HPACK -TESTS = \ - test_Http2DependencyTree \ - test_Http2FrequencyCounter \ - test_HPACK +TESTS = $(check_PROGRAMS) + +test_libhttp2_LDADD = \ + libhttp2.a \ + $(top_builddir)/proxy/hdrs/libhdrs.a \ + $(top_builddir)/src/tscore/libtscore.la \ + $(top_builddir)/src/tscpp/util/libtscpputil.la \ + $(top_builddir)/iocore/eventsystem/libinkevent.a \ + $(top_builddir)/lib/records/librecords_p.a \ + $(top_builddir)/mgmt/libmgmt_p.la \ + $(top_builddir)/proxy/shared/libUglyLogStubs.a \ + @LIBTCL@ \ + @HWLOC_LIBS@ + +test_libhttp2_CPPFLAGS = $(AM_CPPFLAGS)\ + -I$(abs_top_srcdir)/tests/include + +test_libhttp2_SOURCES = \ + unit_tests/test_Http2Frame.cc \ + unit_tests/main.cc test_Http2DependencyTree_LDADD = \ $(top_builddir)/src/tscore/libtscore.la \ diff --git a/proxy/http2/unit_tests/main.cc b/proxy/http2/unit_tests/main.cc new file mode 100644 index 00000000000..4df703661b3 --- /dev/null +++ b/proxy/http2/unit_tests/main.cc @@ -0,0 +1,55 @@ +/** @file + + The main file for test_libhttp2 + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" + +#include "tscore/I_Layout.h" + +#include "I_EventSystem.h" +#include "RecordsConfig.h" + +#include "diags.i" + +#define TEST_THREADS 1 + +struct EventProcessorListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; + + void + testRunStarting(Catch::TestRunInfo const &testRunInfo) override + { + Layout::create(); + init_diags("", nullptr); + RecProcessInit(RECM_STAND_ALONE); + LibRecordsConfigInit(); + + ink_event_system_init(EVENT_SYSTEM_MODULE_VERSION); + eventProcessor.start(TEST_THREADS); + + EThread *main_thread = new EThread; + main_thread->set_specific(); + } +}; + +CATCH_REGISTER_LISTENER(EventProcessorListener); diff --git a/proxy/http2/unit_tests/test_Http2Frame.cc b/proxy/http2/unit_tests/test_Http2Frame.cc new file mode 100644 index 00000000000..d1e695e4fe0 --- /dev/null +++ b/proxy/http2/unit_tests/test_Http2Frame.cc @@ -0,0 +1,64 @@ +/** @file + + Unit tests for Http2Frame + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "catch.hpp" + +#include "Http2Frame.h" + +TEST_CASE("Http2Frame", "[http2][Http2Frame]") +{ + MIOBuffer *miob = new_MIOBuffer(); + IOBufferReader *miob_r = miob->alloc_reader(); + + SECTION("PUSH_PROMISE") + { + Http2StreamId id = 1; + uint8_t flags = HTTP2_FLAGS_PUSH_PROMISE_END_HEADERS; + Http2PushPromise pp{0, 2}; + uint8_t hdr_block[] = {0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef}; + uint8_t hdr_block_len = sizeof(hdr_block); + + Http2PushPromiseFrame frame(id, flags, pp, hdr_block, hdr_block_len); + uint64_t written = frame.write_to(miob); + + CHECK(written == HTTP2_FRAME_HEADER_LEN + sizeof(Http2StreamId) + hdr_block_len); + CHECK(written == miob_r->read_avail()); + + uint8_t buf[32] = {0}; + uint64_t read = miob_r->read(buf, written); + CHECK(read == written); + + uint8_t expected[] = { + 0x00, 0x00, 0x0e, ///< Length + 0x05, ///< Type + 0x04, ///< Flags + 0x00, 0x00, 0x00, 0x01, ///< Stream Identifier (31) + 0x00, 0x00, 0x00, 0x02, ///< Promised Stream ID + 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef ///< Header Block Fragment + }; + + CHECK(memcmp(buf, expected, written) == 0); + } + + free_MIOBuffer(miob); +} diff --git a/proxy/logging/Log.cc b/proxy/logging/Log.cc index 63b34196212..b48afe5f252 100644 --- a/proxy/logging/Log.cc +++ b/proxy/logging/Log.cc @@ -869,6 +869,16 @@ Log::init_fields() global_field_list.add(field, false); ink_hash_table_insert(field_symbol_hash, "ctid", field); + field = new LogField("client_transaction_priority_weight", "ctpw", LogField::sINT, + &LogAccess::marshal_client_http_transaction_priority_weight, &LogAccess::unmarshal_int_to_str); + global_field_list.add(field, false); + ink_hash_table_insert(field_symbol_hash, "ctpw", field); + + field = new LogField("client_transaction_priority_dependence", "ctpd", LogField::sINT, + &LogAccess::marshal_client_http_transaction_priority_dependence, &LogAccess::unmarshal_int_to_str); + global_field_list.add(field, false); + ink_hash_table_insert(field_symbol_hash, "ctpd", field); + init_status |= FIELDS_INITIALIZED; } diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc index 7f8388ae41b..c51a634a878 100644 --- a/proxy/logging/LogAccess.cc +++ b/proxy/logging/LogAccess.cc @@ -2471,6 +2471,38 @@ LogAccess::marshal_client_http_transaction_id(char *buf) return INK_MIN_ALIGN; } +/*------------------------------------------------------------------------- + -------------------------------------------------------------------------*/ + +int +LogAccess::marshal_client_http_transaction_priority_weight(char *buf) +{ + if (buf) { + int64_t id = 0; + if (m_http_sm) { + id = m_http_sm->client_transaction_priority_weight(); + } + marshal_int(buf, id); + } + return INK_MIN_ALIGN; +} + +/*------------------------------------------------------------------------- + -------------------------------------------------------------------------*/ + +int +LogAccess::marshal_client_http_transaction_priority_dependence(char *buf) +{ + if (buf) { + int64_t id = 0; + if (m_http_sm) { + id = m_http_sm->client_transaction_priority_dependence(); + } + marshal_int(buf, id); + } + return INK_MIN_ALIGN; +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ diff --git a/proxy/logging/LogAccess.h b/proxy/logging/LogAccess.h index a4a721aa156..860c089e054 100644 --- a/proxy/logging/LogAccess.h +++ b/proxy/logging/LogAccess.h @@ -232,15 +232,17 @@ class LogAccess // other fields // - inkcoreapi int marshal_transfer_time_ms(char *); // INT - inkcoreapi int marshal_transfer_time_s(char *); // INT - inkcoreapi int marshal_file_size(char *); // INT - inkcoreapi int marshal_plugin_identity_id(char *); // INT - inkcoreapi int marshal_plugin_identity_tag(char *); // STR - inkcoreapi int marshal_process_uuid(char *); // STR - inkcoreapi int marshal_client_http_connection_id(char *); // INT - inkcoreapi int marshal_client_http_transaction_id(char *); // INT - inkcoreapi int marshal_cache_lookup_url_canon(char *); // STR + inkcoreapi int marshal_transfer_time_ms(char *); // INT + inkcoreapi int marshal_transfer_time_s(char *); // INT + inkcoreapi int marshal_file_size(char *); // INT + inkcoreapi int marshal_plugin_identity_id(char *); // INT + inkcoreapi int marshal_plugin_identity_tag(char *); // STR + inkcoreapi int marshal_process_uuid(char *); // STR + inkcoreapi int marshal_client_http_connection_id(char *); // INT + inkcoreapi int marshal_client_http_transaction_id(char *); // INT + inkcoreapi int marshal_client_http_transaction_priority_weight(char *); // INT + inkcoreapi int marshal_client_http_transaction_priority_dependence(char *); // INT + inkcoreapi int marshal_cache_lookup_url_canon(char *); // STR // named fields from within a http header // diff --git a/tests/gold_tests/h2/gold/http2_9_stderr.gold b/tests/gold_tests/h2/gold/http2_9_stderr.gold new file mode 100644 index 00000000000..ea2f1297786 --- /dev/null +++ b/tests/gold_tests/h2/gold/http2_9_stderr.gold @@ -0,0 +1,10 @@ +`` +> GET /status/204 HTTP/2 +> Host: `` +> User-Agent: curl/`` +> Accept: */* +`` +< HTTP/2 204`` +< server: ATS/`` +< date: `` +`` diff --git a/tests/gold_tests/h2/gold/http2_9_stdout.gold b/tests/gold_tests/h2/gold/http2_9_stdout.gold new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/gold_tests/h2/gold/httpbin_0_stdout.gold b/tests/gold_tests/h2/gold/httpbin_0_stdout.gold index cb7b2d62f74..d8e61077a93 100644 --- a/tests/gold_tests/h2/gold/httpbin_0_stdout.gold +++ b/tests/gold_tests/h2/gold/httpbin_0_stdout.gold @@ -1,5 +1,5 @@ { `` - "url":"http://``/get" + "url": "http://``/get" `` } diff --git a/tests/gold_tests/h2/gold/httpbin_3_stderr.gold b/tests/gold_tests/h2/gold/httpbin_3_stderr.gold new file mode 100644 index 00000000000..8109f9de4e4 --- /dev/null +++ b/tests/gold_tests/h2/gold/httpbin_3_stderr.gold @@ -0,0 +1,9 @@ +`` +> POST /post HTTP/2 +`` +> Expect: 100-continue +`` +< HTTP/2 100`` +`` +< HTTP/2 200`` +`` diff --git a/tests/gold_tests/h2/gold/httpbin_3_stdout.gold b/tests/gold_tests/h2/gold/httpbin_3_stdout.gold new file mode 100644 index 00000000000..610580810ce --- /dev/null +++ b/tests/gold_tests/h2/gold/httpbin_3_stdout.gold @@ -0,0 +1,7 @@ +{ + `` + "form": { + "key": "value" + }, + `` +} diff --git a/tests/gold_tests/h2/gold/httpbin_access.gold b/tests/gold_tests/h2/gold/httpbin_access.gold index 3f77947edb3..d409c47d13a 100644 --- a/tests/gold_tests/h2/gold/httpbin_access.gold +++ b/tests/gold_tests/h2/gold/httpbin_access.gold @@ -1,3 +1,4 @@ [``] GET http://127.0.0.1:``/get HTTP/1.1 http/2 `` `` TCP_MISS 200 `` [``] GET http://127.0.0.1:``/bytes/0 HTTP/1.1 http/2 `` `` TCP_MISS 200 0 [``] GET http://127.0.0.1:``/stream-bytes/102400?seed=0 HTTP/1.1 http/2 `` `` TCP_MISS 200 102400 +`` diff --git a/tests/gold_tests/h2/gold/nghttp_0_stdout.gold b/tests/gold_tests/h2/gold/nghttp_0_stdout.gold new file mode 100644 index 00000000000..1487943ef0d --- /dev/null +++ b/tests/gold_tests/h2/gold/nghttp_0_stdout.gold @@ -0,0 +1,17 @@ +`` +[``] send HEADERS frame +`` +``trailer: foo +`` +[``] send DATA frame +`` +[``] send HEADERS frame +``; END_STREAM | END_HEADERS +`` +``foo: bar +`` +[``] recv (stream_id=1) :status: 200 +`` +[``] recv RST_STREAM frame +``(error_code=NO_ERROR(0x00)) +`` diff --git a/tests/gold_tests/h2/http2.test.py b/tests/gold_tests/h2/http2.test.py index 4a4fc29ec84..fd139a34ea3 100644 --- a/tests/gold_tests/h2/http2.test.py +++ b/tests/gold_tests/h2/http2.test.py @@ -70,6 +70,11 @@ {"headers": "GET /huge_resp_hdrs HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}, {"headers": "HTTP/1.1 200 OK\r\nServer: microserver\r\nConnection: close\r\nContent-Length: 6\r\n\r\n", "timestamp": "1469733493.993", "body": "200 OK"}) +# For Test Case 9 - /status/204 +server.addResponse("sessionlog.json", + {"headers": "GET /status/204 HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}, + {"headers": "HTTP/1.1 204 No Content\r\nServer: microserver\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}) + # ---- # Setup ATS # ---- @@ -181,3 +186,11 @@ tr.Processes.Default.Streams.stdout = "gold/http2_8_stdout.gold" tr.Processes.Default.Streams.stderr = "gold/http2_8_stderr.gold" tr.StillRunningAfter = server + +# Test Case 9: Header Only Response - e.g. 204 +tr = Test.AddTestRun() +tr.Processes.Default.Command = 'curl -vs -k --http2 https://127.0.0.1:{0}/status/204'.format(ts.Variables.ssl_port) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "gold/http2_9_stdout.gold" +tr.Processes.Default.Streams.stderr = "gold/http2_9_stderr.gold" +tr.StillRunningAfter = server diff --git a/tests/gold_tests/h2/httpbin.test.py b/tests/gold_tests/h2/httpbin.test.py index f30eea44909..388df74486e 100644 --- a/tests/gold_tests/h2/httpbin.test.py +++ b/tests/gold_tests/h2/httpbin.test.py @@ -78,13 +78,19 @@ Test.Disk.File(os.path.join(ts.Variables.LOGDIR, 'access.log'), exists=True, content='gold/httpbin_access.gold') +# TODO: when httpbin 0.8.0 or later is released, remove below json pretty print hack +json_printer = ''' +python -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2, separators=(',', ': ')))" +''' + # ---- # Test Cases # ---- # Test Case 0: Basic request and resposne test_run = Test.AddTestRun() -test_run.Processes.Default.Command = 'curl -vs -k --http2 https://127.0.0.1:{0}/get'.format(ts.Variables.ssl_port) +test_run.Processes.Default.Command = "curl -vs -k --http2 https://127.0.0.1:{0}/get | {1}".format( + ts.Variables.ssl_port, json_printer) test_run.Processes.Default.ReturnCode = 0 test_run.Processes.Default.StartBefore(httpbin, ready=When.PortOpen(httpbin.Variables.Port)) test_run.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port)) @@ -109,6 +115,16 @@ test_run.Processes.Default.Streams.stderr = "gold/httpbin_2_stderr.gold" test_run.StillRunningAfter = httpbin +# Test Case 3: Expect 100-Continue +test_run = Test.AddTestRun() +test_run.Processes.Default.Command = "curl -vs -k --http2 https://127.0.0.1:{0}/post --data 'key=value' -H 'Expect: 100-continue' --expect100-timeout 1 --max-time 5 | {1}".format( + ts.Variables.ssl_port, json_printer) +test_run.Processes.Default.ReturnCode = 0 +test_run.Processes.Default.Streams.stdout = "gold/httpbin_3_stdout.gold" +test_run.Processes.Default.Streams.stderr = "gold/httpbin_3_stderr.gold" +test_run.StillRunningAfter = httpbin + + # Check Logging test_run = Test.AddTestRun() test_run.DelayStart = 10 diff --git a/tests/gold_tests/h2/nghttp.test.py b/tests/gold_tests/h2/nghttp.test.py new file mode 100644 index 00000000000..33b4c2a93b6 --- /dev/null +++ b/tests/gold_tests/h2/nghttp.test.py @@ -0,0 +1,86 @@ +''' +''' +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +Test.Summary = ''' +Test with nghttp +''' +# need Curl +Test.SkipUnless( + Condition.HasProgram("nghttp", "Nghttp need to be installed on system for this test to work"), +) +Test.ContinueOnFail = True + +# ---- +# Setup Origin Server +# ---- +microserver = Test.MakeOriginServer("microserver") + +# 128KB +post_body = "0123456789abcdef" * 8192 +post_body_file = open(os.path.join(Test.RunDirectory, "post_body"), "w") +post_body_file.write(post_body) +post_body_file.close() + +# For Test Case 0 +microserver.addResponse("sessionlog.json", + {"headers": "POST /post HTTP/1.1\r\nHost: www.example.com\r\nTrailer: foo\r\n\r\n", + "timestamp": "1469733493.993", "body": post_body}, + {"headers": "HTTP/1.1 200 OK\r\nServer: microserver\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}) + +# ---- +# Setup ATS +# ---- +ts = Test.MakeATSProcess("ts", select_ports=False) +ts.Variables.ssl_port = 4443 + +# add ssl materials like key, certificates for the server +ts.addSSLfile("ssl/server.pem") +ts.addSSLfile("ssl/server.key") + +ts.Disk.remap_config.AddLine( + 'map /post http://127.0.0.1:{0}/post'.format(microserver.Variables.Port) +) + +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key' +) + +ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'http', + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port), + 'proxy.config.http.cache.http': 0, + 'proxy.config.http2.active_timeout_in': 3, +}) + +# ---- +# Test Cases +# ---- + +# Test Case 0: Trailer +tr = Test.AddTestRun() +tr.Processes.Default.Command = "nghttp -v --no-dep 'https://127.0.0.1:{0}/post' --trailer 'foo: bar' -d 'post_body'".format( + ts.Variables.ssl_port) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.StartBefore(microserver) +tr.Processes.Default.StartBefore(Test.Processes.ts) +tr.Processes.Default.Streams.stdout = "gold/nghttp_0_stdout.gold" +tr.StillRunningAfter = microserver