diff --git a/src/microdot/asgi.py b/src/microdot/asgi.py index 377f301..d533de1 100644 --- a/src/microdot/asgi.py +++ b/src/microdot/asgi.py @@ -123,7 +123,8 @@ class Microdot(BaseMicrodot): headers, body=body, stream=stream, - sock=(receive, send)) + sock=(receive, send), + scheme=scope.get('scheme')) req.asgi_scope = scope res = await self.dispatch_request(req) diff --git a/src/microdot/microdot.py b/src/microdot/microdot.py index 615a47f..727fecc 100644 --- a/src/microdot/microdot.py +++ b/src/microdot/microdot.py @@ -321,14 +321,17 @@ class Request: def __init__(self, app, client_addr, method, url, http_version, headers, body=None, stream=None, sock=None, url_prefix='', - subapp=None): + subapp=None, scheme=None): #: The application instance to which this request belongs. self.app = app #: The address of the client, as a tuple (host, port). self.client_addr = client_addr #: The HTTP method of the request. self.method = method - #: The request URL, including the path and query string. + #: The scheme of the request, either `http` or `https`. + self.scheme = scheme or 'http' + #: The request URL, including the path and query string, but not the + #: scheme or the host, which is available in the ``Host`` header. self.url = url #: The URL prefix, if the endpoint comes from a mounted #: sub-application, or else ''. @@ -379,7 +382,8 @@ class Request: self.after_request_handlers = [] @staticmethod - async def create(app, client_reader, client_writer, client_addr): + async def create(app, client_reader, client_writer, client_addr, + scheme=None): """Create a request object. :param app: The Microdot application instance. @@ -388,6 +392,7 @@ class Request: :param client_writer: An output stream where the response data can be written. :param client_addr: The address of the client, as a tuple. + :param scheme: The scheme of the request, either 'http' or 'https'. This method is a coroutine. It returns a newly created ``Request`` object. @@ -424,7 +429,7 @@ class Request: return Request(app, client_addr, method, url, http_version, headers, body=body, stream=stream, - sock=(client_reader, client_writer)) + sock=(client_reader, client_writer), scheme=scheme) def _parse_urlencoded(self, urlencoded): data = MultiDict() @@ -949,6 +954,7 @@ class Microdot: self.after_error_request_handlers = [] self.error_handlers = {} self.options_handler = self.default_options_handler + self.ssl = False self.debug = False self.server = None @@ -1242,6 +1248,7 @@ class Microdot: asyncio.run(main()) """ + self.ssl = ssl self.debug = debug async def serve(reader, writer): diff --git a/src/microdot/test_client.py b/src/microdot/test_client.py index a622591..dcf5d65 100644 --- a/src/microdot/test_client.py +++ b/src/microdot/test_client.py @@ -117,6 +117,8 @@ class TestClient: :param app: The Microdot application instance. :param cookies: A dictionary of cookies to use when sending requests to the application. + :param scheme: The scheme to use for requests, either 'http' or 'https'. + :param host: The host to use for requests. The following example shows how to create a test client for an application and send a test request:: @@ -137,9 +139,11 @@ class TestClient: """ __test__ = False # remove this class from pytest's test collection - def __init__(self, app, cookies=None): + def __init__(self, app, cookies=None, scheme=None, host=None): self.app = app self.cookies = cookies or {} + self.scheme = scheme + self.host = host or 'example.com:1234' def _process_body(self, body, headers): if body is None: @@ -152,8 +156,6 @@ class TestClient: body = body.encode() if body and 'Content-Length' not in headers: headers['Content-Length'] = str(len(body)) - if 'Host' not in headers: # pragma: no branch - headers['Host'] = 'example.com:1234' return body, headers def _process_cookies(self, path, headers): @@ -176,6 +178,8 @@ class TestClient: def _render_request(self, method, path, headers, body): request_bytes = '{method} {path} HTTP/1.0\n'.format( method=method, path=path) + if 'Host' not in headers: # pragma: no branch + request_bytes += 'Host: {host}\n'.format(host=self.host) for header, value in headers.items(): request_bytes += '{header}: {value}\n'.format( header=header, value=value) @@ -236,7 +240,7 @@ class TestClient: writer = AsyncBytesIO(b'') req = await Request.create(self.app, reader, writer, - ('127.0.0.1', 1234)) + ('127.0.0.1', 1234), scheme=self.scheme) res = await self.app.dispatch_request(req) if res == Response.already_handled: return TestResponse() diff --git a/src/microdot/wsgi.py b/src/microdot/wsgi.py index 0087892..67b9e1c 100644 --- a/src/microdot/wsgi.py +++ b/src/microdot/wsgi.py @@ -96,7 +96,8 @@ class Microdot(BaseMicrodot): headers, body=body, stream=stream, - sock=sock) + sock=sock, + scheme=environ.get('wsgi.url_scheme')) req.environ = environ res = self.loop.run_until_complete(self.dispatch_request(req)) diff --git a/tests/test_microdot.py b/tests/test_microdot.py index 3f66fca..f22cdde 100644 --- a/tests/test_microdot.py +++ b/tests/test_microdot.py @@ -180,6 +180,30 @@ class TestMicrodot(unittest.TestCase): 'text/plain; charset=UTF-8') self.assertEqual(res.text, method) + def test_http_host(self): + app = Microdot() + + @app.route('/') + def index(req): + return req.scheme + "://" + req.headers['Host'] + + client = TestClient(app, host='foo.com') + res = self._run(client.get('/')) + self.assertEqual(res.status_code, 200) + self.assertEqual(res.text, 'http://foo.com') + + def test_https_host(self): + app = Microdot() + + @app.route('/') + def index(req): + return req.scheme + "://" + req.headers['Host'] + + client = TestClient(app, scheme='https', host='foo.com') + res = self._run(client.get('/')) + self.assertEqual(res.status_code, 200) + self.assertEqual(res.text, 'https://foo.com') + def test_headers(self): app = Microdot()