Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions include/proxy/http2/Http2Stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class Http2Stream : public ProxyTransaction
void increment_data_length(uint64_t length);
bool payload_length_is_valid() const;
bool is_write_vio_done() const;
int64_t write_vio_ntodo() const;
void update_sent_count(unsigned num_bytes);
Http2StreamId get_id() const;
Http2StreamState get_state() const;
Expand All @@ -174,6 +175,7 @@ class Http2Stream : public ProxyTransaction
void set_receive_headers(HTTPHdr &h2_headers);
void reset_receive_headers();
void reset_send_headers();
void set_sent_request_method(int method);
MIOBuffer *read_vio_writer() const;
int64_t read_vio_read_avail();
bool is_read_enabled() const;
Expand Down Expand Up @@ -214,6 +216,7 @@ class Http2Stream : public ProxyTransaction
Http2StreamId _id = -1;
Http2StreamState _state = Http2StreamState::HTTP2_STREAM_STATE_IDLE;
int64_t _http_sm_id = -1;
int _sent_request_method{-1};

HTTPHdr _receive_header;
#if TS_USE_MALLOC_ALLOCATOR
Expand Down Expand Up @@ -314,6 +317,12 @@ Http2Stream::is_write_vio_done() const
return this->write_vio.ntodo() == 0;
}

inline int64_t
Http2Stream::write_vio_ntodo() const
{
return this->write_vio.ntodo();
}

inline void
Http2Stream::update_sent_count(unsigned num_bytes)
{
Expand Down Expand Up @@ -389,6 +398,12 @@ Http2Stream::reset_send_headers()
this->_send_header.create(HTTPType::RESPONSE);
}

inline void
Http2Stream::set_sent_request_method(int method)
{
_sent_request_method = method;
}

// Check entire DATA payload length if content-length: header exists
inline void
Http2Stream::increment_data_length(uint64_t length)
Expand All @@ -405,9 +420,9 @@ Http2Stream::payload_length_is_valid() const

// Skip Content-Length check on [RFC 7230] 3.3.2 conditions
bool is_payload_precluded =
this->is_outbound_connection() && (_send_header.method_get_wksidx() == HTTP_WKSIDX_HEAD ||
(_send_header.method_get_wksidx() == HTTP_WKSIDX_GET && _send_header.presence(mask) &&
_receive_header.status_get() == HTTPStatus::NOT_MODIFIED));
this->is_outbound_connection() &&
(_sent_request_method == HTTP_WKSIDX_HEAD || (_sent_request_method == HTTP_WKSIDX_GET && _send_header.presence(mask) &&
_receive_header.status_get() == HTTPStatus::NOT_MODIFIED));

if (content_length != 0 && !is_payload_precluded && content_length != data_length) {
Warning("Bad payload length content_length=%d data_legnth=%d session_id=%" PRId64, content_length,
Expand Down
15 changes: 14 additions & 1 deletion src/proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len

uint8_t flags = 0x00;
IOBufferReader *resp_reader = stream->get_data_reader_for_send();
bool last_write_vio_payload{false};

SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());

Expand Down Expand Up @@ -2307,6 +2308,16 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len
} else {
payload_length = resp_reader->read_avail();
}
const int64_t remaining_write = stream->write_vio_ntodo();
if (remaining_write != INT64_MAX) {
if (remaining_write > 0) {
last_write_vio_payload = payload_length >= static_cast<size_t>(remaining_write);
payload_length = std::min(payload_length, static_cast<size_t>(remaining_write));
} else {
last_write_vio_payload = true;
payload_length = 0;
}
}
} else {
payload_length = 0;
}
Expand Down Expand Up @@ -2334,7 +2345,8 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len
return Http2SendDataFrameResult::NO_PAYLOAD;
}

