tests: drop http_pipe.py script no longer used
It is unused since commit f7208df7d9
.
Closes #3204
This commit is contained in:
Родитель
8effa8c2b0
Коммит
fc2c9a9614
|
@ -35,7 +35,7 @@ EXTRA_DIST = ftpserver.pl httpserver.pl secureserver.pl runtests.pl getpart.pm \
|
||||||
FILEFORMAT README stunnel.pem memanalyze.pl testcurl.pl valgrind.pm ftp.pm \
|
FILEFORMAT README stunnel.pem memanalyze.pl testcurl.pl valgrind.pm ftp.pm \
|
||||||
sshserver.pl sshhelp.pm pathhelp.pm testcurl.1 runtests.1 \
|
sshserver.pl sshhelp.pm pathhelp.pm testcurl.1 runtests.1 \
|
||||||
serverhelp.pm tftpserver.pl rtspserver.pl directories.pm symbol-scan.pl \
|
serverhelp.pm tftpserver.pl rtspserver.pl directories.pm symbol-scan.pl \
|
||||||
CMakeLists.txt mem-include-scan.pl valgrind.supp http_pipe.py extern-scan.pl \
|
CMakeLists.txt mem-include-scan.pl valgrind.supp extern-scan.pl \
|
||||||
manpage-scan.pl nroff-scan.pl http2-server.pl dictserver.py \
|
manpage-scan.pl nroff-scan.pl http2-server.pl dictserver.py \
|
||||||
negtelnetserver.py $(SMBDEPS)
|
negtelnetserver.py $(SMBDEPS)
|
||||||
|
|
||||||
|
|
|
@ -1,441 +0,0 @@
|
||||||
#!/usr/bin/env 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
|
|
||||||
#
|
|
||||||
# https://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
|
|
||||||
#
|
|
||||||
try:
|
|
||||||
import socketserver
|
|
||||||
except:
|
|
||||||
import SocketServer as 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:
|
|
||||||
|
|
||||||
https://dev.chromium.org/developers/design-documents/network-stack/http-pipelining
|
|
||||||
|
|
||||||
Source code can be found here:
|
|
||||||
|
|
||||||
https://code.google.com/archive/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':
|
|
||||||
body = '0123456789abcdef' * 64
|
|
||||||
result += self._BuildResponse(
|
|
||||||
'200 OK', ['Server: Apache',
|
|
||||||
'Content-Length: 1024',
|
|
||||||
'Cache-Control: max-age=60'], body)
|
|
||||||
|
|
||||||
elif path == '/10k.txt':
|
|
||||||
body = '0123456789abcdef' * 640
|
|
||||||
result += self._BuildResponse(
|
|
||||||
'200 OK', ['Server: Apache',
|
|
||||||
'Content-Length: 10240',
|
|
||||||
'Cache-Control: max-age=60'], body)
|
|
||||||
|
|
||||||
elif path == '/100k.txt':
|
|
||||||
body = '0123456789abcdef' * 6400
|
|
||||||
result += self._BuildResponse(
|
|
||||||
'200 OK',
|
|
||||||
['Server: Apache',
|
|
||||||
'Content-Length: 102400',
|
|
||||||
'Cache-Control: max-age=60'],
|
|
||||||
body)
|
|
||||||
|
|
||||||
elif path == '/100k_chunked.txt':
|
|
||||||
body = self.Chunkify('0123456789abcdef' * 6400, 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:
|
|
||||||
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
|
|
||||||
|
|
||||||
rlist, wlist, xlist = [], [], []
|
|
||||||
fileno = self.request.fileno()
|
|
||||||
if max_poll_time > 0:
|
|
||||||
rlist.append(fileno)
|
|
||||||
if self._send_buffer:
|
|
||||||
wlist.append(fileno)
|
|
||||||
rlist, wlist, xlist = select.select(rlist, wlist, xlist, 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
|
|
||||||
|
|
||||||
if fileno in rlist:
|
|
||||||
self.request.setblocking(False)
|
|
||||||
new_data = self.request.recv(MAX_REQUEST_SIZE)
|
|
||||||
self.request.setblocking(True)
|
|
||||||
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 fileno in wlist:
|
|
||||||
num_bytes_sent = self.request.send(self._send_buffer[0:4096])
|
|
||||||
self._send_buffer = self._send_buffer[num_bytes_sent:]
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
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.ThreadingMixIn, 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()
|
|
Загрузка…
Ссылка в новой задаче