Skip to content
Closed
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
22 changes: 16 additions & 6 deletions proxy/http2/Http2ConnectionState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "Http2Frame.h"
#include "Http2DebugNames.h"
#include "HttpDebugNames.h"
#include "HttpSM.h"

#include "tscore/ink_assert.h"
#include "tscpp/util/PostScript.h"
Expand Down Expand Up @@ -633,9 +634,13 @@ Http2ConnectionState::rcv_rst_stream_frame(const Http2Frame &frame)

if (stream != nullptr) {
Http2StreamDebug(this->session, stream_id, "Parsed RST_STREAM: Error Code: %u", rst_stream.error_code);

stream->set_rx_error_code({ProxyErrorClass::TXN, static_cast<uint32_t>(rst_stream.error_code)});
stream->signal_read_event(VC_EVENT_EOS);

// Pass through origin server side RST_STREAM
if (stream->is_outbound_connection()) {
stream->signal_read_event(VC_EVENT_EOS);
}

stream->initiating_close();
}

Expand Down Expand Up @@ -2197,16 +2202,21 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
// Set END_STREAM on request headers for POST, etc. methods combined with
// an explicit length 0. Some origins RST on request headers with
// explicit zero length and no end stream flag, causing the request to
// fail. We emulate chromium behaviour here prevent such RSTs.
// fail. We emulate chromium behaviour here prevent such RSTs. Transfer-encoding
// implies there’s a body, regardless of whether it is chunked or not.
bool content_method = method == HTTP_WKSIDX_POST || method == HTTP_WKSIDX_PUSH || method == HTTP_WKSIDX_PUT;
bool is_transfer_encoded = send_hdr->presence(MIME_PRESENCE_TRANSFER_ENCODING);
bool has_content_header = send_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH);
bool explicit_zero_length = has_content_header && send_hdr->get_content_length() == 0;
int64_t content_length = has_content_header ? send_hdr->get_content_length() : 0L;
bool is_chunked =
is_transfer_encoded && send_hdr->value_get(MIME_FIELD_TRANSFER_ENCODING) == std::string_view(HTTP_VALUE_CHUNKED);

bool expect_content_stream =
is_transfer_encoded || // transfer encoded content length is unknown
(!content_method && has_content_header && !explicit_zero_length) || // non zero content with GET,etc
(content_method && !explicit_zero_length); // content-length >0 or empty with POST etc
is_transfer_encoded || // transfer encoded content length is unknown
(!content_method && has_content_header && !explicit_zero_length) || // nonzero content with GET,etc
(content_method && !explicit_zero_length) || // content-length >0 or empty with POST etc
stream->get_sm()->get_ua_txn()->has_request_body(content_length, is_chunked); // request has a body

// send END_STREAM if we don't expect any content
if (!expect_content_stream) {
Expand Down
6 changes: 5 additions & 1 deletion proxy/http2/Http2Stream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,11 @@ Http2Stream::do_io_close(int /* flags */)
REMEMBER(NO_EVENT, this->reentrancy_count);
Http2StreamDebug("do_io_close");

if (this->is_state_writeable()) { // Let the other end know we are going away
// Let the other end know we are going away.
// We only need to do this for the client side since we only need to pass through RST_STREAM
// from the server. If a client sends a RST_STREAM, we need to keep the server side alive so
// the background fill can function as intended.
if (!this->is_outbound_connection() && this->is_state_writeable()) {
this->get_connection_state().send_rst_stream_frame(_id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR);
}

Expand Down
20 changes: 20 additions & 0 deletions tests/gold_tests/cache/background_fill.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Test.Summary = 'Exercise Background Fill'
Test.SkipUnless(
Condition.HasCurlFeature('http2'),
Condition.HasProxyVerifierVersion('2.8.0')
)
Test.ContinueOnFail = True

Expand Down Expand Up @@ -130,10 +131,29 @@ def __testCase2(self):
tr.Processes.Default.Streams.stderr = "gold/background_fill_2_stderr.gold"
self.__checkProcessAfter(tr)

def __testCase3(self):
"""
HTTP/2 over TLS using ProxyVerifier
"""
tr = Test.AddTestRun()
self.__checkProcessBefore(tr)
tr.AddVerifierClientProcess(
"pv_client",
"replay/bg_fill.yaml",
http_ports=[
self.ts.Variables.port],
https_ports=[
self.ts.Variables.ssl_port],
other_args='--thread-limit 1')
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stdout = "gold/background_fill_3_stdout.gold"
self.__checkProcessAfter(tr)

def run(self):
self.__testCase0()
self.__testCase1()
self.__testCase2()
self.__testCase3()


BackgroundFillTest().run()
15 changes: 15 additions & 0 deletions tests/gold_tests/cache/gold/background_fill_3_stdout.gold
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
``
[DEBUG]: Received an HTTP/2 response for key 1 with stream id 1:
:status: 200
content-type: text/html
content-length: 11
``
via: https/2 traffic_server (ApacheTrafficServer/`` [cMsSfW])
``
[DEBUG]: Received an HTTP/2 response for key 2 with stream id 1:
:status: 200
content-type: text/html
content-length: 11
``
via: http/1.1 traffic_server (ApacheTrafficServer/`` [cHs f ])
``
106 changes: 106 additions & 0 deletions tests/gold_tests/cache/replay/bg_fill.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 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.

#
# This replay file assumes that caching is enabled and
# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
# test max-age in the client requests.
#


meta:
version: '1.0'
sessions:
- protocol:
- name: http
version: 2
- name: tls
sni: test_sni
- name: tcp
- name: ip
version: 4
transactions:
- client-request:
frames:
- HEADERS:
headers:
fields:
- [:method, GET]
- [:scheme, https]
- [:authority, example.data.com]
- [:path, /a/path]
- [Content-Type, text/html]
- [uuid, 1]
- RST_STREAM:
delay: 1s
error-code: INTERNAL_ERROR

server-response:
frames:
- HEADERS:
headers:
fields:
- [:status, 200]
- [Content-Type, text/html]
- [Content-Length, '11']
- DATA:
delay: 2s
content:
encoding: plain
data: server_test
size: 11

- protocol:
- name: http
version: 2
- name: tls
sni: test_sni
- name: tcp
- name: ip
version: 4
transactions:
- client-request:
delay: 3s
frames:
- HEADERS:
headers:
fields:
- [:method, GET]
- [:scheme, https]
- [:authority, example.data.com]
- [:path, /a/path]
- [Content-Type, text/html]
- [uuid, 2]

server-response:
frames:
- HEADERS:
headers:
fields:
- [:status, 200]
- [Content-Type, text/html]
- [Content-Length, '11']
- DATA:
content:
encoding: plain
data: server_test
size: 11

proxy-response:
content:
encoding: plain
data: server_test
verify: {as: equal}
2 changes: 1 addition & 1 deletion tests/gold_tests/h2/gold/server_after_headers.gold
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
``
``Submitting RST_STREAM frame for key 1 after HEADERS frame with error code ENHANCE_YOUR_CALM.
``Sent RST_STREAM frame for key 1 on stream 3.
``Submitted RST_STREAM frame for key 1 on stream 3.
``
70 changes: 70 additions & 0 deletions tests/gold_tests/h2/h2get_with_body.test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'''
'''
# 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.SkipUnless(
Condition.HasProxyVerifierVersion('2.7.1')
)

Test.Summary = '''
Test http/2 GET method that has a body
'''

pv_server = Test.MakeVerifierServerProcess("pv_server", "h2get_with_body.yaml")

ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True, enable_cache=True)

