Skip to content

Commit

Permalink
Merge branch 'master' into sphinx-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kdelwat committed Jan 19, 2017
2 parents 5cfd8b9 + a79f073 commit 7c4ffa8
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 18 deletions.
15 changes: 15 additions & 0 deletions README.rst
Expand Up @@ -56,6 +56,21 @@ Hello World Example
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
SSL Example
-----------

Optionally pass in an SSLContext:

.. code:: python
import ssl
certificate = "/path/to/certificate"
keyfile = "/path/to/keyfile"
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certificate, keyfile=keyfile)
app.run(host="0.0.0.0", port=8443, ssl=context)
Installation
------------

Expand Down
7 changes: 7 additions & 0 deletions examples/vhosts.py
Expand Up @@ -11,9 +11,16 @@
app = Sanic()
bp = Blueprint("bp", host="bp.example.com")

@app.route('/', host=["example.com",
"somethingelse.com",
"therestofyourdomains.com"])
async def hello(request):
return text("Some defaults")

@app.route('/', host="example.com")
async def hello(request):
return text("Answer")

@app.route('/', host="sub.example.com")
async def hello(request):
return text("42")
Expand Down
23 changes: 20 additions & 3 deletions sanic/blueprints.py
Expand Up @@ -3,6 +3,7 @@

class BlueprintSetup:
"""
Creates a blueprint state like object.
"""

def __init__(self, blueprint, app, options):
Expand Down Expand Up @@ -32,13 +33,13 @@ def add_route(self, handler, uri, methods, host=None):

def add_exception(self, handler, *args, **kwargs):
"""
Registers exceptions to sanic
Registers exceptions to sanic.
"""
self.app.exception(*args, **kwargs)(handler)

def add_static(self, uri, file_or_directory, *args, **kwargs):
"""
Registers static files to sanic
Registers static files to sanic.
"""
if self.url_prefix:
uri = self.url_prefix + uri
Expand All @@ -47,7 +48,7 @@ def add_static(self, uri, file_or_directory, *args, **kwargs):

def add_middleware(self, middleware, *args, **kwargs):
"""
Registers middleware to sanic
Registers middleware to sanic.
"""
if args or kwargs:
self.app.middleware(*args, **kwargs)(middleware)
Expand Down Expand Up @@ -77,18 +78,23 @@ def record(self, func):

def make_setup_state(self, app, options):
"""
Returns a new BlueprintSetup object
"""
return BlueprintSetup(self, app, options)

def register(self, app, options):
"""
Registers the blueprint to the sanic app.
"""
state = self.make_setup_state(app, options)
for deferred in self.deferred_functions:
deferred(state)

def route(self, uri, methods=None, host=None):
"""
Creates a blueprint route from a decorated function.
:param uri: Endpoint at which the route will be accessible.
:param methods: List of acceptable HTTP methods.
"""
def decorator(handler):
self.record(lambda s: s.add_route(handler, uri, methods, host))
Expand All @@ -97,12 +103,18 @@ def decorator(handler):

def add_route(self, handler, uri, methods=None, host=None):
"""
Creates a blueprint route from a function.
:param handler: Function to handle uri request.
:param uri: Endpoint at which the route will be accessible.
:param methods: List of acceptable HTTP methods.
"""
self.record(lambda s: s.add_route(handler, uri, methods, host))
return handler

def listener(self, event):
"""
Create a listener from a decorated function.
:param event: Event to listen to.
"""
def decorator(listener):
self.listeners[event].append(listener)
Expand All @@ -111,6 +123,7 @@ def decorator(listener):

