summaryrefslogtreecommitdiff
path: root/tests/test_functional.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_functional.py')
-rw-r--r--tests/test_functional.py1674
1 files changed, 1674 insertions, 0 deletions
diff --git a/tests/test_functional.py b/tests/test_functional.py
new file mode 100644
index 0000000..e894497
--- /dev/null
+++ b/tests/test_functional.py
@@ -0,0 +1,1674 @@
+import errno
+import logging
+import multiprocessing
+import os
+import signal
+import socket
+import string
+import subprocess
+import sys
+import time
+import unittest
+from waitress import server
+from waitress.compat import httplib, tobytes
+from waitress.utilities import cleanup_unix_socket
+
+dn = os.path.dirname
+here = dn(__file__)
+
+
+class NullHandler(logging.Handler): # pragma: no cover
+ """A logging handler that swallows all emitted messages.
+ """
+
+ def emit(self, record):
+ pass
+
+
+def start_server(app, svr, queue, **kwargs): # pragma: no cover
+ """Run a fixture application.
+ """
+ logging.getLogger("waitress").addHandler(NullHandler())
+ try_register_coverage()
+ svr(app, queue, **kwargs).run()
+
+
+def try_register_coverage(): # pragma: no cover
+ # Hack around multiprocessing exiting early and not triggering coverage's
+ # atexit handler by always registering a signal handler
+
+ if "COVERAGE_PROCESS_START" in os.environ:
+
+ def sigterm(*args):
+ sys.exit(0)
+
+ signal.signal(signal.SIGTERM, sigterm)
+
+
+class FixtureTcpWSGIServer(server.TcpWSGIServer):
+ """A version of TcpWSGIServer that relays back what it's bound to.
+ """
+
+ family = socket.AF_INET # Testing
+
+ def __init__(self, application, queue, **kw): # pragma: no cover
+ # Coverage doesn't see this as it's ran in a separate process.
+ kw["port"] = 0 # Bind to any available port.
+ super(FixtureTcpWSGIServer, self).__init__(application, **kw)
+ host, port = self.socket.getsockname()
+ if os.name == "nt":
+ host = "127.0.0.1"
+ queue.put((host, port))
+
+
+class SubprocessTests(object):
+
+ # For nose: all tests may be ran in separate processes.
+ _multiprocess_can_split_ = True
+
+ exe = sys.executable
+
+ server = None
+
+ def start_subprocess(self, target, **kw):
+ # Spawn a server process.
+ self.queue = multiprocessing.Queue()
+
+ if "COVERAGE_RCFILE" in os.environ:
+ os.environ["COVERAGE_PROCESS_START"] = os.environ["COVERAGE_RCFILE"]
+
+ self.proc = multiprocessing.Process(
+ target=start_server, args=(target, self.server, self.queue), kwargs=kw,
+ )
+ self.proc.start()
+
+ if self.proc.exitcode is not None: # pragma: no cover
+ raise RuntimeError("%s didn't start" % str(target))
+ # Get the socket the server is listening on.
+ self.bound_to = self.queue.get(timeout=5)
+ self.sock = self.create_socket()
+
+ def stop_subprocess(self):
+ if self.proc.exitcode is None:
+ self.proc.terminate()
+ self.sock.close()
+ # This give us one FD back ...
+ self.queue.close()
+ self.proc.join()
+
+ def assertline(self, line, status, reason, version):
+ v, s, r = (x.strip() for x in line.split(None, 2))
+ self.assertEqual(s, tobytes(status))
+ self.assertEqual(r, tobytes(reason))
+ self.assertEqual(v, tobytes(version))
+
+ def create_socket(self):
+ return socket.socket(self.server.family, socket.SOCK_STREAM)
+
+ def connect(self):
+ self.sock.connect(self.bound_to)
+
+ def make_http_connection(self):
+ raise NotImplementedError # pragma: no cover
+
+ def send_check_error(self, to_send):
+ self.sock.send(to_send)
+
+
+class TcpTests(SubprocessTests):
+
+ server = FixtureTcpWSGIServer
+
+ def make_http_connection(self):
+ return httplib.HTTPConnection(*self.bound_to)
+
+
+class SleepyThreadTests(TcpTests, unittest.TestCase):
+ # test that sleepy thread doesnt block other requests
+
+ def setUp(self):
+ from tests.fixtureapps import sleepy
+
+ self.start_subprocess(sleepy.app)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_it(self):
+ getline = os.path.join(here, "fixtureapps", "getline.py")
+ cmds = (
+ [self.exe, getline, "http://%s:%d/sleepy" % self.bound_to],
+ [self.exe, getline, "http://%s:%d/" % self.bound_to],
+ )
+ r, w = os.pipe()
+ procs = []
+ for cmd in cmds:
+ procs.append(subprocess.Popen(cmd, stdout=w))
+ time.sleep(3)
+ for proc in procs:
+ if proc.returncode is not None: # pragma: no cover
+ proc.terminate()
+ proc.wait()
+ # the notsleepy response should always be first returned (it sleeps
+ # for 2 seconds, then returns; the notsleepy response should be
+ # processed in the meantime)
+ result = os.read(r, 10000)
+ os.close(r)
+ os.close(w)
+ self.assertEqual(result, b"notsleepy returnedsleepy returned")
+
+
+class EchoTests(object):
+ def setUp(self):
+ from tests.fixtureapps import echo
+
+ self.start_subprocess(
+ echo.app,
+ trusted_proxy="*",
+ trusted_proxy_count=1,
+ trusted_proxy_headers={"x-forwarded-for", "x-forwarded-proto"},
+ clear_untrusted_proxy_headers=True,
+ )
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def _read_echo(self, fp):
+ from tests.fixtureapps import echo
+
+ line, headers, body = read_http(fp)
+ return line, headers, echo.parse_response(body)
+
+ def test_date_and_server(self):
+ to_send = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(headers.get("server"), "waitress")
+ self.assertTrue(headers.get("date"))
+
+ def test_bad_host_header(self):
+ # https://corte.si/posts/code/pathod/pythonservers/index.html
+ to_send = "GET / HTTP/1.0\r\n Host: 0\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "400", "Bad Request", "HTTP/1.0")
+ self.assertEqual(headers.get("server"), "waitress")
+ self.assertTrue(headers.get("date"))
+
+ def test_send_with_body(self):
+ to_send = "GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
+ to_send += "hello"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(echo.content_length, "5")
+ self.assertEqual(echo.body, b"hello")
+
+ def test_send_empty_body(self):
+ to_send = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(echo.content_length, "0")
+ self.assertEqual(echo.body, b"")
+
+ def test_multiple_requests_with_body(self):
+ orig_sock = self.sock
+ for x in range(3):
+ self.sock = self.create_socket()
+ self.test_send_with_body()
+ self.sock.close()
+ self.sock = orig_sock
+
+ def test_multiple_requests_without_body(self):
+ orig_sock = self.sock
+ for x in range(3):
+ self.sock = self.create_socket()
+ self.test_send_empty_body()
+ self.sock.close()
+ self.sock = orig_sock
+
+ def test_without_crlf(self):
+ data = "Echo\r\nthis\r\nplease"
+ s = tobytes(
+ "GET / HTTP/1.0\r\n"
+ "Connection: close\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(int(echo.content_length), len(data))
+ self.assertEqual(len(echo.body), len(data))
+ self.assertEqual(echo.body, tobytes(data))
+
+ def test_large_body(self):
+ # 1024 characters.
+ body = "This string has 32 characters.\r\n" * 32
+ s = tobytes(
+ "GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(body), body)
+ )
+ self.connect()
+ self.sock.send(s)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(echo.content_length, "1024")
+ self.assertEqual(echo.body, tobytes(body))
+
+ def test_many_clients(self):
+ conns = []
+ for n in range(50):
+ h = self.make_http_connection()
+ h.request("GET", "/", headers={"Accept": "text/plain"})
+ conns.append(h)
+ responses = []
+ for h in conns:
+ response = h.getresponse()
+ self.assertEqual(response.status, 200)
+ responses.append(response)
+ for response in responses:
+ response.read()
+ for h in conns:
+ h.close()
+
+ def test_chunking_request_without_content(self):
+ header = tobytes("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
+ self.connect()
+ self.sock.send(header)
+ self.sock.send(b"0\r\n\r\n")
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ self.assertEqual(echo.body, b"")
+ self.assertEqual(echo.content_length, "0")
+ self.assertFalse("transfer-encoding" in headers)
+
+ def test_chunking_request_with_content(self):
+ control_line = b"20;\r\n" # 20 hex = 32 dec
+ s = b"This string has 32 characters.\r\n"
+ expected = s * 12
+ header = tobytes("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
+ self.connect()
+ self.sock.send(header)
+ fp = self.sock.makefile("rb", 0)
+ for n in range(12):
+ self.sock.send(control_line)
+ self.sock.send(s)
+ self.sock.send(b"\r\n") # End the chunk
+ self.sock.send(b"0\r\n\r\n")
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ self.assertEqual(echo.body, expected)
+ self.assertEqual(echo.content_length, str(len(expected)))
+ self.assertFalse("transfer-encoding" in headers)
+
+ def test_broken_chunked_encoding(self):
+ control_line = "20;\r\n" # 20 hex = 32 dec
+ s = "This string has 32 characters.\r\n"
+ to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
+ to_send += control_line + s + "\r\n"
+ # garbage in input
+ to_send += "garbage\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # receiver caught garbage and turned it into a 400
+ self.assertline(line, "400", "Bad Request", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ self.assertEqual(headers["content-type"], "text/plain")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_broken_chunked_encoding_missing_chunk_end(self):
+ control_line = "20;\r\n" # 20 hex = 32 dec
+ s = "This string has 32 characters.\r\n"
+ to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
+ to_send += control_line + s
+ # garbage in input
+ to_send += "garbage"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # receiver caught garbage and turned it into a 400
+ self.assertline(line, "400", "Bad Request", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(b"Chunk not properly terminated" in response_body)
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ self.assertEqual(headers["content-type"], "text/plain")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_keepalive_http_10(self):
+ # Handling of Keep-Alive within HTTP 1.0
+ data = "Default: Don't keep me alive"
+ s = tobytes(
+ "GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ response = httplib.HTTPResponse(self.sock)
+ response.begin()
+ self.assertEqual(int(response.status), 200)
+ connection = response.getheader("Connection", "")
+ # We sent no Connection: Keep-Alive header
+ # Connection: close (or no header) is default.
+ self.assertTrue(connection != "Keep-Alive")
+
+ def test_keepalive_http10_explicit(self):
+ # If header Connection: Keep-Alive is explicitly sent,
+ # we want to keept the connection open, we also need to return
+ # the corresponding header
+ data = "Keep me alive"
+ s = tobytes(
+ "GET / HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ response = httplib.HTTPResponse(self.sock)
+ response.begin()
+ self.assertEqual(int(response.status), 200)
+ connection = response.getheader("Connection", "")
+ self.assertEqual(connection, "Keep-Alive")
+
+ def test_keepalive_http_11(self):
+ # Handling of Keep-Alive within HTTP 1.1
+
+ # All connections are kept alive, unless stated otherwise
+ data = "Default: Keep me alive"
+ s = tobytes(
+ "GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ response = httplib.HTTPResponse(self.sock)
+ response.begin()
+ self.assertEqual(int(response.status), 200)
+ self.assertTrue(response.getheader("connection") != "close")
+
+ def test_keepalive_http11_explicit(self):
+ # Explicitly set keep-alive
+ data = "Default: Keep me alive"
+ s = tobytes(
+ "GET / HTTP/1.1\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ response = httplib.HTTPResponse(self.sock)
+ response.begin()
+ self.assertEqual(int(response.status), 200)
+ self.assertTrue(response.getheader("connection") != "close")
+
+ def test_keepalive_http11_connclose(self):
+ # specifying Connection: close explicitly
+ data = "Don't keep me alive"
+ s = tobytes(
+ "GET / HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(s)
+ response = httplib.HTTPResponse(self.sock)
+ response.begin()
+ self.assertEqual(int(response.status), 200)
+ self.assertEqual(response.getheader("connection"), "close")
+
+ def test_proxy_headers(self):
+ to_send = (
+ "GET / HTTP/1.0\r\n"
+ "Content-Length: 0\r\n"
+ "Host: www.google.com:8080\r\n"
+ "X-Forwarded-For: 192.168.1.1\r\n"
+ "X-Forwarded-Proto: https\r\n"
+ "X-Forwarded-Port: 5000\r\n\r\n"
+ )
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, echo = self._read_echo(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(headers.get("server"), "waitress")
+ self.assertTrue(headers.get("date"))
+ self.assertIsNone(echo.headers.get("X_FORWARDED_PORT"))
+ self.assertEqual(echo.headers["HOST"], "www.google.com:8080")
+ self.assertEqual(echo.scheme, "https")
+ self.assertEqual(echo.remote_addr, "192.168.1.1")
+ self.assertEqual(echo.remote_host, "192.168.1.1")
+
+
+class PipeliningTests(object):
+ def setUp(self):
+ from tests.fixtureapps import echo
+
+ self.start_subprocess(echo.app_body_only)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_pipelining(self):
+ s = (
+ "GET / HTTP/1.0\r\n"
+ "Connection: %s\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n"
+ "%s"
+ )
+ to_send = b""
+ count = 25
+ for n in range(count):
+ body = "Response #%d\r\n" % (n + 1)
+ if n + 1 < count:
+ conn = "keep-alive"
+ else:
+ conn = "close"
+ to_send += tobytes(s % (conn, len(body), body))
+
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ for n in range(count):
+ expect_body = tobytes("Response #%d\r\n" % (n + 1))
+ line = fp.readline() # status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ headers = parse_headers(fp)
+ length = int(headers.get("content-length")) or None
+ response_body = fp.read(length)
+ self.assertEqual(int(status), 200)
+ self.assertEqual(length, len(response_body))
+ self.assertEqual(response_body, expect_body)
+
+
+class ExpectContinueTests(object):
+ def setUp(self):
+ from tests.fixtureapps import echo
+
+ self.start_subprocess(echo.app_body_only)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_expect_continue(self):
+ # specifying Connection: close explicitly
+ data = "I have expectations"
+ to_send = tobytes(
+ "GET / HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Content-Length: %d\r\n"
+ "Expect: 100-continue\r\n"
+ "\r\n"
+ "%s" % (len(data), data)
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line = fp.readline() # continue status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ self.assertEqual(int(status), 100)
+ self.assertEqual(reason, b"Continue")
+ self.assertEqual(version, b"HTTP/1.1")
+ fp.readline() # blank line
+ line = fp.readline() # next status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ headers = parse_headers(fp)
+ length = int(headers.get("content-length")) or None
+ response_body = fp.read(length)
+ self.assertEqual(int(status), 200)
+ self.assertEqual(length, len(response_body))
+ self.assertEqual(response_body, tobytes(data))
+
+
+class BadContentLengthTests(object):
+ def setUp(self):
+ from tests.fixtureapps import badcl
+
+ self.start_subprocess(badcl.app)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_short_body(self):
+ # check to see if server closes connection when body is too short
+ # for cl header
+ to_send = tobytes(
+ "GET /short_body HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line = fp.readline() # status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ headers = parse_headers(fp)
+ content_length = int(headers.get("content-length"))
+ response_body = fp.read(content_length)
+ self.assertEqual(int(status), 200)
+ self.assertNotEqual(content_length, len(response_body))
+ self.assertEqual(len(response_body), content_length - 1)
+ self.assertEqual(response_body, tobytes("abcdefghi"))
+ # remote closed connection (despite keepalive header); not sure why
+ # first send succeeds
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_long_body(self):
+ # check server doesnt close connection when body is too short
+ # for cl header
+ to_send = tobytes(
+ "GET /long_body HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line = fp.readline() # status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ headers = parse_headers(fp)
+ content_length = int(headers.get("content-length")) or None
+ response_body = fp.read(content_length)
+ self.assertEqual(int(status), 200)
+ self.assertEqual(content_length, len(response_body))
+ self.assertEqual(response_body, tobytes("abcdefgh"))
+ # remote does not close connection (keepalive header)
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line = fp.readline() # status line
+ version, status, reason = (x.strip() for x in line.split(None, 2))
+ headers = parse_headers(fp)
+ content_length = int(headers.get("content-length")) or None
+ response_body = fp.read(content_length)
+ self.assertEqual(int(status), 200)
+
+
+class NoContentLengthTests(object):
+ def setUp(self):
+ from tests.fixtureapps import nocl
+
+ self.start_subprocess(nocl.app)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_http10_generator(self):
+ body = string.ascii_letters
+ to_send = (
+ "GET / HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: %d\r\n\r\n" % len(body)
+ )
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(headers.get("content-length"), None)
+ self.assertEqual(headers.get("connection"), "close")
+ self.assertEqual(response_body, tobytes(body))
+ # remote closed connection (despite keepalive header), because
+ # generators cannot have a content-length divined
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_http10_list(self):
+ body = string.ascii_letters
+ to_send = (
+ "GET /list HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: %d\r\n\r\n" % len(body)
+ )
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(headers["content-length"], str(len(body)))
+ self.assertEqual(headers.get("connection"), "Keep-Alive")
+ self.assertEqual(response_body, tobytes(body))
+ # remote keeps connection open because it divined the content length
+ # from a length-1 list
+ self.sock.send(to_send)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+
+ def test_http10_listlentwo(self):
+ body = string.ascii_letters
+ to_send = (
+ "GET /list_lentwo HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: %d\r\n\r\n" % len(body)
+ )
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(headers.get("content-length"), None)
+ self.assertEqual(headers.get("connection"), "close")
+ self.assertEqual(response_body, tobytes(body))
+ # remote closed connection (despite keepalive header), because
+ # lists of length > 1 cannot have their content length divined
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_http11_generator(self):
+ body = string.ascii_letters
+ to_send = "GET / HTTP/1.1\r\nContent-Length: %s\r\n\r\n" % len(body)
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ expected = b""
+ for chunk in chunks(body, 10):
+ expected += tobytes(
+ "%s\r\n%s\r\n" % (str(hex(len(chunk))[2:].upper()), chunk)
+ )
+ expected += b"0\r\n\r\n"
+ self.assertEqual(response_body, expected)
+ # connection is always closed at the end of a chunked response
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_http11_list(self):
+ body = string.ascii_letters
+ to_send = "GET /list HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body)
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ self.assertEqual(headers["content-length"], str(len(body)))
+ self.assertEqual(response_body, tobytes(body))
+ # remote keeps connection open because it divined the content length
+ # from a length-1 list
+ self.sock.send(to_send)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+
+ def test_http11_listlentwo(self):
+ body = string.ascii_letters
+ to_send = "GET /list_lentwo HTTP/1.1\r\nContent-Length: %s\r\n\r\n" % len(body)
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ expected = b""
+ for chunk in (body[0], body[1:]):
+ expected += tobytes(
+ "%s\r\n%s\r\n" % (str(hex(len(chunk))[2:].upper()), chunk)
+ )
+ expected += b"0\r\n\r\n"
+ self.assertEqual(response_body, expected)
+ # connection is always closed at the end of a chunked response
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
+class WriteCallbackTests(object):
+ def setUp(self):
+ from tests.fixtureapps import writecb
+
+ self.start_subprocess(writecb.app)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_short_body(self):
+ # check to see if server closes connection when body is too short
+ # for cl header
+ to_send = tobytes(
+ "GET /short_body HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # server trusts the content-length header (5)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, 9)
+ self.assertNotEqual(cl, len(response_body))
+ self.assertEqual(len(response_body), cl - 1)
+ self.assertEqual(response_body, tobytes("abcdefgh"))
+ # remote closed connection (despite keepalive header)
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_long_body(self):
+ # check server doesnt close connection when body is too long
+ # for cl header
+ to_send = tobytes(
+ "GET /long_body HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ content_length = int(headers.get("content-length")) or None
+ self.assertEqual(content_length, 9)
+ self.assertEqual(content_length, len(response_body))
+ self.assertEqual(response_body, tobytes("abcdefghi"))
+ # remote does not close connection (keepalive header)
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+
+ def test_equal_body(self):
+ # check server doesnt close connection when body is equal to
+ # cl header
+ to_send = tobytes(
+ "GET /equal_body HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ content_length = int(headers.get("content-length")) or None
+ self.assertEqual(content_length, 9)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ self.assertEqual(content_length, len(response_body))
+ self.assertEqual(response_body, tobytes("abcdefghi"))
+ # remote does not close connection (keepalive header)
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+
+ def test_no_content_length(self):
+ # wtf happens when there's no content-length
+ to_send = tobytes(
+ "GET /no_content_length HTTP/1.0\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line = fp.readline() # status line
+ line, headers, response_body = read_http(fp)
+ content_length = headers.get("content-length")
+ self.assertEqual(content_length, None)
+ self.assertEqual(response_body, tobytes("abcdefghi"))
+ # remote closed connection (despite keepalive header)
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
+class TooLargeTests(object):
+
+ toobig = 1050
+
+ def setUp(self):
+ from tests.fixtureapps import toolarge
+
+ self.start_subprocess(
+ toolarge.app, max_request_header_size=1000, max_request_body_size=1000
+ )
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_request_body_too_large_with_wrong_cl_http10(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ # first request succeeds (content-length 5)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # server trusts the content-length header; no pipelining,
+ # so request fulfilled, extra bytes are thrown away
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_wrong_cl_http10_keepalive(self):
+ body = "a" * self.toobig
+ to_send = (
+ "GET / HTTP/1.0\r\nContent-Length: 5\r\nConnection: Keep-Alive\r\n\r\n"
+ )
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ # first request succeeds (content-length 5)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_no_cl_http10(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.0\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # extra bytes are thrown away (no pipelining), connection closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_no_cl_http10_keepalive(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # server trusts the content-length header (assumed zero)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ line, headers, response_body = read_http(fp)
+ # next response overruns because the extra data appears to be
+ # header data
+ self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_wrong_cl_http11(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ # first request succeeds (content-length 5)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # second response is an error response
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_wrong_cl_http11_connclose(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.1\r\nContent-Length: 5\r\nConnection: close\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # server trusts the content-length header (5)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_no_cl_http11(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.1\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb")
+ # server trusts the content-length header (assumed 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # server assumes pipelined requests due to http/1.1, and the first
+ # request was assumed c-l 0 because it had no content-length header,
+ # so entire body looks like the header of the subsequent request
+ # second response is an error response
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "431", "Request Header Fields Too Large", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_with_no_cl_http11_connclose(self):
+ body = "a" * self.toobig
+ to_send = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
+ to_send += body
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # server trusts the content-length header (assumed 0)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_request_body_too_large_chunked_encoding(self):
+ control_line = "20;\r\n" # 20 hex = 32 dec
+ s = "This string has 32 characters.\r\n"
+ to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
+ repeat = control_line + s
+ to_send += repeat * ((self.toobig // len(repeat)) + 1)
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ # body bytes counter caught a max_request_body_size overrun
+ self.assertline(line, "413", "Request Entity Too Large", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertEqual(headers["content-type"], "text/plain")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
+class InternalServerErrorTests(object):
+ def setUp(self):
+ from tests.fixtureapps import error
+
+ self.start_subprocess(error.app, expose_tracebacks=True)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_before_start_response_http_10(self):
+ to_send = "GET /before_start_response HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(headers["connection"], "close")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_before_start_response_http_11(self):
+ to_send = "GET /before_start_response HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_before_start_response_http_11_close(self):
+ to_send = tobytes(
+ "GET /before_start_response HTTP/1.1\r\nConnection: close\r\n\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ self.assertEqual(headers["connection"], "close")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_after_start_response_http10(self):
+ to_send = "GET /after_start_response HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ self.assertEqual(headers["connection"], "close")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_after_start_response_http11(self):
+ to_send = "GET /after_start_response HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_after_start_response_http11_close(self):
+ to_send = tobytes(
+ "GET /after_start_response HTTP/1.1\r\nConnection: close\r\n\r\n"
+ )
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ self.assertEqual(headers["connection"], "close")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_after_write_cb(self):
+ to_send = "GET /after_write_cb HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ self.assertEqual(response_body, b"")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_in_generator(self):
+ to_send = "GET /in_generator HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+ self.connect()
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ self.assertEqual(response_body, b"")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
+class FileWrapperTests(object):
+ def setUp(self):
+ from tests.fixtureapps import filewrapper
+
+ self.start_subprocess(filewrapper.app)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_filelike_http11(self):
+ to_send = "GET /filelike HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has not been closed
+
+ def test_filelike_nocl_http11(self):
+ to_send = "GET /filelike_nocl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has not been closed
+
+ def test_filelike_shortcl_http11(self):
+ to_send = "GET /filelike_shortcl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, 1)
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377" in response_body)
+ # connection has not been closed
+
+ def test_filelike_longcl_http11(self):
+ to_send = "GET /filelike_longcl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has not been closed
+
+ def test_notfilelike_http11(self):
+ to_send = "GET /notfilelike HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has not been closed
+
+ def test_notfilelike_iobase_http11(self):
+ to_send = "GET /notfilelike_iobase HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has not been closed
+
+ def test_notfilelike_nocl_http11(self):
+ to_send = "GET /notfilelike_nocl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed (no content-length)
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_notfilelike_shortcl_http11(self):
+ to_send = "GET /notfilelike_shortcl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ for t in range(0, 2):
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, 1)
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377" in response_body)
+ # connection has not been closed
+
+ def test_notfilelike_longcl_http11(self):
+ to_send = "GET /notfilelike_longcl HTTP/1.1\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body) + 10)
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_filelike_http10(self):
+ to_send = "GET /filelike HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_filelike_nocl_http10(self):
+ to_send = "GET /filelike_nocl HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_notfilelike_http10(self):
+ to_send = "GET /notfilelike HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_notfilelike_nocl_http10(self):
+ to_send = "GET /notfilelike_nocl HTTP/1.0\r\n\r\n"
+ to_send = tobytes(to_send)
+
+ self.connect()
+
+ self.sock.send(to_send)
+ fp = self.sock.makefile("rb", 0)
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "200", "OK", "HTTP/1.0")
+ ct = headers["content-type"]
+ self.assertEqual(ct, "image/jpeg")
+ self.assertTrue(b"\377\330\377" in response_body)
+ # connection has been closed (no content-length)
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
+class TcpEchoTests(EchoTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpPipeliningTests(PipeliningTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpExpectContinueTests(ExpectContinueTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpBadContentLengthTests(BadContentLengthTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpNoContentLengthTests(NoContentLengthTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpWriteCallbackTests(WriteCallbackTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpTooLargeTests(TooLargeTests, TcpTests, unittest.TestCase):
+ pass
+
+
+class TcpInternalServerErrorTests(
+ InternalServerErrorTests, TcpTests, unittest.TestCase
+):
+ pass
+
+
+class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase):
+ pass
+
+
+if hasattr(socket, "AF_UNIX"):
+
+ class FixtureUnixWSGIServer(server.UnixWSGIServer):
+ """A version of UnixWSGIServer that relays back what it's bound to.
+ """
+
+ family = socket.AF_UNIX # Testing
+
+ def __init__(self, application, queue, **kw): # pragma: no cover
+ # Coverage doesn't see this as it's ran in a separate process.
+ # To permit parallel testing, use a PID-dependent socket.
+ kw["unix_socket"] = "/tmp/waitress.test-%d.sock" % os.getpid()
+ super(FixtureUnixWSGIServer, self).__init__(application, **kw)
+ queue.put(self.socket.getsockname())
+
+ class UnixTests(SubprocessTests):
+
+ server = FixtureUnixWSGIServer
+
+ def make_http_connection(self):
+ return UnixHTTPConnection(self.bound_to)
+
+ def stop_subprocess(self):
+ super(UnixTests, self).stop_subprocess()
+ cleanup_unix_socket(self.bound_to)
+
+ def send_check_error(self, to_send):
+ # Unlike inet domain sockets, Unix domain sockets can trigger a
+ # 'Broken pipe' error when the socket it closed.
+ try:
+ self.sock.send(to_send)
+ except socket.error as exc:
+ self.assertEqual(get_errno(exc), errno.EPIPE)
+
+ class UnixEchoTests(EchoTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixPipeliningTests(PipeliningTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixExpectContinueTests(ExpectContinueTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixBadContentLengthTests(
+ BadContentLengthTests, UnixTests, unittest.TestCase
+ ):
+ pass
+
+ class UnixNoContentLengthTests(NoContentLengthTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixWriteCallbackTests(WriteCallbackTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixTooLargeTests(TooLargeTests, UnixTests, unittest.TestCase):
+ pass
+
+ class UnixInternalServerErrorTests(
+ InternalServerErrorTests, UnixTests, unittest.TestCase
+ ):
+ pass
+
+ class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase):
+ pass
+
+
+def parse_headers(fp):
+ """Parses only RFC2822 headers from a file pointer.
+ """
+ headers = {}
+ while True:
+ line = fp.readline()
+ if line in (b"\r\n", b"\n", b""):
+ break
+ line = line.decode("iso-8859-1")
+ name, value = line.strip().split(":", 1)
+ headers[name.lower().strip()] = value.lower().strip()
+ return headers
+
+
+class UnixHTTPConnection(httplib.HTTPConnection):
+ """Patched version of HTTPConnection that uses Unix domain sockets.
+ """
+
+ def __init__(self, path):
+ httplib.HTTPConnection.__init__(self, "localhost")
+ self.path = path
+
+ def connect(self):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(self.path)
+ self.sock = sock
+
+
+class ConnectionClosed(Exception):
+ pass
+
+
+# stolen from gevent
+def read_http(fp): # pragma: no cover
+ try:
+ response_line = fp.readline()
+ except socket.error as exc:
+ fp.close()
+ # errno 104 is ENOTRECOVERABLE, In WinSock 10054 is ECONNRESET
+ if get_errno(exc) in (errno.ECONNABORTED, errno.ECONNRESET, 104, 10054):
+ raise ConnectionClosed
+ raise
+ if not response_line:
+ raise ConnectionClosed
+
+ header_lines = []
+ while True:
+ line = fp.readline()
+ if line in (b"\r\n", b"\r\n", b""):
+ break
+ else:
+ header_lines.append(line)
+ headers = dict()
+ for x in header_lines:
+ x = x.strip()
+ if not x:
+ continue
+ key, value = x.split(b": ", 1)
+ key = key.decode("iso-8859-1").lower()
+ value = value.decode("iso-8859-1")
+ assert key not in headers, "%s header duplicated" % key
+ headers[key] = value
+
+ if "content-length" in headers:
+ num = int(headers["content-length"])
+ body = b""
+ left = num
+ while left > 0:
+ data = fp.read(left)
+ if not data:
+ break
+ body += data
+ left -= len(data)
+ else:
+ # read until EOF
+ body = fp.read()
+
+ return response_line, headers, body
+
+
+# stolen from gevent
+def get_errno(exc): # pragma: no cover
+ """ Get the error code out of socket.error objects.
+ socket.error in <2.5 does not have errno attribute
+ socket.error in 3.x does not allow indexing access
+ e.args[0] works for all.
+ There are cases when args[0] is not errno.
+ i.e. http://bugs.python.org/issue6471
+ Maybe there are cases when errno is set, but it is not the first argument?
+ """
+ try:
+ if exc.errno is not None:
+ return exc.errno
+ except AttributeError:
+ pass
+ try:
+ return exc.args[0]
+ except IndexError:
+ return None
+
+
+def chunks(l, n):
+ """ Yield successive n-sized chunks from l.
+ """
+ for i in range(0, len(l), n):
+ yield l[i : i + n]