Skip to content

Commit a16c4b6

Browse files
bryancallbneradt
andauthored
Fix chunked pipelined requests (#12071)
* Fix pipelined request for chunked bodies This addresses a variety of bugs concerning pipelined requests. In particular, the HttpTunnel logic had fundamentally assumed that it could consume all bytes available in the producer's reader. If a request was pipelined after a previous request that had a chunked body, this would result in the second request being unparsed and either sent along to the origin or dropped on the floor, depening on configuration. This adds an explicit autest for pipelined requests and addresses these issues. This patch largely does the following: 1. Updates the copy_partial_post_data data to take the number of bytes it consumes rather than consuming all bytes in the reader. It also now returns the number of bytes it consumes, which the tunnel needs to keep track of the number of bytes it processes. 2. Previous to this patch, the HttpTunnel assumed that it could consume all bytes in the reader originally passed to it (all bytes in init_bytes_done). This simply will not work for pipelined requests. This addresses this issue by adding a new variable to the tunnel: bytes_consumed. This way the tunnel can keep track of how many bytes it consumed while processing the request body, which allows the HttpSM to later process just the right number of bytes from its reader rather than eating into any pipelined requests that follow it. 3. The HttpSM must not consume bytes from its client reader that are pipelined requests. It now uses the tunnel's processing bytes_consumed to process bytes from its reader rather than simply consuming all read_available() bytes from it. * Fix bytes consumed chunk computation Fix a possible miscalculation of bytes consumed while parsing chunked content. * is_read_closed fix * Verify expected responses are received. * Updated formatting --------- Co-authored-by: Brian Neradt <brian.neradt@gmail.com>
1 parent 1cca4a2 commit a16c4b6

File tree

15 files changed

+796
-116
lines changed

15 files changed

+796
-116
lines changed

proxy/ProxyTransaction.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ ProxyTransaction::get_version(HTTPHdr &hdr) const
238238
return hdr.version_get();
239239
}
240240

241+
bool
242+
ProxyTransaction::is_read_closed() const
243+
{
244+
return false;
245+
}
246+
241247
bool
242248
ProxyTransaction::allow_half_open() const
243249
{

proxy/ProxyTransaction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ProxyTransaction : public VConnection
4949
virtual void set_inactivity_timeout(ink_hrtime timeout_in);
5050
virtual void cancel_inactivity_timeout();
5151
virtual void cancel_active_timeout();
52+
virtual bool is_read_closed() const;
5253

5354
// Implement VConnection interface.
5455
VIO *do_io_read(Continuation *c, int64_t nbytes = INT64_MAX, MIOBuffer *buf = nullptr) override;

proxy/http/HttpSM.cc

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -973,10 +973,8 @@ HttpSM::wait_for_full_body()
973973
// Next order of business if copy the remaining data from the
974974
// header buffer into new buffer
975975
int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length;
976-
client_request_body_bytes =
977-
post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes);
976+
post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes);
978977

