00001 from __future__ import absolute_import, division, with_statement
00002
00003 import collections
00004 from contextlib import closing
00005 import gzip
00006 import logging
00007 import re
00008 import socket
00009
00010 from tornado.httpclient import AsyncHTTPClient
00011 from tornado.httputil import HTTPHeaders
00012 from tornado.ioloop import IOLoop
00013 from tornado.simple_httpclient import SimpleAsyncHTTPClient, _DEFAULT_CA_CERTS
00014 from tornado.test.httpclient_test import HTTPClientCommonTestCase, ChunkHandler, CountdownHandler, HelloWorldHandler
00015 from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, LogTrapTestCase
00016 from tornado.util import b
00017 from tornado.web import RequestHandler, Application, asynchronous, url
00018
00019
00020 class SimpleHTTPClientCommonTestCase(HTTPClientCommonTestCase):
00021 def get_http_client(self):
00022 client = SimpleAsyncHTTPClient(io_loop=self.io_loop,
00023 force_instance=True)
00024 self.assertTrue(isinstance(client, SimpleAsyncHTTPClient))
00025 return client
00026
00027
00028
00029 del HTTPClientCommonTestCase
00030
00031
00032 class TriggerHandler(RequestHandler):
00033 def initialize(self, queue, wake_callback):
00034 self.queue = queue
00035 self.wake_callback = wake_callback
00036
00037 @asynchronous
00038 def get(self):
00039 logging.info("queuing trigger")
00040 self.queue.append(self.finish)
00041 if self.get_argument("wake", "true") == "true":
00042 self.wake_callback()
00043
00044
00045 class HangHandler(RequestHandler):
00046 @asynchronous
00047 def get(self):
00048 pass
00049
00050
00051 class ContentLengthHandler(RequestHandler):
00052 def get(self):
00053 self.set_header("Content-Length", self.get_argument("value"))
00054 self.write("ok")
00055
00056
00057 class HeadHandler(RequestHandler):
00058 def head(self):
00059 self.set_header("Content-Length", "7")
00060
00061
00062 class OptionsHandler(RequestHandler):
00063 def options(self):
00064 self.set_header("Access-Control-Allow-Origin", "*")
00065 self.write("ok")
00066
00067
00068 class NoContentHandler(RequestHandler):
00069 def get(self):
00070 if self.get_argument("error", None):
00071 self.set_header("Content-Length", "7")
00072 self.set_status(204)
00073
00074
00075 class SeeOther303PostHandler(RequestHandler):
00076 def post(self):
00077 assert self.request.body == b("blah")
00078 self.set_header("Location", "/303_get")
00079 self.set_status(303)
00080
00081
00082 class SeeOther303GetHandler(RequestHandler):
00083 def get(self):
00084 assert not self.request.body
00085 self.write("ok")
00086
00087
00088 class HostEchoHandler(RequestHandler):
00089 def get(self):
00090 self.write(self.request.headers["Host"])
00091
00092
00093 class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase):
00094 def setUp(self):
00095 super(SimpleHTTPClientTestCase, self).setUp()
00096 self.http_client = SimpleAsyncHTTPClient(self.io_loop)
00097
00098 def get_app(self):
00099
00100 self.triggers = collections.deque()
00101 return Application([
00102 url("/trigger", TriggerHandler, dict(queue=self.triggers,
00103 wake_callback=self.stop)),
00104 url("/chunk", ChunkHandler),
00105 url("/countdown/([0-9]+)", CountdownHandler, name="countdown"),
00106 url("/hang", HangHandler),
00107 url("/hello", HelloWorldHandler),
00108 url("/content_length", ContentLengthHandler),
00109 url("/head", HeadHandler),
00110 url("/options", OptionsHandler),
00111 url("/no_content", NoContentHandler),
00112 url("/303_post", SeeOther303PostHandler),
00113 url("/303_get", SeeOther303GetHandler),
00114 url("/host_echo", HostEchoHandler),
00115 ], gzip=True)
00116
00117 def test_singleton(self):
00118
00119 self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is
00120 SimpleAsyncHTTPClient(self.io_loop))
00121
00122 self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is not
00123 SimpleAsyncHTTPClient(self.io_loop,
00124 force_instance=True))
00125
00126 io_loop2 = IOLoop()
00127 self.assertTrue(SimpleAsyncHTTPClient(self.io_loop) is not
00128 SimpleAsyncHTTPClient(io_loop2))
00129
00130 def test_connection_limit(self):
00131 client = SimpleAsyncHTTPClient(self.io_loop, max_clients=2,
00132 force_instance=True)
00133 self.assertEqual(client.max_clients, 2)
00134 seen = []
00135
00136
00137 for i in range(4):
00138 client.fetch(self.get_url("/trigger"),
00139 lambda response, i=i: (seen.append(i), self.stop()))
00140 self.wait(condition=lambda: len(self.triggers) == 2)
00141 self.assertEqual(len(client.queue), 2)
00142
00143
00144 self.triggers.popleft()()
00145 self.triggers.popleft()()
00146 self.wait(condition=lambda: (len(self.triggers) == 2 and
00147 len(seen) == 2))
00148 self.assertEqual(set(seen), set([0, 1]))
00149 self.assertEqual(len(client.queue), 0)
00150
00151
00152 self.triggers.popleft()()
00153 self.triggers.popleft()()
00154 self.wait(condition=lambda: len(seen) == 4)
00155 self.assertEqual(set(seen), set([0, 1, 2, 3]))
00156 self.assertEqual(len(self.triggers), 0)
00157
00158 def test_redirect_connection_limit(self):
00159
00160 client = SimpleAsyncHTTPClient(self.io_loop, max_clients=1,
00161 force_instance=True)
00162 client.fetch(self.get_url('/countdown/3'), self.stop,
00163 max_redirects=3)
00164 response = self.wait()
00165 response.rethrow()
00166
00167 def test_default_certificates_exist(self):
00168 open(_DEFAULT_CA_CERTS).close()
00169
00170 def test_gzip(self):
00171
00172
00173
00174
00175 response = self.fetch("/chunk", use_gzip=False,
00176 headers={"Accept-Encoding": "gzip"})
00177 self.assertEqual(response.headers["Content-Encoding"], "gzip")
00178 self.assertNotEqual(response.body, b("asdfqwer"))
00179
00180 self.assertEqual(len(response.body), 34)
00181 f = gzip.GzipFile(mode="r", fileobj=response.buffer)
00182 self.assertEqual(f.read(), b("asdfqwer"))
00183
00184 def test_max_redirects(self):
00185 response = self.fetch("/countdown/5", max_redirects=3)
00186 self.assertEqual(302, response.code)
00187
00188
00189 self.assertTrue(response.request.url.endswith("/countdown/5"))
00190 self.assertTrue(response.effective_url.endswith("/countdown/2"))
00191 self.assertTrue(response.headers["Location"].endswith("/countdown/1"))
00192
00193 def test_header_reuse(self):
00194
00195
00196 headers = HTTPHeaders({'User-Agent': 'Foo'})
00197 self.fetch("/hello", headers=headers)
00198 self.assertEqual(list(headers.get_all()), [('User-Agent', 'Foo')])
00199
00200 def test_303_redirect(self):
00201 response = self.fetch("/303_post", method="POST", body="blah")
00202 self.assertEqual(200, response.code)
00203 self.assertTrue(response.request.url.endswith("/303_post"))
00204 self.assertTrue(response.effective_url.endswith("/303_get"))
00205
00206 self.assertEqual("POST", response.request.method)
00207
00208 def test_request_timeout(self):
00209 response = self.fetch('/trigger?wake=false', request_timeout=0.1)
00210 self.assertEqual(response.code, 599)
00211 self.assertTrue(0.099 < response.request_time < 0.11, response.request_time)
00212 self.assertEqual(str(response.error), "HTTP 599: Timeout")
00213
00214 self.triggers.popleft()()
00215
00216 def test_ipv6(self):
00217 if not socket.has_ipv6:
00218
00219 return
00220 try:
00221 self.http_server.listen(self.get_http_port(), address='::1')
00222 except socket.gaierror, e:
00223 if e.args[0] == socket.EAI_ADDRFAMILY:
00224
00225
00226 return
00227 raise
00228 url = self.get_url("/hello").replace("localhost", "[::1]")
00229
00230
00231 self.http_client.fetch(url, self.stop)
00232 response = self.wait()
00233 self.assertEqual(response.code, 599)
00234
00235 self.http_client.fetch(url, self.stop, allow_ipv6=True)
00236 response = self.wait()
00237 self.assertEqual(response.body, b("Hello world!"))
00238
00239 def test_multiple_content_length_accepted(self):
00240 response = self.fetch("/content_length?value=2,2")
00241 self.assertEqual(response.body, b("ok"))
00242 response = self.fetch("/content_length?value=2,%202,2")
00243 self.assertEqual(response.body, b("ok"))
00244
00245 response = self.fetch("/content_length?value=2,4")
00246 self.assertEqual(response.code, 599)
00247 response = self.fetch("/content_length?value=2,%202,3")
00248 self.assertEqual(response.code, 599)
00249
00250 def test_head_request(self):
00251 response = self.fetch("/head", method="HEAD")
00252 self.assertEqual(response.code, 200)
00253 self.assertEqual(response.headers["content-length"], "7")
00254 self.assertFalse(response.body)
00255
00256 def test_options_request(self):
00257 response = self.fetch("/options", method="OPTIONS")
00258 self.assertEqual(response.code, 200)
00259 self.assertEqual(response.headers["content-length"], "2")
00260 self.assertEqual(response.headers["access-control-allow-origin"], "*")
00261 self.assertEqual(response.body, b("ok"))
00262
00263 def test_no_content(self):
00264 response = self.fetch("/no_content")
00265 self.assertEqual(response.code, 204)
00266
00267
00268 self.assertEqual(response.headers["Content-length"], "0")
00269
00270
00271 response = self.fetch("/no_content?error=1")
00272 self.assertEqual(response.code, 599)
00273
00274 def test_host_header(self):
00275 host_re = re.compile(b("^localhost:[0-9]+$"))
00276 response = self.fetch("/host_echo")
00277 self.assertTrue(host_re.match(response.body))
00278
00279 url = self.get_url("/host_echo").replace("http://", "http://me:secret@")
00280 self.http_client.fetch(url, self.stop)
00281 response = self.wait()
00282 self.assertTrue(host_re.match(response.body), response.body)
00283
00284
00285 class CreateAsyncHTTPClientTestCase(AsyncTestCase, LogTrapTestCase):
00286 def setUp(self):
00287 super(CreateAsyncHTTPClientTestCase, self).setUp()
00288 self.saved = AsyncHTTPClient._save_configuration()
00289
00290 def tearDown(self):
00291 AsyncHTTPClient._restore_configuration(self.saved)
00292 super(CreateAsyncHTTPClientTestCase, self).tearDown()
00293
00294 def test_max_clients(self):
00295
00296
00297 AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
00298 with closing(AsyncHTTPClient(
00299 self.io_loop, force_instance=True)) as client:
00300 self.assertEqual(client.max_clients, 10)
00301 with closing(AsyncHTTPClient(
00302 self.io_loop, 11, force_instance=True)) as client:
00303 self.assertEqual(client.max_clients, 11)
00304 with closing(AsyncHTTPClient(
00305 self.io_loop, max_clients=11, force_instance=True)) as client:
00306 self.assertEqual(client.max_clients, 11)
00307
00308
00309
00310 AsyncHTTPClient.configure(SimpleAsyncHTTPClient, max_clients=12)
00311 with closing(AsyncHTTPClient(
00312 self.io_loop, force_instance=True)) as client:
00313 self.assertEqual(client.max_clients, 12)
00314 with closing(AsyncHTTPClient(
00315 self.io_loop, max_clients=13, force_instance=True)) as client:
00316 self.assertEqual(client.max_clients, 13)
00317 with closing(AsyncHTTPClient(
00318 self.io_loop, max_clients=14, force_instance=True)) as client:
00319 self.assertEqual(client.max_clients, 14)