在 WAI 中使用请求参数不会 "IO" 导致问题

Using Request Parameters in WAI Without "IO" Causing Problems

我正在为使用 WAI 获取 API 和 运行 的基础知识而苦苦挣扎。主要问题是处理 IO 感染一切。我相信一旦我更好地理解 Monads,我的问题就会解决,但希望这个问题的答案将是一个很好的起点。

以下是一个简短的示例,它在根目录 url 上提供静态 html 页面,并接受用户名 /api/my-data 的请求 return对应用户的数据。我无法弄清楚如何使用请求的 body IO Bytestring 进行地图查找、检索数据并将结果发送回编码为 json.

我尝试使用 fmap 提取 Bytestring 然后 unpack 将其转换为字符串以供查找,但无论我做什么,我最终都会遇到类型错误与该死的 IO monad 有关。

无论如何,这是相关代码:

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import qualified Data.Map as Map
import Data.Aeson
import Network.Wai
import Network.Wai.Parse
import Network.Wai.Middleware.Static
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)

userInfo :: Map.Map String (Map.Map String String)
userInfo = Map.fromList [("jsmith", Map.fromList [("firstName", "John"),
                                                  ("lastName", "Smith"),
                                                  ("email", "jsmith@gmail.com"),
                                                  ("password", "Testing012")]),
                         ("jeff.walker", Map.fromList [("firstName", "Jeff"),
                                                       ("lastName", "Walker"),
                                                       ("email", "jeff.walker@gmail.com"),
                                                       ("password", "Testing012")])]

getUserInfo :: B.ByteString -> Map.Map String String
getUserInfo body =
  case Map.lookup (B8.unpack body) userInfo of
    (Just x) -> x
    Nothing  -> Map.empty

app :: Application
app request respond = do
  case rawPathInfo request of
    "/"            -> respond index
    "/api/my-data" -> respond $ myData (getUserInfo (requestBody request))
    _              -> respond notFound

index :: Response
index = responseFile
  status200
  [("Content-Type", "text/html")]
  "../client/index.html"
  Nothing

myData :: IO (Map.Map String String) -> Response
myData user = responseLBS
  status200
  [("Content-Type", "application/json")]
  (encode user)


notFound :: Response
notFound = responseLBS
  status404
  [("Content-Type", "text/plain")]
  "404 - Not Found"

main :: IO ()
main = do
  putStrLn $ "http://localhost:8080/"
  run 8080 $ staticPolicy (addBase "../client/") $ app

这会导致此错误:

src/Core/Main.hs:32:54:
    Couldn't match expected type ‘B8.ByteString’
                with actual type ‘IO B8.ByteString’
    In the first argument of ‘getUserInfo’, namely
      ‘(requestBody request)’
    In the first argument of ‘myData’, namely
      ‘(getUserInfo (requestBody request))’

我可以轻松地将 getUserInfomyData 的类型更改为 IO Bytestring -> IO (Map.Map String String)IO (Map.Map String String) -> Response,但我最终会遇到更多类型错误。类型让我头晕目眩。

因为 requestBody 具有以下类型:

requestBody :: Request -> IO ByteString

生成的表达式不能直接传递给接受 ByteStringgetUserInfo

你可以做的是,假设 Application 只是 Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived 并且在 app 中你在 IO monad 中,提取 ByteString使用 do 这样的表示法:

str <- requestBody request 

然后将str传给getUserInfo,像这样:

app :: Application
app request respond = do
  str <- requestBody request
  case rawPathInfo request of
    "/"            -> respond index
    "/api/my-data" -> respond $ myData (getUserInfo str)
    _              -> respond notFound

此时myData可以简单的接受一个Map:

myData :: Map.Map String String -> Response

在深入了解 WAI 之前,您绝对应该阅读更多关于 monads 和 IO 的一般知识。