979-
ua_txn->get_remote_reader()->consume(client_request_body_bytes);
980978
p = tunnel.add_producer(ua_entry->vc, post_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_BUFFER_READ, "ua post buffer");
981979
if (chunked) {
982980
bool const drop_chunked_trailers = t_state.http_config_param->oride.http_drop_chunked_trailers == 1;
@@ -3633,7 +3631,24 @@ int
36333631
HttpSM::tunnel_handler_post_ua(int event, HttpTunnelProducer *p)
36343632
{
36353633
STATE_ENTER(&HttpSM::tunnel_handler_post_ua, event);
3636-
client_request_body_bytes = p->init_bytes_done + p->bytes_read;
3634+
3635+
// Now that the tunnel is done, it can tell us how many bytes were in the
3636+
// body.
3637+
if (client_request_body_bytes == 0) {
3638+
// This is invoked multiple times for a transaction when buffering request
3639+
// body data, so we only call this the first time when
3640+
// client_request_body_bytes is 0.
3641+
client_request_body_bytes = p->bytes_consumed;
3642+
IOBufferReader *client_reader = ua_txn->get_remote_reader();
3643+
// p->bytes_consumed represents the number of body bytes the tunnel parsed
3644+
// and consumed from the client. However, not all those bytes may have been
3645+
// written to our _ua client transaction reader. We must not consume past
3646+
// the number of bytes available.
3647+
int64_t const bytes_to_consume = std::min(p->bytes_consumed, client_reader->read_avail());
3648+
SMDebug("http_tunnel", "Consuming %" PRId64 " bytes from client reader with p->bytes_consumed: %" PRId64 " available: %" PRId64,
3649+
bytes_to_consume, p->bytes_consumed, client_reader->read_avail());
3650+
client_reader->consume(bytes_to_consume);
3651+
}
36373652

36383653
switch (event) {
36393654
case VC_EVENT_INACTIVITY_TIMEOUT:
@@ -6095,8 +6110,8 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
60956110
IOBufferReader *postdata_producer_reader = postdata_producer_buffer->alloc_reader();
60966111

60976112
postdata_producer_buffer->write(this->_postbuf.postdata_copy_buffer_start);
6098-
int64_t post_bytes = postdata_producer_reader->read_avail();
6099-
transfered_bytes = post_bytes;
6113+
int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length;
6114+
transferred_bytes = post_bytes;
61006115
p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, post_bytes, postdata_producer_reader, (HttpProducerHandler) nullptr,
61016116
HT_STATIC, "redirect static agent post");
61026117
} else {
@@ -6125,12 +6140,27 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
61256140

61266141
// Next order of business if copy the remaining data from the
61276142
// header buffer into new buffer
6128-
client_request_body_bytes =
6129-
post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes);
61306143

6131-
ua_txn->get_remote_reader()->consume(client_request_body_bytes);
6132-
p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_HTTP_CLIENT,
6133-
"user agent post");
6144+
int64_t num_body_bytes = 0;
6145+
// If is_using_post_buffer has been used, then client_request_body_bytes
6146+
// will have already been sent in wait_for_full_body and there will be
6147+
// zero bytes in this user agent buffer. We don't want to clobber
6148+
// client_request_body_bytes with a zero value here in those cases.
6149+
if (client_request_body_bytes > 0) {
6150+
num_body_bytes = client_request_body_bytes;
6151+
} else {
6152+
num_body_bytes =
6153+
post_buffer->write(ua_txn->get_remote_reader(), chunked ? ua_txn->get_remote_reader()->read_avail() : post_bytes);
6154+
}
6155+
// Don't consume post_bytes here from ua_txn->get_remote_reader() since
6156+
// we are not sure how many bytes the tunnel will use yet. Wait until
6157+
// HttpSM::tunnel_handler_post_ua to consume the bytes.
6158+
// The user agent has already sent all it has
6159+
if (ua_txn->is_read_closed()) {
6160+
post_bytes = num_body_bytes;
6161+
}
6162+
p = tunnel.add_producer(ua_entry->vc, post_bytes - transferred_bytes, buf_start, &HttpSM::tunnel_handler_post_ua,
6163+
HT_HTTP_CLIENT, "user agent post");
61346164
}
61356165
ua_entry->in_tunnel = true;
61366166

