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
相关推荐
码事漫谈5 分钟前
C++模板元编程从入门到精通
后端
_風箏5 分钟前
Java【代码 14】一个用于判断磁盘空间和分区表是否需要清理的工具类
后端
_風箏8 分钟前
Java【代码 13】前端动态添加一条记后端使用JDK1.8实现map对象根据key的部分值进行分组(将map对象封装成指定entity对象)
后端
_風箏11 分钟前
Java【代码 12】判断一个集合是否包含另一个集合中的一个或多个元素 retainAll() 及其他方法
后端
Java中文社群26 分钟前
Coze开源版?别吹了!
人工智能·后端·开源
懂得节能嘛.30 分钟前
【SpringAI实战】ChatPDF实现RAG知识库
java·后端·spring
站大爷IP1 小时前
Python爬虫库性能与选型实战指南:从需求到落地的全链路解析
后端
小杰来搬砖1 小时前
在 Java 的 MyBatis 框架中,# 和 $ 的区别
后端
wenb1n1 小时前
【安全漏洞】隐藏在HTTP请求中的“隐形杀手”:Host头攻击漏洞深度剖析
java·后端
snakeshe10101 小时前
Java开发中的最佳实践与代码优化技巧
后端