ts.addDefaultSSLFiles()
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.http.server_ports": f"{ts.Variables.port} {ts.Variables.ssl_port}:ssl",
"proxy.config.http.background_fill_active_timeout": "0",
"proxy.config.http.background_fill_completed_threshold": "0.0",
"proxy.config.http.cache.required_headers": 0, # Force cache
"proxy.config.http.insert_response_via_str": 2,
'proxy.config.http.server_session_sharing.pool': 'thread',
'proxy.config.http.server_session_sharing.match': 'ip,sni,cert',
'proxy.config.exec_thread.autoconfig.scale': 1.0,
'proxy.config.exec_thread.limit': 1,
'proxy.config.ssl.server.cert.path': f"{ts.Variables.SSLDir}",
'proxy.config.ssl.server.private_key.path': f"{ts.Variables.SSLDir}",
'proxy.config.ssl.client.alpn_protocols': 'h2,http/1.1',
'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
"proxy.config.diags.debug.enabled": 3,
"proxy.config.diags.debug.tags": "http",
})

ts.Disk.remap_config.AddLines([f'map / https://127.0.0.1:{pv_server.Variables.https_port}'])

tr = Test.AddTestRun()
tr.Processes.Default.StartBefore(pv_server)
tr.Processes.Default.StartBefore(ts)
tr.AddVerifierClientProcess(
"pv_client", "h2get_with_body.yaml", http_ports=[
ts.Variables.port], https_ports=[
ts.Variables.ssl_port], other_args='--thread-limit 1')
tr.Processes.Default.ReturnCode = 0

tr.Processes.Default.Streams.All += Testers.ContainsExpression(
'Equals Success: Key: "1", Content Data: "body", Value: "server_test"',
'Response check')

pv_server.Streams.All += Testers.ContainsExpression(
'Equals Success: Key: "1", Content Data: "body", Value: "client_test"',
'Request check')
79 changes: 79 additions & 0 deletions tests/gold_tests/h2/h2get_with_body.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 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.

#
# This replay file assumes that caching is enabled and
# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
# test max-age in the client requests.
#


meta:
version: '1.0'
sessions:
- protocol:
- name: http
version: 2
- name: tls
sni: test_sni
- name: tcp
- name: ip
version: 4
transactions:
- client-request:
frames:
- HEADERS:
headers:
fields:
- [:method, GET]
- [:scheme, https]
- [:authority, example.data.com]
- [:path, /a/path]
- [Content-Type, text/html]
- [uuid, 1]
- DATA:
delay: 1s
content:
encoding: plain
data: client_test
size: 11

proxy-request:
content:
encoding: plain
data: client_test
verify: {as: equal}

server-response:
frames:
- HEADERS:
headers:
fields:
- [:status, 200]
- [Content-Type, text/html]
- [Content-Length, '11']
- DATA:
delay: 2s
content:
encoding: plain
data: server_test
size: 11

proxy-response:
content:
encoding: plain
data: server_test
verify: {as: equal}
Loading