第2章:路由与请求
2.1 路由定义
Scotty 提供了简洁的路由定义语法,支持所有标准 HTTP 方法:
使用到的方法如 get, post, put, delete, options 等方法
格式: method route action
- method 为 get, post, put, delete, options 等方法
- route 为 完整的 pathname, 带参数的 pathname, 正则 pathname, 通配符 等
- action 为 操作函数
添加以下代码到 app/Lib.hs 里面
app/Lib.hs
haskell
{-# LANGUAGE OverloadedStrings #-}
routes :: ScottyM ()
routes = do
-- 基本路由
get "/" $ text "Welcome to Scotty!"
-- 带参数的路由
get "/greet/:name" $ do
name <- pathParam "name"
text $ "Hello, " <> name <> "!"
-- JSON 响应
get "/api/status" $ json $ object
[ "status" .= ("ok" :: Text)
, "service" .= ("hello-scotty" :: Text)
]
-- POST 请求
post "/some_path" $ text "This is a POST method to /some_path"
-- PUT 请求
put "/some_path" $ text "This is a PUT method to /some_path"
-- DELETE 请求
delete "/some_path" $ text "This is a DELETE method to /some_path"
编译运行,然后用 curl 测试
bash
$ curl -XPOST http://127.0.0.1:3000/some_path
This is a POST method to /some_path
$ curl -XPUT http://127.0.0.1:3000/some_path
This is a PUT method to /some_path
$ curl -XDELETE http://127.0.0.1:3000/some_path
This is a DELETE method to /some_path
2.2 参数处理
路径参数
有时候我们需要将参数放到请求的链接上,我们可以通过 pathParam
来获取
haskell
-- URL: /users/123/posts/456
get "/users/:userId/posts/:postId" $ do
userId <- pathParam "userId"
postId <- pathParam "postId"
text $ "User: " <> userId <> ", Post: " <> postId
将代码添加都 routes 里面,编译运行,然后 curl 测试:
bash
$ curl http://127.0.0.1:3000/users/123/posts/231
User: 123, Post: 231
查询参数
通过 queryParam
来获取请求的参数, 如果参数是可选的话用 catch
来捕获异常,然后给一个默认值。
haskell
import Control.Exception (SomeException)
-- URL: /search?q=haskell&limit=10
get "/search" $ do
query <- queryParam "q" -- 必需参数
limit <- queryParam "limit" `catch` (\(_ :: SomeException) -> return "20") -- 可选参数
text $ "Query: " <> query <> ", Limit: " <> limit
将代码添加到 routes 里面,并从 Control.Exception
导入 SomeException
。 编译运行,然后 curl 测试:
bash
$ curl 'http://127.0.0.1:3000/search?q=haskell&limit=10'
Query: haskell, Limit: 10
$ curl 'http://127.0.0.1:3000/search?q=haskell'
Query: haskell, Limit: 20
安全的参数解析
有时候我们需要一个数字的参数,但用户传了一个文本进来,这时候我们需要处理以下:
haskell
import Network.HTTP.Types.Status
import Text.Read (readMaybe)
import Data.Text.Lazy (pack)
get "/users/:id" $ do
idText <- pathParam "id"
case readMaybe idText of
Nothing -> do
status status400
text "Invalid user ID"
Just userId -> do
-- 处理有效的用户ID
text $ "User ID: " <> pack (show (userId :: Int))
添加http-types
到 package.yml 的 dependencies 上, 将代码添加到 routes 里面, 编译运行,然后 curl 测试:
bash
$ curl 'http://127.0.0.1:3000/users/1000'
User ID: 1000
$ curl 'http://127.0.0.1:3000/users/str10'
Invalid user ID
2.3 请求体处理
表单数据
提交数据,做常用的就是表单提交,表单提交的数据可以通过 formParam
来获取. 如果参数是可选的话用 catch
来捕获异常,然后给一个默认值。
haskell
post "/login" $ do
username <- formParam "username" :: ActionM Text
password <- formParam "password" :: ActionM Text
-- 验证逻辑
if username == "admin" && password == "secret"
then text "Login successful"
else do
status status401
text "Invalid credentials"
将代码添加到 routes 里面, 编译运行,然后 curl 测试:
bash
$ curl http://127.0.0.1:3000/login -d username=username -d password=password
Invalid credentials
$ curl http://127.0.0.1:3000/login -d username=admin -d password=secret
Login successful
JSON 请求体
有时候我们直接 post JSON 的数据,我们可以通过 jsonData
来获取
haskell
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data User = User
{ userName :: Text
, userEmail :: Text
} deriving (Generic, Show)
instance FromJSON User
instance ToJSON User
post "/users" $ do
user <- jsonData :: ActionM User
liftIO $ print user -- 处理用户数据
json user -- 返回创建的用户
将代码添加到 routes 里面, 编译运行,然后 curl 测试:
bash
$ curl http://127.0.0.1:3000/users -d '{"userName": "admin", "userEmail": "admin@test.com"}'
{"userEmail":"admin@test.com","userName":"admin"}
2.4 文件上传
当我们上传文件的时候可以用 files
来获取表单提交里面文件的内容.
haskell
import Network.Wai.Parse (fileContent, fileName)
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as L
post "/upload" $ do
inFiles <- files
case inFiles of
[] -> text "No file uploaded"
((_, fileInfo):_) -> do
let content = fileContent fileInfo
name = fileName fileInfo
liftIO $ L.writeFile ("uploads/" ++ BC.unpack name) content
text $ "File uploaded: " <> pack (BC.unpack name)
添加bytestring
, wai-extra
到 package.yml 的 dependencies 上, 将代码添加到 routes 里面, 编译运行,然后 curl 测试:
bash
$ curl http://127.0.0.1:3000/upload -F file=@README.md
File uploaded: README.md