Merge branch 'master' into streaming

This commit is contained in:
L. Kärkkäinen 2020-03-02 14:05:15 +02:00
commit 9baa2419cd
8 changed files with 56 additions and 34 deletions

View File

@ -13,7 +13,7 @@ dependencies:
- sphinx==1.8.3 - sphinx==1.8.3
- sphinx_rtd_theme==0.4.2 - sphinx_rtd_theme==0.4.2
- recommonmark==0.5.0 - recommonmark==0.5.0
- httpx==0.9.3 - httpx==0.11.1
- sphinxcontrib-asyncio>=0.2.0 - sphinxcontrib-asyncio>=0.2.0
- docutils==0.14 - docutils==0.14
- pygments==2.3.1 - pygments==2.3.1

View File

@ -1177,6 +1177,12 @@ class Sanic:
try: try:
self.is_running = True 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 workers == 1:
if auto_reload and os.name != "posix": if auto_reload and os.name != "posix":
# This condition must be removed after implementing # This condition must be removed after implementing

View File

@ -1,11 +1,11 @@
import asyncio import asyncio
import multiprocessing
import os import os
import sys import sys
from asyncio import CancelledError from asyncio import CancelledError
from functools import partial from functools import partial
from inspect import isawaitable from inspect import isawaitable
from multiprocessing import Process
from signal import SIG_IGN, SIGINT, SIGTERM, Signals from signal import SIG_IGN, SIGINT, SIGTERM, Signals
from signal import signal as signal_func from signal import signal as signal_func
from socket import SO_REUSEADDR, SOL_SOCKET, socket 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(SIGINT, lambda s, f: sig_handler(s, f))
signal_func(SIGTERM, 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): for _ in range(workers):
process = Process(target=serve, kwargs=server_settings) process = mp.Process(target=serve, kwargs=server_settings)
process.daemon = True process.daemon = True
process.start() process.start()
processes.append(process) processes.append(process)

View File

