aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorLinus Nielsen Feltzing <linus@haxx.se>2013-02-15 11:50:45 +0100
committerLinus Nielsen Feltzing <linus@haxx.se>2013-03-13 23:55:24 +0100
commit0f147887b0d592d5fa72215282e84103eb165ad7 (patch)
tree4ad5bc8185f3f8df0eed54ee45afd26d692b44a0 /tests
parent911b2d3f677eb1538d8469d43df5780e8b7c7abc (diff)
Multiple pipelines and limiting the number of connections.
Introducing a number of options to the multi interface that allows for multiple pipelines to the same host, in order to optimize the balance between the penalty for opening new connections and the potential pipelining latency. Two new options for limiting the number of connections: CURLMOPT_MAX_HOST_CONNECTIONS - Limits the number of running connections to the same host. When adding a handle that exceeds this limit, that handle will be put in a pending state until another handle is finished, so we can reuse the connection. CURLMOPT_MAX_TOTAL_CONNECTIONS - Limits the number of connections in total. When adding a handle that exceeds this limit, that handle will be put in a pending state until another handle is finished. The free connection will then be reused, if possible, or closed if the pending handle can't reuse it. Several new options for pipelining: CURLMOPT_MAX_PIPELINE_LENGTH - Limits the pipeling length. If a pipeline is "full" when a connection is to be reused, a new connection will be opened if the CURLMOPT_MAX_xxx_CONNECTIONS limits allow it. If not, the handle will be put in a pending state until a connection is ready (either free or a pipe got shorter). CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE - A pipelined connection will not be reused if it is currently processing a transfer with a content length that is larger than this. CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE - A pipelined connection will not be reused if it is currently processing a chunk larger than this. CURLMOPT_PIPELINING_SITE_BL - A blacklist of hosts that don't allow pipelining. CURLMOPT_PIPELINING_SERVER_BL - A blacklist of server types that don't allow pipelining. See the curl_multi_setopt() man page for details.
Diffstat (limited to 'tests')
-rw-r--r--tests/README1
-rw-r--r--tests/data/Makefile.am3
-rw-r--r--tests/data/test190057
-rw-r--r--tests/data/test190158
-rw-r--r--tests/data/test190257
-rw-r--r--tests/data/test190357
-rw-r--r--tests/data/test2033144
-rw-r--r--tests/data/test5302
-rw-r--r--tests/data/test5368
-rw-r--r--tests/data/test5842
-rwxr-xr-xtests/http_pipe.py447
-rw-r--r--tests/libtest/.gitignore2
-rw-r--r--tests/libtest/Makefile.inc10
-rw-r--r--tests/libtest/lib1900.c256
-rw-r--r--tests/libtest/lib530.c15
-rw-r--r--tests/libtest/libntlmconnect.c6
-rwxr-xr-xtests/runtests.pl116
-rw-r--r--tests/serverhelp.pm4
18 files changed, 1231 insertions, 14 deletions
diff --git a/tests/README b/tests/README
index fff618ef2..3ddc1c93e 100644
--- a/tests/README
+++ b/tests/README
@@ -39,6 +39,7 @@ The cURL Test Suite
1.1 Requires to run
perl (and a unix-style shell)
+ python (and a unix-style shell)
diff (when a test fails, a diff is shown)
stunnel (for HTTPS and FTPS tests)
OpenSSH or SunSSH (for SCP, SFTP and SOCKS4/5 tests)
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index f65fe0688..7c2e648f5 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -95,13 +95,14 @@ test1400 test1401 test1402 test1403 test1404 test1405 test1406 test1407 \
test1408 test1409 test1410 test1411 test1412 test1413 \
test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
test1508 \
+test1900 test1901 test1902 test1903 \
test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
test2008 test2009 test2010 test2011 test2012 test2013 test2014 test2015 \
test2016 test2017 test2018 test2019 test2020 test2021 test2022 \
test2023 test2024 test2025 \
test2026 test2027 test2028 \
test2029 test2030 test2031 \
-test2032
+test2032 test2033
EXTRA_DIST = $(TESTCASES) DISABLED
diff --git a/tests/data/test1900 b/tests/data/test1900
new file mode 100644
index 000000000..857c6096b
--- /dev/null
+++ b/tests/data/test1900
@@ -0,0 +1,57 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+pipelining
+multi
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+Adding handle 0
+Handle 0 Completed with status 0
+Adding handle 1
+Adding handle 2
+Adding handle 3
+Adding handle 4
+Adding handle 5
+Adding handle 6
+Handle 4 Completed with status 0
+Handle 5 Completed with status 0
+Handle 6 Completed with status 0
+Handle 1 Completed with status 0
+Handle 2 Completed with status 0
+Handle 3 Completed with status 0
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http-pipe
+</server>
+<tool>
+lib1900
+</tool>
+ <name>
+HTTP GET using pipelining
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPIPEPORT/
+</command>
+<file name="log/urls.txt">
+0 1k.txt
+1000 100k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test1901 b/tests/data/test1901
new file mode 100644
index 000000000..bacf9cb09
--- /dev/null
+++ b/tests/data/test1901
@@ -0,0 +1,58 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+pipelining
+multi
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+Adding handle 0
+Handle 0 Completed with status 0
+Adding handle 1
+Adding handle 2
+Adding handle 3
+Adding handle 4
+Adding handle 5
+Adding handle 6
+Handle 2 Completed with status 0
+Handle 3 Completed with status 0
+Handle 4 Completed with status 0
+Handle 1 Completed with status 0
+Handle 5 Completed with status 0
+Handle 6 Completed with status 0
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http-pipe
+</server>
+<tool>
+lib1900
+</tool>
+ <name>
+HTTP GET using pipelining, blacklisted site
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPIPEPORT/
+</command>
+<file name="log/urls.txt">
+blacklist_site 127.0.0.1:%HTTPPIPEPORT
+0 1k.txt
+1000 100k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test1902 b/tests/data/test1902
new file mode 100644
index 000000000..22f262176
--- /dev/null
+++ b/tests/data/test1902
@@ -0,0 +1,57 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+pipelining
+multi
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+Adding handle 0
+Handle 0 Completed with status 0
+Adding handle 1
+Adding handle 2
+Adding handle 3
+Adding handle 4
+Adding handle 5
+Adding handle 6
+Handle 1 Completed with status 0
+Handle 4 Completed with status 0
+Handle 5 Completed with status 0
+Handle 6 Completed with status 0
+Handle 2 Completed with status 0
+Handle 3 Completed with status 0
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http-pipe
+</server>
+<tool>
+lib1900
+</tool>
+ <name>
+HTTP GET using pipelining, broken pipe
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPIPEPORT/
+</command>
+<file name="log/urls.txt">
+0 1k.txt
+1000 connection_close.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+0 1k.txt
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test1903 b/tests/data/test1903
new file mode 100644
index 000000000..01efa67f8
--- /dev/null
+++ b/tests/data/test1903
@@ -0,0 +1,57 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+pipelining
+multi
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+Adding handle 0
+Handle 0 Completed with status 0
+Adding handle 1
+Adding handle 2
+Adding handle 3
+Adding handle 4
+Adding handle 5
+Adding handle 6
+Handle 2 Completed with status 0
+Handle 3 Completed with status 0
+Handle 4 Completed with status 0
+Handle 5 Completed with status 0
+Handle 6 Completed with status 0
+Handle 1 Completed with status 0
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http-pipe
+</server>
+<tool>
+lib1900
+</tool>
+ <name>
+HTTP GET using pipelining, penalized on content-length
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPIPEPORT/
+</command>
+<file name="log/urls.txt">
+0 1k.txt
+1000 100k.txt
+550 alphabet.txt
+10 alphabet.txt
+10 alphabet.txt
+10 alphabet.txt
+10 alphabet.txt
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test2033 b/tests/data/test2033
new file mode 100644
index 000000000..ad926ebce
--- /dev/null
+++ b/tests/data/test2033
@@ -0,0 +1,144 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+HTTP Basic auth
+HTTP NTLM auth
+pipelining
+</keywords>
+</info>
+# Server-side
+<reply>
+
+<!-- Basic auth -->
+<data100>
+HTTP/1.1 401 Need Basic or NTLM auth
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 29
+WWW-Authenticate: NTLM
+WWW-Authenticate: Basic realm="testrealm"
+
+This is a bad password page!
+</data100>
+
+<!-- NTML auth -->
+<data200>
+HTTP/1.1 401 Need Basic or NTLM auth (2)
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 27
+WWW-Authenticate: NTLM
+WWW-Authenticate: Basic realm="testrealm"
+
+This is not the real page!
+</data200>
+
+<data1201>
+HTTP/1.1 401 NTLM intermediate (2)
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 33
+WWW-Authenticate: NTLM TlRMTVNTUAACAAAACAAIADAAAAAGggEAq6U1NAWaJCIAAAAAAAAAAAAAAAA4AAAATlRMTUF1dGg=
+
+This is still not the real page!
+</data1201>
+
+<data1202>
+HTTP/1.1 200 Things are fine in server land
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 32
+
+Finally, this is the real page!
+</data1202>
+
+<datacheck>
+HTTP/1.1 401 Need Basic or NTLM auth
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 29
+WWW-Authenticate: NTLM
+WWW-Authenticate: Basic realm="testrealm"
+
+This is a bad password page!
+HTTP/1.1 401 Need Basic or NTLM auth
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 29
+WWW-Authenticate: NTLM
+WWW-Authenticate: Basic realm="testrealm"
+
+This is a bad password page!
+HTTP/1.1 401 NTLM intermediate (2)
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 33
+WWW-Authenticate: NTLM TlRMTVNTUAACAAAACAAIADAAAAAGggEAq6U1NAWaJCIAAAAAAAAAAAAAAAA4AAAATlRMTUF1dGg=
+
+HTTP/1.1 200 Things are fine in server land
+Server: Microsoft-IIS/5.0
+Content-Type: text/html; charset=iso-8859-1
+Content-Length: 32
+
+Finally, this is the real page!
+</datacheck>
+
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib2033
+</tool>
+
+ <name>
+NTLM connection mapping, pipelining enabled
+ </name>
+ <setenv>
+# we force our own host name, in order to make the test machine independent
+CURL_GETHOSTNAME=curlhost
+# we try to use the LD_PRELOAD hack, if not a debug build
+LD_PRELOAD=%PWD/libtest/.libs/libhostname.so
+ </setenv>
+ <command>
+http://%HOSTIP:%HTTPPORT/2032
+</command>
+<precheck>
+chkhostname curlhost
+</precheck>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /20320100 HTTP/1.1
+Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
+Host: 127.0.0.1:8990
+Accept: */*
+
+GET /20320100 HTTP/1.1
+Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
+Host: 127.0.0.1:8990
+Accept: */*
+
+GET /20320200 HTTP/1.1
+Authorization: NTLM TlRMTVNTUAABAAAABoIIAAAAAAAAAAAAAAAAAAAAAAA=
+Host: 127.0.0.1:8990
+Accept: */*
+
+GET /20320200 HTTP/1.1
+Authorization: NTLM TlRMTVNTUAADAAAAGAAYAEAAAAAYABgAWAAAAAAAAABwAAAACAAIAHAAAAAIAAgAeAAAAAAAAAAAAAAABoIBAI+/Fp9IERAQ74OsdNPbBpg7o8CVwLSO4DtFyIcZHUMKVktWIu92s2892OVpd2JzqnRlc3R1c2VyY3VybGhvc3Q=
+Host: 127.0.0.1:8990
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test530 b/tests/data/test530
index 359d04cf1..09e742179 100644
--- a/tests/data/test530
+++ b/tests/data/test530
@@ -2,7 +2,7 @@
<info>
<keywords>
HTTP
-Pipelining
+pipelining
multi
</keywords>
</info>
diff --git a/tests/data/test536 b/tests/data/test536
index 334c07f06..ef8263d98 100644
--- a/tests/data/test536
+++ b/tests/data/test536
@@ -1,4 +1,12 @@
<testcase>
+<info>
+<keywords>
+HTTP
+pipelining
+multi
+</keywords>
+</info>
+
<reply>
<data mode="text">
HTTP/1.1 404 Badness
diff --git a/tests/data/test584 b/tests/data/test584
index 81d6a083d..8d1ca92f9 100644
--- a/tests/data/test584
+++ b/tests/data/test584
@@ -2,7 +2,7 @@
<info>
<keywords>
HTTP
-Pipelining
+pipelining
multi
</keywords>
</info>
diff --git a/tests/http_pipe.py b/tests/http_pipe.py
new file mode 100755
index 000000000..67185be8c
--- /dev/null
+++ b/tests/http_pipe.py
@@ -0,0 +1,447 @@
+#!/usr/bin/python
+
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed 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.
+#
+# Modified by Linus Nielsen Feltzing for inclusion in the libcurl test
+# framework
+#
+import SocketServer
+import argparse
+import re
+import select
+import socket
+import time
+import pprint
+import os
+
+INFO_MESSAGE = '''
+This is a test server to test the libcurl pipelining functionality.
+It is a modified version if Google's HTTP pipelining test server. More
+information can be found here:
+
+http://dev.chromium.org/developers/design-documents/network-stack/http-pipelining
+
+Source code can be found here:
+
+http://code.google.com/p/http-pipelining-test/
+'''
+MAX_REQUEST_SIZE = 1024 # bytes
+MIN_POLL_TIME = 0.01 # seconds. Minimum time to poll, in order to prevent
+ # excessive looping because Python refuses to poll for
+ # small timeouts.
+SEND_BUFFER_TIME = 0.5 # seconds
+TIMEOUT = 30 # seconds
+
+
+class Error(Exception):
+ pass
+
+
+class RequestTooLargeError(Error):
+ pass
+
+
+class ServeIndexError(Error):
+ pass
+
+
+class UnexpectedMethodError(Error):
+ pass
+
+
+class RequestParser(object):
+ """Parses an input buffer looking for HTTP GET requests."""
+
+ global logfile
+
+ LOOKING_FOR_GET = 1
+ READING_HEADERS = 2
+
+ HEADER_RE = re.compile('([^:]+):(.*)\n')
+ REQUEST_RE = re.compile('([^ ]+) ([^ ]+) HTTP/(\d+)\.(\d+)\n')
+
+ def __init__(self):
+ """Initializer."""
+ self._buffer = ""
+ self._pending_headers = {}
+ self._pending_request = ""
+ self._state = self.LOOKING_FOR_GET
+ self._were_all_requests_http_1_1 = True
+ self._valid_requests = []
+
+ def ParseAdditionalData(self, data):
+ """Finds HTTP requests in |data|.
+
+ Args:
+ data: (String) Newly received input data from the socket.
+
+ Returns:
+ (List of Tuples)
+ (String) The request path.
+ (Map of String to String) The header name and value.
+
+ Raises:
+ RequestTooLargeError: If the request exceeds MAX_REQUEST_SIZE.
+ UnexpectedMethodError: On a non-GET method.
+ Error: On a programming error.
+ """
+ logfile = open('log/server.input', 'a')
+ logfile.write(data)
+ logfile.close()
+ self._buffer += data.replace('\r', '')
+ should_continue_parsing = True
+ while should_continue_parsing:
+ if self._state == self.LOOKING_FOR_GET:
+ should_continue_parsing = self._DoLookForGet()
+ elif self._state == self.READING_HEADERS:
+ should_continue_parsing = self._DoReadHeader()
+ else:
+ raise Error('Unexpected state: ' + self._state)
+ if len(self._buffer) > MAX_REQUEST_SIZE:
+ raise RequestTooLargeError(
+ 'Request is at least %d bytes' % len(self._buffer))
+ valid_requests = self._valid_requests
+ self._valid_requests = []
+ return valid_requests
+
+ @property
+ def were_all_requests_http_1_1(self):
+ return self._were_all_requests_http_1_1
+
+ def _DoLookForGet(self):
+ """Tries to parse an HTTTP request line.
+
+ Returns:
+ (Boolean) True if a request was found.
+
+ Raises:
+ UnexpectedMethodError: On a non-GET method.
+ """
+ m = self.REQUEST_RE.match(self._buffer)
+ if not m:
+ return False
+ method, path, http_major, http_minor = m.groups()
+
+ if method != 'GET':
+ raise UnexpectedMethodError('Unexpected method: ' + method)
+ if path in ['/', '/index.htm', '/index.html']:
+ raise ServeIndexError()
+
+ if http_major != '1' or http_minor != '1':
+ self._were_all_requests_http_1_1 = False
+
+# print method, path
+
+ self._pending_request = path
+ self._buffer = self._buffer[m.end():]
+ self._state = self.READING_HEADERS
+ return True
+
+ def _DoReadHeader(self):
+ """Tries to parse a HTTP header.
+
+ Returns:
+ (Boolean) True if it found the end of the request or a HTTP header.
+ """
+ if self._buffer.startswith('\n'):
+ self._buffer = self._buffer[1:]
+ self._state = self.LOOKING_FOR_GET
+ self._valid_requests.append((self._pending_request,
+ self._pending_headers))
+ self._pending_headers = {}
+ self._pending_request = ""
+ return True
+
+ m = self.HEADER_RE.match(self._buffer)
+ if not m:
+ return False
+
+ header = m.group(1).lower()
+ value = m.group(2).strip().lower()
+ if header not in self._pending_headers:
+ self._pending_headers[header] = value
+ self._buffer = self._buffer[m.end():]
+ return True
+
+
+class ResponseBuilder(object):
+ """Builds HTTP responses for a list of accumulated requests."""
+
+ def __init__(self):
+ """Initializer."""
+ self._max_pipeline_depth = 0
+ self._requested_paths = []
+ self._processed_end = False
+ self._were_all_requests_http_1_1 = True
+
+ def QueueRequests(self, requested_paths, were_all_requests_http_1_1):
+ """Adds requests to the queue of requests.
+
+ Args:
+ requested_paths: (List of Strings) Requested paths.
+ """
+ self._requested_paths.extend(requested_paths)
+ self._were_all_requests_http_1_1 = were_all_requests_http_1_1
+
+ def Chunkify(self, data, chunksize):
+ """ Divides a string into chunks
+ """
+ return [hex(chunksize)[2:] + "\r\n" + data[i:i+chunksize] + "\r\n" for i in range(0, len(data), chunksize)]
+
+ def BuildResponses(self):
+ """Converts the queue of requests into responses.
+
+ Returns:
+ (String) Buffer containing all of the responses.
+ """
+ result = ""
+ self._max_pipeline_depth = max(self._max_pipeline_depth,
+ len(self._requested_paths))
+ for path, headers in self._requested_paths:
+ if path == '/verifiedserver':
+ body = "WE ROOLZ: {}\r\n".format(os.getpid());
+ result += self._BuildResponse(
+ '200 OK', ['Server: Apache',
+ 'Content-Length: {}'.format(len(body)),
+ 'Cache-Control: no-store'], body)
+
+ elif path == '/alphabet.txt':
+ body = 'abcdefghijklmnopqrstuvwxyz'
+ result += self._BuildResponse(
+ '200 OK', ['Server: Apache',
+ 'Content-Length: 26',
+ 'Cache-Control: no-store'], body)
+
+ elif path == '/reverse.txt':
+ body = 'zyxwvutsrqponmlkjihgfedcba'
+ result += self._BuildResponse(
+ '200 OK', ['Content-Length: 26', 'Cache-Control: no-store'], body)
+
+ elif path == '/chunked.txt':
+ body = ('7\r\nchunked\r\n'
+ '8\r\nencoding\r\n'
+ '2\r\nis\r\n'
+ '3\r\nfun\r\n'
+ '0\r\n\r\n')
+ result += self._BuildResponse(
+ '200 OK', ['Transfer-Encoding: chunked', 'Cache-Control: no-store'],
+ body)
+
+ elif path == '/cached.txt':
+ body = 'azbycxdwevfugthsirjqkplomn'
+ result += self._BuildResponse(
+ '200 OK', ['Content-Length: 26', 'Cache-Control: max-age=60'], body)
+
+ elif path == '/connection_close.txt':
+ body = 'azbycxdwevfugthsirjqkplomn'
+ result += self._BuildResponse(
+ '200 OK', ['Content-Length: 26', 'Cache-Control: max-age=60', 'Connection: close'], body)
+ self._processed_end = True
+
+ elif path == '/1k.txt':
+ str = '0123456789abcdef'
+ body = ''.join([str for num in xrange(64)])
+ result += self._BuildResponse(
+ '200 OK', ['Server: Apache',
+ 'Content-Length: 1024',
+ 'Cache-Control: max-age=60'], body)
+
+ elif path == '/10k.txt':
+ str = '0123456789abcdef'
+ body = ''.join([str for num in xrange(640)])
+ result += self._BuildResponse(
+ '200 OK', ['Server: Apache',
+ 'Content-Length: 10240',
+ 'Cache-Control: max-age=60'], body)
+
+ elif path == '/100k.txt':
+ str = '0123456789abcdef'
+ body = ''.join([str for num in xrange(6400)])
+ result += self._BuildResponse(
+ '200 OK',
+ ['Server: Apache',
+ 'Content-Length: 102400',
+ 'Cache-Control: max-age=60'],
+ body)
+
+ elif path == '/100k_chunked.txt':
+ str = '0123456789abcdef'
+ moo = ''.join([str for num in xrange(6400)])
+ body = self.Chunkify(moo, 20480)
+ body.append('0\r\n\r\n')
+ body = ''.join(body)
+
+ result += self._BuildResponse(
+ '200 OK', ['Transfer-Encoding: chunked', 'Cache-Control: no-store'], body)
+
+ elif path == '/stats.txt':
+ results = {
+ 'max_pipeline_depth': self._max_pipeline_depth,
+ 'were_all_requests_http_1_1': int(self._were_all_requests_http_1_1),
+ }
+ body = ','.join(['%s:%s' % (k, v) for k, v in results.items()])
+ result += self._BuildResponse(
+ '200 OK',
+ ['Content-Length: %s' % len(body), 'Cache-Control: no-store'], body)
+ self._processed_end = True
+
+ else:
+ result += self._BuildResponse('404 Not Found', ['Content-Length: 7'], 'Go away')
+ if self._processed_end:
+ break
+ self._requested_paths = []
+ return result
+
+ def WriteError(self, status, error):
+ """Returns an HTTP response for the specified error.
+
+ Args:
+ status: (String) Response code and descrtion (e.g. "404 Not Found")
+
+ Returns:
+ (String) Text of HTTP response.
+ """
+ return self._BuildResponse(
+ status, ['Connection: close', 'Content-Type: text/plain'], error)
+
+ @property
+ def processed_end(self):
+ return self._processed_end
+
+ def _BuildResponse(self, status, headers, body):
+ """Builds an HTTP response.
+
+ Args:
+ status: (String) Response code and descrtion (e.g. "200 OK")
+ headers: (List of Strings) Headers (e.g. "Connection: close")
+ body: (String) Response body.
+
+ Returns:
+ (String) Text of HTTP response.
+ """
+ return ('HTTP/1.1 %s\r\n'
+ '%s\r\n'
+ '\r\n'
+ '%s' % (status, '\r\n'.join(headers), body))
+
+
+class PipelineRequestHandler(SocketServer.BaseRequestHandler):
+ """Called on an incoming TCP connection."""
+
+ def _GetTimeUntilTimeout(self):
+ return self._start_time + TIMEOUT - time.time()
+
+ def _GetTimeUntilNextSend(self):
+ if not self._last_queued_time:
+ return TIMEOUT
+ return self._last_queued_time + SEND_BUFFER_TIME - time.time()
+
+ def handle(self):
+ self._request_parser = RequestParser()
+ self._response_builder = ResponseBuilder()
+ self._last_queued_time = 0
+ self._num_queued = 0
+ self._num_written = 0
+ self._send_buffer = ""
+ self._start_time = time.time()
+ try:
+ poller = select.epoll(sizehint=1)
+ poller.register(self.request.fileno(), select.EPOLLIN)
+ while not self._response_builder.processed_end or self._send_buffer:
+
+ time_left = self._GetTimeUntilTimeout()
+ time_until_next_send = self._GetTimeUntilNextSend()
+ max_poll_time = min(time_left, time_until_next_send) + MIN_POLL_TIME
+
+ events = None
+ if max_poll_time > 0:
+ if self._send_buffer:
+ poller.modify(self.request.fileno(),
+ select.EPOLLIN | select.EPOLLOUT)
+ else:
+ poller.modify(self.request.fileno(), select.EPOLLIN)
+ events = poller.poll(timeout=max_poll_time)
+
+ if self._GetTimeUntilTimeout() <= 0:
+ return
+
+ if self._GetTimeUntilNextSend() <= 0:
+ self._send_buffer += self._response_builder.BuildResponses()
+ self._num_written = self._num_queued
+ self._last_queued_time = 0
+
+ for fd, mode in events:
+ if mode & select.EPOLLIN:
+ new_data = self.request.recv(MAX_REQUEST_SIZE, socket.MSG_DONTWAIT)
+ if not new_data:
+ return
+ new_requests = self._request_parser.ParseAdditionalData(new_data)
+ self._response_builder.QueueRequests(
+ new_requests, self._request_parser.were_all_requests_http_1_1)
+ self._num_queued += len(new_requests)
+ self._last_queued_time = time.time()
+ elif mode & select.EPOLLOUT:
+ num_bytes_sent = self.request.send(self._send_buffer[0:4096])
+ self._send_buffer = self._send_buffer[num_bytes_sent:]
+ time.sleep(0.05)
+ else:
+ return
+
+ except RequestTooLargeError as e:
+ self.request.send(self._response_builder.WriteError(
+ '413 Request Entity Too Large', e))
+ raise
+ except UnexpectedMethodError as e:
+ self.request.send(self._response_builder.WriteError(
+ '405 Method Not Allowed', e))
+ raise
+ except ServeIndexError:
+ self.request.send(self._response_builder.WriteError(
+ '200 OK', INFO_MESSAGE))
+ except Exception as e:
+ print e
+ self.request.close()
+
+
+class PipelineServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
+ pass
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--port", action="store", default=0,
+ type=int, help="port to listen on")
+parser.add_argument("--verbose", action="store", default=0,
+ type=int, help="verbose output")
+parser.add_argument("--pidfile", action="store", default=0,
+ help="file name for the PID")
+parser.add_argument("--logfile", action="store", default=0,
+ help="file name for the log")
+parser.add_argument("--srcdir", action="store", default=0,
+ help="test directory")
+parser.add_argument("--id", action="store", default=0,
+ help="server ID")
+parser.add_argument("--ipv4", action="store_true", default=0,
+ help="IPv4 flag")
+args = parser.parse_args()
+
+if args.pidfile:
+ pid = os.getpid()
+ f = open(args.pidfile, 'w')
+ f.write('{}'.format(pid))
+ f.close()
+
+server = PipelineServer(('0.0.0.0', args.port), PipelineRequestHandler)
+server.allow_reuse_address = True
+server.serve_forever()
diff --git a/tests/libtest/.gitignore b/tests/libtest/.gitignore
index 28e83c08d..7fd6e7e30 100644
--- a/tests/libtest/.gitignore
+++ b/tests/libtest/.gitignore
@@ -1,5 +1,7 @@
chkhostname
lib5[0-9][0-9]
lib150[0-9]
+lib19[0-9][0-9]
+lib2033
libauthretry
libntlmconnect
diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
index 88fc7d8fa..391c6255e 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -23,7 +23,9 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib582 lib583 lib585 lib586 lib587 \
lib590 lib591 lib597 lib598 lib599 \
\
- lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508
+ lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \
+ lib1900 \
+ lib2033
chkhostname_SOURCES = chkhostname.c ../../lib/curl_gethostname.c
chkhostname_LDADD = @CURL_NETWORK_LIBS@
@@ -323,3 +325,9 @@ lib1507_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1507
lib1508_SOURCES = lib1508.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1508_LDADD = $(TESTUTIL_LIBS)
lib1508_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1508
+
+lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1900_CPPFLAGS = $(AM_CPPFLAGS)
+
+lib2033_SOURCES = libntlmconnect.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib2033_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_PIPELINING
diff --git a/tests/libtest/lib1900.c b/tests/libtest/lib1900.c
new file mode 100644
index 000000000..b2a943440
--- /dev/null
+++ b/tests/libtest/lib1900.c
@@ -0,0 +1,256 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2013, Linus Nielsen Feltzing, <linus@haxx.se>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+#define MAX_URLS 200
+#define MAX_BLACKLIST 20
+
+int urltime[MAX_URLS];
+char *urlstring[MAX_URLS];
+CURL *handles[MAX_URLS];
+char *site_blacklist[MAX_BLACKLIST];
+char *server_blacklist[MAX_BLACKLIST];
+int num_handles;
+int blacklist_num_servers;
+int blacklist_num_sites;
+
+int parse_url_file(const char *filename);
+void free_urls(void);
+int create_handles(void);
+void setup_handle(char *base_url, CURLM *m, int handlenum);
+void remove_handles(void);
+
+static size_t
+write_callback(void *contents, size_t size, size_t nmemb, void *userp)
+{
+ size_t realsize = size * nmemb;
+ (void)contents;
+ (void)userp;
+
+ return realsize;
+}
+
+int parse_url_file(const char *filename)
+{
+ FILE *f;
+ int time;
+ char buf[200];
+
+ num_handles = 0;
+ blacklist_num_sites = 0;
+ blacklist_num_servers = 0;
+
+ f = fopen(filename, "rb");
+ if(!f)
+ return 0;
+
+ while(!feof(f)) {
+ if(fscanf(f, "%d %s\n", &time, buf)) {
+ urltime[num_handles] = time;
+ urlstring[num_handles] = strdup(buf);
+ num_handles++;
+ continue;
+ }
+
+ if(fscanf(f, "blacklist_site %s\n", buf)) {
+ site_blacklist[blacklist_num_sites] = strdup(buf);
+ blacklist_num_sites++;
+ continue;
+ }
+
+ break;
+ }
+ fclose(f);
+
+ site_blacklist[blacklist_num_sites] = NULL;
+ server_blacklist[blacklist_num_servers] = NULL;
+ return num_handles;
+}
+
+void free_urls(void)
+{
+ int i;
+ for(i = 0;i < num_handles;i++) {
+ free(urlstring[i]);
+ }
+ for(i = 0;i < blacklist_num_servers;i++) {
+ free(server_blacklist[i]);
+ }
+ for(i = 0;i < blacklist_num_sites;i++) {
+ free(site_blacklist[i]);
+ }
+}
+
+int create_handles(void)
+{
+ int i;
+
+ for(i = 0;i < num_handles;i++) {
+ handles[i] = curl_easy_init();
+ }
+ return 0;
+}
+
+void setup_handle(char *base_url, CURLM *m, int handlenum)
+{
+ char urlbuf[256];
+
+ sprintf(urlbuf, "%s%s", base_url, urlstring[handlenum]);
+ curl_easy_setopt(handles[handlenum], CURLOPT_URL, urlbuf);
+ curl_easy_setopt(handles[handlenum], CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt(handles[handlenum], CURLOPT_FAILONERROR, 1L);
+ curl_easy_setopt(handles[handlenum], CURLOPT_WRITEFUNCTION, write_callback);
+ curl_easy_setopt(handles[handlenum], CURLOPT_WRITEDATA, NULL);
+ curl_multi_add_handle(m, handles[handlenum]);
+}
+
+void remove_handles(void)
+{
+ int i;
+
+ for(i = 0;i < num_handles;i++) {
+ if(handles[i])
+ curl_easy_cleanup(handles[i]);
+ }
+}
+
+int test(char *URL)
+{
+ int res = 0;
+ CURLM *m = NULL;
+ CURLMsg *msg; /* for picking up messages with the transfer status */
+ int msgs_left; /* how many messages are left */
+ int running;
+ int handlenum = 0;
+ struct timeval last_handle_add;
+
+ if(parse_url_file("log/urls.txt") <= 0)
+ goto test_cleanup;
+
+ start_test_timing();
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+ m = curl_multi_init();
+
+ create_handles();
+
+ multi_setopt(m, CURLMOPT_PIPELINING, 1L);
+ multi_setopt(m, CURLMOPT_MAX_HOST_CONNECTIONS, 2L);
+ multi_setopt(m, CURLMOPT_MAX_PIPELINE_LENGTH, 3L);
+ multi_setopt(m, CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, 15000L);
+ multi_setopt(m, CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, 10000L);
+
+ multi_setopt(m, CURLMOPT_PIPELINING_SITE_BL, site_blacklist);
+ multi_setopt(m, CURLMOPT_PIPELINING_SERVER_BL, server_blacklist);
+
+ gettimeofday(&last_handle_add, NULL);
+
+ for(;;) {
+ struct timeval interval;
+ struct timeval now;
+ long int msnow, mslast;
+ fd_set rd, wr, exc;
+ int maxfd = -99;
+ long timeout;
+
+ interval.tv_sec = 1;
+ interval.tv_usec = 0;
+
+ if(handlenum < num_handles) {
+ gettimeofday(&now, NULL);
+ msnow = now.tv_sec * 1000 + now.tv_usec / 1000;
+ mslast = last_handle_add.tv_sec * 1000 + last_handle_add.tv_usec / 1000;
+ if(msnow - mslast >= urltime[handlenum] && handlenum < num_handles) {
+ fprintf(stdout, "Adding handle %d\n", handlenum);
+ setup_handle(URL, m, handlenum);
+ last_handle_add = now;
+ handlenum++;
+ }
+ }
+
+ curl_multi_perform(m, &running);
+
+ abort_on_test_timeout();
+
+ /* See how the transfers went */
+ while ((msg = curl_multi_info_read(m, &msgs_left))) {
+ if (msg->msg == CURLMSG_DONE) {
+ int i, found = 0;
+
+ /* Find out which handle this message is about */
+ for (i = 0; i < num_handles; i++) {
+ found = (msg->easy_handle == handles[i]);
+ if(found)
+ break;
+ }
+
+ printf("Handle %d Completed with status %d\n", i, msg->data.result);
+ curl_multi_remove_handle(m, handles[i]);
+ }
+ }
+
+ if(handlenum == num_handles && !running) {
+ break; /* done */
+ }
+
+ FD_ZERO(&rd);
+ FD_ZERO(&wr);
+ FD_ZERO(&exc);
+
+ curl_multi_fdset(m, &rd, &wr, &exc, &maxfd);
+
+ /* At this point, maxfd is guaranteed to be greater or equal than -1. */
+
+ curl_multi_timeout(m, &timeout);
+
+ if(timeout < 0)
+ timeout = 1;
+
+ interval.tv_sec = timeout / 1000;
+ interval.tv_usec = (timeout % 1000) * 1000;
+
+ interval.tv_sec = 0;
+ interval.tv_usec = 1000;
+
+ select_test(maxfd+1, &rd, &wr, &exc, &interval);
+
+ abort_on_test_timeout();
+ }
+
+test_cleanup:
+
+ remove_handles();
+
+ /* undocumented cleanup sequence - type UB */
+
+ curl_multi_cleanup(m);
+ curl_global_cleanup();
+
+ free_urls();
+ return res;
+}
diff --git a/tests/libtest/lib530.c b/tests/libtest/lib530.c
index ad84ff8a5..06a846439 100644
--- a/tests/libtest/lib530.c
+++ b/tests/libtest/lib530.c
@@ -37,6 +37,7 @@ int test(char *URL)
CURLM *m = NULL;
int i;
char target_url[256];
+ int handles_added = 0;
for(i=0; i < NUM_HANDLES; i++)
curl[i] = NULL;
@@ -59,10 +60,13 @@ int test(char *URL)
easy_setopt(curl[i], CURLOPT_VERBOSE, 1L);
/* include headers */
easy_setopt(curl[i], CURLOPT_HEADER, 1L);
- /* add handle to multi */
- multi_add_handle(m, curl[i]);
}
+ /* Add the first handle to multi. We do this to let libcurl detect
+ that the server can do pipelining. The rest of the handles will be
+ added later. */
+ multi_add_handle(m, curl[handles_added++]);
+
multi_setopt(m, CURLMOPT_PIPELINING, 1L);
fprintf(stderr, "Start at URL 0\n");
@@ -79,9 +83,14 @@ int test(char *URL)
abort_on_test_timeout();
- if(!running)
+ if(!running && handles_added >= NUM_HANDLES)
break; /* done */
+ /* Add the rest of the handles now that the first handle has sent the
+ request. */
+ while(handles_added < NUM_HANDLES)
+ multi_add_handle(m, curl[handles_added++]);
+
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_ZERO(&exc);
diff --git a/tests/libtest/libntlmconnect.c b/tests/libtest/libntlmconnect.c
index b540ebf58..cd507dfa1 100644
--- a/tests/libtest/libntlmconnect.c
+++ b/tests/libtest/libntlmconnect.c
@@ -125,6 +125,12 @@ int test(char *url)
multi_init(multi);
+#ifdef USE_PIPELINING
+ multi_setopt(multi, CURLMOPT_PIPELINING, 1);
+ multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, 5);
+ multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, 10);
+#endif
+
for(;;) {
struct timeval interval;
fd_set fdread;
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 4915f2e81..cc6999cdc 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -140,6 +140,7 @@ my $GOPHER6PORT; # Gopher IPv6 server port
my $HTTPTLSPORT; # HTTP TLS (non-stunnel) server port
my $HTTPTLS6PORT; # HTTP TLS (non-stunnel) IPv6 server port
my $HTTPPROXYPORT; # HTTP proxy port, when using CONNECT
+my $HTTPPIPEPORT; # HTTP pipelining port
my $srcdir = $ENV{'srcdir'} || '.';
my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
@@ -339,10 +340,10 @@ delete $ENV{'CURL_CA_BUNDLE'} if($ENV{'CURL_CA_BUNDLE'});
# Load serverpidfile hash with pidfile names for all possible servers.
#
sub init_serverpidfile_hash {
- for my $proto (('ftp', 'http', 'imap', 'pop3', 'smtp')) {
+ for my $proto (('ftp', 'http', 'imap', 'pop3', 'smtp', 'http')) {
for my $ssl (('', 's')) {
for my $ipvnum ((4, 6)) {
- for my $idnum ((1, 2)) {
+ for my $idnum ((1, 2, 3)) {
my $serv = servername_id("$proto$ssl", $ipvnum, $idnum);
my $pidf = server_pidfilename("$proto$ssl", $ipvnum, $idnum);
$serverpidfile{$serv} = $pidf;
@@ -642,11 +643,11 @@ sub stopserver {
# All servers relative to the given one must be stopped also
#
my @killservers;
- if($server =~ /^(ftp|http|imap|pop3|smtp)s((\d*)(-ipv6|))$/) {
+ if($server =~ /^(ftp|http|imap|pop3|smtp|httppipe)s((\d*)(-ipv6|))$/) {
# given a stunnel based ssl server, also kill non-ssl underlying one
push @killservers, "${1}${2}";
}
- elsif($server =~ /^(ftp|http|imap|pop3|smtp)((\d*)(-ipv6|))$/) {
+ elsif($server =~ /^(ftp|http|imap|pop3|smtp|httppipe)((\d*)(-ipv6|))$/) {
# given a non-ssl server, also kill stunnel based ssl piggybacking one
push @killservers, "${1}s${2}";
}
@@ -1105,6 +1106,7 @@ my %protofunc = ('http' => \&verifyhttp,
'pop3' => \&verifyftp,
'imap' => \&verifyftp,
'smtp' => \&verifyftp,
+ 'httppipe' => \&verifyhttp,
'ftps' => \&verifyftp,
'tftp' => \&verifyftp,
'ssh' => \&verifyssh,
@@ -1170,6 +1172,7 @@ sub runhttpserver {
my $pidfile;
my $logfile;
my $flags = "";
+ my $exe = "$perl $srcdir/httpserver.pl";
if($alt eq "ipv6") {
# if IPv6, use a different setup
@@ -1180,6 +1183,11 @@ sub runhttpserver {
# basically the same, but another ID
$idnum = 2;
}
+ elsif($alt eq "pipe") {
+ # basically the same, but another ID
+ $idnum = 3;
+ $exe = "python $srcdir/http_pipe.py";
+ }
$server = servername_id($proto, $ipvnum, $idnum);
@@ -1207,7 +1215,82 @@ sub runhttpserver {
$flags .= "--id $idnum " if($idnum > 1);
$flags .= "--ipv$ipvnum --port $port --srcdir \"$srcdir\"";
- my $cmd = "$perl $srcdir/httpserver.pl $flags";
+ my $cmd = "$exe $flags";
+ my ($httppid, $pid2) = startnew($cmd, $pidfile, 15, 0);
+
+ if($httppid <= 0 || !kill(0, $httppid)) {
+ # it is NOT alive
+ logmsg "RUN: failed to start the $srvrname server\n";
+ stopserver($server, "$pid2");
+ displaylogs($testnumcheck);
+ $doesntrun{$pidfile} = 1;
+ return (0,0);
+ }
+
+ # Server is up. Verify that we can speak to it.
+ my $pid3 = verifyserver($proto, $ipvnum, $idnum, $ip, $port);
+ if(!$pid3) {
+ logmsg "RUN: $srvrname server failed verification\n";
+ # failed to talk to it properly. Kill the server and return failure
+ stopserver($server, "$httppid $pid2");
+ displaylogs($testnumcheck);
+ $doesntrun{$pidfile} = 1;
+ return (0,0);
+ }
+ $pid2 = $pid3;
+
+ if($verbose) {
+ logmsg "RUN: $srvrname server is now running PID $httppid\n";
+ }
+
+ sleep(1);
+
+ return ($httppid, $pid2);
+}
+
+#######################################################################
+# start the http server
+#
+sub runhttp_pipeserver {
+ my ($proto, $verbose, $alt, $port) = @_;
+ my $ip = $HOSTIP;
+ my $ipvnum = 4;
+ my $idnum = 1;
+ my $server;
+ my $srvrname;
+ my $pidfile;
+ my $logfile;
+ my $flags = "";
+
+ if($alt eq "ipv6") {
+ # No IPv6
+ }
+
+ $server = servername_id($proto, $ipvnum, $idnum);
+
+ $pidfile = $serverpidfile{$server};
+
+ # don't retry if the server doesn't work
+ if ($doesntrun{$pidfile}) {
+ return (0,0);
+ }
+
+ my $pid = processexists($pidfile);
+ if($pid > 0) {
+ stopserver($server, "$pid");
+ }
+ unlink($pidfile) if(-f $pidfile);
+
+ $srvrname = servername_str($proto, $ipvnum, $idnum);
+
+ $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum);
+
+ $flags .= "--verbose " if($debugprotocol);
+ $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" ";
+ $flags .= "--id $idnum " if($idnum > 1);
+ $flags .= "--port $port --srcdir \"$srcdir\"";
+
+ my $cmd = "$srcdir/http_pipe.py $flags";
my ($httppid, $pid2) = startnew($cmd, $pidfile, 15, 0);
if($httppid <= 0 || !kill(0, $httppid)) {
@@ -2276,6 +2359,9 @@ sub checksystem {
# 'http-proxy' is used in test cases to do CONNECT through
push @protocols, 'http-proxy';
+ # 'http-pipe' is the special server for testing pipelining
+ push @protocols, 'http-pipe';
+
# 'none' is used in test cases to mean no server
push @protocols, 'none';
}
@@ -2477,6 +2563,7 @@ sub checksystem {
}
logmsg "\n";
}
+ logmsg sprintf("* HTTP-PIPE/%d \n", $HTTPPIPEPORT);
$has_textaware = ($^O eq 'MSWin32') || ($^O eq 'msys');
@@ -2505,6 +2592,7 @@ sub subVariables {
$$thing =~ s/%HTTP6PORT/$HTTP6PORT/g;
$$thing =~ s/%HTTPSPORT/$HTTPSPORT/g;
$$thing =~ s/%HTTPPORT/$HTTPPORT/g;
+ $$thing =~ s/%HTTPPIPEPORT/$HTTPPIPEPORT/g;
$$thing =~ s/%PROXYPORT/$HTTPPROXYPORT/g;
$$thing =~ s/%IMAP6PORT/$IMAP6PORT/g;
@@ -3870,6 +3958,23 @@ sub startservers {
$run{'http-ipv6'}="$pid $pid2";
}
}
+ elsif($what eq "http-pipe") {
+ if($torture && $run{'http-pipe'} &&
+ !responsive_http_server("http", $verbose, "pipe",
+ $HTTPPIPEPORT)) {
+ stopserver('http-pipe');
+ }
+ if(!$run{'http-pipe'}) {
+ ($pid, $pid2) = runhttpserver("http", $verbose, "pipe",
+ $HTTPPIPEPORT);
+ if($pid <= 0) {
+ return "failed starting HTTP-pipe server";
+ }
+ logmsg sprintf ("* pid http-pipe => %d %d\n", $pid, $pid2)
+ if($verbose);
+ $run{'http-pipe'}="$pid $pid2";
+ }
+ }
elsif($what eq "rtsp") {
if($torture && $run{'rtsp'} &&
!responsive_rtsp_server($verbose)) {
@@ -4512,6 +4617,7 @@ $GOPHER6PORT = $base++; # Gopher IPv6 server port
$HTTPTLSPORT = $base++; # HTTP TLS (non-stunnel) server port
$HTTPTLS6PORT = $base++; # HTTP TLS (non-stunnel) IPv6 server port
$HTTPPROXYPORT = $base++; # HTTP proxy port, when using CONNECT
+$HTTPPIPEPORT = $base++; # HTTP pipelining port
#######################################################################
# clear and create logging directory:
diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm
index a1d1dc367..b0b5b7492 100644
--- a/tests/serverhelp.pm
+++ b/tests/serverhelp.pm
@@ -79,7 +79,7 @@ sub serverfactors {
my $idnum;
if($server =~
- /^((ftp|http|imap|pop3|smtp)s?)(\d*)(-ipv6|)$/) {
+ /^((ftp|http|imap|pop3|smtp|http-pipe)s?)(\d*)(-ipv6|)$/) {
$proto = $1;
$idnum = ($3 && ($3 > 1)) ? $3 : 1;
$ipvnum = ($4 && ($4 =~ /6$/)) ? 6 : 4;
@@ -105,7 +105,7 @@ sub servername_str {
$proto = uc($proto) if($proto);
die "unsupported protocol: '$proto'" unless($proto &&
- ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER|HTTPTLS))$/));
+ ($proto =~ /^(((FTP|HTTP|IMAP|POP3|SMTP|HTTP-PIPE)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|GOPHER|HTTPTLS))$/));
$ipver = (not $ipver) ? 'ipv4' : lc($ipver);
die "unsupported IP version: '$ipver'" unless($ipver &&