]> granicus.if.org Git - php/commitdiff
Request non-keep-alive connections by default in HTTP 1.1 requests.
authorAdam Harvey <aharvey@php.net>
Wed, 11 Sep 2013 21:11:29 +0000 (14:11 -0700)
committerAdam Harvey <aharvey@php.net>
Wed, 11 Sep 2013 21:11:29 +0000 (14:11 -0700)
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).

NEWS
UPGRADING
ext/standard/http_fopen_wrapper.c
ext/standard/tests/http/bug65634.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index bc3912602b4dcb112d72dcc7432e41a2787e3574..3848161f97c3a3508b38696168de349a26e458af 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -44,4 +44,8 @@ PHP                                                                        NEWS
     improvements based on this.
     (RFC: https://wiki.php.net/rfc/operator_overloading_gmp). (Nikita)
 
+- Standard:
+  . Implemented FR #65634 (HTTP wrapper is very slow with protocol_version
+    1.1). (Adam)
+
 <<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
index f392edd54d655d5a03cd97f3c7af85200ec62ded..e9fba517d45b60ecc63a444c5a71c3ec40a78827 100755 (executable)
--- a/UPGRADING
+++ b/UPGRADING
@@ -98,3 +98,7 @@ PHP X.Y UPGRADE NOTES
 
 - File upload:
   Uploads equal or greater than 2GB in size are now accepted.
+
+- HTTP stream wrapper:
+  HTTP 1.1 requests now include a Connection: close header unless explicitly
+  overridden by setting a Connection header via the header context option.
index ac6fdad4fef3aa7a889cbb481a6c1ecebca948a6..8762fa4849db1f61736567432d5dd80909808f65 100644 (file)
@@ -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 (file)
index 0000000..8f358cc
--- /dev/null
@@ -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
+