Sử dụng thư viện Req để thực hiện HTTP Request trong Haskell

Giới thiệu về thư viện Req

Req là một thư viện HTTP client cấp cao trong Haskell, nổi bật với tính an toàn về kiểu dữ liệu, khả năng mở rộng và dễ sử dụng. Thư viện này tích hợp tốt với hệ thống kiểu của Haskell và hỗ trợ xử lý các request HTTP một cách rõ ràng, mạnh mẽ.

Cài đặt thư viện qua Cabal:

cabal install req

Sau khi cài đặt, bạn có thể bắt đầu sử dụng trong môi trường GHCi:

:m +Network.HTTP.Req

Ví dụ cơ bản với Req

Dưới đây là một số ví dụ minh họa cách gửi các loại request phổ biến như GET, POST, PUT và DELETE đến các API công cộng như httpbin.orgjsonplaceholder.typicode.com.

GET Request – Lấy dữ liệu dạng thô

Gửi yêu cầu GET để nhận dữ liệu nhị phân từ httpbin.org/bytes:

{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Req
import qualified Data.ByteString.Char8 as BS

fetchBytes :: IO ()
fetchBytes = runReq defaultHttpConfig $ do
  response <- req GET (https "httpbin.org" /: "bytes" /~ (5 :: Int)) 
                   NoReqBody 
                   bsResponse 
                   mempty
  liftIO $ BS.putStrLn (responseBody response)

GET với tham số truy vấn và xác thực

Khi URL hoặc các tùy chọn được tạo động, bạn có thể dùng parseUrlHttps để phân tích chuỗi URL:

fetchWithAuth :: IO ()
fetchWithAuth = runReq defaultHttpConfig $ do
  let (url, baseOptions) = parseUrlHttps "https://httpbin.org/get?mode=debug" 
        & fromMaybe (error "Invalid URL")
      customOpts = "from" =: (10 :: Int) 
                <> "to" =: (20 :: Int) 
                <> basicAuth "user" "pass"
                <> baseOptions
                <> port 443
  response <- req GET url NoReqBody jsonResponse customOpts
  liftIO $ print (responseBody response :: Value)

POST với dữ liệu JSON

Để gửi dữ liệu JSON, định nghĩa kiểu dữ liệu và triển khai ToJSON, FromJSON:

{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

data Payload = Payload
  { count  :: Int
  , label  :: String
  } deriving (Show, Generic)

instance ToJSON Payload
instance FromJSON Payload

sendJsonPost :: IO ()
sendJsonPost = runReq defaultHttpConfig $ do
  let payload = Payload 42 "demo-label"
  result <- req POST (https "httpbin.org" /: "post")
                  (ReqBodyJson payload)
                  jsonResponse
                  mempty
  liftIO $ print (responseBody result :: Value)

POST với form-urlencoded

Gửi dữ liệu dưới dạng form:

sendForm :: IO ()
sendForm = runReq defaultHttpConfig $ do
  let formData = "name" =: ("Alice" :: Text)
             <> "active" =: True
             <> queryFlag "verbose"
  res <- req POST (https "httpbin.org" /: "post")
               (ReqBodyUrlEnc formData)
               jsonResponse
               mempty
  liftIO $ print (responseBody res :: Value)

Tương tác với JSONPlaceholder API

Sử dụng Req để làm việc với API giả lập jsonplaceholder.typicode.com.

data Post = Post
  { userId :: Int
  , postId :: Int
  , title  :: Text
  , content :: Text
  } deriving (Show, Generic)

instance FromJSON Post
instance ToJSON Post

getSinglePost :: Int -> IO ()
getSinglePost n = runReq defaultHttpConfig $ do
  resp <- req GET (https "jsonplaceholder.typicode.com" /: "posts" /~ n)
               NoReqBody
               jsonResponse
               mempty
  liftIO $ print (responseBody resp :: Post)

createNewPost :: IO ()
createNewPost = runReq defaultHttpConfig $ do
  let newPost = Post 11 0 "Haskell Req" "Learning HTTP in pure functional style"
  resp <- req POST (https "jsonplaceholder.typicode.com" /: "posts")
                   (ReqBodyJson newPost)
                   jsonResponse
                   mempty
  liftIO $ print (responseBody resp :: Post)

modifyPost :: Int -> IO ()
modifyPost id = runReq defaultHttpConfig $ do
  let updated = Post 99 0 "Updated Title" "Modified content"
  resp <- req PUT (https "jsonplaceholder.typicode.com" /: "posts" /~ id)
                 (ReqBodyJson updated)
                 jsonResponse
                 mempty
  liftIO $ print (responseBody resp :: Post)

removePost :: Int -> IO ()
removePost id = runReq defaultHttpConfig $ do
  _ <- req DELETE (https "jsonplaceholder.typicode.com" /: "posts" /~ id)
                  NoReqBody
                  ignoreResponse
                  mempty
  liftIO $ putStrLn $ "Deleted post " ++ show id

Phân tích hàm chính: req và runReq

Hàm req là lõi của thư viện, với chữ ký kiểu mạnh:

req :: (MonadHttp m, HttpMethod method, HttpBody body, HttpResponse response)
    => method
    -> Url scheme
    -> body
    -> Proxy response
    -> Option scheme
    -> m response
  • method: Phương thức HTTP như GET, POST, v.v.
  • Url: Được xây dựng bằng toán tử /: (dành cho đoạn path cố định) và /~ (cho giá trị động).
  • body: Dữ liệu gửi đi, ví dụ NoReqBody, ReqBodyJson data.
  • Proxy response: Chỉ định cách xử lý phản hồi — bsResponse, jsonResponse.
  • Option: Các tuỳ chọn như header, tham số truy vấn, xác thực.

Hàm runReq thực thi ngữ cảnh Req và trả về kết quả:

runReq :: MonadIO m => HttpConfig -> Req a -> m a

Bạn thường dùng defaultHttpConfig (từ Data.Default.Class) để sử dụng cấu hình mặc định, bao gồm tự động đọc proxy từ biến môi trường HTTP_PROXY.

Kết luận

Thư viện req cung cấp một cách tiếp cận kiểu-an-toàn và sạch sẽ để thực hiện HTTP request trong Haskell. Với cú pháp rõ ràng, hỗ trợ JSON tích hợp và khả năng mở rộng cao, nó rất phù hợp cho cả ứng dụng nhỏ lẫn dự án quy mô lớn.

Thẻ: Haskell req http-client JSON type-safety

Đăng vào ngày 29 tháng 6 lúc 13:21