Project: userver (App Definition and Development / Streaming & Messaging)
File: http_server_middlewares.md

Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component: Chooses whether to pass the request to the next component in the pipeline. Can perform work before and after the next component in the pipeline. userver provides interfaces to implement these pipeline pieces and to configure them as well as the resulting pipeline for the http server. The general idea behind middlewares is no different from that in other web-frameworks like , , , , etc., but the exact implementation details differ here and there. If you've already made yourself familiar with how middlewares are implemented in userver you may want to skip straight to @ref middlewaresusageand_configuration "Usage and configuration". Every http request handled by the components::Server is processed in a number of steps: The request is parsed from the byte stream coming from a network connection The request is traced, accounted for, decompressed/rate-limited/authorized/etc if needed, parsed into application-requested format (say, JSON), logged if configured to do so, and a bunch of other steps The request is routed into corresponding server::handlers::HttpHandlerBase The response created by the handler is traced, accounted for, logged if configured to do so... you get the idea The response is serialized into byte stream and sent to the wire Steps 2. and 4. are implemented by a number of logically-independent middlewares, which allows us to tune/enhance/rearrange things without going into massive refactoring, and allows our users to adjust the pipeline as they see fit. An important detail of the middlewares implementation is that technically middleware is not a @ref scripts/docs/en/userver/componentsystem.md "Component", but rather a @ref userverclients "Client": given M middlewares and H handlers, there will be M x H instances of middlewares in total, M instances for each handler. Such arguably more complicated approach is justified by the configurability-freedom it provides: instead of somehow adjusting the middleware behavior in the code (based on a route, for example), one can configure the middleware at per-handler basis in exactly the same way most things in userver are configured: by a static config. Middlewares very well might want to interact with the component system of userver, be that for sharing some common resource (say, global rate-limiting) or for performing database/http queries. Since a middleware is not a component, it doesn't have a way to lookup it's dependencies from the @ref components::ComponentContext itself, but rather the dependencies should be injected into a middleware instance. This is where a MiddlewareFactory comes into

Every @ref server::middlewares::HttpMiddlewareBase "Middleware" is paired with a corresponding @ref server::middlewares::HttpMiddlewareFactoryBase "Factory", which is a Component, and is responsible for creating middleware instances and injecting required dependencies into these instances. It goes like this: when a handler is constructed, it looks up all the factories specified by the pipeline configuration, requests each factory to create a middleware instance, and then builds the resulting pipeline from these instances. So, to emphasize once again: a Middleware instance is not a Component but rather a Client, and there might be multiple instances of the same Middleware type, but a MiddlewareFactory is a Component, hence is a singleton. For reference, this is the userver-provided default middlewares pipeline: @snippet core/src/server/middlewares/configuration.cpp Middlewares sample - default pipeline In general, one should be very careful when modifying the response after the downstream part of the pipeline completed, and the reason for that is simple: the modification could be overwritten by the upstream part. This is especially apparent when exceptions are involved: consider the case when an exception is propagating through the pipeline, and a middleware is adding a header to the response. Depending on the exception type and where in the pipeline middleware is put, the header may or may not be overridden by the ExceptionsHandling middleware. Due to this, response-modifying middlewares should be either put before ExceptionsHandling middleware for their changes to reliably take effect if the downstream pipeline threw, or a middleware should handle downstream exceptions itself. Middlewares also should never throw on their own: it's ok to let the downstream exception pass through, but throwing from the middlewares logic could lead to some nonsensical responses being sent to the client, up to violating the http specification. The default userver-provided middlewares pipeline handles all these cases and is guaranteed to still have all the tracing headers and a meaningful status code (500, likely) present for the response even if the user-built part of the middleware pipeline threw some random exception, but if you reorder or hijack the default pipeline, you are on your own. @anchor middlewaresusageand_configuration This is how a minimal implementation of a middleware looks like: @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - minimal implementation It doesn't have any logic in it and just passes the execution to the downstream. This is how a factory for this middleware looks: @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - minimal factory implementation which feels too verbose for the amount of logic the code performs, so we have a shortcut version, which does the same and also passes the handler into the middleware constructor. Given the middleware that performs some logic @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - some middleware implementation the factory implementation is just this: @snippet

Middlewares sample - some middleware factory implementation Do not forget to add components configs: @snippet samples/httpmiddlewareservice/static_config.yaml Middlewares sample - noop-middleware and server-middleware components configs Basically, the whole point of having MiddlewareFactory-ies separated from Middleware-s, is to have a possibility to configure a middleware at a per-handler basis. In the snippet above that's what "handler-middleware.header-value" is for: given the middleware (which actually resembles pretty close to how tracing headers are set to the response in userver) @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - configurable middleware implementation and the factory implementation @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - configurable middleware factory implementation one can configure the middleware behavior (header value, in this particular case) in the handler's static config. If a global configuration is desired (that is, for every middleware instance there is), the easiest way to achieve that would be to have a configuration in the Factory config, and for Factory to pass the configuration into the Middleware constructor. This takes away the possibility to declare a Factory as a SimpleHttpMiddlewareFactory, but we find this tradeoff acceptable (after all, if a middleware needs a configuration it isn't that "Simple" already). Do not forget to add components configs: @snippet samples/httpmiddlewareservice/static_config.yaml Middlewares sample - handler-middleware component config Now, after we have a middleware and its factory implemented, it would be nice to actually use the middleware in the pipeline. userver provides two interfaces for configuring middleware pipelines: one for a server-wide configuration, and one for a more granular per-handler configuration. The server-wide pipeline is server::middlewares::PipelineBuilder. In its simple form, it takes server::middlewares::DefaultPipeline and appends the given middlewares to it, which looks like this: @snippet samples/httpmiddlewareservice/static_config.yaml Middlewares sample - pipeline builder configuration If a more sophisticated behavior is desired, derive from server::middlewares::PipelineBuilder and override its `BuildPipeline` method. Then set the custom pipeline component's name in the config of components::Server: ``` server: middleware-pipeline-builder: custom-pipeline-builder ``` Remember that messing with the default userver-provided pipeline is error-prone and leaves you on your own. To configure the pipeline at a per-handler basis userver provides server::middlewares::HandlerPipelineBuilder interface. By default, it returns the server-wide pipeline without any modifications to it. To change the behavior one should derive from it, override the `BuildPipeline` method and specify the builder as the pipeline-builder for the handler. For example: @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - custom handler pipeline builder and to use the class as a pipeline builder we should append it to the @ref components::ComponentList "ComponentList" @snippet samples/httpmiddlewareservice/httpmiddlewareservice.cpp Middlewares sample - custom handler pipeline builder registration and specify as a pipeline-builder for the handler (notice the middlewares.pipeline-builder section): @snippet samples/httpmiddlewareservice/static_config.yaml Middlewares sample - custom handler pipeline builder configuration