@ -23,7 +23,7 @@ class SanicTestClient:
self.host = host self.host = host
def get_new_session(self): def get_new_session(self):
return httpx.Client() return httpx.AsyncClient(verify=False)
async def _local_request(self, method, url, *args, **kwargs): async def _local_request(self, method, url, *args, **kwargs):
logger.info(url) logger.info(url)
@ -38,7 +38,7 @@ class SanicTestClient:
try: try:
response = await getattr(session, method.lower())( response = await getattr(session, method.lower())(
url, verify=False, *args, **kwargs url, *args, **kwargs
) )
except httpx.exceptions.ConnectionClosed: except httpx.exceptions.ConnectionClosed:
logger.error( logger.error(
@ -48,15 +48,17 @@ class SanicTestClient:
except NameError: except NameError:
raise Exception(response.status_code) 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: try:
response.json = response.json() response.json = response.json()
except (JSONDecodeError, UnicodeDecodeError): except (JSONDecodeError, UnicodeDecodeError):
response.json = None response.json = None
response.body = await response.read()
response.status = response.status_code
response.content_type = response.headers.get("content-type")
if raw_cookies: if raw_cookies:
response.raw_cookies = {} response.raw_cookies = {}
@ -189,11 +191,11 @@ async def app_call_with_return(self, scope, receive, send):
return await asgi_app() return await asgi_app()
class SanicASGIDispatch(httpx.dispatch.ASGIDispatch): class SanicASGIDispatch(httpx.dispatch.asgi.ASGIDispatch):
pass pass
class SanicASGITestClient(httpx.Client): class SanicASGITestClient(httpx.AsyncClient):
def __init__( def __init__(
self, self,
app, app,

View File

@ -85,7 +85,7 @@ requirements = [
"aiofiles>=0.3.0", "aiofiles>=0.3.0",
"websockets>=7.0,<9.0", "websockets>=7.0,<9.0",
"multidict>=4.0,<5.0", "multidict>=4.0,<5.0",
"httpx==0.9.3", "httpx==0.11.1",
] ]
tests_require = [ tests_require = [

View File

@ -18,6 +18,22 @@ old_conn = None
class ReusableSanicConnectionPool( class ReusableSanicConnectionPool(
httpx.dispatch.connection_pool.ConnectionPool 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): async def acquire_connection(self, origin, timeout):
global old_conn global old_conn
connection = self.pop_connection(origin) connection = self.pop_connection(origin)
@ -26,14 +42,17 @@ class ReusableSanicConnectionPool(
pool_timeout = None if timeout is None else timeout.pool_timeout pool_timeout = None if timeout is None else timeout.pool_timeout
await self.max_connections.acquire(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( connection = httpx.dispatch.connection.HTTPConnection(
origin, origin,
verify=self.verify, ssl=ssl_config,
cert=self.cert,
http2=self.http2,
backend=self.backend, backend=self.backend,
release_func=self.release_connection, release_func=self.release_connection,
trust_env=self.trust_env,
uds=self.uds, uds=self.uds,
) )
@ -49,7 +68,7 @@ class ReusableSanicConnectionPool(
return connection return connection
class ResusableSanicSession(httpx.Client): class ResusableSanicSession(httpx.AsyncClient):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
dispatch = ReusableSanicConnectionPool() dispatch = ReusableSanicConnectionPool()
super().__init__(dispatch=dispatch, *args, **kwargs) super().__init__(dispatch=dispatch, *args, **kwargs)
@ -159,7 +178,7 @@ class ReuseableSanicTestClient(SanicTestClient):
self._server = None self._server = None
if self._session: if self._session:
self._loop.run_until_complete(self._session.close()) self._loop.run_until_complete(self._session.aclose())
self._session = None self._session = None
except Exception as e3: except Exception as e3:
@ -178,7 +197,7 @@ class ReuseableSanicTestClient(SanicTestClient):
self._session = self.get_new_session() self._session = self.get_new_session()
try: try:
response = await getattr(self._session, method.lower())( response = await getattr(self._session, method.lower())(
url, verify=False, timeout=request_keepalive, *args, **kwargs url, timeout=request_keepalive, *args, **kwargs
) )
except NameError: except NameError:
raise Exception(response.status_code) raise Exception(response.status_code)
@ -188,7 +207,7 @@ class ReuseableSanicTestClient(SanicTestClient):
except (JSONDecodeError, UnicodeDecodeError): except (JSONDecodeError, UnicodeDecodeError):
response.json = None response.json = None
response.body = await response.read() response.body = await response.aread()
response.status = response.status_code response.status = response.status_code
response.content_type = response.headers.get("content-type") response.content_type = response.headers.get("content-type")

View File

@ -14,18 +14,15 @@ class DelayableHTTPConnection(httpx.dispatch.connection.HTTPConnection):
self._request_delay = kwargs.pop("request_delay") self._request_delay = kwargs.pop("request_delay")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
async def send(self, request, verify=None, cert=None, timeout=None): async def send(self, request, timeout=None):
if self.h11_connection is None and self.h2_connection is None:
await self.connect(verify=verify, cert=cert, timeout=timeout) if self.connection is None:
self.connection = (await self.connect(timeout=timeout))
if self._request_delay: if self._request_delay:
await asyncio.sleep(self._request_delay) await asyncio.sleep(self._request_delay)
if self.h2_connection is not None: response = await self.connection.send(request, timeout=timeout)
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)
return response return response
@ -46,12 +43,9 @@ class DelayableSanicConnectionPool(
await self.max_connections.acquire(timeout=pool_timeout) await self.max_connections.acquire(timeout=pool_timeout)
connection = DelayableHTTPConnection( connection = DelayableHTTPConnection(
origin, origin,
verify=self.verify, ssl=self.ssl,
cert=self.cert,
http2=self.http2,
backend=self.backend, backend=self.backend,
release_func=self.release_connection, release_func=self.release_connection,
trust_env=self.trust_env,
uds=self.uds, uds=self.uds,
request_delay=self._request_delay, request_delay=self._request_delay,
) )
@ -61,7 +55,7 @@ class DelayableSanicConnectionPool(
return connection return connection
class DelayableSanicSession(httpx.Client): class DelayableSanicSession(httpx.AsyncClient):
def __init__(self, request_delay=None, *args, **kwargs) -> None: def __init__(self, request_delay=None, *args, **kwargs) -> None:
dispatch = DelayableSanicConnectionPool(request_delay=request_delay) dispatch = DelayableSanicConnectionPool(request_delay=request_delay)
super().__init__(dispatch=dispatch, *args, **kwargs) super().__init__(dispatch=dispatch, *args, **kwargs)

View File

@ -13,7 +13,7 @@ deps =
pytest-sanic pytest-sanic
pytest-sugar pytest-sugar
httpcore==0.3.0 httpcore==0.3.0
httpx==0.9.3 httpx==0.11.1
chardet<=2.3.0 chardet<=2.3.0
beautifulsoup4 beautifulsoup4
gunicorn gunicorn