00001
00002
00003
00004 from __future__ import absolute_import, division, print_function, with_statement
00005 from tornado import netutil
00006 from tornado.escape import json_decode, json_encode, utf8, _unicode, recursive_unicode, native_str
00007 from tornado import gen
00008 from tornado.http1connection import HTTP1Connection
00009 from tornado.httpserver import HTTPServer
00010 from tornado.httputil import HTTPHeaders, HTTPMessageDelegate, HTTPServerConnectionDelegate, ResponseStartLine
00011 from tornado.iostream import IOStream
00012 from tornado.log import gen_log, app_log
00013 from tornado.netutil import ssl_options_to_context
00014 from tornado.simple_httpclient import SimpleAsyncHTTPClient
00015 from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, ExpectLog, gen_test
00016 from tornado.test.util import unittest, skipOnTravis
00017 from tornado.util import u, bytes_type
00018 from tornado.web import Application, RequestHandler, asynchronous, stream_request_body
00019 from contextlib import closing
00020 import datetime
00021 import gzip
00022 import os
00023 import shutil
00024 import socket
00025 import ssl
00026 import sys
00027 import tempfile
00028
00029 try:
00030 from io import BytesIO
00031 except ImportError:
00032 from cStringIO import StringIO as BytesIO
00033
00034
00035 def read_stream_body(stream, callback):
00036 """Reads an HTTP response from `stream` and runs callback with its
00037 headers and body."""
00038 chunks = []
00039 class Delegate(HTTPMessageDelegate):
00040 def headers_received(self, start_line, headers):
00041 self.headers = headers
00042
00043 def data_received(self, chunk):
00044 chunks.append(chunk)
00045
00046 def finish(self):
00047 callback((self.headers, b''.join(chunks)))
00048 conn = HTTP1Connection(stream, True)
00049 conn.read_response(Delegate())
00050
00051
00052 class HandlerBaseTestCase(AsyncHTTPTestCase):
00053 def get_app(self):
00054 return Application([('/', self.__class__.Handler)])
00055
00056 def fetch_json(self, *args, **kwargs):
00057 response = self.fetch(*args, **kwargs)
00058 response.rethrow()
00059 return json_decode(response.body)
00060
00061
00062 class HelloWorldRequestHandler(RequestHandler):
00063 def initialize(self, protocol="http"):
00064 self.expected_protocol = protocol
00065
00066 def get(self):
00067 if self.request.protocol != self.expected_protocol:
00068 raise Exception("unexpected protocol")
00069 self.finish("Hello world")
00070
00071 def post(self):
00072 self.finish("Got %d bytes in POST" % len(self.request.body))
00073
00074
00075
00076
00077
00078
00079
00080 skipIfOldSSL = unittest.skipIf(
00081 getattr(ssl, 'OPENSSL_VERSION_INFO', (0, 0)) < (1, 0),
00082 "old version of ssl module and/or openssl")
00083
00084
00085 class BaseSSLTest(AsyncHTTPSTestCase):
00086 def get_app(self):
00087 return Application([('/', HelloWorldRequestHandler,
00088 dict(protocol="https"))])
00089
00090
00091 class SSLTestMixin(object):
00092 def get_ssl_options(self):
00093 return dict(ssl_version=self.get_ssl_version(),
00094 **AsyncHTTPSTestCase.get_ssl_options())
00095
00096 def get_ssl_version(self):
00097 raise NotImplementedError()
00098
00099 def test_ssl(self):
00100 response = self.fetch('/')
00101 self.assertEqual(response.body, b"Hello world")
00102
00103 def test_large_post(self):
00104 response = self.fetch('/',
00105 method='POST',
00106 body='A' * 5000)
00107 self.assertEqual(response.body, b"Got 5000 bytes in POST")
00108
00109 def test_non_ssl_request(self):
00110
00111
00112
00113 with ExpectLog(gen_log, '(SSL Error|uncaught exception)'):
00114 with ExpectLog(gen_log, 'Uncaught exception', required=False):
00115 self.http_client.fetch(
00116 self.get_url("/").replace('https:', 'http:'),
00117 self.stop,
00118 request_timeout=3600,
00119 connect_timeout=3600)
00120 response = self.wait()
00121 self.assertEqual(response.code, 599)
00122
00123
00124
00125
00126
00127
00128
00129 class SSLv23Test(BaseSSLTest, SSLTestMixin):
00130 def get_ssl_version(self):
00131 return ssl.PROTOCOL_SSLv23
00132
00133
00134 @skipIfOldSSL
00135 class SSLv3Test(BaseSSLTest, SSLTestMixin):
00136 def get_ssl_version(self):
00137 return ssl.PROTOCOL_SSLv3
00138
00139
00140 @skipIfOldSSL
00141 class TLSv1Test(BaseSSLTest, SSLTestMixin):
00142 def get_ssl_version(self):
00143 return ssl.PROTOCOL_TLSv1
00144
00145
00146 @unittest.skipIf(not hasattr(ssl, 'SSLContext'), 'ssl.SSLContext not present')
00147 class SSLContextTest(BaseSSLTest, SSLTestMixin):
00148 def get_ssl_options(self):
00149 context = ssl_options_to_context(
00150 AsyncHTTPSTestCase.get_ssl_options(self))
00151 assert isinstance(context, ssl.SSLContext)
00152 return context
00153
00154
00155 class BadSSLOptionsTest(unittest.TestCase):
00156 def test_missing_arguments(self):
00157 application = Application()
00158 self.assertRaises(KeyError, HTTPServer, application, ssl_options={
00159 "keyfile": "/__missing__.crt",
00160 })
00161
00162 def test_missing_key(self):
00163 """A missing SSL key should cause an immediate exception."""
00164
00165 application = Application()
00166 module_dir = os.path.dirname(__file__)
00167 existing_certificate = os.path.join(module_dir, 'test.crt')
00168
00169 self.assertRaises(ValueError, HTTPServer, application, ssl_options={
00170 "certfile": "/__mising__.crt",
00171 })
00172 self.assertRaises(ValueError, HTTPServer, application, ssl_options={
00173 "certfile": existing_certificate,
00174 "keyfile": "/__missing__.key"
00175 })
00176
00177
00178 HTTPServer(application, ssl_options={
00179 "certfile": existing_certificate,
00180 "keyfile": existing_certificate
00181 })
00182
00183
00184 class MultipartTestHandler(RequestHandler):
00185 def post(self):
00186 self.finish({"header": self.request.headers["X-Header-Encoding-Test"],
00187 "argument": self.get_argument("argument"),
00188 "filename": self.request.files["files"][0].filename,
00189 "filebody": _unicode(self.request.files["files"][0]["body"]),
00190 })
00191
00192
00193
00194 class HTTPConnectionTest(AsyncHTTPTestCase):
00195 def get_handlers(self):
00196 return [("/multipart", MultipartTestHandler),
00197 ("/hello", HelloWorldRequestHandler)]
00198
00199 def get_app(self):
00200 return Application(self.get_handlers())
00201
00202 def raw_fetch(self, headers, body):
00203 with closing(IOStream(socket.socket())) as stream:
00204 stream.connect(('127.0.0.1', self.get_http_port()), self.stop)
00205 self.wait()
00206 stream.write(
00207 b"\r\n".join(headers +
00208 [utf8("Content-Length: %d\r\n" % len(body))]) +
00209 b"\r\n" + body)
00210 read_stream_body(stream, self.stop)
00211 headers, body = self.wait()
00212 return body
00213
00214 def test_multipart_form(self):
00215
00216
00217 response = self.raw_fetch([
00218 b"POST /multipart HTTP/1.0",
00219 b"Content-Type: multipart/form-data; boundary=1234567890",
00220 b"X-Header-encoding-test: \xe9",
00221 ],
00222 b"\r\n".join([
00223 b"Content-Disposition: form-data; name=argument",
00224 b"",
00225 u("\u00e1").encode("utf-8"),
00226 b"--1234567890",
00227 u('Content-Disposition: form-data; name="files"; filename="\u00f3"').encode("utf8"),
00228 b"",
00229 u("\u00fa").encode("utf-8"),
00230 b"--1234567890--",
00231 b"",
00232 ]))
00233 data = json_decode(response)
00234 self.assertEqual(u("\u00e9"), data["header"])
00235 self.assertEqual(u("\u00e1"), data["argument"])
00236 self.assertEqual(u("\u00f3"), data["filename"])
00237 self.assertEqual(u("\u00fa"), data["filebody"])
00238
00239 def test_100_continue(self):
00240
00241
00242
00243 stream = IOStream(socket.socket(), io_loop=self.io_loop)
00244 stream.connect(("localhost", self.get_http_port()), callback=self.stop)
00245 self.wait()
00246 stream.write(b"\r\n".join([b"POST /hello HTTP/1.1",
00247 b"Content-Length: 1024",
00248 b"Expect: 100-continue",
00249 b"Connection: close",
00250 b"\r\n"]), callback=self.stop)
00251 self.wait()
00252 stream.read_until(b"\r\n\r\n", self.stop)
00253 data = self.wait()
00254 self.assertTrue(data.startswith(b"HTTP/1.1 100 "), data)
00255 stream.write(b"a" * 1024)
00256 stream.read_until(b"\r\n", self.stop)
00257 first_line = self.wait()
00258 self.assertTrue(first_line.startswith(b"HTTP/1.1 200"), first_line)
00259 stream.read_until(b"\r\n\r\n", self.stop)
00260 header_data = self.wait()
00261 headers = HTTPHeaders.parse(native_str(header_data.decode('latin1')))
00262 stream.read_bytes(int(headers["Content-Length"]), self.stop)
00263 body = self.wait()
00264 self.assertEqual(body, b"Got 1024 bytes in POST")
00265 stream.close()
00266
00267
00268 class EchoHandler(RequestHandler):
00269 def get(self):
00270 self.write(recursive_unicode(self.request.arguments))
00271
00272 def post(self):
00273 self.write(recursive_unicode(self.request.arguments))
00274
00275
00276 class TypeCheckHandler(RequestHandler):
00277 def prepare(self):
00278 self.errors = {}
00279 fields = [
00280 ('method', str),
00281 ('uri', str),
00282 ('version', str),
00283 ('remote_ip', str),
00284 ('protocol', str),
00285 ('host', str),
00286 ('path', str),
00287 ('query', str),
00288 ]
00289 for field, expected_type in fields:
00290 self.check_type(field, getattr(self.request, field), expected_type)
00291
00292 self.check_type('header_key', list(self.request.headers.keys())[0], str)
00293 self.check_type('header_value', list(self.request.headers.values())[0], str)
00294
00295 self.check_type('cookie_key', list(self.request.cookies.keys())[0], str)
00296 self.check_type('cookie_value', list(self.request.cookies.values())[0].value, str)
00297
00298
00299 self.check_type('arg_key', list(self.request.arguments.keys())[0], str)
00300 self.check_type('arg_value', list(self.request.arguments.values())[0][0], bytes_type)
00301
00302 def post(self):
00303 self.check_type('body', self.request.body, bytes_type)
00304 self.write(self.errors)
00305
00306 def get(self):
00307 self.write(self.errors)
00308
00309 def check_type(self, name, obj, expected_type):
00310 actual_type = type(obj)
00311 if expected_type != actual_type:
00312 self.errors[name] = "expected %s, got %s" % (expected_type,
00313 actual_type)
00314
00315
00316 class HTTPServerTest(AsyncHTTPTestCase):
00317 def get_app(self):
00318 return Application([("/echo", EchoHandler),
00319 ("/typecheck", TypeCheckHandler),
00320 ("//doubleslash", EchoHandler),
00321 ])
00322
00323 def test_query_string_encoding(self):
00324 response = self.fetch("/echo?foo=%C3%A9")
00325 data = json_decode(response.body)
00326 self.assertEqual(data, {u("foo"): [u("\u00e9")]})
00327
00328 def test_empty_query_string(self):
00329 response = self.fetch("/echo?foo=&foo=")
00330 data = json_decode(response.body)
00331 self.assertEqual(data, {u("foo"): [u(""), u("")]})
00332
00333 def test_empty_post_parameters(self):
00334 response = self.fetch("/echo", method="POST", body="foo=&bar=")
00335 data = json_decode(response.body)
00336 self.assertEqual(data, {u("foo"): [u("")], u("bar"): [u("")]})
00337
00338 def test_types(self):
00339 headers = {"Cookie": "foo=bar"}
00340 response = self.fetch("/typecheck?foo=bar", headers=headers)
00341 data = json_decode(response.body)
00342 self.assertEqual(data, {})
00343
00344 response = self.fetch("/typecheck", method="POST", body="foo=bar", headers=headers)
00345 data = json_decode(response.body)
00346 self.assertEqual(data, {})
00347
00348 def test_double_slash(self):
00349
00350
00351
00352 response = self.fetch("//doubleslash")
00353 self.assertEqual(200, response.code)
00354 self.assertEqual(json_decode(response.body), {})
00355
00356 def test_malformed_body(self):
00357
00358
00359
00360
00361 if str is bytes_type:
00362 return
00363 with ExpectLog(gen_log, 'Invalid x-www-form-urlencoded body'):
00364 response = self.fetch(
00365 '/echo', method="POST",
00366 headers={'Content-Type': 'application/x-www-form-urlencoded'},
00367 body=b'\xe9')
00368 self.assertEqual(200, response.code)
00369 self.assertEqual(b'{}', response.body)
00370
00371
00372 class HTTPServerRawTest(AsyncHTTPTestCase):
00373 def get_app(self):
00374 return Application([
00375 ('/echo', EchoHandler),
00376 ])
00377
00378 def setUp(self):
00379 super(HTTPServerRawTest, self).setUp()
00380 self.stream = IOStream(socket.socket())
00381 self.stream.connect(('localhost', self.get_http_port()), self.stop)
00382 self.wait()
00383
00384 def tearDown(self):
00385 self.stream.close()
00386 super(HTTPServerRawTest, self).tearDown()
00387
00388 def test_empty_request(self):
00389 self.stream.close()
00390 self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)
00391 self.wait()
00392
00393 def test_malformed_first_line(self):
00394 with ExpectLog(gen_log, '.*Malformed HTTP request line'):
00395 self.stream.write(b'asdf\r\n\r\n')
00396
00397
00398 self.io_loop.add_timeout(datetime.timedelta(seconds=0.01),
00399 self.stop)
00400 self.wait()
00401
00402 def test_malformed_headers(self):
00403 with ExpectLog(gen_log, '.*Malformed HTTP headers'):
00404 self.stream.write(b'GET / HTTP/1.0\r\nasdf\r\n\r\n')
00405 self.io_loop.add_timeout(datetime.timedelta(seconds=0.01),
00406 self.stop)
00407 self.wait()
00408
00409 def test_chunked_request_body(self):
00410
00411
00412 self.stream.write(b"""\
00413 POST /echo HTTP/1.1
00414 Transfer-Encoding: chunked
00415 Content-Type: application/x-www-form-urlencoded
00416
00417 4
00418 foo=
00419 3
00420 bar
00421 0
00422
00423 """.replace(b"\n", b"\r\n"))
00424 read_stream_body(self.stream, self.stop)
00425 headers, response = self.wait()
00426 self.assertEqual(json_decode(response), {u('foo'): [u('bar')]})
00427
00428
00429 class XHeaderTest(HandlerBaseTestCase):
00430 class Handler(RequestHandler):
00431 def get(self):
00432 self.write(dict(remote_ip=self.request.remote_ip,
00433 remote_protocol=self.request.protocol))
00434
00435 def get_httpserver_options(self):
00436 return dict(xheaders=True)
00437
00438 def test_ip_headers(self):
00439 self.assertEqual(self.fetch_json("/")["remote_ip"], "127.0.0.1")
00440
00441 valid_ipv4 = {"X-Real-IP": "4.4.4.4"}
00442 self.assertEqual(
00443 self.fetch_json("/", headers=valid_ipv4)["remote_ip"],
00444 "4.4.4.4")
00445
00446 valid_ipv4_list = {"X-Forwarded-For": "127.0.0.1, 4.4.4.4"}
00447 self.assertEqual(
00448 self.fetch_json("/", headers=valid_ipv4_list)["remote_ip"],
00449 "4.4.4.4")
00450
00451 valid_ipv6 = {"X-Real-IP": "2620:0:1cfe:face:b00c::3"}
00452 self.assertEqual(
00453 self.fetch_json("/", headers=valid_ipv6)["remote_ip"],
00454 "2620:0:1cfe:face:b00c::3")
00455
00456 valid_ipv6_list = {"X-Forwarded-For": "::1, 2620:0:1cfe:face:b00c::3"}
00457 self.assertEqual(
00458 self.fetch_json("/", headers=valid_ipv6_list)["remote_ip"],
00459 "2620:0:1cfe:face:b00c::3")
00460
00461 invalid_chars = {"X-Real-IP": "4.4.4.4<script>"}
00462 self.assertEqual(
00463 self.fetch_json("/", headers=invalid_chars)["remote_ip"],
00464 "127.0.0.1")
00465
00466 invalid_chars_list = {"X-Forwarded-For": "4.4.4.4, 5.5.5.5<script>"}
00467 self.assertEqual(
00468 self.fetch_json("/", headers=invalid_chars_list)["remote_ip"],
00469 "127.0.0.1")
00470
00471 invalid_host = {"X-Real-IP": "www.google.com"}
00472 self.assertEqual(
00473 self.fetch_json("/", headers=invalid_host)["remote_ip"],
00474 "127.0.0.1")
00475
00476 def test_scheme_headers(self):
00477 self.assertEqual(self.fetch_json("/")["remote_protocol"], "http")
00478
00479 https_scheme = {"X-Scheme": "https"}
00480 self.assertEqual(
00481 self.fetch_json("/", headers=https_scheme)["remote_protocol"],
00482 "https")
00483
00484 https_forwarded = {"X-Forwarded-Proto": "https"}
00485 self.assertEqual(
00486 self.fetch_json("/", headers=https_forwarded)["remote_protocol"],
00487 "https")
00488
00489 bad_forwarded = {"X-Forwarded-Proto": "unknown"}
00490 self.assertEqual(
00491 self.fetch_json("/", headers=bad_forwarded)["remote_protocol"],
00492 "http")
00493
00494
00495 class SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):
00496 def get_app(self):
00497 return Application([('/', XHeaderTest.Handler)])
00498
00499 def get_httpserver_options(self):
00500 output = super(SSLXHeaderTest, self).get_httpserver_options()
00501 output['xheaders'] = True
00502 return output
00503
00504 def test_request_without_xprotocol(self):
00505 self.assertEqual(self.fetch_json("/")["remote_protocol"], "https")
00506
00507 http_scheme = {"X-Scheme": "http"}
00508 self.assertEqual(
00509 self.fetch_json("/", headers=http_scheme)["remote_protocol"], "http")
00510
00511 bad_scheme = {"X-Scheme": "unknown"}
00512 self.assertEqual(
00513 self.fetch_json("/", headers=bad_scheme)["remote_protocol"], "https")
00514
00515
00516 class ManualProtocolTest(HandlerBaseTestCase):
00517 class Handler(RequestHandler):
00518 def get(self):
00519 self.write(dict(protocol=self.request.protocol))
00520
00521 def get_httpserver_options(self):
00522 return dict(protocol='https')
00523
00524 def test_manual_protocol(self):
00525 self.assertEqual(self.fetch_json('/')['protocol'], 'https')
00526
00527
00528 @unittest.skipIf(not hasattr(socket, 'AF_UNIX') or sys.platform == 'cygwin',
00529 "unix sockets not supported on this platform")
00530 class UnixSocketTest(AsyncTestCase):
00531 """HTTPServers can listen on Unix sockets too.
00532
00533 Why would you want to do this? Nginx can proxy to backends listening
00534 on unix sockets, for one thing (and managing a namespace for unix
00535 sockets can be easier than managing a bunch of TCP port numbers).
00536
00537 Unfortunately, there's no way to specify a unix socket in a url for
00538 an HTTP client, so we have to test this by hand.
00539 """
00540 def setUp(self):
00541 super(UnixSocketTest, self).setUp()
00542 self.tmpdir = tempfile.mkdtemp()
00543 self.sockfile = os.path.join(self.tmpdir, "test.sock")
00544 sock = netutil.bind_unix_socket(self.sockfile)
00545 app = Application([("/hello", HelloWorldRequestHandler)])
00546 self.server = HTTPServer(app, io_loop=self.io_loop)
00547 self.server.add_socket(sock)
00548 self.stream = IOStream(socket.socket(socket.AF_UNIX), io_loop=self.io_loop)
00549 self.stream.connect(self.sockfile, self.stop)
00550 self.wait()
00551
00552 def tearDown(self):
00553 self.stream.close()
00554 self.server.stop()
00555 shutil.rmtree(self.tmpdir)
00556 super(UnixSocketTest, self).tearDown()
00557
00558 def test_unix_socket(self):
00559 self.stream.write(b"GET /hello HTTP/1.0\r\n\r\n")
00560 self.stream.read_until(b"\r\n", self.stop)
00561 response = self.wait()
00562 self.assertEqual(response, b"HTTP/1.0 200 OK\r\n")
00563 self.stream.read_until(b"\r\n\r\n", self.stop)
00564 headers = HTTPHeaders.parse(self.wait().decode('latin1'))
00565 self.stream.read_bytes(int(headers["Content-Length"]), self.stop)
00566 body = self.wait()
00567 self.assertEqual(body, b"Hello world")
00568
00569 def test_unix_socket_bad_request(self):
00570
00571
00572 with ExpectLog(gen_log, "Malformed HTTP message from"):
00573 self.stream.write(b"garbage\r\n\r\n")
00574 self.stream.read_until_close(self.stop)
00575 response = self.wait()
00576 self.assertEqual(response, b"")
00577
00578
00579 class KeepAliveTest(AsyncHTTPTestCase):
00580 """Tests various scenarios for HTTP 1.1 keep-alive support.
00581
00582 These tests don't use AsyncHTTPClient because we want to control
00583 connection reuse and closing.
00584 """
00585 def get_app(self):
00586 class HelloHandler(RequestHandler):
00587 def get(self):
00588 self.finish('Hello world')
00589
00590 class LargeHandler(RequestHandler):
00591 def get(self):
00592
00593
00594 self.write(''.join(chr(i % 256) * 1024 for i in range(512)))
00595
00596 class FinishOnCloseHandler(RequestHandler):
00597 @asynchronous
00598 def get(self):
00599 self.flush()
00600
00601 def on_connection_close(self):
00602
00603
00604
00605 self.finish('closed')
00606
00607 return Application([('/', HelloHandler),
00608 ('/large', LargeHandler),
00609 ('/finish_on_close', FinishOnCloseHandler)])
00610
00611 def setUp(self):
00612 super(KeepAliveTest, self).setUp()
00613 self.http_version = b'HTTP/1.1'
00614
00615 def tearDown(self):
00616
00617
00618 self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)
00619 self.wait()
00620
00621 if hasattr(self, 'stream'):
00622 self.stream.close()
00623 super(KeepAliveTest, self).tearDown()
00624
00625
00626 def connect(self):
00627 self.stream = IOStream(socket.socket(), io_loop=self.io_loop)
00628 self.stream.connect(('localhost', self.get_http_port()), self.stop)
00629 self.wait()
00630
00631 def read_headers(self):
00632 self.stream.read_until(b'\r\n', self.stop)
00633 first_line = self.wait()
00634 self.assertTrue(first_line.startswith(self.http_version + b' 200'), first_line)
00635 self.stream.read_until(b'\r\n\r\n', self.stop)
00636 header_bytes = self.wait()
00637 headers = HTTPHeaders.parse(header_bytes.decode('latin1'))
00638 return headers
00639
00640 def read_response(self):
00641 self.headers = self.read_headers()
00642 self.stream.read_bytes(int(self.headers['Content-Length']), self.stop)
00643 body = self.wait()
00644 self.assertEqual(b'Hello world', body)
00645
00646 def close(self):
00647 self.stream.close()
00648 del self.stream
00649
00650 def test_two_requests(self):
00651 self.connect()
00652 self.stream.write(b'GET / HTTP/1.1\r\n\r\n')
00653 self.read_response()
00654 self.stream.write(b'GET / HTTP/1.1\r\n\r\n')
00655 self.read_response()
00656 self.close()
00657
00658 def test_request_close(self):
00659 self.connect()
00660 self.stream.write(b'GET / HTTP/1.1\r\nConnection: close\r\n\r\n')
00661 self.read_response()
00662 self.stream.read_until_close(callback=self.stop)
00663 data = self.wait()
00664 self.assertTrue(not data)
00665 self.close()
00666
00667
00668 def test_http10(self):
00669 self.http_version = b'HTTP/1.0'
00670 self.connect()
00671 self.stream.write(b'GET / HTTP/1.0\r\n\r\n')
00672 self.read_response()
00673 self.stream.read_until_close(callback=self.stop)
00674 data = self.wait()
00675 self.assertTrue(not data)
00676 self.assertTrue('Connection' not in self.headers)
00677 self.close()
00678
00679 def test_http10_keepalive(self):
00680 self.http_version = b'HTTP/1.0'
00681 self.connect()
00682 self.stream.write(b'GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n')
00683 self.read_response()
00684 self.assertEqual(self.headers['Connection'], 'Keep-Alive')
00685 self.stream.write(b'GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n')
00686 self.read_response()
00687 self.assertEqual(self.headers['Connection'], 'Keep-Alive')
00688 self.close()
00689
00690 def test_pipelined_requests(self):
00691 self.connect()
00692 self.stream.write(b'GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\n\r\n')
00693 self.read_response()
00694 self.read_response()
00695 self.close()
00696
00697 def test_pipelined_cancel(self):
00698 self.connect()
00699 self.stream.write(b'GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\n\r\n')
00700
00701 self.read_response()
00702 self.close()
00703
00704 def test_cancel_during_download(self):
00705 self.connect()
00706 self.stream.write(b'GET /large HTTP/1.1\r\n\r\n')
00707 self.read_headers()
00708 self.stream.read_bytes(1024, self.stop)
00709 self.wait()
00710 self.close()
00711
00712 def test_finish_while_closed(self):
00713 self.connect()
00714 self.stream.write(b'GET /finish_on_close HTTP/1.1\r\n\r\n')
00715 self.read_headers()
00716 self.close()
00717
00718
00719 class GzipBaseTest(object):
00720 def get_app(self):
00721 return Application([('/', EchoHandler)])
00722
00723 def post_gzip(self, body):
00724 bytesio = BytesIO()
00725 gzip_file = gzip.GzipFile(mode='w', fileobj=bytesio)
00726 gzip_file.write(utf8(body))
00727 gzip_file.close()
00728 compressed_body = bytesio.getvalue()
00729 return self.fetch('/', method='POST', body=compressed_body,
00730 headers={'Content-Encoding': 'gzip'})
00731
00732 def test_uncompressed(self):
00733 response = self.fetch('/', method='POST', body='foo=bar')
00734 self.assertEquals(json_decode(response.body), {u('foo'): [u('bar')]})
00735
00736
00737 class GzipTest(GzipBaseTest, AsyncHTTPTestCase):
00738 def get_httpserver_options(self):
00739 return dict(decompress_request=True)
00740
00741 def test_gzip(self):
00742 response = self.post_gzip('foo=bar')
00743 self.assertEquals(json_decode(response.body), {u('foo'): [u('bar')]})
00744
00745
00746 class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):
00747 def test_gzip_unsupported(self):
00748
00749
00750
00751 with ExpectLog(gen_log, "Unsupported Content-Encoding"):
00752 response = self.post_gzip('foo=bar')
00753 self.assertEquals(json_decode(response.body), {})
00754
00755
00756 class StreamingChunkSizeTest(AsyncHTTPTestCase):
00757
00758 BODY = b'01234567890123456789012345678901234567890123456789'
00759 CHUNK_SIZE = 16
00760
00761 def get_http_client(self):
00762
00763
00764 return SimpleAsyncHTTPClient(io_loop=self.io_loop)
00765
00766 def get_httpserver_options(self):
00767 return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)
00768
00769 class MessageDelegate(HTTPMessageDelegate):
00770 def __init__(self, connection):
00771 self.connection = connection
00772
00773 def headers_received(self, start_line, headers):
00774 self.chunk_lengths = []
00775
00776 def data_received(self, chunk):
00777 self.chunk_lengths.append(len(chunk))
00778
00779 def finish(self):
00780 response_body = utf8(json_encode(self.chunk_lengths))
00781 self.connection.write_headers(
00782 ResponseStartLine('HTTP/1.1', 200, 'OK'),
00783 HTTPHeaders({'Content-Length': str(len(response_body))}))
00784 self.connection.write(response_body)
00785 self.connection.finish()
00786
00787 def get_app(self):
00788 class App(HTTPServerConnectionDelegate):
00789 def start_request(self, connection):
00790 return StreamingChunkSizeTest.MessageDelegate(connection)
00791 return App()
00792
00793 def fetch_chunk_sizes(self, **kwargs):
00794 response = self.fetch('/', method='POST', **kwargs)
00795 response.rethrow()
00796 chunks = json_decode(response.body)
00797 self.assertEqual(len(self.BODY), sum(chunks))
00798 for chunk_size in chunks:
00799 self.assertLessEqual(chunk_size, self.CHUNK_SIZE,
00800 'oversized chunk: ' + str(chunks))
00801 self.assertGreater(chunk_size, 0,
00802 'empty chunk: ' + str(chunks))
00803 return chunks
00804
00805 def compress(self, body):
00806 bytesio = BytesIO()
00807 gzfile = gzip.GzipFile(mode='w', fileobj=bytesio)
00808 gzfile.write(body)
00809 gzfile.close()
00810 compressed = bytesio.getvalue()
00811 if len(compressed) >= len(body):
00812 raise Exception("body did not shrink when compressed")
00813 return compressed
00814
00815 def test_regular_body(self):
00816 chunks = self.fetch_chunk_sizes(body=self.BODY)
00817
00818 self.assertEqual([16, 16, 16, 2], chunks)
00819
00820 def test_compressed_body(self):
00821 self.fetch_chunk_sizes(body=self.compress(self.BODY),
00822 headers={'Content-Encoding': 'gzip'})
00823
00824
00825
00826 def test_chunked_body(self):
00827 def body_producer(write):
00828 write(self.BODY[:20])
00829 write(self.BODY[20:])
00830 chunks = self.fetch_chunk_sizes(body_producer=body_producer)
00831
00832 self.assertEqual([16, 4, 16, 14], chunks)
00833
00834 def test_chunked_compressed(self):
00835 compressed = self.compress(self.BODY)
00836 self.assertGreater(len(compressed), 20)
00837 def body_producer(write):
00838 write(compressed[:20])
00839 write(compressed[20:])
00840 self.fetch_chunk_sizes(body_producer=body_producer,
00841 headers={'Content-Encoding': 'gzip'})
00842
00843
00844 class MaxHeaderSizeTest(AsyncHTTPTestCase):
00845 def get_app(self):
00846 return Application([('/', HelloWorldRequestHandler)])
00847
00848 def get_httpserver_options(self):
00849 return dict(max_header_size=1024)
00850
00851 def test_small_headers(self):
00852 response = self.fetch("/", headers={'X-Filler': 'a' * 100})
00853 response.rethrow()
00854 self.assertEqual(response.body, b"Hello world")
00855
00856 def test_large_headers(self):
00857 with ExpectLog(gen_log, "Unsatisfiable read"):
00858 response = self.fetch("/", headers={'X-Filler': 'a' * 1000})
00859 self.assertEqual(response.code, 599)
00860
00861
00862 @skipOnTravis
00863 class IdleTimeoutTest(AsyncHTTPTestCase):
00864 def get_app(self):
00865 return Application([('/', HelloWorldRequestHandler)])
00866
00867 def get_httpserver_options(self):
00868 return dict(idle_connection_timeout=0.1)
00869
00870 def setUp(self):
00871 super(IdleTimeoutTest, self).setUp()
00872 self.streams = []
00873
00874 def tearDown(self):
00875 super(IdleTimeoutTest, self).tearDown()
00876 for stream in self.streams:
00877 stream.close()
00878
00879 def connect(self):
00880 stream = IOStream(socket.socket())
00881 stream.connect(('localhost', self.get_http_port()), self.stop)
00882 self.wait()
00883 self.streams.append(stream)
00884 return stream
00885
00886 def test_unused_connection(self):
00887 stream = self.connect()
00888 stream.set_close_callback(self.stop)
00889 self.wait()
00890
00891 def test_idle_after_use(self):
00892 stream = self.connect()
00893 stream.set_close_callback(lambda: self.stop("closed"))
00894
00895
00896 for i in range(2):
00897 stream.write(b"GET / HTTP/1.1\r\n\r\n")
00898 stream.read_until(b"\r\n\r\n", self.stop)
00899 self.wait()
00900 stream.read_bytes(11, self.stop)
00901 data = self.wait()
00902 self.assertEqual(data, b"Hello world")
00903
00904
00905 data = self.wait()
00906 self.assertEqual(data, "closed")
00907
00908
00909 class BodyLimitsTest(AsyncHTTPTestCase):
00910 def get_app(self):
00911 class BufferedHandler(RequestHandler):
00912 def put(self):
00913 self.write(str(len(self.request.body)))
00914
00915 @stream_request_body
00916 class StreamingHandler(RequestHandler):
00917 def initialize(self):
00918 self.bytes_read = 0
00919
00920 def prepare(self):
00921 if 'expected_size' in self.request.arguments:
00922 self.request.connection.set_max_body_size(
00923 int(self.get_argument('expected_size')))
00924 if 'body_timeout' in self.request.arguments:
00925 self.request.connection.set_body_timeout(
00926 float(self.get_argument('body_timeout')))
00927
00928 def data_received(self, data):
00929 self.bytes_read += len(data)
00930
00931 def put(self):
00932 self.write(str(self.bytes_read))
00933
00934 return Application([('/buffered', BufferedHandler),
00935 ('/streaming', StreamingHandler)])
00936
00937 def get_httpserver_options(self):
00938 return dict(body_timeout=3600, max_body_size=4096)
00939
00940 def get_http_client(self):
00941
00942
00943 return SimpleAsyncHTTPClient(io_loop=self.io_loop)
00944
00945 def test_small_body(self):
00946 response = self.fetch('/buffered', method='PUT', body=b'a' * 4096)
00947 self.assertEqual(response.body, b'4096')
00948 response = self.fetch('/streaming', method='PUT', body=b'a' * 4096)
00949 self.assertEqual(response.body, b'4096')
00950
00951 def test_large_body_buffered(self):
00952 with ExpectLog(gen_log, '.*Content-Length too long'):
00953 response = self.fetch('/buffered', method='PUT', body=b'a' * 10240)
00954 self.assertEqual(response.code, 599)
00955
00956 def test_large_body_buffered_chunked(self):
00957 with ExpectLog(gen_log, '.*chunked body too large'):
00958 response = self.fetch('/buffered', method='PUT',
00959 body_producer=lambda write: write(b'a' * 10240))
00960 self.assertEqual(response.code, 599)
00961
00962 def test_large_body_streaming(self):
00963 with ExpectLog(gen_log, '.*Content-Length too long'):
00964 response = self.fetch('/streaming', method='PUT', body=b'a' * 10240)
00965 self.assertEqual(response.code, 599)
00966
00967 def test_large_body_streaming_chunked(self):
00968 with ExpectLog(gen_log, '.*chunked body too large'):
00969 response = self.fetch('/streaming', method='PUT',
00970 body_producer=lambda write: write(b'a' * 10240))
00971 self.assertEqual(response.code, 599)
00972
00973 def test_large_body_streaming_override(self):
00974 response = self.fetch('/streaming?expected_size=10240', method='PUT',
00975 body=b'a' * 10240)
00976 self.assertEqual(response.body, b'10240')
00977
00978 def test_large_body_streaming_chunked_override(self):
00979 response = self.fetch('/streaming?expected_size=10240', method='PUT',
00980 body_producer=lambda write: write(b'a' * 10240))
00981 self.assertEqual(response.body, b'10240')
00982
00983 @gen_test
00984 def test_timeout(self):
00985 stream = IOStream(socket.socket())
00986 try:
00987 yield stream.connect(('127.0.0.1', self.get_http_port()))
00988
00989
00990 stream.write(b'PUT /streaming?body_timeout=0.1 HTTP/1.0\r\n'
00991 b'Content-Length: 42\r\n\r\n')
00992 with ExpectLog(gen_log, 'Timeout reading body'):
00993 response = yield stream.read_until_close()
00994 self.assertEqual(response, b'')
00995 finally:
00996 stream.close()
00997
00998 @gen_test
00999 def test_body_size_override_reset(self):
01000
01001 stream = IOStream(socket.socket())
01002 try:
01003 yield stream.connect(('127.0.0.1', self.get_http_port()))
01004
01005 stream.write(b'PUT /streaming?expected_size=10240 HTTP/1.1\r\n'
01006 b'Content-Length: 10240\r\n\r\n')
01007 stream.write(b'a' * 10240)
01008 headers, response = yield gen.Task(read_stream_body, stream)
01009 self.assertEqual(response, b'10240')
01010
01011 stream.write(b'PUT /streaming HTTP/1.1\r\n'
01012 b'Content-Length: 10240\r\n\r\n')
01013 with ExpectLog(gen_log, '.*Content-Length too long'):
01014 data = yield stream.read_until_close()
01015 self.assertEqual(data, b'')
01016 finally:
01017 stream.close()
01018
01019
01020 class LegacyInterfaceTest(AsyncHTTPTestCase):
01021 def get_app(self):
01022
01023
01024
01025 def handle_request(request):
01026 message = b"Hello world"
01027 request.write(utf8("HTTP/1.1 200 OK\r\n"
01028 "Content-Length: %d\r\n\r\n" % len(message)))
01029 request.write(message)
01030 request.finish()
01031 return handle_request
01032
01033 def test_legacy_interface(self):
01034 response = self.fetch('/')
01035 self.assertEqual(response.body, b"Hello world")