def middleware(self, *args, **kwargs):
"""
Creates a blueprint middleware from a decorated function.
"""
def register_middleware(middleware):
self.record(
Expand All @@ -127,6 +140,7 @@ def register_middleware(middleware):

def exception(self, *args, **kwargs):
"""
Creates a blueprint exception from a decorated function.
"""
def decorator(handler):
self.record(lambda s: s.add_exception(handler, *args, **kwargs))
Expand All @@ -135,6 +149,9 @@ def decorator(handler):

def static(self, uri, file_or_directory, *args, **kwargs):
"""
Creates a blueprint static route from a decorated function.
:param uri: Endpoint at which the route will be accessible.
:param file_or_directory: Static asset.
"""
self.record(
lambda s: s.add_static(uri, file_or_directory, *args, **kwargs))
2 changes: 2 additions & 0 deletions sanic/exceptions.py
Expand Up @@ -174,6 +174,7 @@ def response(self, request, exception):
try:
response = handler(request=request, exception=exception)
except:
log.error(format_exc())
if self.sanic.debug:
response_message = (
'Exception raised in exception handler "{}" '
Expand All @@ -186,6 +187,7 @@ def response(self, request, exception):
return response

def default(self, request, exception):
log.error(format_exc())
if issubclass(type(exception), SanicException):
return text(
'Error: {}'.format(exception),
Expand Down
24 changes: 24 additions & 0 deletions sanic/response.py
Expand Up @@ -143,21 +143,45 @@ def cookies(self):


def json(body, status=200, headers=None):
"""
Returns response object with body in json format.
:param body: Response data to be serialized.
:param status: Response code.
:param headers: Custom Headers.
"""
return HTTPResponse(json_dumps(body), headers=headers, status=status,
content_type="application/json")


def text(body, status=200, headers=None):
"""
Returns response object with body in text format.
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
"""
return HTTPResponse(body, status=status, headers=headers,
content_type="text/plain; charset=utf-8")


def html(body, status=200, headers=None):
"""
Returns response object with body in html format.
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
"""
return HTTPResponse(body, status=status, headers=headers,
content_type="text/html; charset=utf-8")


async def file(location, mime_type=None, headers=None):
"""
Returns response object with file data.
:param location: Location of file on system.
:param mime_type: Specific mime_type.
:param headers: Custom Headers.
"""
filename = path.split(location)[-1]

async with open_async(location, mode='rb') as _file:
Expand Down
45 changes: 38 additions & 7 deletions sanic/router.py
Expand Up @@ -3,6 +3,7 @@
from functools import lru_cache
from .config import Config
from .exceptions import NotFound, InvalidUsage
from .views import CompositionView

Route = namedtuple('Route', ['handler', 'methods', 'pattern', 'parameters'])
Parameter = namedtuple('Parameter', ['name', 'cast'])
Expand Down Expand Up @@ -84,11 +85,15 @@ def add(self, uri, methods, handler, host=None):
if self.hosts is None:
self.hosts = set(host)
else:
if isinstance(host, list):
host = set(host)
self.hosts.add(host)
uri = host + uri

if uri in self.routes_all:
raise RouteExists("Route already registered: {}".format(uri))
if isinstance(host, str):
uri = host + uri
else:
for h in host:
self.add(uri, methods, handler, h)
return

# Dict for faster lookups of if method allowed
if methods:
Expand Down Expand Up @@ -122,9 +127,35 @@ def add_parameter(match):
pattern_string = re.sub(r'<(.+?)>', add_parameter, uri)
pattern = re.compile(r'^{}$'.format(pattern_string))

route = Route(
handler=handler, methods=methods, pattern=pattern,
parameters=parameters)
def merge_route(route, methods, handler):
# merge to the existing route when possible.
if not route.methods or not methods:
# method-unspecified routes are not mergeable.
raise RouteExists(
"Route already registered: {}".format(uri))
elif route.methods.intersection(methods):
# already existing method is not overloadable.
duplicated = methods.intersection(route.methods)
raise RouteExists(
"Route already registered: {} [{}]".format(
uri, ','.join(list(duplicated))))
if isinstance(route.handler, CompositionView):
view = route.handler
else:
view = CompositionView()
view.add(route.methods, route.handler)
view.add(methods, handler)
route = route._replace(
handler=view, methods=methods.union(route.methods))
return route

route = self.routes_all.get(uri)
if route:
route = merge_route(route, methods, handler)
else:
route = Route(
handler=handler, methods=methods, pattern=pattern,
parameters=parameters)

self.routes_all[uri] = route
if properties['unhashable']:
Expand Down
18 changes: 12 additions & 6 deletions sanic/sanic.py
Expand Up @@ -237,7 +237,7 @@ async def handle_request(self, request, response_callback):
e, format_exc()))
else:
response = HTTPResponse(
"An error occured while handling an error")
"An error occurred while handling an error")

