You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An interceptor is a class annotated with the `@injectable()` decorator and implements the EllarInterceptor interface.
4
-
During request-response cycle, interceptors are called after middleware execution before route handler execution.
3
+
An interceptor is a class marked with the `@injectable()` decorator and adhering to the **`EllarInterceptor`** interface.
4
+
They execute additional logic before or after method invocation.
5
5
6
-
Interceptors have a set of useful capabilities which are inspired by the [AspectOriented Programming (AOP) technique](https://en.wikipedia.org/wiki/Aspect-oriented_programming){target="_blank"} technique.
7
-
They make it possible to:
6
+
Inspired by the principles of [Aspect-Oriented Programming (AOP)](https://en.wikipedia.org/wiki/Aspect-oriented_programming){target="_blank"},
7
+
interceptors offer several functionalities:
8
8
9
-
-bind extra logic before / after method execution
10
-
-transform the result returned from a function
11
-
-transform the exception thrown from a function
12
-
-extend the basic function behavior
13
-
-completely override a function depending on specific conditions (e.g., for caching purposes)
9
+
-Pre- and post-processing of method executions.
10
+
-Transformation of return values.
11
+
-Handling exceptions thrown during execution.
12
+
-Extension of method behavior.
13
+
-Conditional method override, useful for tasks like caching.
14
14
15
15
## **Basic**
16
-
Each interceptor implements the `intercept()` method, which takes two arguments.
17
-
The first one is the `ExecutionContext` instance (exactly the same object as for [guards](guards.md){target="_blank"}) and
18
-
`next_interceptor` awaitable function that executes the next interceptor in the execution chain.
16
+
Each interceptor class includes an `intercept()` method, accepting two parameters.
17
+
The first parameter is an instance of the `ExecutionContext` class, which is identical to the object used for [guards](guards.md).
18
+
The second parameter is a callable asynchronous function called `next_interceptor`,
19
+
responsible for executing the subsequent interceptor in the execution sequence.
19
20
20
21
```python
21
22
import typing as t
@@ -30,38 +31,27 @@ class EllarInterceptor(ABC):
30
31
) -> t.Any:
31
32
"""implementation comes here"""
32
33
```
34
+
33
35
!!! note
34
-
`intercept` function of interceptor class is an asynchronous function.
36
+
The `intercept()` method within an interceptor class is an asynchronous function.
35
37
36
38
## **Execution context**
37
-
The `ExecutionContext`adds several new helper methods that provide additional details about the current execution process.
38
-
These details can be helpful in building more generic interceptors that can work across a broad set of controllers, methods, and execution contexts.
39
-
Learn more about `ExecutionContext`[here](../basics/execution-context.md){target="_blank"}.
39
+
The `ExecutionContext`introduces several auxiliary methods that offer further insights into the ongoing execution process.
40
+
This additional information can be valuable for creating more versatile interceptors capable of functioning across various controllers, methods, and execution contexts.
41
+
For further details on `ExecutionContext`, refer to the documentation [here](../basics/execution-context.md).
40
42
41
43
## **Next Interceptor Handler**
42
-
The second argument, `next_interceptor`, in `intercept` of EllarInterceptor class is used to invoke the route handler method at some point in your interceptor.
43
-
If you don't call the `next_interceptor` method in your implementation of the `intercept()` method, the route handler method won't be executed at all.
44
-
45
-
This approach means that the `intercept()` method effectively wraps the request/response cycle.
46
-
As a result, you may implement custom logic **both before and after** the execution of the final route handler.
47
-
It's clear that you can write code in your `intercept()` method that executes before calling `next_interceptor()`,
48
-
but how do you affect what happens afterward? depending on the nature of the data returned by `next_interceptor()`,
49
-
further manipulation can be done before final response to the client.
50
-
51
-
Using Aspect Oriented Programming terminology, the invocation of the route handler
52
-
(i.e., calling `next_interceptor()`) is called a Pointcut, indicating that it's the point at which our
53
-
additional logic is inserted.
54
-
55
-
Consider, for example, an incoming `POST /car` request. This request is destined for the `create()` handler
56
-
defined inside the `CarController`. If an interceptor which does not call the `next_interceptor()`
57
-
method is called anywhere along the way, the `create()` method won't be executed.
58
-
Once `next_interceptor()` is called, the `create()` handler will be triggered. And once the response is returned,
59
-
additional operations can be performed on the data returned, and a final result returned to the client.
44
+
The `next_interceptor` parameter in the `intercept()` method of the `EllarInterceptor` class serves a crucial role in invoking the route handler method within your interceptor. Omitting the invocation of `next_interceptor` within your implementation of `intercept()` will result in the route handler method not being executed.
45
+
46
+
This mechanism essentially encapsulates the request/response cycle within the `intercept()` method. Consequently, you have the flexibility to incorporate custom logic both before and after the execution of the final route handler. While it's evident how to include code before calling `next_interceptor()`, influencing the behavior afterward depends on the data returned by `next_interceptor()`.
47
+
48
+
From the perspective of Aspect-Oriented Programming, the invocation of the route handler (i.e., calling `next_interceptor()`) represents a Pointcut, denoting the point at which additional logic is injected.
49
+
50
+
For instance, consider an incoming `POST /car` request targeting the `create()` handler in the `CarController`. If an interceptor fails to call `next_interceptor()` at any point, the `create()` method won't execute. However, once `next_interceptor()` is invoked, the `create()` handler proceeds. Subsequently, upon receiving the response, additional operations can be performed on the returned data before delivering the final result to the client.
60
51
61
52
62
53
## **Aspect interception**
63
-
The first use case we'll look at is to use an interceptor to log user interaction (e.g., storing user calls, asynchronously dispatching events or calculating a timestamp).
64
-
We show a simple LoggingInterceptor below:
54
+
Here's a simple example demonstrating the use of an interceptor to log user interactions. The **LoggingInterceptor** intercepts requests before and after the route handler execution to log relevant information such as start time, end time, and duration of execution.
65
55
66
56
```python
67
57
import typing as t
@@ -70,10 +60,8 @@ import time
70
60
from ellar.common import EllarInterceptor, IExecutionContext
71
61
from ellar.di import injectable
72
62
73
-
74
63
logger = logging.getLogger('ellar')
75
64
76
-
77
65
@injectable()
78
66
classLoggingInterceptor(EllarInterceptor):
79
67
asyncdefintercept(
@@ -82,17 +70,21 @@ class LoggingInterceptor(EllarInterceptor):
82
70
logger.info('Before Route Handler Execution...')
83
71
start_time = time.time()
84
72
73
+
# Invoke the next interceptor in the chain (or the route handler)
Interceptors, like controllers, providers, guards, and so on, can inject dependencies through their `constructor`.
82
+
This interceptor captures the timing of the request execution by recording the start time before invoking the route handler and calculating the duration after execution. It utilizes the logging module to output the relevant information.
83
+
84
+
Remember, like other components such as controllers and providers, interceptors can also inject dependencies through their constructor, enabling seamless integration with other parts of the application.
92
85
93
86
## **Binding interceptors**
94
-
In order to set up the interceptor, we use the `@UseInterceptors()` decorator imported from the `ellar.common` package.
95
-
Like **guards**, interceptors can be controller-scoped, method-scoped, or global-scoped.
87
+
To set up an interceptor, we utilize the `@UseInterceptors()` decorator from the `ellar.common` package. Similar to guards, interceptors can be scoped at the controller level, method level, or globally.
96
88
97
89
```python
98
90
from ellar.common import UseInterceptors, Controller
@@ -103,8 +95,7 @@ class CarController:
103
95
...
104
96
```
105
97
106
-
Note that we passed the LoggingInterceptor type (instead of an instance), leaving responsibility for instantiation to the framework and enabling dependency injection.
107
-
As with guards, we can also pass an in-place instance:
98
+
In the above code snippet, we apply the `UseInterceptors()` decorator to the `CarController` class, specifying `LoggingInterceptor` as the interceptor to be used. Note that we pass the type of the interceptor (not an instance), allowing the framework to handle instantiation and enabling dependency injection. Alternatively, we can directly pass an instance:
108
99
109
100
```python
110
101
from ellar.common import UseInterceptors, Controller
@@ -115,10 +106,9 @@ class CarController:
115
106
...
116
107
```
117
108
118
-
As mentioned, the construction above attaches the interceptor to every handler declared by this controller.
119
-
If we want to restrict the interceptor's scope to a single method, we simply apply the decorator at the method level.
109
+
This construction attaches the interceptor to every handler declared within the controller. If we want to limit the scope of the interceptor to a specific method, we apply the decorator at the method level.
120
110
121
-
In order to set up a global interceptor, we use the use_global_interceptors() method of the Ellar application instance:
111
+
For setting up a global interceptor, we utilize the `use_global_interceptors()` method of the Ellar application instance:
This approach ensures that the interceptor is applied to every request processed by the Ellar application, regardless of the controller or method handling the request.
123
+
132
124
## **Exception Handling**
133
-
You can also handle exception through on the process of request/response cycle before it gets handled by system exception handlers.
125
+
You can also manage exceptions during the request/response cycle before they are handled by system exception handlers.
134
126
135
127
```python
136
128
classCustomException(Exception):
@@ -145,7 +137,12 @@ class InterceptCustomException(EllarInterceptor):
145
137
try:
146
138
returnawait next_interceptor()
147
139
except CustomException as cex:
140
+
# Access the response object from the context
148
141
res = context.switch_to_http_connection().get_response()
142
+
# Set the status code to 400 for a custom exception
149
143
res.status_code =400
144
+
# Return a JSON response with the exception message
150
145
return {"message": str(cex)}
151
146
```
147
+
148
+
In the above code, the `InterceptCustomException` interceptor catches any `CustomException` raised during the execution of the request/response cycle. It then modifies the response object to set the status code to 400 and returns a JSON response containing the exception message. This allows for custom handling of exceptions within the interceptor before they are propagated to the system's exception handlers.
0 commit comments