r"""=================Shield Middleware=================Middleware to shield application handlers by method or URL.Usage=====.. code-block:: python from aiohttp import web from aiohttp_middlewares import ( NON_IDEMPOTENT_METHODS, shield_middleware, ) # Basic usage (shield by handler method) app = web.Application( middlewares=[shield_middleware(methods=IDEMPOTENT_METHODS)] ) # Shield by handler URL app = web.Application( middlewares=[shield_middleware(urls=["/", "/about-us"])] ) # Shield by handler method, but ignore shielding list of URLs app = web.Application( middlewares=[ shield_middleware( methods=NON_IDEMPOTENT_METHODS, ignore={"/api/documents", "/api/comments"}, ) ] ) # Combine shielding by method and URL SHIELD_URLS = { "/api/documents": ["POST", "DELETE"], re.compile(r"/api/documents/\d+"): [ "DELETE", "PUT", "PATCH", ], } app = web.Application( middlewares=[shield_middleware(urls=SHIELD_URLS)] )"""importasyncioimportloggingfromtypingimportUnionfromaiohttpimportwebfromaiohttp_middlewares.annotationsimport(Handler,Middleware,StrCollection,Urls,)fromaiohttp_middlewares.utilsimportmatch_requestlogger=logging.getLogger(__name__)
[docs]defshield_middleware(*,methods:Union[StrCollection,None]=None,urls:Union[Urls,None]=None,ignore:Union[Urls,None]=None,)->Middleware:""" Ensure that handler execution would not break on :class:`asyncio.CancelledError`. Shielding handlers allow to avoid breaking handler execution on :class:`asyncio.CancelledError` (this happens for example while client closes conneciton, but server still not ready to fullify response). In most cases you need to shield non-idempotent methods (``POST``, ``PUT``, ``PATCH``, ``DELETE``) and ignore shielding idempotent ``GET``, ``HEAD``, ``OPTIONS`` and ``TRACE`` requests. More details about shielding coroutines in official Python docs: :func:`asyncio.shield` Other possibility to allow shielding request handlers by URLs dict. In that case order of dict keys is necessary as they will be processed from first to last added. To shield all non-idempotent methods you need to: .. code-block:: python from aiohttp import web app = web.Application( middlewares=[ shield_middleware(methods=NON_IDEMPOTENT_METHODS) ] ) To shield all non-idempotent methods and ``GET`` requests to ``/downloads/*`` URLs: .. code-block:: python import re app = web.Application( middlewares=[ shield_middleware( urls={ re.compile(r"^/downloads/.*$"): "GET", re.compile(r".*"): NON_IDEMPOTENT_METHODS, } ) ] ) :param methods: Methods to shield. Use ``aiohttp_middlewares.IDEMPOTENT_METHODS`` to shield requests to all idempotent methods (``POST``, ``PUT``, ``PATCH``, ``DELETE``). Do not mix with ``urls``. :param urls: URLs to shield. Supports passing collection of strings or regexps or dict where key is a string or regexp and value is a method or collection of methods to shield. Do not mix with ``methods``. :param ignore: When ``methods`` specified ignore next collection of URL strings or regexps from shielding. Do not mix with ``urls``. """ifnotmethodsandnoturls:raiseValueError("None of methods or urls argument passed.")ifmethodsandurls:raiseValueError("Both methods and urls arguments passed, while only one expected.")ifurlsandignore:raiseValueError("Unable to mix urls and ignore arguments.")# Lower case methods to shield (if any)methods_to_shield={item.lower()foriteminmethodsor[]}@web.middlewareasyncdefmiddleware(request:web.Request,handler:Handler)->web.StreamResponse:"""Shield handler execution if necessary."""request_method=request.method.lower()request_path=request.rel_url.pathlog_extra={"method":request.method,"path":request_path}# First attempt to process methods to shieldifmethods_to_shield:ifrequest_methodnotinmethods_to_shield:returnawaithandler(request)ifignoreandmatch_request(ignore,request_method,request_path):logger.debug("Ignore path from handler shielding.",extra=log_extra)returnawaithandler(request)logger.debug("Activate shield middleware by matched method",extra=log_extra)returnawaitasyncio.shield(handler(request))# Then attempt to shield handler by URLs collection / mappingifurlsandmatch_request(urls,request_method,request_path):logger.debug("Activate shield middleware by matched path",extra=log_extra)returnawaitasyncio.shield(handler(request))returnawaithandler(request)returnmiddleware