From f8d01514c56ee47db3ec9644f419112c853360c6 Mon Sep 17 00:00:00 2001 From: Masaori Koshiba Date: Mon, 2 Sep 2019 14:31:56 +0900 Subject: [PATCH] Dechunk chunked contents on HttpTunnel if client protocol is HTTP/2 or HTTP/3 --- proxy/http/HttpTransact.cc | 15 +++--- proxy/http2/Http2Stream.cc | 93 ++------------------------------------ proxy/http2/Http2Stream.h | 18 +------- 3 files changed, 15 insertions(+), 111 deletions(-) diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index f5898f01676..8438f4c3efb 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -6757,11 +6757,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->plugin_tag && (!strncmp(s->state_machine->plugin_tag, "http/2", 6)))) && + // 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, and HTTP/3) + 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 @@ -6794,10 +6793,12 @@ 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, we will close the connection - // unless we are going to use chunked encoding or the client issued - // a PUSH request + // 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->state_machine->client_protocol && (IP_PROTO_TAG_HTTP_3.compare(s->state_machine->client_protocol) == 0 || + 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))) { diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index fad170cf55e..8d2d5c62a49 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -575,7 +575,7 @@ void Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, bool call_update) { if (!this->is_client_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr || - (buf_reader == nullptr && write_len == 0)) { + (buf_reader == nullptr && write_len == 0) || this->response_reader == nullptr) { return; } @@ -589,22 +589,7 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, 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) { @@ -619,7 +604,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; } @@ -654,17 +639,7 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, h2_proxy_ssn->connection_state.send_headers_frame(this); } - // 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); - - // 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 (is_done) { - this->mark_body_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); } @@ -677,9 +652,6 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, break; } } else { - if (write_vio.ntodo() == bytes_avail || is_done) { - this->mark_body_done(); - } this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); this->send_response_body(call_update); } @@ -820,7 +792,6 @@ Http2Stream::destroy() if (header_blocks) { ats_free(header_blocks); } - chunked_handler.clear(); clear_timers(); clear_io_events(); http_parser_clear(&http_parser); @@ -829,53 +800,10 @@ Http2Stream::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 @@ -1053,17 +981,6 @@ Http2Stream::_switch_thread_if_not_on_right_thread(int event, void *edata) return true; } -void -Http2Stream::mark_body_done() -{ - body_done = true; - if (response_is_chunked()) { - ink_assert(chunked_handler.state == ChunkedHandler::CHUNK_READ_DONE || - chunked_handler.state == ChunkedHandler::CHUNK_READ_ERROR); - this->write_vio.nbytes = response_header.length_get() + chunked_handler.dechunked_size; - } -} - int Http2Stream::get_transaction_priority_weight() const { diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index 759b0f28171..58baee67281 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -24,9 +24,8 @@ #pragma once #include "HTTP2.h" -#include "../ProxyTransaction.h" +#include "ProxyTransaction.h" #include "Http2DebugNames.h" -#include "../http/HttpTunnel.h" // To get ChunkedHandler #include "Http2DependencyTree.h" #include "tscore/History.h" #include "Milestones.h" @@ -107,7 +106,6 @@ class Http2Stream : public ProxyTransaction 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); @@ -115,7 +113,6 @@ class Http2Stream : public ProxyTransaction void increment_data_length(uint64_t length); bool payload_length_is_valid() const; bool is_body_done() const; - void mark_body_done(); void update_sent_count(unsigned num_bytes); Http2StreamId get_id() const; Http2StreamState get_state() const; @@ -146,8 +143,6 @@ class Http2Stream : public ProxyTransaction 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); @@ -173,8 +168,6 @@ class Http2Stream : public ProxyTransaction Milestones(Http2StreamMilestone::LAST_ENTRY)> _milestones; bool trailing_header = false; - bool body_done = false; - bool chunked = false; // A brief discussion of similar flags and state variables: _state, closed, terminate_stream // @@ -208,7 +201,6 @@ class Http2Stream : public ProxyTransaction 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; @@ -240,7 +232,7 @@ Http2Stream::mark_milestone(Http2StreamMilestone type) inline bool Http2Stream::is_body_done() const { - return body_done; + return this->write_vio.ntodo() == 0; } inline void @@ -300,12 +292,6 @@ Http2Stream::payload_length_is_valid() const return content_length == 0 || content_length == data_length; } -inline bool -Http2Stream::response_is_chunked() const -{ - return chunked; -} - inline bool Http2Stream::allow_half_open() const {