591 lines
20 KiB
Python
591 lines
20 KiB
Python
import cgi
|
|
import os
|
|
import socket
|
|
from tests import skipped, LimitedTestCase
|
|
from unittest import main
|
|
|
|
from eventlet import api
|
|
from eventlet import util
|
|
from eventlet import greenio
|
|
from eventlet import wsgi
|
|
from eventlet import processes
|
|
|
|
from tests import find_command
|
|
|
|
try:
|
|
from cStringIO import StringIO
|
|
except ImportError:
|
|
from StringIO import StringIO
|
|
|
|
|
|
def hello_world(env, start_response):
|
|
if env['PATH_INFO'] == 'notexist':
|
|
start_response('404 Not Found', [('Content-type', 'text/plain')])
|
|
return ["not found"]
|
|
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
return ["hello world"]
|
|
|
|
|
|
def chunked_app(env, start_response):
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
yield "this"
|
|
yield "is"
|
|
yield "chunked"
|
|
|
|
|
|
def big_chunks(env, start_response):
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
line = 'a' * 8192
|
|
for x in range(10):
|
|
yield line
|
|
|
|
def use_write(env, start_response):
|
|
if env['PATH_INFO'] == '/a':
|
|
write = start_response('200 OK', [('Content-type', 'text/plain'),
|
|
('Content-Length', '5')])
|
|
write('abcde')
|
|
if env['PATH_INFO'] == '/b':
|
|
write = start_response('200 OK', [('Content-type', 'text/plain')])
|
|
write('abcde')
|
|
return []
|
|
|
|
def chunked_post(env, start_response):
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
if env['PATH_INFO'] == '/a':
|
|
return [env['wsgi.input'].read()]
|
|
elif env['PATH_INFO'] == '/b':
|
|
return [x for x in iter(lambda: env['wsgi.input'].read(4096), '')]
|
|
elif env['PATH_INFO'] == '/c':
|
|
return [x for x in iter(lambda: env['wsgi.input'].read(1), '')]
|
|
|
|
class Site(object):
|
|
def __init__(self):
|
|
self.application = hello_world
|
|
|
|
def __call__(self, env, start_response):
|
|
return self.application(env, start_response)
|
|
|
|
|
|
CONTENT_LENGTH = 'content-length'
|
|
|
|
|
|
"""
|
|
HTTP/1.1 200 OK
|
|
Date: foo
|
|
Content-length: 11
|
|
|
|
hello world
|
|
"""
|
|
|
|
class ConnectionClosed(Exception):
|
|
pass
|
|
|
|
|
|
def read_http(sock):
|
|
fd = sock.makefile()
|
|
try:
|
|
response_line = fd.readline()
|
|
except socket.error, exc:
|
|
if exc[0] == 10053:
|
|
raise ConnectionClosed
|
|
raise
|
|
if not response_line:
|
|
raise ConnectionClosed
|
|
|
|
header_lines = []
|
|
while True:
|
|
line = fd.readline()
|
|
if line == '\r\n':
|
|
break
|
|
else:
|
|
header_lines.append(line)
|
|
headers = dict()
|
|
for x in header_lines:
|
|
x = x.strip()
|
|
if not x:
|
|
continue
|
|
key, value = x.split(': ', 1)
|
|
headers[key.lower()] = value
|
|
|
|
if CONTENT_LENGTH in headers:
|
|
num = int(headers[CONTENT_LENGTH])
|
|
body = fd.read(num)
|
|
#print body
|
|
else:
|
|
body = None
|
|
|
|
return response_line, headers, body
|
|
|
|
|
|
class TestHttpd(LimitedTestCase):
|
|
mode = 'static'
|
|
def setUp(self):
|
|
super(TestHttpd, self).setUp()
|
|
self.logfile = StringIO()
|
|
self.site = Site()
|
|
listener = api.tcp_listener(('localhost', 0))
|
|
self.port = listener.getsockname()[1]
|
|
self.killer = api.spawn(
|
|
wsgi.server,
|
|
listener,
|
|
self.site,
|
|
max_size=128,
|
|
log=self.logfile)
|
|
|
|
def tearDown(self):
|
|
super(TestHttpd, self).tearDown()
|
|
api.kill(self.killer)
|
|
api.sleep(0)
|
|
|
|
def test_001_server(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
result = fd.read()
|
|
fd.close()
|
|
## The server responds with the maximum version it supports
|
|
self.assert_(result.startswith('HTTP'), result)
|
|
self.assert_(result.endswith('hello world'))
|
|
|
|
def test_002_keepalive(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
read_http(sock)
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
read_http(sock)
|
|
fd.close()
|
|
|
|
def test_003_passing_non_int_to_read(self):
|
|
# This should go in greenio_test
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
cancel = api.exc_after(1, RuntimeError)
|
|
self.assertRaises(TypeError, fd.read, "This shouldn't work")
|
|
cancel.cancel()
|
|
fd.close()
|
|
|
|
def test_004_close_keepalive(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
read_http(sock)
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
read_http(sock)
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
self.assertRaises(ConnectionClosed, read_http, sock)
|
|
fd.close()
|
|
|
|
@skipped
|
|
def test_005_run_apachebench(self):
|
|
url = 'http://localhost:12346/'
|
|
# ab is apachebench
|
|
out = processes.Process(find_command('ab'),
|
|
['-c','64','-n','1024', '-k', url])
|
|
print out.read()
|
|
|
|
def test_006_reject_long_urls(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
path_parts = []
|
|
for ii in range(3000):
|
|
path_parts.append('path')
|
|
path = '/'.join(path_parts)
|
|
request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
|
|
fd = sock.makefile()
|
|
fd.write(request)
|
|
fd.flush()
|
|
result = fd.readline()
|
|
if result:
|
|
# windows closes the socket before the data is flushed,
|
|
# so we never get anything back
|
|
status = result.split(' ')[1]
|
|
self.assertEqual(status, '414')
|
|
fd.close()
|
|
|
|
def test_007_get_arg(self):
|
|
# define a new handler that does a get_arg as well as a read_body
|
|
def new_app(env, start_response):
|
|
body = env['wsgi.input'].read()
|
|
a = cgi.parse_qs(body).get('a', [1])[0]
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
return ['a is %s, body is %s' % (a, body)]
|
|
self.site.application = new_app
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
request = '\r\n'.join((
|
|
'POST / HTTP/1.0',
|
|
'Host: localhost',
|
|
'Content-Length: 3',
|
|
'',
|
|
'a=a'))
|
|
fd = sock.makefile()
|
|
fd.write(request)
|
|
fd.flush()
|
|
|
|
# send some junk after the actual request
|
|
fd.write('01234567890123456789')
|
|
reqline, headers, body = read_http(sock)
|
|
self.assertEqual(body, 'a is a, body is a=a')
|
|
fd.close()
|
|
|
|
def test_008_correctresponse(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
response_line_200,_,_ = read_http(sock)
|
|
fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
response_line_404,_,_ = read_http(sock)
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
response_line_test,_,_ = read_http(sock)
|
|
self.assertEqual(response_line_200,response_line_test)
|
|
fd.close()
|
|
|
|
def test_009_chunked_response(self):
|
|
self.site.application = chunked_app
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
self.assert_('Transfer-Encoding: chunked' in fd.read())
|
|
|
|
def test_010_no_chunked_http_1_0(self):
|
|
self.site.application = chunked_app
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
self.assert_('Transfer-Encoding: chunked' not in fd.read())
|
|
|
|
def test_011_multiple_chunks(self):
|
|
self.site.application = big_chunks
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
headers = ''
|
|
while True:
|
|
line = fd.readline()
|
|
if line == '\r\n':
|
|
break
|
|
else:
|
|
headers += line
|
|
self.assert_('Transfer-Encoding: chunked' in headers)
|
|
chunks = 0
|
|
chunklen = int(fd.readline(), 16)
|
|
while chunklen:
|
|
chunks += 1
|
|
chunk = fd.read(chunklen)
|
|
fd.readline()
|
|
chunklen = int(fd.readline(), 16)
|
|
self.assert_(chunks > 1)
|
|
|
|
def test_012_ssl_server(self):
|
|
def wsgi_app(environ, start_response):
|
|
start_response('200 OK', {})
|
|
return [environ['wsgi.input'].read()]
|
|
|
|
certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
|
|
private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
|
|
|
|
server_sock = api.ssl_listener(('localhost', 0), certificate_file, private_key_file)
|
|
|
|
api.spawn(wsgi.server, server_sock, wsgi_app, log=StringIO())
|
|
|
|
sock = api.connect_tcp(('localhost', server_sock.getsockname()[1]))
|
|
sock = util.wrap_ssl(sock)
|
|
sock.write('POST /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nContent-length:3\r\n\r\nabc')
|
|
result = sock.read(8192)
|
|
self.assertEquals(result[-3:], 'abc')
|
|
|
|
def test_013_empty_return(self):
|
|
def wsgi_app(environ, start_response):
|
|
start_response("200 OK", [])
|
|
return [""]
|
|
|
|
certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
|
|
private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
|
|
server_sock = api.ssl_listener(('localhost', 0), certificate_file, private_key_file)
|
|
api.spawn(wsgi.server, server_sock, wsgi_app, log=StringIO())
|
|
|
|
sock = api.connect_tcp(('localhost', server_sock.getsockname()[1]))
|
|
sock = util.wrap_ssl(sock)
|
|
sock.write('GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
result = sock.read(8192)
|
|
self.assertEquals(result[-4:], '\r\n\r\n')
|
|
|
|
def test_014_chunked_post(self):
|
|
self.site.application = chunked_post
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('PUT /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
|
|
'Transfer-Encoding: chunked\r\n\r\n'
|
|
'2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n')
|
|
fd.flush()
|
|
while True:
|
|
if fd.readline() == '\r\n':
|
|
break
|
|
response = fd.read()
|
|
self.assert_(response == 'oh hai', 'invalid response %s' % response)
|
|
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('PUT /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
|
|
'Transfer-Encoding: chunked\r\n\r\n'
|
|
'2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n')
|
|
fd.flush()
|
|
while True:
|
|
if fd.readline() == '\r\n':
|
|
break
|
|
response = fd.read()
|
|
self.assert_(response == 'oh hai', 'invalid response %s' % response)
|
|
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('PUT /c HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
|
|
'Transfer-Encoding: chunked\r\n\r\n'
|
|
'2\r\noh\r\n4\r\n hai\r\n0\r\n\r\n')
|
|
fd.flush()
|
|
while True:
|
|
if fd.readline() == '\r\n':
|
|
break
|
|
response = fd.read(8192)
|
|
self.assert_(response == 'oh hai', 'invalid response %s' % response)
|
|
|
|
def test_015_write(self):
|
|
self.site.application = use_write
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
response_line, headers, body = read_http(sock)
|
|
self.assert_('content-length' in headers)
|
|
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET /b HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
response_line, headers, body = read_http(sock)
|
|
self.assert_('transfer-encoding' in headers)
|
|
self.assert_(headers['transfer-encoding'] == 'chunked')
|
|
|
|
def test_016_repeated_content_length(self):
|
|
"""
|
|
content-length header was being doubled up if it was set in
|
|
start_response and could also be inferred from the iterator
|
|
"""
|
|
def wsgi_app(environ, start_response):
|
|
start_response('200 OK', [('Content-Length', '7')])
|
|
return ['testing']
|
|
self.site.application = wsgi_app
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET /a HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
|
fd.flush()
|
|
header_lines = []
|
|
while True:
|
|
line = fd.readline()
|
|
if line == '\r\n':
|
|
break
|
|
else:
|
|
header_lines.append(line)
|
|
self.assertEquals(1, len([l for l in header_lines
|
|
if l.lower().startswith('content-length')]))
|
|
|
|
def test_017_ssl_zeroreturnerror(self):
|
|
|
|
def server(sock, site, log):
|
|
try:
|
|
serv = wsgi.Server(sock, sock.getsockname(), site, log)
|
|
client_socket = sock.accept()
|
|
serv.process_request(client_socket)
|
|
return True
|
|
except:
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def wsgi_app(environ, start_response):
|
|
start_response('200 OK', [])
|
|
return [environ['wsgi.input'].read()]
|
|
|
|
certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
|
|
private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
|
|
|
|
sock = api.ssl_listener(('localhost', 0), certificate_file, private_key_file)
|
|
|
|
from eventlet import coros
|
|
server_coro = coros.execute(server, sock, wsgi_app, self.logfile)
|
|
|
|
client = api.connect_tcp(('localhost', sock.getsockname()[1]))
|
|
client = util.wrap_ssl(client)
|
|
client.write('X') # non-empty payload so that SSL handshake occurs
|
|
greenio.shutdown_safe(client)
|
|
client.close()
|
|
|
|
success = server_coro.wait()
|
|
self.assert_(success)
|
|
|
|
def test_018_http_10_keepalive(self):
|
|
# verify that if an http/1.0 client sends connection: keep-alive
|
|
# that we don't close the connection
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
|
|
fd.flush()
|
|
|
|
response_line, headers, body = read_http(sock)
|
|
self.assert_('connection' in headers)
|
|
self.assertEqual('keep-alive', headers['connection'])
|
|
# repeat request to verify connection is actually still open
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n')
|
|
fd.flush()
|
|
response_line, headers, body = read_http(sock)
|
|
self.assert_('connection' in headers)
|
|
self.assertEqual('keep-alive', headers['connection'])
|
|
|
|
def test_019_fieldstorage_compat(self):
|
|
def use_fieldstorage(environ, start_response):
|
|
import cgi
|
|
fs = cgi.FieldStorage(fp=environ['wsgi.input'],
|
|
environ=environ)
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
return ['hello!']
|
|
|
|
self.site.application = use_fieldstorage
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
|
|
fd = sock.makefile()
|
|
fd.write('POST / HTTP/1.1\r\n'
|
|
'Host: localhost\r\n'
|
|
'Connection: close\r\n'
|
|
'Transfer-Encoding: chunked\r\n\r\n'
|
|
'2\r\noh\r\n'
|
|
'4\r\n hai\r\n0\r\n\r\n')
|
|
fd.flush()
|
|
self.assert_('hello!' in fd.read())
|
|
|
|
def test_020_x_forwarded_for(self):
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
sock.sendall('GET / HTTP/1.1\r\nHost: localhost\r\nX-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n')
|
|
sock.recv(1024)
|
|
sock.close()
|
|
self.assert_('1.2.3.4,5.6.7.8,127.0.0.1' in self.logfile.getvalue())
|
|
|
|
# turning off the option should work too
|
|
self.logfile = StringIO()
|
|
api.kill(self.killer)
|
|
listener = api.tcp_listener(('localhost', 0))
|
|
self.port = listener.getsockname()[1]
|
|
self.killer = api.spawn(
|
|
wsgi.server,
|
|
listener,
|
|
self.site,
|
|
max_size=128,
|
|
log=self.logfile,
|
|
log_x_forwarded_for=False)
|
|
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
sock.sendall('GET / HTTP/1.1\r\nHost: localhost\r\nX-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n')
|
|
sock.recv(1024)
|
|
sock.close()
|
|
self.assert_('1.2.3.4' not in self.logfile.getvalue())
|
|
self.assert_('5.6.7.8' not in self.logfile.getvalue())
|
|
self.assert_('127.0.0.1' in self.logfile.getvalue())
|
|
|
|
def test_021_environ_clobbering(self):
|
|
def clobberin_time(environ, start_response):
|
|
for environ_var in ['wsgi.version', 'wsgi.url_scheme',
|
|
'wsgi.input', 'wsgi.errors', 'wsgi.multithread',
|
|
'wsgi.multiprocess', 'wsgi.run_once', 'REQUEST_METHOD',
|
|
'SCRIPT_NAME', 'PATH_INFO', 'QUERY_STRING', 'CONTENT_TYPE',
|
|
'CONTENT_LENGTH', 'SERVER_NAME', 'SERVER_PORT',
|
|
'SERVER_PROTOCOL']:
|
|
environ[environ_var] = None
|
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
|
return []
|
|
self.site.application = clobberin_time
|
|
sock = api.connect_tcp(('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.1\r\n'
|
|
'Host: localhost\r\n'
|
|
'Connection: close\r\n'
|
|
'\r\n\r\n')
|
|
fd.flush()
|
|
self.assert_('200 OK' in fd.read())
|
|
|
|
def test_022_custom_pool(self):
|
|
# just test that it accepts the parameter for now
|
|
# TODO: test that it uses the pool and that you can waitall() to
|
|
# ensure that all clients finished
|
|
from eventlet import pool
|
|
p = pool.Pool(max_size=5)
|
|
api.kill(self.killer)
|
|
listener = api.tcp_listener(('localhost', 0))
|
|
self.port = listener.getsockname()[1]
|
|
self.killer = api.spawn(
|
|
wsgi.server,
|
|
listener,
|
|
self.site,
|
|
max_size=128,
|
|
log=self.logfile,
|
|
custom_pool=p)
|
|
|
|
# this stuff is copied from test_001_server, could be better factored
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
|
fd.flush()
|
|
result = fd.read()
|
|
fd.close()
|
|
self.assert_(result.startswith('HTTP'), result)
|
|
self.assert_(result.endswith('hello world'))
|
|
|
|
def test_023_bad_content_length(self):
|
|
sock = api.connect_tcp(
|
|
('localhost', self.port))
|
|
fd = sock.makefile()
|
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nContent-length: argh\r\n\r\n')
|
|
fd.flush()
|
|
result = fd.read()
|
|
fd.close()
|
|
self.assert_(result.startswith('HTTP'), result)
|
|
self.assert_('400 Bad Request' in result)
|
|
self.assert_('500' not in result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|