Haskell Scotty 网络编程指南-路由与请求

第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
相关推荐
编程乐趣1 分钟前
C#版本LINQ增强开源库
后端
tonydf2 分钟前
记一次近6万多个文件的备份过程
windows·后端
前端付豪2 分钟前
13、你还在 print 调试🧾?教你写出自己的日志系统
后端·python
加瓦点灯3 分钟前
Spring AI + Milvus 实现 RAG 智能问答系统实战
后端
JohnYan4 分钟前
Bun技术评估 - 07 S3
javascript·后端·bun
vivo互联网技术6 分钟前
号码生成系统的创新实践:游戏周周乐幸运码设计
redis·后端·架构
这里有鱼汤7 分钟前
hvPlot:用你熟悉的 Pandas,画出你没见过的炫图
后端·python
寻月隐君9 分钟前
告别 Vec!掌握 Rust bytes 库,解锁零拷贝的真正威力
后端·rust·github
程序员岳焱10 分钟前
Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析
后端·mysql·性能优化
掘金一周14 分钟前
别再用 100vh 了!移动端视口高度的终极解决方案| 掘金一周7.3
前端·后端