@@ -6847,6 +6877,8 @@ HttpSM::server_transfer_init(MIOBuffer *buf, int hdr_size)
68476877
// we'll get is already in the buffer
68486878
nbytes = server_txn->get_remote_reader()->read_avail() + hdr_size;
68496879
} else if (t_state.hdr_info.response_content_length == HTTP_UNDEFINED_CL) {
6880+
// Chunked or otherwise, no length is defined. Pass -1 to tell the
6881+
// tunnel that the size is unknown.
68506882
nbytes = -1;
68516883
} else {
68526884
// Set to copy to the number of bytes we want to write as
@@ -8564,16 +8596,18 @@ HttpSM::rewind_state_machine()
85648596

85658597
// YTS Team, yamsat Plugin
85668598
// Function to copy the partial Post data while tunnelling
8567-
void
8568-
PostDataBuffers::copy_partial_post_data()
8599+
int64_t
8600+
PostDataBuffers::copy_partial_post_data(int64_t consumed_bytes)
85698601
{
85708602
if (post_data_buffer_done) {
8571-
return;
8603+
return 0;
85728604
}
8573-
Debug("http_redirect", "[PostDataBuffers::copy_partial_post_data] wrote %" PRId64 " bytes to buffers %" PRId64 "",
8574-
this->ua_buffer_reader->read_avail(), this->postdata_copy_buffer_start->read_avail());
8575-
this->postdata_copy_buffer->write(this->ua_buffer_reader);
8576-
this->ua_buffer_reader->consume(this->ua_buffer_reader->read_avail());
8605+
int64_t const bytes_to_copy = std::min(consumed_bytes, this->ua_buffer_reader->read_avail());
8606+
Debug("http_redirect", "given %" PRId64 " bytes consumed, copying %" PRId64 " bytes to buffers with %" PRId64 " available bytes",
8607+
consumed_bytes, bytes_to_copy, this->ua_buffer_reader->read_avail());
8608+
this->postdata_copy_buffer->write(this->ua_buffer_reader, bytes_to_copy);
8609+
this->ua_buffer_reader->consume(bytes_to_copy);
8610+
return bytes_to_copy;
85778611
}
85788612

85798613
IOBufferReader *

proxy/http/HttpSM.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class PostDataBuffers
179179

180180
void clear();
181181
void init(IOBufferReader *ua_reader);
182-
void copy_partial_post_data();
182+
int64_t copy_partial_post_data(int64_t consumed_bytes);
183183
IOBufferReader *get_post_data_buffer_clone_reader();
184184
void
185185
set_post_data_buffer_done(bool done)
@@ -313,8 +313,8 @@ class HttpSM : public Continuation, public PluginUserArgs<TS_USER_ARGS_TXN>
313313
bool debug_on = false; // Transaction specific debug flag
314314
char *redirect_url = nullptr; // url for force redirect (provide users a functionality to redirect to another url when needed)
315315
int redirect_url_len = 0;
316-
int redirection_tries = 0; // To monitor number of redirections
317-
int64_t transfered_bytes = 0; // Added to calculate POST data
316+
int redirection_tries = 0; // To monitor number of redirections
317+
int64_t transferred_bytes = 0; // Added to calculate POST data
318318

319319
// Tunneling request to plugin
320320
HttpPluginTunnel_t plugin_tunnel_type = HTTP_NO_PLUGIN_TUNNEL;
@@ -331,7 +331,7 @@ class HttpSM : public Continuation, public PluginUserArgs<TS_USER_ARGS_TXN>
331331
int64_t postbuf_buffer_avail();
332332
void postbuf_clear();
333333
void disable_redirect();
334-
void postbuf_copy_partial_data();
334+
int64_t postbuf_copy_partial_data(int64_t consumed_bytes);
335335
void postbuf_init(IOBufferReader *ua_reader);
336336
void set_postbuf_done(bool done);
337337
IOBufferReader *get_postbuf_clone_reader();
@@ -750,10 +750,10 @@ HttpSM::disable_redirect()
750750
this->_postbuf.clear();
751751
}
752752

753-
inline void
754-
HttpSM::postbuf_copy_partial_data()
753+
inline int64_t
754+
HttpSM::postbuf_copy_partial_data(int64_t consumed_bytes)
755755
{
756-
this->_postbuf.copy_partial_post_data();
756+
return this->_postbuf.copy_partial_post_data(consumed_bytes);
757757
}
758758

759759
inline void

0 commit comments

Comments
 (0)