summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorAdam Harvey <aharvey@php.net>2013-09-11 14:11:29 -0700
committerAdam Harvey <aharvey@php.net>2013-09-11 14:11:29 -0700
commit8983a38d5130c11bd96643dfa2b2f1393ac5969d (patch)
treef343d77eadaa9fc09f051a442bb369cf5156ba38 /ext
parentfba290c061027c24e4c8effdba37addd3430c3d4 (diff)
downloadphp-git-8983a38d5130c11bd96643dfa2b2f1393ac5969d.tar.gz
Request non-keep-alive connections by default in HTTP 1.1 requests.
As noted in FR #65634, at present we don't send a Connection request header when the protocol version is set to 1.1, which means that RFC-compliant Web servers should respond with keep-alive connections. Since there's no way of reusing the HTTP connection at present, this simply means that PHP will appear to hang until the remote server hits its connection timeout, which may be quite some time. This commit sends a "Connection: close" header by default when HTTP 1.1 (or later) is requested by the user via the context options. It can be overridden by specifying a Connection header in the context options. It isn't possible to disable sending of the Connection header, but given "Connection: keep-alive" is the same as the default HTTP 1.1 behaviour, I don't see this as a significant issue — users who want to opt in for that still can. As a note, although I've removed an efree(protocol_version), this doesn't result in a memory leak: protocol_version is freed in the out: block at the end of the function anyway, and there are no returns between the removed efree() and the later call. Yes, I ran the tests with valgrind to check that. ☺ Implements FR #65634 (HTTP wrapper is very slow with protocol_version 1.1).
Diffstat (limited to 'ext')
-rw-r--r--ext/standard/http_fopen_wrapper.c18
-rw-r--r--ext/standard/tests/http/bug65634.phpt81
2 files changed, 97 insertions, 2 deletions
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index ac6fdad4fe..8762fa4849 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -80,6 +80,7 @@
#define HTTP_HEADER_FROM 8
#define HTTP_HEADER_CONTENT_LENGTH 16
#define HTTP_HEADER_TYPE 32
+#define HTTP_HEADER_CONNECTION 64
#define HTTP_WRAPPER_HEADER_INIT 1
#define HTTP_WRAPPER_REDIRECTED 2
@@ -386,8 +387,6 @@ finish:
strlcat(scratch, " HTTP/", scratch_len);
strlcat(scratch, protocol_version, scratch_len);
strlcat(scratch, "\r\n", scratch_len);
- efree(protocol_version);
- protocol_version = NULL;
} else {
strlcat(scratch, " HTTP/1.0\r\n", scratch_len);
}
@@ -490,6 +489,11 @@ finish:
*(s-1) == '\t' || *(s-1) == ' ')) {
have_header |= HTTP_HEADER_TYPE;
}
+ if ((s = strstr(tmp, "connection:")) &&
+ (s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
+ *(s-1) == '\t' || *(s-1) == ' ')) {
+ have_header |= HTTP_HEADER_CONNECTION;
+ }
/* remove Proxy-Authorization header */
if (use_proxy && use_ssl && (s = strstr(tmp, "proxy-authorization:")) &&
(s == tmp || *(s-1) == '\r' || *(s-1) == '\n' ||
@@ -563,6 +567,16 @@ finish:
}
}
+ /* Send a Connection: close header when using HTTP 1.1 or later to avoid
+ * hanging when the server interprets the RFC literally and establishes a
+ * keep-alive connection, unless the user specifically requests something
+ * else by specifying a Connection header in the context options. */
+ if (protocol_version &&
+ ((have_header & HTTP_HEADER_CONNECTION) == 0) &&
+ (strncmp(protocol_version, "1.0", MIN(protocol_version_len, 3)) > 0)) {
+ php_stream_write_string(stream, "Connection: close\r\n");
+ }
+
if (context &&
php_stream_context_get_option(context, "http", "user_agent", &ua_zval) == SUCCESS &&
Z_TYPE_PP(ua_zval) == IS_STRING) {
diff --git a/ext/standard/tests/http/bug65634.phpt b/ext/standard/tests/http/bug65634.phpt
new file mode 100644
index 0000000000..8f358cc6cf
--- /dev/null
+++ b/ext/standard/tests/http/bug65634.phpt
@@ -0,0 +1,81 @@
+--TEST--
+Bug #65634 (HTTP wrapper is very slow with protocol_version 1.1)
+--INI--
+allow_url_fopen=1
+--SKIPIF--
+<?php require 'server.inc'; http_server_skipif('tcp://127.0.0.1:12342'); ?>
+--FILE--
+<?php
+require 'server.inc';
+
+function do_test($version, $connection) {
+ $options = [
+ 'http' => [
+ 'protocol_version' => $version,
+ ],
+ ];
+
+ if ($connection) {
+ $options['http']['header'] = "Connection: $connection";
+ }
+
+ $ctx = stream_context_create($options);
+
+ $responses = ["data://text/plain,HTTP/$version 204 No Content\r\n\r\n"];
+ $pid = http_server('tcp://127.0.0.1:12342', $responses, $output);
+
+ $fd = fopen('http://127.0.0.1:12342/', 'rb', false, $ctx);
+ fseek($output, 0, SEEK_SET);
+ echo stream_get_contents($output);
+
+ http_server_kill($pid);
+}
+
+echo "HTTP/1.0, default behaviour:\n";
+do_test('1.0', null);
+
+echo "HTTP/1.0, connection: close:\n";
+do_test('1.0', 'close');
+
+echo "HTTP/1.0, connection: keep-alive:\n";
+do_test('1.0', 'keep-alive');
+
+echo "HTTP/1.1, default behaviour:\n";
+do_test('1.1', null);
+
+echo "HTTP/1.1, connection: close:\n";
+do_test('1.1', 'close');
+
+echo "HTTP/1.1, connection: keep-alive:\n";
+do_test('1.1', 'keep-alive');
+?>
+--EXPECT--
+HTTP/1.0, default behaviour:
+GET / HTTP/1.0
+Host: 127.0.0.1:12342
+
+HTTP/1.0, connection: close:
+GET / HTTP/1.0
+Host: 127.0.0.1:12342
+Connection: close
+
+HTTP/1.0, connection: keep-alive:
+GET / HTTP/1.0
+Host: 127.0.0.1:12342
+Connection: keep-alive
+
+HTTP/1.1, default behaviour:
+GET / HTTP/1.1
+Host: 127.0.0.1:12342
+Connection: close
+
+HTTP/1.1, connection: close:
+GET / HTTP/1.1
+Host: 127.0.0.1:12342
+Connection: close
+
+HTTP/1.1, connection: keep-alive:
+GET / HTTP/1.1
+Host: 127.0.0.1:12342
+Connection: keep-alive
+