diff --git a/Makefile b/Makefile index 21eb37e0748..1f98de90345 100644 --- a/Makefile +++ b/Makefile @@ -48,8 +48,11 @@ cov-dev: .develop @echo "open file://`pwd`/coverage/index.html" cov-dev-full: .develop + @echo "Run without extensions" @AIOHTTP_NO_EXTENSIONS=1 py.test --cov=aiohttp tests + @echo "Run in debug mode" @PYTHONASYNCIODEBUG=1 py.test --cov=aiohttp --cov-append tests + @echo "Regular run" @py.test --cov=aiohttp --cov-report=term --cov-report=html --cov-append tests @echo "open file://`pwd`/coverage/index.html" diff --git a/aiohttp/pytest_plugin.py b/aiohttp/pytest_plugin.py index d0c23f32c70..6a2d81f51fb 100644 --- a/aiohttp/pytest_plugin.py +++ b/aiohttp/pytest_plugin.py @@ -6,7 +6,9 @@ from aiohttp.web import Application from .test_utils import unused_port as _unused_port -from .test_utils import (TestClient, TestServer, loop_context, setup_test_loop, +from .test_utils import (RawTestServer, TestClient, + TestServer, + loop_context, setup_test_loop, teardown_test_loop) @@ -81,6 +83,27 @@ def finalize(): loop.run_until_complete(finalize()) +@pytest.yield_fixture +def raw_test_server(loop): + servers = [] + + @asyncio.coroutine + def go(handler, **kwargs): + server = RawTestServer(handler, loop=loop) + yield from server.start_server(**kwargs) + servers.append(server) + return server + + yield go + + @asyncio.coroutine + def finalize(): + while servers: + yield from servers.pop().close() + + loop.run_until_complete(finalize()) + + @pytest.yield_fixture def test_client(loop): clients = [] @@ -97,6 +120,11 @@ def go(__param, *args, **kwargs): assert __param.app.loop is loop, \ "TestServer is attached to other event loop" client = TestClient(__param, **kwargs) + elif isinstance(__param, RawTestServer): + assert not args, "args should be empty" + assert __param._loop is loop, \ + "TestServer is attached to other event loop" + client = TestClient(__param, **kwargs) else: __param = __param(loop, *args, **kwargs) client = TestClient(__param) diff --git a/aiohttp/test_utils.py b/aiohttp/test_utils.py index 44618e6a2a2..c4061ed1386 100644 --- a/aiohttp/test_utils.py +++ b/aiohttp/test_utils.py @@ -7,6 +7,7 @@ import socket import sys import unittest +from abc import ABC, abstractmethod from unittest import mock from multidict import CIMultiDict @@ -19,7 +20,7 @@ from .helpers import sentinel from .protocol import HttpVersion, RawRequestMessage from .signals import Signal -from .web import Application, Request, UrlMappingMatchInfo +from .web import Application, Request, UrlMappingMatchInfo, WebServer PY_35 = sys.version_info >= (3, 5) @@ -39,10 +40,8 @@ def unused_port(): return s.getsockname()[1] -class TestServer: - def __init__(self, app, *, scheme=sentinel, host='127.0.0.1'): - self.app = app - self._loop = app.loop +class BaseTestServer(ABC): + def __init__(self, *, scheme=sentinel, host='127.0.0.1'): self.port = None self.server = None self.handler = None @@ -66,17 +65,30 @@ def start_server(self, **kwargs): self._root = URL('{}://{}:{}'.format(self.scheme, self.host, self.port)) - yield from self.app.startup() - self.handler = self.app.make_handler(**kwargs) - self.server = yield from self._loop.create_server(self.handler, + + handler = yield from self._make_factory(**kwargs) + self.server = yield from self._loop.create_server(handler, self.host, self.port, ssl=self._ssl) + @abstractmethod # pragma: no cover + @asyncio.coroutine + def _make_factory(self): + pass + def make_url(self, path): assert path.startswith('/') return URL(str(self._root) + path) + @property + def started(self): + return self.server is not None + + @property + def closed(self): + return self._closed + @asyncio.coroutine def close(self): """Close all fixtures created by the test client. @@ -90,16 +102,19 @@ def close(self): exit when used as a context manager. """ - if self.server is not None and not self._closed: + if self.started and not self.closed: self.server.close() yield from self.server.wait_closed() - yield from self.app.shutdown() - yield from self.handler.finish_connections() - yield from self.app.cleanup() self._root = None self.port = None + yield from self._close_hook() self._closed = True + @abstractmethod + @asyncio.coroutine + def _close_hook(self): + pass # pragma: no cover + def __enter__(self): self._loop.run_until_complete(self.start_server()) return self @@ -118,6 +133,43 @@ def __aexit__(self, exc_type, exc_value, traceback): yield from self.close() +class TestServer(BaseTestServer): + def __init__(self, app, *, scheme=sentinel, host='127.0.0.1'): + self.app = app + self._loop = app.loop + super().__init__(scheme=scheme, host=host) + + @asyncio.coroutine + def _make_factory(self, **kwargs): + yield from self.app.startup() + self.handler = self.app.make_handler(**kwargs) + return self.handler + + @asyncio.coroutine + def _close_hook(self): + yield from self.app.shutdown() + yield from self.handler.shutdown() + yield from self.app.cleanup() + + +class RawTestServer(BaseTestServer): + def __init__(self, handler, + *, loop=None, scheme=sentinel, host='127.0.0.1'): + if loop is None: + loop = asyncio.get_event_loop() + self._loop = loop + self._handler = handler + super().__init__(scheme=scheme, host=host) + + @asyncio.coroutine + def _make_factory(self, **kwargs): + return WebServer(self._handler, loop=self._loop) + + @asyncio.coroutine + def _close_hook(self): + return + + class TestClient: """ A test client implementation, for a aiohttp.web.Application. @@ -136,7 +188,7 @@ class TestClient: def __init__(self, app_or_server, *, scheme=sentinel, host=sentinel, cookie_jar=None, **kwargs): - if isinstance(app_or_server, TestServer): + if isinstance(app_or_server, BaseTestServer): if scheme is not sentinel or host is not sentinel: raise ValueError("scheme and host are mutable exclusive " "with TestServer parameter") @@ -149,7 +201,7 @@ def __init__(self, app_or_server, *, scheme=sentinel, host=sentinel, else: raise TypeError("app_or_server should be either web.Application " "or TestServer instance") - self._loop = self._server.app.loop + self._loop = self._server._loop if cookie_jar is None: cookie_jar = aiohttp.CookieJar(unsafe=True, loop=self._loop) diff --git a/aiohttp/web.py b/aiohttp/web.py index f80ba0c38eb..135b1220f51 100644 --- a/aiohttp/web.py +++ b/aiohttp/web.py @@ -7,15 +7,16 @@ from yarl import URL -from . import hdrs, web_exceptions, web_reqrep, web_urldispatcher, web_ws +from . import (hdrs, web_exceptions, web_reqrep, web_server, web_urldispatcher, + web_ws) from .abc import AbstractMatchInfo, AbstractRouter -from .helpers import FrozenList, TimeService, sentinel +from .helpers import FrozenList, sentinel from .log import access_logger, web_logger from .protocol import HttpVersion # noqa -from .server import ServerHttpProtocol from .signals import PostSignal, PreSignal, Signal from .web_exceptions import * # noqa from .web_reqrep import * # noqa +from .web_server import WebServer from .web_urldispatcher import * # noqa from .web_ws import * # noqa @@ -23,168 +24,27 @@ web_exceptions.__all__ + web_urldispatcher.__all__ + web_ws.__all__ + - ('Application', 'RequestHandler', - 'RequestHandlerFactory', 'HttpVersion', - 'MsgType')) - - -class RequestHandler(ServerHttpProtocol): - - _request = None - - def __init__(self, manager, app, router, time_service, *, - secure_proxy_ssl_header=None, **kwargs): - super().__init__(**kwargs) - - self._manager = manager - self._app = app - self._router = router - self._secure_proxy_ssl_header = secure_proxy_ssl_header - self._time_service = time_service - - def __repr__(self): - if self._request is None: - meth = 'none' - path = 'none' - else: - meth = self._request.method - path = self._request.rel_url.raw_path - return "<{} {}:{} {}>".format( - self.__class__.__name__, meth, path, - 'connected' if self.transport is not None else 'disconnected') - - def connection_made(self, transport): - super().connection_made(transport) - - self._manager.connection_made(self, transport) - - def connection_lost(self, exc): - self._manager.connection_lost(self, exc) - - super().connection_lost(exc) - - @asyncio.coroutine - def handle_request(self, message, payload): - self._manager._requests_count += 1 - if self.access_log: - now = self._loop.time() - - request = web_reqrep.Request( - message, payload, - self.transport, self.reader, self.writer, - self._time_service, - secure_proxy_ssl_header=self._secure_proxy_ssl_header) - self._request = request - try: - match_info = yield from self._router.resolve(request) - assert isinstance(match_info, AbstractMatchInfo), match_info - match_info.add_app(self._app) - match_info.freeze() - - resp = None - request._match_info = match_info - expect = request.headers.get(hdrs.EXPECT) - if expect: - resp = ( - yield from match_info.expect_handler(request)) - - if resp is None: - handler = match_info.handler - for app in match_info.apps: - for factory in reversed(app.middlewares): - handler = yield from factory(app, handler) - resp = yield from handler(request) - - assert isinstance(resp, web_reqrep.StreamResponse), \ - ("Handler {!r} should return response instance, " - "got {!r} [middlewares {!r}]").format( - match_info.handler, type(resp), self._middlewares) - except web_exceptions.HTTPException as exc: - resp = exc - - resp_msg = yield from resp.prepare(request) - yield from resp.write_eof() - - # notify server about keep-alive - self.keep_alive(resp.keep_alive) - - # Restore default state. - # Should be no-op if server code didn't touch these attributes. - self.writer.set_tcp_cork(False) - self.writer.set_tcp_nodelay(True) - - # log access - if self.access_log: - self.log_access(message, None, resp_msg, self._loop.time() - now) - - # for repr - self._request = None - - -class RequestHandlerFactory: - - def __init__(self, app, router, *, - handler=RequestHandler, loop=None, - secure_proxy_ssl_header=None, **kwargs): - self._app = app - self._router = router - self._handler = handler - self._loop = loop - self._connections = {} - self._secure_proxy_ssl_header = secure_proxy_ssl_header - self._kwargs = kwargs - self._kwargs.setdefault('logger', app.logger) - self._requests_count = 0 - self._time_service = TimeService(self._loop) - - @property - def requests_count(self): - """Number of processed requests.""" - return self._requests_count - - @property - def secure_proxy_ssl_header(self): - return self._secure_proxy_ssl_header - - @property - def connections(self): - return list(self._connections.keys()) - - def connection_made(self, handler, transport): - self._connections[handler] = transport - - def connection_lost(self, handler, exc=None): - if handler in self._connections: - del self._connections[handler] - - @asyncio.coroutine - def finish_connections(self, timeout=None): - coros = [conn.shutdown(timeout) for conn in self._connections] - yield from asyncio.gather(*coros, loop=self._loop) - self._connections.clear() - self._time_service.stop() - - def __call__(self): - return self._handler( - self, self._app, self._router, self._time_service, loop=self._loop, - secure_proxy_ssl_header=self._secure_proxy_ssl_header, - **self._kwargs) + web_server.__all__ + + ('Application', 'HttpVersion', 'MsgType')) class Application(MutableMapping): def __init__(self, *, logger=web_logger, loop=None, - router=None, handler_factory=RequestHandlerFactory, - middlewares=(), debug=False): + router=None, + middlewares=(), debug=...): if loop is None: loop = asyncio.get_event_loop() if router is None: router = web_urldispatcher.UrlDispatcher(self) assert isinstance(router, AbstractRouter), router + if debug is ...: + debug = loop.get_debug() + self._debug = debug self._router = router - self._handler_factory = handler_factory + self._secure_proxy_ssl_header = None self._loop = loop self.logger = logger @@ -299,7 +159,7 @@ def loop(self): def middlewares(self): return self._middlewares - def make_handler(self, **kwargs): + def make_handler(self, *, secure_proxy_ssl_header=None, **kwargs): debug = kwargs.pop('debug', sentinel) if debug is not sentinel: warnings.warn( @@ -316,8 +176,11 @@ def make_handler(self, **kwargs): "web_reference.html#aiohttp.web.Application" ) self.freeze() - return self._handler_factory(self, self.router, debug=self.debug, - loop=self.loop, **kwargs) + self._secure_proxy_ssl_header = secure_proxy_ssl_header + return WebServer(self._handle, + request_factory=self._make_request, + debug=self.debug, loop=self.loop, + **kwargs) @asyncio.coroutine def startup(self): @@ -356,8 +219,41 @@ def register_on_finish(self, func, *args, **kwargs): warnings.warn("Use .on_cleanup.append() instead", DeprecationWarning) self.on_cleanup.append(lambda app: func(app, *args, **kwargs)) - def copy(self): - raise NotImplementedError + def _make_request(self, message, payload, protocol): + return web_reqrep.Request( + message, payload, + protocol.transport, protocol.reader, protocol.writer, + protocol.time_service, + secure_proxy_ssl_header=self._secure_proxy_ssl_header) + + @asyncio.coroutine + def _handle(self, request): + match_info = yield from self._router.resolve(request) + assert isinstance(match_info, AbstractMatchInfo), match_info + match_info.add_app(self) + match_info.freeze() + + resp = None + request._match_info = match_info + expect = request.headers.get(hdrs.EXPECT) + if expect: + resp = ( + yield from match_info.expect_handler(request)) + + if resp is None: + handler = match_info.handler + for app in match_info.apps: + for factory in reversed(app.middlewares): + handler = yield from factory(app, handler) + resp = yield from handler(request) + + assert isinstance(resp, web_reqrep.StreamResponse), \ + ("Handler {!r} should return response instance, " + "got {!r} [middlewares {!r}]").format( + match_info.handler, type(resp), + [middleware for middleware in app.middlewares + for app in match_info.apps]) + return resp def __call__(self): """gunicorn compatibility""" @@ -405,7 +301,7 @@ def run_app(app, *, host='0.0.0.0', port=None, srv.close() loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.shutdown()) - loop.run_until_complete(handler.finish_connections(shutdown_timeout)) + loop.run_until_complete(handler.shutdown(shutdown_timeout)) loop.run_until_complete(app.cleanup()) loop.close() diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py index d5c6a3756a9..f484cc12666 100644 --- a/aiohttp/web_reqrep.py +++ b/aiohttp/web_reqrep.py @@ -22,7 +22,7 @@ from .protocol import HttpVersion10, HttpVersion11 __all__ = ( - 'ContentCoding', 'Request', 'StreamResponse', 'Response', + 'ContentCoding', 'BaseRequest', 'Request', 'StreamResponse', 'Response', 'json_response' ) @@ -44,7 +44,7 @@ class ContentCoding(enum.Enum): ############################################################ -class Request(collections.MutableMapping, HeadersMixin): +class BaseRequest(collections.MutableMapping, HeadersMixin): POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT, hdrs.METH_TRACE, hdrs.METH_DELETE} @@ -59,10 +59,6 @@ def __init__(self, message, payload, transport, reader, writer, self._post = None self._post_files_cache = None - # matchdict, route_name, handler - # or information about traversal lookup - self._match_info = None # initialized after route resolving - self._payload = payload self._read_bytes = None @@ -99,7 +95,7 @@ def clone(self, *, method=sentinel, rel_url=sentinel, message = self._message._replace(**dct) - return Request( + return self.__class__( message, self._payload, self._transport, @@ -284,16 +280,6 @@ def keep_alive(self): else: return not self._message.should_close - @property - def match_info(self): - """Result of route resolving.""" - return self._match_info - - @reify - def app(self): - """Application instance.""" - return self._match_info.apps[-1] - @property def transport(self): """Transport used for request processing.""" @@ -439,6 +425,36 @@ def __repr__(self): return "<{} {} {} >".format(self.__class__.__name__, self.method, ascii_encodable_path) + @asyncio.coroutine + def _prepare_hook(self, response): + return + yield # pragma: no cover + + +class Request(BaseRequest): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # matchdict, route_name, handler + # or information about traversal lookup + self._match_info = None # initialized after route resolving + + @property + def match_info(self): + """Result of route resolving.""" + return self._match_info + + @reify + def app(self): + """Application instance.""" + return self._match_info.apps[-1] + + @asyncio.coroutine + def _prepare_hook(self, response): + for app in self.match_info.apps: + yield from app.on_response_prepare.send(self, response) + ############################################################ # HTTP Response classes @@ -733,8 +749,7 @@ def prepare(self, request): resp_impl = self._start_pre_check(request) if resp_impl is not None: return resp_impl - for app in request.match_info.apps: - yield from app.on_response_prepare.send(request, self) + yield from request._prepare_hook(self) return self._start(request) diff --git a/aiohttp/web_server.py b/aiohttp/web_server.py new file mode 100644 index 00000000000..f26fba957f1 --- /dev/null +++ b/aiohttp/web_server.py @@ -0,0 +1,138 @@ +"""Low level HTTP server.""" + +import asyncio + +from .helpers import TimeService +from .server import ServerHttpProtocol +from .web_exceptions import HTTPException +from .web_reqrep import BaseRequest + +__all__ = ('RequestHandler', 'WebServer') + + +class RequestHandler(ServerHttpProtocol): + _request = None + + def __init__(self, manager, **kwargs): + super().__init__(**kwargs) + self._manager = manager + self._request_factory = manager.request_factory + self._handler = manager.handler + self.time_service = manager.time_service + + def __repr__(self): + if self._request is None: + meth = 'none' + path = 'none' + else: + meth = self._request.method + path = self._request.rel_url.raw_path + return "<{} {}:{} {}>".format( + self.__class__.__name__, meth, path, + 'connected' if self.transport is not None else 'disconnected') + + def connection_made(self, transport): + super().connection_made(transport) + + self._manager.connection_made(self, transport) + + def connection_lost(self, exc): + self._manager.connection_lost(self, exc) + + super().connection_lost(exc) + self._request_factory = None + self._manager = None + self.time_service = None + self._handler = None + + @asyncio.coroutine + def handle_request(self, message, payload): + self._manager._requests_count += 1 + if self.access_log: + now = self._loop.time() + + request = self._request_factory(message, payload, self) + self._request = request + + try: + resp = yield from self._handler(request) + except HTTPException as exc: + resp = exc + + resp_msg = yield from resp.prepare(request) + yield from resp.write_eof() + + # notify server about keep-alive + self.keep_alive(resp.keep_alive) + + # Restore default state. + # Should be no-op if server code didn't touch these attributes. + self.writer.set_tcp_cork(False) + self.writer.set_tcp_nodelay(True) + + # log access + if self.access_log: + self.log_access(message, None, resp_msg, self._loop.time() - now) + + # for repr + self._request = None + + +class WebServer: + + def __init__(self, handler, *, request_factory=None, loop=None, **kwargs): + self._handler = handler + self._request_factory = request_factory or self._make_request + self._loop = loop + self._connections = {} + self._kwargs = kwargs + self._requests_count = 0 + self._time_service = TimeService(self._loop) + + @property + def requests_count(self): + """Number of processed requests.""" + return self._requests_count + + @property + def handler(self): + return self._handler + + @property + def request_factory(self): + return self._request_factory + + @property + def time_service(self): + return self._time_service + + @property + def connections(self): + return list(self._connections.keys()) + + def connection_made(self, handler, transport): + self._connections[handler] = transport + + def connection_lost(self, handler, exc=None): + if handler in self._connections: + del self._connections[handler] + + def _make_request(self, message, payload, protocol): + return BaseRequest( + message, payload, + protocol.transport, protocol.reader, protocol.writer, + protocol.time_service) + + @asyncio.coroutine + def shutdown(self, timeout=None): + coros = [conn.shutdown(timeout) for conn in self._connections] + yield from asyncio.gather(*coros, loop=self._loop) + self._connections.clear() + self._time_service.stop() + + finish_connections = shutdown + + def __call__(self): + return RequestHandler( + self, loop=self._loop, + **self._kwargs) diff --git a/aiohttp/worker.py b/aiohttp/worker.py index 94a9c446050..e782007aeb7 100644 --- a/aiohttp/worker.py +++ b/aiohttp/worker.py @@ -74,7 +74,7 @@ def close(self): # stop alive connections tasks = [ - handler.finish_connections( + handler.shutdown( timeout=self.cfg.graceful_timeout / 100 * 95) for handler in servers.values()] yield from asyncio.gather(*tasks, loop=self.loop) diff --git a/demos/polls/aiohttpdemo_polls/main.py b/demos/polls/aiohttpdemo_polls/main.py index 21a56ffad13..44e297bf22d 100644 --- a/demos/polls/aiohttpdemo_polls/main.py +++ b/demos/polls/aiohttpdemo_polls/main.py @@ -6,7 +6,7 @@ import aiohttp_jinja2 from aiohttp import web -from aiohttpdemo_polls.db import init_pg, close_pg +from aiohttpdemo_polls.db import close_pg, init_pg from aiohttpdemo_polls.middlewares import setup_middlewares from aiohttpdemo_polls.routes import setup_routes from aiohttpdemo_polls.utils import load_config diff --git a/docs/web.rst b/docs/web.rst index 20d91d8847a..28be6859e61 100644 --- a/docs/web.rst +++ b/docs/web.rst @@ -1095,7 +1095,7 @@ Proper finalization procedure has three steps: 2. Fire :meth:`Application.shutdown` event. 3. Close accepted connections from clients by - :meth:`RequestHandlerFactory.finish_connections` call with + :meth:`RequestHandlerFactory.shutdown` call with reasonable small delay. 4. Call registered application finalizers by :meth:`Application.cleanup`. @@ -1116,7 +1116,7 @@ finalizing. It's pretty close to :func:`run_app` utility function:: srv.close() loop.run_until_complete(srv.wait_closed()) loop.run_until_complete(app.shutdown()) - loop.run_until_complete(handler.finish_connections(60.0)) + loop.run_until_complete(handler.shutdown(60.0)) loop.run_until_complete(app.cleanup()) loop.close() diff --git a/docs/web_reference.rst b/docs/web_reference.rst index f5283d4e52b..12bc7355fb5 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -1360,11 +1360,17 @@ RequestHandlerFactory .. versionadded:: 1.0 - .. coroutinemethod:: RequestHandlerFactory.finish_connections(timeout) + .. coroutinemethod:: RequestHandlerFactory.shutdown(timeout) A :ref:`coroutine` that should be called to close all opened connections. + .. coroutinemethod:: RequestHandlerFactory.finish_connections(timeout) + + .. deprecated:: 1.2 + + A deprecated alias for :meth:`shutdown`. + Router ^^^^^^ diff --git a/tests/test_test_utils.py b/tests/test_test_utils.py index 668a3d70506..7da535a3765 100644 --- a/tests/test_test_utils.py +++ b/tests/test_test_utils.py @@ -8,7 +8,7 @@ from aiohttp import web, web_reqrep from aiohttp.test_utils import TestClient as _TestClient from aiohttp.test_utils import TestServer as _TestServer -from aiohttp.test_utils import (AioHTTPTestCase, loop_context, +from aiohttp.test_utils import (AioHTTPTestCase, RawTestServer, loop_context, make_mocked_request, setup_test_loop, teardown_test_loop, unittest_run_loop) @@ -266,3 +266,12 @@ def test_client_host_mutually_exclusive_with_server(loop): def test_client_unsupported_arg(): with pytest.raises(TypeError): _TestClient('string') + + +def test_raw_server_implicit_loop(loop): + @asyncio.coroutine + def handler(request): + pass + asyncio.set_event_loop(loop) + srv = RawTestServer(handler) + assert srv._loop is loop diff --git a/tests/test_web_application.py b/tests/test_web_application.py index 1a74bcf333b..63950d62c98 100644 --- a/tests/test_web_application.py +++ b/tests/test_web_application.py @@ -18,12 +18,6 @@ def test_app_call(loop): assert app is app() -def test_app_copy(loop): - app = web.Application(loop=loop) - with pytest.raises(NotImplementedError): - app.copy() - - def test_app_default_loop(loop): asyncio.set_event_loop(loop) app = web.Application() @@ -34,16 +28,18 @@ def test_app_default_loop(loop): def test_app_make_handler_debug_exc(loop, mocker, debug): app = web.Application(loop=loop, debug=debug) - mocker.spy(app, '_handler_factory') + srv = mocker.patch('aiohttp.web.WebServer') app.make_handler() with pytest.warns(DeprecationWarning) as exc: app.make_handler(debug=debug) assert 'parameter is deprecated' in exc[0].message.args[0] - assert app._handler_factory.call_count == 2 - app._handler_factory.assert_called_with(app, app.router, loop=loop, - debug=debug) + assert srv.call_count == 2 + srv.assert_called_with(app._handle, + request_factory=app._make_request, + loop=loop, + debug=debug) with pytest.raises(ValueError) as exc: app.make_handler(debug=not debug) @@ -175,3 +171,16 @@ def test_app_delitem(loop): assert len(app) == 1 del app['key'] assert len(app) == 0 + + +def test_secure_proxy_ssl_header_default(loop): + app = web.Application(loop=loop) + assert app._secure_proxy_ssl_header is None + + +@asyncio.coroutine +def test_secure_proxy_ssl_header_non_default(loop): + app = web.Application(loop=loop) + hdr = ('X-Forwarded-Proto', 'https') + app.make_handler(secure_proxy_ssl_header=hdr) + assert app._secure_proxy_ssl_header is hdr diff --git a/tests/test_web_request_handler.py b/tests/test_web_request_handler.py index 6c0c7ce912c..b724e474568 100644 --- a/tests/test_web_request_handler.py +++ b/tests/test_web_request_handler.py @@ -64,9 +64,3 @@ def test_finish_connection_timeout(loop): manager.connection_lost(handler, None) assert manager.connections == [] handler.shutdown.assert_called_with(0.1) - - -def test_secure_proxy_ssl_header_default(loop): - app = web.Application(loop=loop) - manager = app.make_handler() - assert manager.secure_proxy_ssl_header is None diff --git a/tests/test_web_server.py b/tests/test_web_server.py new file mode 100644 index 00000000000..818d84402f2 --- /dev/null +++ b/tests/test_web_server.py @@ -0,0 +1,17 @@ +import asyncio + +from aiohttp import web + + +@asyncio.coroutine +def test_simple_server(raw_test_server, test_client): + @asyncio.coroutine + def handler(request): + return web.Response(text=str(request.rel_url)) + + server = yield from raw_test_server(handler) + client = yield from test_client(server) + resp = yield from client.get('/path/to') + assert resp.status == 200 + txt = yield from resp.text() + assert txt == '/path/to' diff --git a/tests/test_worker.py b/tests/test_worker.py index 315f3142d1f..820968ecb7a 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -238,8 +238,8 @@ def test_close(worker, loop): app = worker.wsgi = mock.Mock() app.cleanup = make_mocked_coro(None) handler.connections = [object()] - handler.finish_connections.return_value = helpers.create_future(loop) - handler.finish_connections.return_value.set_result(1) + handler.shutdown.return_value = helpers.create_future(loop) + handler.shutdown.return_value.set_result(1) app.shutdown.return_value = helpers.create_future(loop) app.shutdown.return_value.set_result(None) @@ -247,7 +247,7 @@ def test_close(worker, loop): yield from worker.close() app.shutdown.assert_called_with() app.cleanup.assert_called_with() - handler.finish_connections.assert_called_with(timeout=95.0) + handler.shutdown.assert_called_with(timeout=95.0) srv.close.assert_called_with() assert worker.servers is None