if (stream->is_write_vio_done() && !resp_reader->is_read_avail_more_than(payload_length) && !stream->expect_send_trailer()) {
if (stream->is_write_vio_done() && (last_write_vio_payload || !resp_reader->is_read_avail_more_than(payload_length)) &&
!stream->expect_send_trailer()) {
Http2StreamDebug(this->session, stream->get_id(), "End of Data Frame");
flags |= HTTP2_FLAGS_DATA_END_STREAM;
}
Expand Down Expand Up @@ -2456,6 +2468,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
flags |= HTTP2_FLAGS_HEADERS_END_HEADERS;
if (stream->is_outbound_connection()) { // Will be sending a request_header
int method = send_hdr->method_get_wksidx();
stream->set_sent_request_method(method);

// Set END_STREAM on request headers for POST, etc. methods combined with
// an explicit length 0. Some origins RST on request headers with
Expand Down
2 changes: 1 addition & 1 deletion tests/gold_tests/h2/gold/http-request-method-metrics.gold
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
proxy.process.http.get_requests 4
proxy.process.http.post_requests 11
proxy.process.http.put_requests 0
proxy.process.http.put_requests 1
6 changes: 4 additions & 2 deletions tests/gold_tests/h2/h2origin.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
timeout = 30
watcher = tr.Processes.Process("watcher")
watcher.Command = f"sleep {timeout}"
watcher.Ready = When.FileContains(ts.Disk.squid_log.Name, r'14 http/1.1 http/2')
watcher.Ready = When.FileContains(ts.Disk.squid_log.Name, r'16 http/2 http/2')
watcher.TimeOut = timeout
tr.StillRunningAfter = ts
tr.StillRunningAfter = server
Expand All @@ -99,7 +99,7 @@
tr.Processes.Default.ReturnCode = 0

# UUIDs 1-4 should be http/1.1 clients and H2 origin
# UUIDs 5-9 should be http/2 clients and H2 origins
# UUIDs 5-11 and 15-16 should be http/2 clients and H2 origins
ts.Disk.squid_log.Content = Testers.ContainsExpression(" [1-4] http/1.1 http/2", "cases 1-4 request http/1.1")
Comment thread
bneradt marked this conversation as resolved.
ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [1-4] http/2 http/2", "cases 1-4 request http/1.1")
ts.Disk.squid_log.Content += Testers.ContainsExpression(" 1[1-4] http/1.1 http/2", "cases 12-14 request http/1.1")
Expand All @@ -108,6 +108,8 @@
ts.Disk.squid_log.Content += Testers.ExcludesExpression(" [5-9] http/1.1 http/2", "cases 5-11 request http/2")
ts.Disk.squid_log.Content += Testers.ContainsExpression(" 1[0-1] http/2 http/2", "cases 5-11 request http/2")
ts.Disk.squid_log.Content += Testers.ExcludesExpression(" 1[0-1] http/1.1 http/2", "cases 5-11 request http/2")
ts.Disk.squid_log.Content += Testers.ContainsExpression(" 1[5-6] http/2 http/2", "cases 15-16 request http/2")
ts.Disk.squid_log.Content += Testers.ExcludesExpression(" 1[5-6] http/1.1 http/2", "cases 15-16 request http/2")

tr = Test.AddTestRun("Test HTTP method Metrics")
tr.Processes.Default.Command = (
Expand Down
123 changes: 123 additions & 0 deletions tests/gold_tests/h2/replay_h2origin/h2-origin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,126 @@ sessions:
content:
encoding: plain
size: 3200

#
# Test 8: HEAD response with a non-zero Content-Length and no body.
#
- all: { headers: { fields: [[ uuid, 15 ]]}}

client-request:
version: '2'
scheme: https
method: HEAD
url: /some/head
headers:
encoding: esc_json
fields:
- [ Host, data.brian.example.com ]
content:
encoding: plain
size: 0

proxy-request:
protocol:
stack: http2
tls:
version: TLSv1.2
sni: data.brian.example.com
proxy-verify-mode: 1
proxy-provided-cert: false
version: '2'
scheme: https
method: HEAD
url: /some/head
headers:
encoding: esc_json
fields:
- [ Host, data.brian.example.com ]
- [ Content-Length, 0 ]
content:
encoding: plain
size: 0

server-response:
version: '2'
status: 200
headers:
encoding: esc_json
fields:
- [ Content-Length, 100 ]
content:
encoding: plain
size: 0

proxy-response:
version: '2'
status: 200
headers:
encoding: esc_json
fields:
- [ Content-Length, 100 ]
content:
encoding: plain
size: 0

#
# Test 9: large PUT body with a large response.
#
- all: { headers: { fields: [[ uuid, 16 ]]}}

client-request:
version: '2'
scheme: https
method: PUT
url: /some/large-put
headers:
encoding: esc_json
fields:
- [ Host, data.brian.example.com ]
- [ Content-Length, 300000 ]
content:
encoding: plain
size: 300000

proxy-request:
protocol:
stack: http2
tls:
version: TLSv1.2
sni: data.brian.example.com
proxy-verify-mode: 1
proxy-provided-cert: false
version: '2'
scheme: https
method: PUT
url: /some/large-put
headers:
encoding: esc_json
fields:
- [ Host, data.brian.example.com ]
- [ Content-Length, 300000 ]
content:
encoding: plain
size: 300000

server-response:
version: '2'
status: 200
headers:
encoding: esc_json
fields:
- [ Content-Length, 300000 ]
content:
encoding: plain
size: 300000

proxy-response:
version: '2'
status: 200
headers:
encoding: esc_json
fields:
- [ Content-Length, 300000 ]
content:
encoding: plain
size: 300000