每个请求数据的中间件

时间:2018-10-15 04:24:58

标签: haskell clojure haskell-wai

clojure中,我可以这样写:

(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))))

在此中间件中,我正在检查my-header的{​​{1}}中是否有非null值,如果是,我将在:headers映射中附加一些数据。这表明我可以将requestrequest视为有点“有状态”的数据。

我仍然对Haskell陌生,并想对response做类似的事情。在查看middleware的类型之后,我可以创建这样的中间件:

scotty

凝视了很长时间之后,我仍然不知道该怎么做。一些阅读和思考使我认为这是不可能的,myMiddleware :: Middleware myMiddleware app req respond = app req respond 仅能使处理程序短路和/或更改生成的响应。这是真的吗?


1 个答案:

答案 0 :(得分:5)

这也让我很困惑!但是弄清楚它为我提供了一种了解Haskell库类型的有用技术。

首先,我将从未定义的中间件开始:

myMiddleware :: Middleware
myMiddleware = undefined

那么Middleware是什么?关键是看一下definition of the type

type Middleware = Application -> Application

让我们从第一层(或抽象级别)开始,让中间件获取一个Application并返回一个Application。我们不知道如何修改应用程序,因此我们将完全返回当前传递的信息。

myMiddleware :: Application -> Application
myMiddleware theOriginalApp = theOriginalApp

但是什么是应用程序?同样,让我们​​turn to Hackage

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

应用程序就是一种功能!我们可能不确切知道每个部分应该做什么或要做什么,但是我们可以找到答案。让我们将类型签名中的Application替换为函数类型:

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

现在我们可以看到这种类型应该允许我们访问Request!但是我们如何使用它呢?

我们可以将函数定义中的theOriginalApp扩展为与返回类型匹配的lambda表达式:

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

我们现在可以处理请求:

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

现在undefined呢?好吧,我们正在尝试将lambda与该返回函数的类型进行匹配,该返回函数接受一个Request和一个函数(我们不在乎)并返回一个IO ResponseReceived

因此,我们需要一些可以使用myModifiedRequest并返回IO ResponseReceived的东西。幸运的是,我们的类型签名表明theOriginalApp具有正确的类型!为了使其适合,我们也只需要赋予它sendResponse函数。

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)

就是这样,它将起作用!我们可以通过将类型注释简化回Middleware并摆脱lambda来提高可读性。 (我们也可以eta-reduce,并将sendResponse项从参数和定义中删除,但我认为如果保留则更清楚。)

结果:

myMiddleware :: Middleware
myMiddleware theOriginalApp req sendResponse =
  let myModifiedRequest = addSomeHeadersIfMissing req in
    theOriginalApp myModifiedRequest sendResponse