The Request Pipeline
The Request Pipeline (or simply Pipeline) lies at the core of Veronica's functionality. It describes how a Route generates a Response given a certain Request. Briefly, a Pipeline consists of a series of ordered stages of processing that Request and Response objects need to go through.
The most intuitive way to understand how the Pipeline operates is through a practical example. Therefore, we are now going to present the requirements of a hypothetical Veronica Application. Next, we will see how to structure a Pipeline that suites our needs.
Suppose that we want to develop a website with a simple registration and login functionality. We need such feature to restrict access to parts of your site to registered users only, and you need to validate the data submitted by new users when they sign up. Upon registration, you want to send a confirmation message to the user's email inbox, in order to verify the identity of your new subscriber. Also, we want our application to always make use of database transactions in order to avoid data corruption.
Now, let's try to define the steps of the process that registers users to your website:
- The first step would be to validate the data submitted by users. We could, for example, enforce a user to enter a valid username that is not already taken by another user. Also, we would like to verify that the user's email address is indeed a valid address.
- After validating the user input and before storing the user's data on our website, we would like to start a database transaction, in order to avoid data corruption.
- We are now ready to store the newly registered user's data in the database, and we proceed by doing so.
- Next, we are done interacting with the database, and therefore we would like to close the database transaction.
- Then, we would like to generate a confirmation message to display to the user, reminding them to check their email inbox.
- We are now ready to send the user the generated response, which displays the confirmation message.
- Finally, we want to send the confirmation email message to the user's address. We specifically want to do this asyncronously, in order to avoid having to wait for the email message to be sent before sending the http response.
Since we have clearly defined all of the necessary steps in the user registration process, we are now ready to design our Pipeline:
- Pre-Filters are the first group of stages in the Pipeline. They are responsible for processing the Request before a Response is generated. In general, they represent all of the checks that need to be executed before the action requested by the user is performed.
In our case, both the input validation and the database transaction start are steps that need to be performed before storing the user's data in the database, and are executed by two different Pre-Filters. - The Request Handler is responsible for executing the action requested by the user, once all of the preliminary checks have passed. In our case, this would be expressed by storing the user's data in the database. The Request Handler is also responsible for generating a Response object, which contains the logical representation of the output that will be sent to the client.
- Post-Filters are executed immediately after the Request gets processed by the Request Handler. Compared to Pre-Filters, they have access to the Response object and are therefore able to manipulate it along the Request.
In our practical example, the database transaction end would be executed by a Post-Filter, since the transaction must be closed after the user's data is stored in the database. - At this stage, the Response is ready to be sent to the client. But before we do so, the Response must be rendered. In fact, the Response is still purely logical and does not contain a concrete representation of the body of the Http response that will be output.
This is where the Response Renderer comes into play. Its duty is to attach a body to the Response object, which will be sent as the body of the Http response.
In our case, we can think of the Response object before rendering as containing a reference to the html file that needs to be sent to the user. After the rendering process, the Response will hold the Http response body that will be output. - Finally, the Request and Response objects are managed by the Post-Processors. These stages have access to the rendered Response and can process it before the Http response is output. Alternatively, they can schedule asynchronous operations that are completed after the response is sent to the client.
In our example, the confirmation email would be sent by an asynchronous Post-Processor, that starts a new thread responsible for sending the message.
Pipeline Exceptions
The pipeline structure we have described until now is perfectly able to model the problem that we want to tackle. But what happens when things don't go as expected? For instance, take the PreFilter that is supposed to validate the user's data: how should this filter behave when the input data is not valid?
To answer this question, we should consider the problem both from the user's perspective and from the programmer's perspective
- From the programmer's perspective, we would want our application to interrupt the execution of the pipeline, in order to avoid inserting invalid data into our database.
- From the user's perspective, we would want the application to show us a nice error message, explaining why the data that we inserted is invalid.
To do so, we make use of PipelineBreakExceptions. When thrown by any pipeline stage, these exceptions signal that the execution of the pipeline should be interrupted. Moreover, since these exceptions may prevent the generation of a Response object, they specify what Response should be rendered by the ResponseRenderer and eventually output.
ResponseRenderer objects instead are able to throw their own type of exception, namely ResponseRenderingException objects. These special exceptions are an extension of the PipelineBreakException, and they require the Response to be already rendered. That means, that the Response field of the ResponseRenderingException should include the final response body, which corresponds to the body of the Http response that will be output.
Updated over 5 years ago