在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
映射中附加一些数据。这表明我可以将request
和request
视为有点“有状态”的数据。
我仍然对Haskell陌生,并想对response
做类似的事情。在查看middleware的类型之后,我可以创建这样的中间件:
scotty
凝视了很长时间之后,我仍然不知道该怎么做。一些阅读和思考使我认为这是不可能的,myMiddleware :: Middleware
myMiddleware app req respond = app req respond
仅能使处理程序短路和/或更改生成的响应。这是真的吗?
答案 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