request 0.2.3.0 → 0.3.0.0
raw patch · 4 files changed
+132/−39 lines, 4 files
Files
- README.md +62/−22
- request.cabal +2/−1
- src/Network/HTTP/Request.hs +48/−16
- test/Spec.hs +20/−0
README.md view
@@ -16,9 +16,40 @@ import Network.HTTP.Request resp <- get "https://api.leancloud.cn/1.1/date"-print $ requestStatus resp+print $ responseStatus resp ``` +## Record Dot Syntax Support++This library supports modern Haskell record dot syntax. To use it, enable these language extensions:++```haskell+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+```++### Creating Records with Dot Syntax++```haskell+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}++import Network.HTTP.Request+import qualified Data.ByteString as BS++-- Create request using record dot syntax+let req = Request { method = GET, url = "https://api.leancloud.cn/1.1/date", headers = [], body = Nothing }++-- Response with ByteString body+responseBS <- send req :: IO (Response BS.ByteString)+print responseBS.status -- 200+print responseBS.body -- ByteString response++-- Response with String body+responseStr <- send req :: IO (Response String)+print responseStr.body -- String response+```+ ## Core API Request's API has three core concepts: `Request` record type, `Response` record type, `send` function.@@ -28,11 +59,11 @@ `Request` is all about the information you will send to the target URL. ```haskell-data Request = Request- { requestMethod :: Method- , requestUrl :: String- , requestHeaders :: Headers- , requestBody :: Maybe Data.ByteString.ByteString+data Request a = Request+ { method :: Method+ , url :: String+ , headers :: Headers+ , body :: Maybe a } deriving (Show) ``` @@ -41,7 +72,7 @@ Once you have constructed your own `Request` record, you can call the `send` function to send it to the server. The `send` function's type is: ```haskell-send :: Request -> IO Response+send :: (IsString a) => Request a -> IO (Response a) ``` ### Response@@ -49,17 +80,26 @@ `Response` is what you got from the server URL. ```haskell-data Response = Response- { responseStatus :: Int- , responseHeaders :: Headers- , responseBody :: Data.ByteString.ByteString+data Response a = Response+ { status :: Int+ , headers :: Headers+ , body :: a } deriving (Show) ``` +The response body type `a` can be any type that implements the `IsString` constraint, allowing flexible handling of response data.++### Backward Compatibility++For users who prefer not to use the language extensions, you can still:++- Create requests using positional arguments: `Request GET "url" [] Nothing`+- Use prefixed accessor functions: `requestStatus response`, `requestHeaders response`, etc.+ ### Example ```haskell-:set -XOverloadedStrings+{-# LANGUAGE OverloadedStrings #-} import Network.HTTP.Request @@ -67,8 +107,8 @@ let req = Request GET "https://api.leancloud.cn/1.1/date" [] Nothing -- Send it. res <- send req--- access the fields on Response.-print $ requestStatus resp+-- Access the fields on Response.+print $ responseStatus res ``` ## Shortcuts@@ -76,21 +116,21 @@ As you expected, there are some shortcuts for the most used scenarios. ```haskell-get :: String -> IO Response+get :: String -> IO (Response BS.ByteString) get url =- send $ Request GET url [] Nothing+ send $ Request { method = GET, url = url, headers = [], body = Nothing } -delete :: String -> IO Response+delete :: String -> IO (Response BS.ByteString) delete url =- send $ Request DELETE url [] Nothing+ send $ Request { method = DELETE, url = url, headers = [], body = Nothing } -post :: (String, Maybe Data.ByteString.ByteString) -> IO Response+post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) post (url, body) =- send $ Request POST url [] body+ send $ Request { method = POST, url = url, headers = [], body = body } -put :: (String, Maybe Data.ByteString.ByteString) -> IO Response+put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) put (url, body) =- send $ Request PUT url [] body+ send $ Request { method = PUT, url = url, headers = [], body = body } ``` These shortcuts' definitions are simple and direct. You are encouraged to add your own if the built-in does not match your use cases, like add custom headers in every request.
request.cabal view
@@ -1,5 +1,5 @@ name: request-version: 0.2.3.0+version: 0.3.0.0 -- synopsis: description: "HTTP client for haskell, inpired by requests and http-dispatch." homepage: https://github.com/aisk/request#readme@@ -35,4 +35,5 @@ build-depends: base , request , hspec+ , bytestring >= 0.10.12 && < 0.13 default-language: Haskell2010
src/Network/HTTP/Request.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Network.HTTP.Request@@ -11,6 +13,13 @@ post, put, send,+ requestMethod,+ requestUrl,+ requestHeaders,+ requestBody,+ responseStatus,+ responseHeaders,+ responseBody, ) where @@ -51,13 +60,26 @@ show (Method method) = method data Request a = Request- { requestMethod :: Method,- requestUrl :: String,- requestHeaders :: Headers,- requestBody :: Maybe a+ { method :: Method,+ url :: String,+ headers :: Headers,+ body :: Maybe a } deriving (Show) +-- Compatibility accessor functions+requestMethod :: Request a -> Method+requestMethod req = req.method++requestUrl :: Request a -> String+requestUrl req = req.url++requestHeaders :: Request a -> Headers+requestHeaders req = req.headers++requestBody :: Request a -> Maybe a+requestBody req = req.body+ toLowlevelRequest :: (S.IsString a) => Request a -> IO LowLevelClient.Request toLowlevelRequest req = do initReq <- LowLevelClient.parseRequest $ requestUrl req@@ -67,17 +89,27 @@ LowLevelClient.requestHeaders = map (\(k, v) -> (CI.mk k, v)) $ requestHeaders req } -data Response = Response- { responseStatus :: Int,- responseHeaders :: Headers,- responseBody :: BS.ByteString+data Response a = Response+ { status :: Int,+ headers :: Headers,+ body :: a } deriving (Show) -fromLowLevelRequest :: LowLevelClient.Response LBS.ByteString -> Response+-- Compatibility accessor functions for Response+responseStatus :: Response a -> Int+responseStatus res = res.status++responseHeaders :: Response a -> Headers+responseHeaders res = res.headers++responseBody :: Response a -> a+responseBody res = res.body++fromLowLevelRequest :: (S.IsString a) => LowLevelClient.Response LBS.ByteString -> Response a fromLowLevelRequest res = let status = LowLevelStatus.statusCode . LowLevelClient.responseStatus $ res- body = LBS.toStrict $ LowLevelClient.responseBody res+ body = S.fromString . C.unpack . LBS.toStrict $ LowLevelClient.responseBody res headers = LowLevelClient.responseHeaders res in Response status@@ -90,29 +122,29 @@ ) body -send :: (S.IsString a) => Request a -> IO Response+send :: (S.IsString a) => Request a -> IO (Response a) send req = do manager <- LowLevelTLSClient.getGlobalManager llreq <- toLowlevelRequest req llres <- LowLevelClient.httpLbs llreq manager return $ fromLowLevelRequest llres -get :: String -> IO Response+get :: String -> IO (Response BS.ByteString) get url = send $ Request GET url [] Nothing -delete :: String -> IO Response+delete :: String -> IO (Response BS.ByteString) delete url = send $ Request DELETE url [] Nothing -post :: (String, Maybe BS.ByteString) -> IO Response+post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) post (url, body) = send $ Request POST url [] body -put :: (String, Maybe BS.ByteString) -> IO Response+put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) put (url, body) = send $ Request PUT url [] body -patch :: (String, Maybe BS.ByteString) -> IO Response+patch :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) patch (url, body) = send $ Request PATCH url [] body
test/Spec.hs view
@@ -1,9 +1,12 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Network.HTTP.Request import Test.Hspec+import qualified Data.ByteString as BS main :: IO () main = hspec $ do@@ -27,3 +30,20 @@ it "should patch to httpbin.org/patch and return 200 OK" $ do response <- patch ("https://postman-echo.com/patch", Just "Hello!") responseStatus response `shouldBe` 200++ it "should use dot record syntax to create and access request/response" $ do+ let req = Request { method = GET, url = "http://example.com", headers = [("User-Agent", "Haskell-Request")], body = Nothing }+ response <- send req+ response.status `shouldBe` 200+ response.headers `shouldSatisfy` (not . null)++ it "should access response body with different IsString types" $ do+ -- Test with ByteString body+ let req1 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }+ response1 <- send req1 :: IO (Response BS.ByteString)+ BS.length response1.body `shouldSatisfy` (> 0)++ -- Test with String body+ let req2 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }+ response2 <- send req2 :: IO (Response String)+ not (null response2.body) `shouldBe` True