diff --git a/environment.yml b/environment.yml index dc88e646..a689bdef 100644 --- a/environment.yml +++ b/environment.yml @@ -13,7 +13,7 @@ dependencies: - sphinx==1.8.3 - sphinx_rtd_theme==0.4.2 - recommonmark==0.5.0 - - httpx==0.9.3 + - httpx==0.11.1 - sphinxcontrib-asyncio>=0.2.0 - docutils==0.14 - pygments==2.3.1 diff --git a/sanic/app.py b/sanic/app.py index 18913e09..b63b71cb 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1177,6 +1177,12 @@ class Sanic: try: self.is_running = True + if workers > 1 and os.name != "posix": + logger.warn( + f"Multiprocessing is currently not supported on {os.name}," + " using workers=1 instead" + ) + workers = 1 if workers == 1: if auto_reload and os.name != "posix": # This condition must be removed after implementing diff --git a/sanic/server.py b/sanic/server.py index d5977e6e..48ad79c9 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -1,11 +1,11 @@ import asyncio +import multiprocessing import os import sys from asyncio import CancelledError from functools import partial from inspect import isawaitable -from multiprocessing import Process from signal import SIG_IGN, SIGINT, SIGTERM, Signals from signal import signal as signal_func from socket import SO_REUSEADDR, SOL_SOCKET, socket @@ -613,9 +613,10 @@ def serve_multiple(server_settings, workers): signal_func(SIGINT, lambda s, f: sig_handler(s, f)) signal_func(SIGTERM, lambda s, f: sig_handler(s, f)) + mp = multiprocessing.get_context("fork") for _ in range(workers): - process = Process(target=serve, kwargs=server_settings) + process = mp.Process(target=serve, kwargs=server_settings) process.daemon = True process.start() processes.append(process) diff --git a/sanic/testing.py b/sanic/testing.py index fbbd10b2..8c1cc7a4 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -23,7 +23,7 @@ class SanicTestClient: self.host = host def get_new_session(self): - return httpx.Client() + return httpx.AsyncClient(verify=False) async def _local_request(self, method, url, *args, **kwargs): logger.info(url) @@ -38,7 +38,7 @@ class SanicTestClient: try: response = await getattr(session, method.lower())( - url, verify=False, *args, **kwargs + url, *args, **kwargs ) except httpx.exceptions.ConnectionClosed: logger.error( @@ -48,15 +48,17 @@ class SanicTestClient: except NameError: raise Exception(response.status_code) + response.body = await response.aread() + response.status = response.status_code + response.content_type = response.headers.get("content-type") + + # response can be decoded as json after response._content + # is set by response.aread() try: response.json = response.json() except (JSONDecodeError, UnicodeDecodeError): response.json = None - response.body = await response.read() - response.status = response.status_code - response.content_type = response.headers.get("content-type") - if raw_cookies: response.raw_cookies = {} @@ -189,11 +191,11 @@ async def app_call_with_return(self, scope, receive, send): return await asgi_app() -class SanicASGIDispatch(httpx.dispatch.ASGIDispatch): +class SanicASGIDispatch(httpx.dispatch.asgi.ASGIDispatch): pass -class SanicASGITestClient(httpx.Client): +class SanicASGITestClient(httpx.AsyncClient): def __init__( self, app, diff --git a/setup.py b/setup.py index 263c0db1..332229ff 100644 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ requirements = [ "aiofiles>=0.3.0", "websockets>=7.0,<9.0", "multidict>=4.0,<5.0", - "httpx==0.9.3", + "httpx==0.11.1", ] tests_require = [ diff --git a/tests/test_keep_alive_timeout.py b/tests/test_keep_alive_timeout.py index ec9d9975..78f95655 100644 --- a/tests/test_keep_alive_timeout.py +++ b/tests/test_keep_alive_timeout.py @@ -18,6 +18,22 @@ old_conn = None class ReusableSanicConnectionPool( httpx.dispatch.connection_pool.ConnectionPool ): + @property + def cert(self): + return self.ssl.cert + + @property + def verify(self): + return self.ssl.verify + + @property + def trust_env(self): + return self.ssl.trust_env + + @property + def http2(self): + return self.ssl.http2 + async def acquire_connection(self, origin, timeout): global old_conn connection = self.pop_connection(origin) @@ -26,14 +42,17 @@ class ReusableSanicConnectionPool( pool_timeout = None if timeout is None else timeout.pool_timeout await self.max_connections.acquire(timeout=pool_timeout) + ssl_config = httpx.config.SSLConfig( + cert=self.cert, + verify=self.verify, + trust_env=self.trust_env, + http2=self.http2 + ) connection = httpx.dispatch.connection.HTTPConnection( origin, - verify=self.verify, - cert=self.cert, - http2=self.http2, + ssl=ssl_config, backend=self.backend, release_func=self.release_connection, - trust_env=self.trust_env, uds=self.uds, ) @@ -49,7 +68,7 @@ class ReusableSanicConnectionPool( return connection -class ResusableSanicSession(httpx.Client): +class ResusableSanicSession(httpx.AsyncClient): def __init__(self, *args, **kwargs) -> None: dispatch = ReusableSanicConnectionPool() super().__init__(dispatch=dispatch, *args, **kwargs) @@ -159,7 +178,7 @@ class ReuseableSanicTestClient(SanicTestClient): self._server = None if self._session: - self._loop.run_until_complete(self._session.close()) + self._loop.run_until_complete(self._session.aclose()) self._session = None except Exception as e3: @@ -178,7 +197,7 @@ class ReuseableSanicTestClient(SanicTestClient): self._session = self.get_new_session() try: response = await getattr(self._session, method.lower())( - url, verify=False, timeout=request_keepalive, *args, **kwargs + url, timeout=request_keepalive, *args, **kwargs ) except NameError: raise Exception(response.status_code) @@ -188,7 +207,7 @@ class ReuseableSanicTestClient(SanicTestClient): except (JSONDecodeError, UnicodeDecodeError): response.json = None - response.body = await response.read() + response.body = await response.aread() response.status = response.status_code response.content_type = response.headers.get("content-type") diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index c518f087..f698ae12 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -14,18 +14,15 @@ class DelayableHTTPConnection(httpx.dispatch.connection.HTTPConnection): self._request_delay = kwargs.pop("request_delay") super().__init__(*args, **kwargs) - async def send(self, request, verify=None, cert=None, timeout=None): - if self.h11_connection is None and self.h2_connection is None: - await self.connect(verify=verify, cert=cert, timeout=timeout) + async def send(self, request, timeout=None): + + if self.connection is None: + self.connection = (await self.connect(timeout=timeout)) if self._request_delay: await asyncio.sleep(self._request_delay) - if self.h2_connection is not None: - response = await self.h2_connection.send(request, timeout=timeout) - else: - assert self.h11_connection is not None - response = await self.h11_connection.send(request, timeout=timeout) + response = await self.connection.send(request, timeout=timeout) return response @@ -46,12 +43,9 @@ class DelayableSanicConnectionPool( await self.max_connections.acquire(timeout=pool_timeout) connection = DelayableHTTPConnection( origin, - verify=self.verify, - cert=self.cert, - http2=self.http2, + ssl=self.ssl, backend=self.backend, release_func=self.release_connection, - trust_env=self.trust_env, uds=self.uds, request_delay=self._request_delay, ) @@ -61,7 +55,7 @@ class DelayableSanicConnectionPool( return connection -class DelayableSanicSession(httpx.Client): +class DelayableSanicSession(httpx.AsyncClient): def __init__(self, request_delay=None, *args, **kwargs) -> None: dispatch = DelayableSanicConnectionPool(request_delay=request_delay) super().__init__(dispatch=dispatch, *args, **kwargs) diff --git a/tox.ini b/tox.ini index bb7e5f1e..321e3a55 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ deps = pytest-sanic pytest-sugar httpcore==0.3.0 - httpx==0.9.3 + httpx==0.11.1 chardet<=2.3.0 beautifulsoup4 gunicorn