Middleware
A Middleware
is basically an instance that will do something beforehand and postprocess the results afterwards. In carefree-drawboard
🎨, we need Middleware
because:
- We want to define a powerful interface (i.e.,
ISocketMessage
) that can handle most of the applications. - We want to make it convenient to use the framework and let users to do as less things as possible.
And Middleware
is designed to be the bridge between these two targets. Here's the interface:
class Subscription(str, Enum):
ALL = "__all__"
class IMiddleware(ABC):
hash: str
plugin: IPlugin
# abstract
@property
@abstractmethod
def subscriptions(self) -> Union[List[PluginType], Subscription]:
pass
@abstractmethod
async def process(self, response: Any) -> ISocketMessage:
"""
If `can_handle_message` is `False`, the `response` here could be anything except
`ISocketMessage`, because in this case if `response` is already an `ISocketMessage`,
it will be returned directly in the `__call__` method.
"""
# optional callbacks
@property
def can_handle_message(self) -> bool:
return False
async def before(self, request: ISocketRequest) -> None:
self.hash = request.hash
# api
def __init__(self, plugin: IPlugin) -> None:
self.plugin = plugin
async def __call__(self, response: Any) -> Any:
if (
self.subscriptions != Subscription.ALL
and self.plugin.type not in self.subscriptions
):
return response
if isinstance(response, ISocketMessage) and not self.can_handle_message:
return response
return await self.process(response)
And here's an example of how we use it:
async def __call__(self, data):
middlewares = [...]
for middleware in middlewares:
await middleware.before(data)
response = ...
for middleware in middlewares:
await response = middleware(response)
return response
Built-in Middlewares
ResponseMiddleware
Source codes: cfdraw/plugins/middlewares/response.py
This middleware is used to convert the raw response from the plugins to an ISocketMessage
instance.
Currently, it handles str
, PIL.Image
, or list of them.
SendSocketMessageMiddleware
Source codes: cfdraw/plugins/middlewares/send_message.py
After the ISocketMessage
, we need to send it to the frontend. In order to avoid manually sending it here and there, we use this middleware to send it automatically.
This middleware also injects some special attributes to the response data:
class SendSocketMessageMiddleware(IMiddleware):
@property
def subscriptions(self) -> Union[List[PluginType], Subscription]:
return Subscription.ALL
@property
def can_handle_message(self) -> bool:
return True
async def process(self, response):
if response is None:
return None
if self.plugin.extra_responses:
if response.data.final is None:
response.data.final = {}
response.data.final["extra"] = self.plugin.extra_responses
response.data.injections = self.plugin.injections
await self.plugin.send_message(response)
return response
TimerMiddleware
Source codes: cfdraw/plugins/middlewares/timer.py
This middleware only serves IFieldsPlugin
, and it is relatively simple:
class TimerMiddleware(IMiddleware):
@property
def can_handle_message(self) -> bool:
return True
@property
def subscriptions(self) -> List[PluginType]:
return [PluginType.FIELDS]
async def process(self, response: ISocketMessage) -> ISocketMessage:
self.plugin.elapsed_times.end()
response.data.elapsedTimes = self.plugin.elapsed_times
return response
Check ElapsedTimes for the API reference.
Customize Middlewares
To define your own middleware, you can:
- Go to
cfdraw/plugins/middlewares
, create a file, and implement it. - Go to
cfdraw/plugins/base.py
, create a new class which inherits from theISocketPlugin
class. - Re-implement the
middlewares
property of the new class, make sure include your new middleware in the list.