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)