Merge pull request #4 from channelcat/master

pull from master
This commit is contained in:
Pahaz Blinov 2016-11-06 21:35:20 +05:00 committed by GitHub
commit 50f63142db
6 changed files with 80 additions and 7 deletions

38
examples/cache_example.py Normal file
View File

@ -0,0 +1,38 @@
"""
Example of caching using aiocache package. To run it you will need a Redis
instance running in localhost:6379.
Running this example you will see that the first call lasts 3 seconds and
the rest are instant because the value is retrieved from the Redis.
If you want more info about the package check
https://github.com/argaen/aiocache
"""
import asyncio
import aiocache
from sanic import Sanic
from sanic.response import json
from sanic.log import log
from aiocache import RedisCache, cached
from aiocache.serializers import JsonSerializer
app = Sanic(__name__)
aiocache.set_defaults(cache=RedisCache)
@cached(key="my_custom_key", serializer=JsonSerializer())
async def expensive_call():
log.info("Expensive has been called")
await asyncio.sleep(3)
return {"test": True}
@app.route("/")
async def test(request):
log.info("Received GET /")
return json(await expensive_call())
app.run(host="0.0.0.0", port=8000, loop=asyncio.get_event_loop())

View File

@ -2,6 +2,7 @@ httptools
ujson ujson
uvloop uvloop
aiohttp aiohttp
aiocache
pytest pytest
coverage coverage
tox tox
@ -9,4 +10,4 @@ gunicorn
bottle bottle
kyoukai kyoukai
falcon falcon
tornado tornado

View File

@ -1,3 +1,4 @@
httptools httptools
ujson ujson
uvloop uvloop
aiofiles

View File

@ -109,8 +109,9 @@ class Blueprint:
# Detect which way this was called, @middleware or @middleware('AT') # Detect which way this was called, @middleware or @middleware('AT')
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
middleware = args[0]
args = [] args = []
return register_middleware(args[0]) return register_middleware(middleware)
else: else:
return register_middleware return register_middleware

View File

@ -1,6 +1,8 @@
import asyncio import asyncio
from functools import partial
from inspect import isawaitable from inspect import isawaitable
from signal import SIGINT, SIGTERM from signal import SIGINT, SIGTERM
from time import time
import httptools import httptools
@ -17,6 +19,9 @@ class Signal:
stopped = False stopped = False
current_time = None
class HttpProtocol(asyncio.Protocol): class HttpProtocol(asyncio.Protocol):
__slots__ = ( __slots__ = (
# event loop, connection # event loop, connection
@ -26,7 +31,7 @@ class HttpProtocol(asyncio.Protocol):
# request config # request config
'request_handler', 'request_timeout', 'request_max_size', 'request_handler', 'request_timeout', 'request_max_size',
# connection management # connection management
'_total_request_size', '_timeout_handler') '_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, signal=Signal(), def __init__(self, *, loop, request_handler, signal=Signal(),
connections={}, request_timeout=60, connections={}, request_timeout=60,
@ -44,6 +49,7 @@ class HttpProtocol(asyncio.Protocol):
self.request_max_size = request_max_size self.request_max_size = request_max_size
self._total_request_size = 0 self._total_request_size = 0
self._timeout_handler = None self._timeout_handler = None
self._last_request_time = None
# -------------------------------------------- # # -------------------------------------------- #
# Connection # Connection
@ -54,6 +60,7 @@ class HttpProtocol(asyncio.Protocol):
self._timeout_handler = self.loop.call_later( self._timeout_handler = self.loop.call_later(
self.request_timeout, self.connection_timeout) self.request_timeout, self.connection_timeout)
self.transport = transport self.transport = transport
self._last_request_time = current_time
def connection_lost(self, exc): def connection_lost(self, exc):
del self.connections[self] del self.connections[self]
@ -61,7 +68,14 @@ class HttpProtocol(asyncio.Protocol):
self.cleanup() self.cleanup()
def connection_timeout(self): def connection_timeout(self):
self.bail_out("Request timed out, connection closed") # Check if
time_elapsed = current_time - self._last_request_time
if time_elapsed < self.request_timeout:
time_left = self.request_timeout - time_elapsed
self._timeout_handler = \
self.loop.call_later(time_left, self.connection_timeout)
else:
self.bail_out("Request timed out, connection closed")
# -------------------------------------------- # # -------------------------------------------- #
# Parsing # Parsing
@ -131,13 +145,15 @@ class HttpProtocol(asyncio.Protocol):
if not keep_alive: if not keep_alive:
self.transport.close() self.transport.close()
else: else:
# Record that we received data
self._last_request_time = current_time
self.cleanup() self.cleanup()
except Exception as e: except Exception as e:
self.bail_out( self.bail_out(
"Writing request failed, connection closed {}".format(e)) "Writing request failed, connection closed {}".format(e))
def bail_out(self, message): def bail_out(self, message):
log.error(message) log.debug(message)
self.transport.close() self.transport.close()
def cleanup(self): def cleanup(self):
@ -158,6 +174,18 @@ class HttpProtocol(asyncio.Protocol):
return False return False
def update_current_time(loop):
"""
Caches the current time, since it is needed
at the end of every keep-alive request to update the request timeout time
:param loop:
:return:
"""
global current_time
current_time = time()
loop.call_later(1, partial(update_current_time, loop))
def trigger_events(events, loop): def trigger_events(events, loop):
""" """
:param events: one or more sync or async functions to execute :param events: one or more sync or async functions to execute
@ -212,6 +240,10 @@ def serve(host, port, request_handler, before_start=None, after_start=None,
request_max_size=request_max_size, request_max_size=request_max_size,
), host, port, reuse_port=reuse_port, sock=sock) ), host, port, reuse_port=reuse_port, sock=sock)
# Instead of pulling time at the end of every request,
# pull it once per minute
loop.call_soon(partial(update_current_time, loop))
try: try:
http_server = loop.run_until_complete(server_coroutine) http_server = loop.run_until_complete(server_coroutine)
except Exception: except Exception:

View File

@ -56,7 +56,7 @@ def test_query_string():
async def handler(request): async def handler(request):
return text('OK') return text('OK')
request, response = sanic_endpoint_test(app, params=[("test1", 1), ("test2", "false"), ("test2", "true")]) request, response = sanic_endpoint_test(app, params=[("test1", "1"), ("test2", "false"), ("test2", "true")])
assert request.args.get('test1') == '1' assert request.args.get('test1') == '1'
assert request.args.get('test2') == 'false' assert request.args.get('test2') == 'false'