diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h index 30fcf6c9ae5..c7c43e14b98 100644 --- a/proxy/hdrs/HTTP.h +++ b/proxy/hdrs/HTTP.h @@ -623,7 +623,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); @@ -642,6 +642,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: @@ -1001,6 +1002,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; + } +} + /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ @@ -1151,7 +1170,7 @@ http_hdr_status_get(HTTPHdrImpl *hh) -------------------------------------------------------------------------*/ inline HTTPStatus -HTTPHdr::status_get() +HTTPHdr::status_get() const { ink_assert(valid()); diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index eb98c4cbf96..8da3d862782 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -639,6 +639,17 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len, 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); + } + + this->signal_write_event(call_update); + if (this->response_reader->is_read_avail_more_than(0)) { this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); this->send_response_body(call_update); diff --git a/tests/gold_tests/h2/gold/httpbin_0_stdout.gold b/tests/gold_tests/h2/gold/httpbin_0_stdout.gold index c46139dc0bf..d8e61077a93 100644 --- a/tests/gold_tests/h2/gold/httpbin_0_stdout.gold +++ b/tests/gold_tests/h2/gold/httpbin_0_stdout.gold @@ -1,10 +1,5 @@ { - "args": {}, `` - "headers": { - "Accept": "*/*", `` - "Host": `` - "User-Agent": "curl/`` - }, `` - "origin": `` + `` "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/httpbin.test.py b/tests/gold_tests/h2/httpbin.test.py index d31247c5b09..d77fcc282be 100644 --- a/tests/gold_tests/h2/httpbin.test.py +++ b/tests/gold_tests/h2/httpbin.test.py @@ -78,14 +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))" +''' + # ---- # Test Cases # ---- # Test Case 0: Basic request and resposne test_run = Test.AddTestRun() -# TODO: when httpbin 0.8.0 or later is released, remove below json pretty print hack -test_run.Processes.Default.Command = "curl -vs -k --http2 https://127.0.0.1:{0}/get | python -c 'import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))".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) @@ -110,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