Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Middleware for per-request data

In clojure, I can write something like this:

(defn wrap-my-header
  [handler]
  (fn [request]
    (let [request (if (get-in request [:headers "my-header"])
                    (assoc request :has-my-header? true)
                    request)]
      (handler request))))

In this middleware, I'm checking if I have a non-nil value in my-header in :headers, if yes, I'll attach some data in the request map. This demonstrates that I can treat request and response as a somewhat "stateful" data.

I'm still new in haskell and wanted to do similar things with scotty. After looking at the type of middleware, I can create a middleware like this:

myMiddleware :: Middleware 
myMiddleware app req respond = app req respond

After staring at the type for a long time, I still don't know how to do it. Some reading and thinking makes me assume that this is not possible, Middleware can only only short-circuit the handler and/or alter the generated response. Is this true?


like image 580
autumn322 Avatar asked Oct 15 '18 04:10

autumn322


People also ask

What is middleware in HTTP request?

Middleware is a design pattern to eloquently add cross cutting concerns like logging, handling authentication, or gzip compression without having many code contact points.

How do I add middleware to the application request pipeline?

Now, we need to add our custom middleware in the request pipeline by using Use extension method as shown below. We can add middleware using app. UseMiddleware<MyMiddleware>() method of IApplicationBuilder also. Thus, we can add custom middleware in the ASP.NET Core application.

What is a middleware in Fastapi?

A "middleware" is a function that works with every request before it is processed by any specific path operation. And also with every response before returning it. It takes each request that comes to your application. It can then do something to that request or run any needed code.


1 Answers

This confused me for a long time too! But figuring it out gave me a helpful technique for understanding Haskell library types.

First I'll start with my middleware being undefined:

myMiddleware :: Middleware
myMiddleware = undefined

So what is Middleware? The key is to look at the definition of the type:

type Middleware = Application -> Application

Let's start at the first layer (or level of abstraction) by having the middleware take an Application and return an Application. We don't know how to modify an application, so we'll return exactly what's passed in for now.

myMiddleware :: Application -> Application
myMiddleware theOriginalApp = theOriginalApp

But what is an Application? Again, let's turn to Hackage:

type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

An Application is a function! We may not know exactly what each part is supposed to do or be, but we can find out. Let's replace Application in our type signature with the function type:

myMiddleware :: (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived) 
             -> (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived)
myMiddleware theOriginalApp = theOriginalApp

Now we can see this type should allow us to access a Request! But how do we use it?

We can expand theOriginalApp in the function definition into a lambda expression that matches the return type:

myMiddleware :: (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived) 
             -> (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived)
myMiddleware theOriginalApp = (\req sendResponse -> undefined)

We can do whatever we want with the request now:

myMiddleware :: (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived) 
             -> (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived)
myMiddleware theOriginalApp = (\req sendResponse ->
  let myModifiedRequest = addSomeHeadersIfMissing req in
    undefined)

Now what about that undefined? Well, we're trying to match our lambda to the type of that return function, which takes a Request and a function (that we don't care about) and returns an IO ResponseReceived.

So, we need something that can use myModifiedRequest and return an IO ResponseReceived. Luckily our type signature indicates that theOriginalApp has the right type! To make it fit, we only need to give it the sendResponse function too.

myMiddleware :: (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived) 
             -> (Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived)
myMiddleware theOriginalApp = (\req sendResponse ->
  let myModifiedRequest = addSomeHeadersIfMissing req in
    theOriginalApp myModifiedRequest sendResponse)

And that's it, that will work! We can improve the readability by simplifying the type annotation back to Middleware, and getting rid of the lambda. (We can also eta-reduce and remove the sendResponse term from both the arguments and the definition, but I think it's clearer if it stays.)

The result:

myMiddleware :: Middleware
myMiddleware theOriginalApp req sendResponse =
  let myModifiedRequest = addSomeHeadersIfMissing req in
    theOriginalApp myModifiedRequest sendResponse
like image 81
Libby Avatar answered Sep 30 '22 00:09

Libby