Merge pull request #278 from r0fls/vhosts

add support for virtual hosts
This commit is contained in:
Eli Uriegas 2017-01-10 15:18:36 -06:00 committed by GitHub
commit 57f27c41e0
4 changed files with 71 additions and 10 deletions

18
examples/vhosts.py Normal file
View File

@ -0,0 +1,18 @@
from sanic.response import text
from sanic import Sanic
# Usage
# curl -H "Host: example.com" localhost:8000
# curl -H "Host: sub.example.com" localhost:8000
app = Sanic()
@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")
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8000)

View File

@ -55,8 +55,9 @@ class Router:
self.routes_static = {} self.routes_static = {}
self.routes_dynamic = defaultdict(list) self.routes_dynamic = defaultdict(list)
self.routes_always_check = [] self.routes_always_check = []
self.hosts = None
def add(self, uri, methods, handler): def add(self, uri, methods, handler, host=None):
""" """
Adds a handler to the route list Adds a handler to the route list
:param uri: Path to match :param uri: Path to match
@ -66,6 +67,17 @@ class Router:
When executed, it should provide a response object. When executed, it should provide a response object.
:return: Nothing :return: Nothing
""" """
if host is not None:
# we want to track if there are any
# vhosts on the Router instance so that we can
# default to the behavior without vhosts
if self.hosts is None:
self.hosts = set(host)
else:
self.hosts.add(host)
uri = host + uri
if uri in self.routes_all: if uri in self.routes_all:
raise RouteExists("Route already registered: {}".format(uri)) raise RouteExists("Route already registered: {}".format(uri))
@ -113,7 +125,9 @@ class Router:
else: else:
self.routes_static[uri] = route self.routes_static[uri] = route
def remove(self, uri, clean_cache=True): def remove(self, uri, clean_cache=True, host=None):
if host is not None:
uri = host + uri
try: try:
route = self.routes_all.pop(uri) route = self.routes_all.pop(uri)
except KeyError: except KeyError:
@ -137,10 +151,14 @@ class Router:
:param request: Request object :param request: Request object
:return: handler, arguments, keyword arguments :return: handler, arguments, keyword arguments
""" """
return self._get(request.url, request.method) if self.hosts is None:
return self._get(request.url, request.method, '')
else:
return self._get(request.url, request.method,
request.headers.get("Host", ''))
@lru_cache(maxsize=Config.ROUTER_CACHE_SIZE) @lru_cache(maxsize=Config.ROUTER_CACHE_SIZE)
def _get(self, url, method): def _get(self, url, method, host=None):
""" """
Gets a request handler based on the URL of the request, or raises an Gets a request handler based on the URL of the request, or raises an
error. Internal method for caching. error. Internal method for caching.
@ -148,6 +166,7 @@ class Router:
:param method: Request method :param method: Request method
:return: handler, arguments, keyword arguments :return: handler, arguments, keyword arguments
""" """
url = host + url
# Check against known static routes # Check against known static routes
route = self.routes_static.get(url) route = self.routes_static.get(url)
if route: if route:

View File

@ -51,7 +51,7 @@ class Sanic:
# -------------------------------------------------------------------- # # -------------------------------------------------------------------- #
# Decorator # Decorator
def route(self, uri, methods=None): def route(self, uri, methods=None, host=None):
""" """
Decorates a function to be registered as a route Decorates a function to be registered as a route
:param uri: path of the URL :param uri: path of the URL
@ -65,12 +65,13 @@ class Sanic:
uri = '/' + uri uri = '/' + uri
def response(handler): def response(handler):
self.router.add(uri=uri, methods=methods, handler=handler) self.router.add(uri=uri, methods=methods, handler=handler,
host=host)
return handler return handler
return response return response
def add_route(self, handler, uri, methods=None): def add_route(self, handler, uri, methods=None, host=None):
""" """
A helper method to register class instance or A helper method to register class instance or
functions as a handler to the application url functions as a handler to the application url
@ -80,11 +81,11 @@ class Sanic:
:param methods: list or tuple of methods allowed :param methods: list or tuple of methods allowed
:return: function or class instance :return: function or class instance
""" """
self.route(uri=uri, methods=methods)(handler) self.route(uri=uri, methods=methods, host=host)(handler)
return handler return handler
def remove_route(self, uri, clean_cache=True): def remove_route(self, uri, clean_cache=True, host=None):
self.router.remove(uri, clean_cache) self.router.remove(uri, clean_cache, host)
# Decorator # Decorator
def exception(self, *exceptions): def exception(self, *exceptions):

23
tests/test_vhosts.py Normal file
View File

@ -0,0 +1,23 @@
from sanic import Sanic
from sanic.response import json, text
from sanic.utils import sanic_endpoint_test
def test_vhosts():
app = Sanic('test_text')
@app.route('/', host="example.com")
async def handler(request):
return text("You're at example.com!")
@app.route('/', host="subdomain.example.com")
async def handler(request):
return text("You're at subdomain.example.com!")
headers = {"Host": "example.com"}
request, response = sanic_endpoint_test(app, headers=headers)
assert response.text == "You're at example.com!"
headers = {"Host": "subdomain.example.com"}
request, response = sanic_endpoint_test(app, headers=headers)
assert response.text == "You're at subdomain.example.com!"