sapi/cli: support Expect 100-continue in PHP dev server - #23245
Conversation
1b0cb8d to
0052e12
Compare
|
@php/release-managers-86 @mbeccati Is this something you want in PHP 8.6? |
|
@Sjord Seems like a useful fix to me. I will discuss with the team. EDIT: we agree it's perfectly fine for 8.6 |
|
@iluuu1994 @Girgias Could you review this please? |
|
ok will try my best to review, I have some questions ... |
| append_http_status_line(&buffer, client->parser.http_major * 100 + client->parser.http_minor, 100, 0); | ||
| smart_str_appendl(&buffer, "\r\n", 2); | ||
| smart_str_0(&buffer); | ||
| php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); |
There was a problem hiding this comment.
Is the dev server meant to exit when the peer aborts here? If I m not mistaken, php_cli_server_client_send_through() call is reached from a place where a failed send() is fatal to the whole process, right ?
There was a problem hiding this comment.
When reading the code I come to the same conclusion, but I cannot reproduce it. php_cli_server_client_send_through is also used in sapi_cli_server_send_headers, so I thought that this would be the appropriate function to use.
There was a problem hiding this comment.
right right ... note that sapi_cli_server_send_headers is wrapped in zend_try (and is a sapi handler) ; this is where I would like Gina opinion.
There was a problem hiding this comment.
I got a bit further with this.
- The test now sets the SO_LINGER option on the socket (if possible), which makes the
sendinphp_cli_server_client_send_throughfail. This indeed made the server exit. - In the calling code
php_cli_server_client_send_throughis now wrapped within zend_try to handle the error.
So this seems solved, but I am also a little bit out of my depth here so it would be nice to get another set of eyes on this.
|
Would it be possible to add this test ? |
| } | ||
| client->parser.data = client; | ||
| nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read); | ||
| if (client->expect_continue && !client->request_read) { |
There was a problem hiding this comment.
so here it is send before the marlformed check below if I m not mistaken ?
There was a problem hiding this comment.
I swapped these two blocks. That indeed looks nicer and makes a little bit more sense.
There was a problem hiding this comment.
Almost there
if (client->expect_continue && !client->request_read) {
client->expect_continue = false;
if (client->parser.http_major * 100 + client->parser.http_minor >= 101) {
/* RFC 9110 10.1.1: a 100-continue expectation in an HTTP/1.0 request must be
* ignored, so the interim response is only ever sent as HTTP/1.1. */
static const char continue_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
bool send_success = false;
zend_try {
size_t sent = php_cli_server_client_send_through(client, continue_response, sizeof(continue_response) - 1);
send_success = sent == sizeof(continue_response) - 1;
} zend_end_try();
if (!send_success) {
*errstr = php_socket_strerror(php_socket_errno(), NULL, 0);
return -1;
}
}
}|
I think it makes sense this get merged first and you rebase from it. |
When posting large payloads, curl checks whether the server is ready for the body. It sends an `Expect: 100-continue` header and expects `HTTP/1.1 100 Continue` as the response before sending the body. The PHP development server did not support this, causing a timeout in curl. This made such requests take one second longer. - https://everything.curl.dev/http/post/expect100.html - php#23242
This is not a curl test, but a test of the behavior of the PHP development server. It should thus not be in the curl directory but in the sapi/cli directory.
If php_cli_server_client_send_through fails while sending `HTTP/1.1 100 Continue` we don't want the server to exit.
- fix the return value of php_cli_server_client_send_through so that it returns the number of bytes sent. - check that return value, and report error if we didn't send all bytes. - add @devnexen's test.
Use php_cli_server.inc. Return the output file to the caller so that we can check the error message in the output.
This does not functionally change anything, but it's nicer to have the `nbytes_consumed` close together.
6115983 to
0d4332a
Compare
| } | ||
| client->parser.data = client; | ||
| nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read); | ||
| if (client->expect_continue && !client->request_read) { |
There was a problem hiding this comment.
Almost there
if (client->expect_continue && !client->request_read) {
client->expect_continue = false;
if (client->parser.http_major * 100 + client->parser.http_minor >= 101) {
/* RFC 9110 10.1.1: a 100-continue expectation in an HTTP/1.0 request must be
* ignored, so the interim response is only ever sent as HTTP/1.1. */
static const char continue_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
bool send_success = false;
zend_try {
size_t sent = php_cli_server_client_send_through(client, continue_response, sizeof(continue_response) - 1);
send_success = sent == sizeof(continue_response) - 1;
} zend_end_try();
if (!send_success) {
*errstr = php_socket_strerror(php_socket_errno(), NULL, 0);
return -1;
}
}
}HTTP/1.0 doesn't support this. Check for HTTP/1.1 before setting client->expect_continue. This also makes it possible to hardcode the response string, which makes this a bit simpler. Add test that checks whether the server send something (100 Continue) using stream_select. We expect that the server did not send anything, since the test uses HTTP/1.0.
When posting large payloads, curl checks whether the server is ready for the body. It sends an
Expect: 100-continueheader and expectsHTTP/1.1 100 Continueas the response before sending the body. The PHP development server did not support this, causing a timeout in curl. This made such requests take one second longer.