response_callback(response)

Expand All @@ -246,9 +246,10 @@ async def handle_request(self, request, response_callback):
# -------------------------------------------------------------------- #

def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
after_start=None, before_stop=None, after_stop=None, sock=None,
workers=1, loop=None, protocol=HttpProtocol, backlog=100,
stop_event=None, logger=None):
after_start=None, before_stop=None, after_stop=None, ssl=None,
sock=None, workers=1, loop=None, protocol=HttpProtocol,
backlog=100, stop_event=None):

"""
Runs the HTTP Server and listens until keyboard interrupt or term
signal. On termination, drains connections before closing.
Expand All @@ -264,7 +265,7 @@ def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
received before it is respected
:param after_stop: Functions to be executed when all requests are
complete
:param ssl: SSLContext for SSL encryption of worker(s)
:param sock: Socket for the server to accept connections from
:param workers: Number of processes
received before it is respected
Expand All @@ -285,6 +286,7 @@ def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
'host': host,
'port': port,
'sock': sock,
'ssl': ssl,
'debug': debug,
'request_handler': self.handle_request,
'error_handler': self.error_handler,
Expand Down Expand Up @@ -322,7 +324,11 @@ def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
log.debug(self.config.LOGO)

# Serve
log.info('Goin\' Fast @ http://{}:{}'.format(host, port))
if ssl is None:
proto = "http"
else:
proto = "https"
log.info('Goin\' Fast @ {}://{}:{}'.format(proto, host, port))

try:
if workers == 1:
Expand Down
4 changes: 3 additions & 1 deletion sanic/server.py
Expand Up @@ -248,7 +248,7 @@ def trigger_events(events, loop):

def serve(host, port, request_handler, error_handler, before_start=None,
after_start=None, before_stop=None, after_stop=None, debug=False,
request_timeout=60, sock=None, request_max_size=None,
request_timeout=60, ssl=None, sock=None, request_max_size=None,
reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100):
"""
Starts asynchronous HTTP Server on an individual process.
Expand All @@ -269,6 +269,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
argument `loop`
:param debug: Enables debug output (slows server)
:param request_timeout: time in seconds
:param ssl: SSLContext
:param sock: Socket for the server to accept connections from
:param request_max_size: size in bytes, `None` for no limit
:param reuse_port: `True` for multiple workers
Expand Down Expand Up @@ -301,6 +302,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
server,
host,
port,
ssl=ssl,
reuse_port=reuse_port,
sock=sock,
backlog=backlog
Expand Down
35 changes: 35 additions & 0 deletions sanic/views.py
Expand Up @@ -65,3 +65,38 @@ def view(*args, **kwargs):
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
return view


class CompositionView:
""" Simple method-function mapped view for the sanic.
You can add handler functions to methods (get, post, put, patch, delete)
for every HTTP method you want to support.
For example:
view = CompositionView()
view.add(['GET'], lambda request: text('I am get method'))
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
etc.
If someone tries to use a non-implemented method, there will be a
405 response.
"""

def __init__(self):
self.handlers = {}

def add(self, methods, handler):
for method in methods:
if method in self.handlers:
raise KeyError(
'Method {} already is registered.'.format(method))
self.handlers[method] = handler

def __call__(self, request, *args, **kwargs):
handler = self.handlers.get(request.method.upper(), None)
if handler is None:
raise InvalidUsage(
'Method {} not allowed for URL {}'.format(
request.method, request.url), status_code=405)
return handler(request, *args, **kwargs)

0 comments on commit 7c4ffa8

Please sign in to comment.