summaryrefslogtreecommitdiff
path: root/aiohttp_cors/urldispatcher_router_adapter.py
diff options
context:
space:
mode:
Diffstat (limited to 'aiohttp_cors/urldispatcher_router_adapter.py')
-rw-r--r--aiohttp_cors/urldispatcher_router_adapter.py209
1 files changed, 50 insertions, 159 deletions
diff --git a/aiohttp_cors/urldispatcher_router_adapter.py b/aiohttp_cors/urldispatcher_router_adapter.py
index 92bfbb3..1a65e99 100644
--- a/aiohttp_cors/urldispatcher_router_adapter.py
+++ b/aiohttp_cors/urldispatcher_router_adapter.py
@@ -14,9 +14,7 @@
"""AbstractRouterAdapter for aiohttp.web.UrlDispatcher.
"""
-import asyncio
import collections
-import re
from typing import Union
@@ -24,6 +22,7 @@ from aiohttp import web
from aiohttp import hdrs
from .abc import AbstractRouterAdapter
+from .mixin import CorsViewMixin
# There several usage patterns of routes which should be handled
@@ -89,6 +88,22 @@ class _ResourceConfig:
self.method_config = {}
+def _is_web_view(entity, strict=True):
+ webview = False
+ if isinstance(entity, web.AbstractRoute):
+ handler = entity.handler
+ if isinstance(handler, type) and issubclass(handler, web.View):
+ webview = True
+ if not issubclass(handler, CorsViewMixin):
+ if strict:
+ raise ValueError("web view should be derived from "
+ "aiohttp_cors.WebViewMixig for working "
+ "with the library")
+ else:
+ return False
+ return webview
+
+
class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
"""Adapter for `UrlDispatcher` for Resources-based routing only.
@@ -138,6 +153,22 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
if resource in self._resources_with_preflight_handlers:
# Preflight handler already added for this resource.
return
+ for route_obj in resource:
+ if route_obj.method == hdrs.METH_OPTIONS:
+ if route_obj.handler is handler:
+ return # already added
+ else:
+ raise ValueError(
+ "{!r} already has OPTIONS handler {!r}"
+ .format(resource, route_obj.handler))
+ elif route_obj.method == hdrs.METH_ANY:
+ if _is_web_view(route_obj):
+ self._preflight_routes.add(route_obj)
+ self._resources_with_preflight_handlers.add(resource)
+ return
+ else:
+ raise ValueError("{!r} already has a '*' handler "
+ "for all methods".format(resource))
preflight_route = resource.add_route(hdrs.METH_OPTIONS, handler)
self._preflight_routes.add(preflight_route)
@@ -160,13 +191,8 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
elif isinstance(routing_entity, web.ResourceRoute):
route = routing_entity
- # Preflight handler for Route's Resource already must be
- # configured.
if not self.is_cors_for_resource(route.resource):
- raise ValueError(
- "Can't setup CORS for {!r} request, "
- "CORS must be enabled for route's resource first.".format(
- route))
+ self.add_preflight_handler(route.resource, handler)
else:
raise ValueError(
@@ -187,8 +213,10 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
def is_preflight_request(self, request: web.Request) -> bool:
"""Is `request` is a CORS preflight request."""
-
- return self._request_route(request) in self._preflight_routes
+ route = self._request_route(request)
+ if _is_web_view(route, strict=False):
+ return request.method == 'OPTIONS'
+ return route in self._preflight_routes
def is_cors_enabled_on_request(self, request: web.Request) -> bool:
"""Is `request` is a request for CORS-enabled resource."""
@@ -219,6 +247,9 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
# Add resource's route configuration or fail if it's already added.
if route.resource not in self._resource_config:
+ self.set_config_for_routing_entity(route.resource, config)
+
+ if route.resource not in self._resource_config:
raise ValueError(
"Can't setup CORS for {!r} request, "
"CORS must be enabled for route's resource first.".format(
@@ -239,8 +270,7 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
"Resource or ResourceRoute expected, got {!r}".format(
routing_entity))
- @asyncio.coroutine
- def get_preflight_request_config(
+ async def get_preflight_request_config(
self,
preflight_request: web.Request,
origin: str,
@@ -279,155 +309,16 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
resource_config = self._resource_config[resource]
# Take Route config (if any) with defaults from Resource CORS
# configuration and global defaults.
+ route = request.match_info.route
+ if _is_web_view(route, strict=False):
+ method_config = request.match_info.handler.get_request_config(
+ request, request.method)
+ else:
+ method_config = resource_config.method_config.get(request.method,
+ {})
defaulted_config = collections.ChainMap(
- resource_config.method_config.get(request.method, {}),
+ method_config,
resource_config.default_config,
self._default_config)
return defaulted_config
-
-
-class OldRoutesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
- """Adapter for `UrlDispatcher` for old-style routing only.
-
- In all use cases when Resource is not explicitly used,
- Resource will automatically allocated for old route.
- In this case all routes will have it's own resource, and to find
- related routes (routes that shares same path) we need to iterate over
- all routes with enabled CORS and check is they handle specific path.
-
- This whole class should go away when user will migrate to proper
- Resource/Route usage scheme.
- """
-
- def __init__(self,
- router: web.UrlDispatcher,
- defaults):
- """
- :param defaults:
- Default CORS configuration.
- """
- self._router = router
-
- # Default configuration for all routes.
- self._default_config = defaults
-
- # Mapping from route to config.
- self._route_config = collections.OrderedDict()
-
- self._preflight_routes = set()
-
- def add_preflight_handler(
- self,
- route: web.AbstractRoute,
- handler):
- """Add OPTIONS handler for same paths that `route` handles."""
-
- assert isinstance(route, web.AbstractRoute)
-
- if isinstance(route, web.ResourceRoute):
- # New-style route (which Resource is not used explicitly,
- # otherwise it would be handled by other adapter).
- preflight_route = route.resource.add_route(
- hdrs.METH_OPTIONS, handler)
-
- elif isinstance(route, web.Route):
- # Old-style route.
-
- if isinstance(route, web.StaticRoute):
- # TODO: Use custom matches that uses `str.startswith()`
- # if regexp performance is not enough.
- pattern = re.compile("^" + re.escape(route._prefix))
- preflight_route = web.DynamicRoute(
- hdrs.METH_OPTIONS, handler, None, pattern, "")
- self._router.register_route(preflight_route)
-
- elif isinstance(route, web.PlainRoute):
- # May occur only if user manually creates PlainRoute.
- preflight_route = self._router.add_route(
- hdrs.METH_OPTIONS, route._path, handler)
-
- elif isinstance(route, web.DynamicRoute):
- # May occur only if user manually creates DynamicRoute.
- preflight_route = web.DynamicRoute(
- hdrs.METH_OPTIONS, handler, None,
- route._pattern, route._formatter)
- self._router.register_route(preflight_route)
-
- else:
- raise RuntimeError(
- "Unhandled deprecated route type {!r}".format(route))
-
- else:
- raise RuntimeError("Unhandled route type {!r}".format(route))
-
- self._preflight_routes.add(preflight_route)
-
- def _request_route(self, request: web.Request) -> web.ResourceRoute:
- match_info = request.match_info
- assert isinstance(match_info, web.UrlMappingMatchInfo)
- return match_info.route
-
- def is_preflight_request(self, request: web.Request) -> bool:
- """Is `request` is a CORS preflight request."""
-
- return self._request_route(request) in self._preflight_routes
-
- def is_cors_enabled_on_request(self, request: web.Request) -> bool:
- """Is `request` is a request for CORS-enabled resource."""
-
- return self._request_route(request) in self._route_config
-
- def set_config_for_routing_entity(
- self,
- route: web.AbstractRoute,
- config):
- """Record CORS configuration for route."""
-
- assert isinstance(route, web.AbstractRoute)
-
- if any(options.allow_methods is not None
- for options in config.values()):
- raise ValueError(
- "'allow_methods' parameter is not supported on old-style "
- "routes. You specified {!r} for {!r}. "
- "Use Resources to configure CORS.".format(
- config, route))
-
- if route in self._route_config:
- raise ValueError(
- "CORS is already configured for {!r} route.".format(
- route))
-
- self._route_config[route] = config
-
- @asyncio.coroutine
- def get_preflight_request_config(
- self,
- preflight_request: web.Request,
- origin: str,
- requested_method: str):
- assert self.is_preflight_request(preflight_request)
-
- request = preflight_request.clone(method=requested_method)
- for route, config in self._route_config.items():
- match_info, allowed_methods = yield from route.resource.resolve(
- request)
- if match_info is not None:
- return collections.ChainMap(config, self._default_config)
- else:
- raise KeyError
-
- def get_non_preflight_request_config(self, request: web.Request):
- """Get stored CORS configuration for routing entity that handles
- specified request."""
-
- assert self.is_cors_enabled_on_request(request)
-
- route = self._request_route(request)
- route_config = self._route_config[route]
-
- defaulted_config = collections.ChainMap(
- route_config, self._default_config)
-
- return defaulted_config