@@ -5,10 +5,11 @@ request.
55
66This page covers:
77
8- - [X] Introduction to middlewares.
8+ - [X] Introduction to BlackSheep middlewares.
99- [X] How to use function decorators to avoid code repetition.
1010- [X] Middleware management with MiddlewareList and MiddlewareCategory.
1111- [X] Organizing middlewares by categories and priorities.
12+ - [X] How to integrate ASGI middlewares.
1213
1314## Introduction to middlewares
1415
@@ -106,7 +107,7 @@ When middlewares are defined for an application, resolution chains are built at
106107its start. Every handler configured in the application router is replaced by a
107108chain, executing middlewares in order, down to the registered handler.
108109
109- ## Middleware management with MiddlewareList and MiddlewareCategory
110+ ## Middleware management
110111
111112/// admonition | New in BlackSheep 2.4.4
112113 type: info
@@ -350,7 +351,217 @@ def headers(additional_headers: Tuple[Tuple[str, str], ...]):
350351 return decorator
351352```
352353
353- !!! warning
354- The ` ensure_response ` function is necessary to support scenarios
355- when the request handlers defined by the user doesn't return an instance of
356- Response class (see _ [ request handlers normalization] ( request-handlers.md ) _ ).
354+ /// admonition | Additional dependencies.
355+ type: warning
356+
357+ The ` ensure_response ` function is necessary to support scenarios
358+ when the request handlers defined by the user doesn't return an instance of
359+ Response class (see _ [ request handlers normalization] ( request-handlers.md ) _ ).
360+
361+ ///
362+
363+ ## How to integrate ASGI middlewares
364+
365+ BlackSheep middlewares cannot be mixed with ASGI middlewares because they use different
366+ code APIs. However, the ` Application ` class itself in BlackSheep supports the signature
367+ of ASGI middlewares, and can be mixed with them at the application level instead of the
368+ middleware chain level.
369+
370+ Consider the following example, where the ` Starlette ` ` TrustedHostMiddleware ` is used
371+ with a BlackSheep application, following the pattern described in the Starlette
372+ documentation at [ _ Using Middleware In Other Frameworks_ ] ( https://starlette.dev/middleware/#using-middleware-in-other-frameworks ) .
373+
374+ ``` python {hl_lines='12'}
375+ from blacksheep import Application, get
376+ from starlette.middleware.trustedhost import TrustedHostMiddleware
377+
378+
379+ app = Application()
380+
381+
382+ @get (" /" )
383+ async def home ():
384+ return " Hello!"
385+
386+ app = TrustedHostMiddleware(app, allowed_hosts = [" localhost" ])
387+ ```
388+
389+ Below is an example where ` FastAPI-Events ` is used with a BlackSheep application:
390+
391+ ``` python {hl_lines='27-30'}
392+ from blacksheep import Application, get
393+ from fastapi_events.dispatcher import dispatch
394+ from fastapi_events.middleware import EventHandlerASGIMiddleware
395+ from fastapi_events.handlers.local import LocalHandler
396+ from fastapi_events.typing import Event
397+
398+
399+ app = Application()
400+
401+
402+ async def handle_all_events (event : Event):
403+ """ Handler for all events"""
404+ print (f " Event received: { event} " )
405+
406+
407+ # Create a local handler for events
408+ local_handler = LocalHandler()
409+ local_handler.register(handle_all_events)
410+
411+
412+ @get (" /" )
413+ async def home ():
414+ dispatch(" my-fancy-event" , payload = {" id" : 1 }) # Emit events anywhere in your code
415+ return " Hello!"
416+
417+
418+ app = EventHandlerASGIMiddleware(
419+ app,
420+ handlers = [local_handler]
421+ )
422+ ```
423+
424+ ### Creating a custom application class for ASGI middleware management
425+
426+ While the direct wrapping approach shown above works well for simple cases, you may
427+ want to create a custom application class if you need to manage multiple ASGI middlewares
428+ or prefer a more explicit API that's consistent with BlackSheep's middleware system.
429+
430+ The following example shows how to define such a custom class that supports adding ASGI
431+ middlewares through a dedicated method:
432+
433+ ``` python
434+ # yourapp.py
435+ from typing import Callable
436+
437+ from blacksheep import Application, Router
438+ from blacksheep.server.routing import MountRegistry
439+ from rodi import ContainerProtocol
440+
441+
442+ class CustomApplication (Application ):
443+ """
444+ Application subclass that supports ASGI middleware at the application level.
445+
446+ ASGI middleware are applied before BlackSheep processes the request, providing
447+ a clean separation between ASGI-level and BlackSheep-level middleware.
448+
449+ Usage:
450+ app = CustomApplication()
451+
452+ # Add ASGI middleware (order matters - first added wraps outermost)
453+ app.add_asgi_middleware(some_asgi_middleware)
454+ app.add_asgi_middleware(another_asgi_middleware)
455+ """
456+
457+ def __init__ (
458+ self ,
459+ * ,
460+ router : Router | None = None ,
461+ services : ContainerProtocol | None = None ,
462+ show_error_details : bool = False ,
463+ mount : MountRegistry | None = None ,
464+ ):
465+ super ().__init__ (
466+ router = router,
467+ services = services,
468+ show_error_details = show_error_details,
469+ mount = mount,
470+ )
471+ self ._asgi_chain = super ().__call__
472+ self ._asgi_middlewares: list[Callable] = []
473+
474+ def add_asgi_middleware (self , middleware : Callable) -> None :
475+ """
476+ Adds an ASGI middleware to the application.
477+
478+ The middleware should be a callable with signature:
479+ async def middleware(app, scope, receive, send) -> None
480+
481+ Or a factory that returns such a callable:
482+ def middleware_factory(app) -> Callable
483+
484+ Middleware are applied in the order they are added, with the first
485+ added being the outermost layer.
486+
487+ Args:
488+ middleware: An ASGI middleware callable or factory
489+ """
490+ self ._asgi_middlewares.append(middleware)
491+
492+ async def start (self ):
493+ self ._asgi_chain = self ._build_asgi_chain()
494+ return await super ().start()
495+
496+ async def __call__ (self , scope , receive , send ):
497+ return await self ._asgi_chain(scope, receive, send)
498+
499+ def _build_asgi_chain (self ) -> Callable:
500+ """
501+ Builds the ASGI middleware chain, with the base Application.__call__
502+ as the innermost application.
503+ """
504+ # Start with the base application handler
505+ app = super ().__call__
506+
507+ # Wrap with each middleware in reverse order (last added wraps innermost)
508+ for middleware in reversed (self ._asgi_middlewares):
509+ # Check if it's a factory (single parameter) or direct middleware
510+ import inspect
511+ sig = inspect.signature(middleware)
512+ params = list (sig.parameters.keys())
513+
514+ # Factory pattern: middleware(app) -> callable (single parameter)
515+ if len (params) == 1 :
516+ app = middleware(app)
517+ # Direct ASGI callable: needs to be wrapped
518+ elif len (params) == 3 and params == [' scope' , ' receive' , ' send' ]:
519+ # Wrap to provide app parameter
520+ wrapped_app = app
521+ async def asgi_wrapper (scope , receive , send , mw = middleware, inner = wrapped_app):
522+ await mw(inner, scope, receive, send)
523+ app = asgi_wrapper # type: ignore
524+ else :
525+ raise TypeError (
526+ f " ASGI middleware must have signature (app, scope, receive, send) "
527+ f " or be a factory with signature (app). Got: { sig} "
528+ )
529+
530+ return app
531+ ```
532+
533+ The following example demonstrates how to use the custom ` CustomApplication ` class.
534+ Notice the use of a lambda function to wrap the middleware initialization—this factory
535+ pattern ensures the middleware receives the application instance correctly:
536+
537+ ``` python
538+ from blacksheep import get
539+ from fastapi_events.dispatcher import dispatch
540+ from fastapi_events.middleware import EventHandlerASGIMiddleware
541+ from fastapi_events.handlers.local import LocalHandler
542+ from fastapi_events.typing import Event
543+ from yourapp import CustomApplication
544+
545+
546+ app = CustomApplication()
547+
548+
549+ async def handle_all_events (event : Event):
550+ """ Handler for all events"""
551+ print (f " Event received: { event} " )
552+
553+
554+ # Create a local handler for events
555+ local_handler = LocalHandler()
556+ local_handler.register(handle_all_events)
557+
558+
559+ @get (" /" )
560+ async def home ():
561+ dispatch(" my-fancy-event" , payload = {" id" : 1 }) # Emit events anywhere in your code
562+ return " Hello!"
563+
564+
565+ # Note how the factory pattern is used below:
566+ app.add_asgi_middleware(lambda app : EventHandlerASGIMiddleware(app, handlers = [local_handler]))
567+ ```
0 commit comments