polysemy-http 0.2.0.4 → 0.3.0.0
raw patch · 8 files changed
+169/−44 lines, 8 filesdep +timedep −http-conduit
Dependencies added: time
Dependencies removed: http-conduit
Files
- integration/Main.hs +3/−1
- integration/Polysemy/Http/CookieTest.hs +44/−0
- integration/Polysemy/Http/Server.hs +5/−1
- lib/Polysemy/Http.hs +33/−2
- lib/Polysemy/Http/Data/Request.hs +2/−0
- lib/Polysemy/Http/Native.hs +18/−34
- lib/Polysemy/Http/Request.hs +59/−2
- polysemy-http.cabal +5/−4
integration/Main.hs view
@@ -2,6 +2,7 @@ import Test.Tasty (TestTree, defaultMain, testGroup) +import Polysemy.Http.CookieTest (test_cookies) import Polysemy.Http.RequestTest (test_request) import Polysemy.Http.StreamTest (test_httpStream) import Polysemy.Http.Test (unitTest)@@ -10,7 +11,8 @@ tests = testGroup "integration" [ unitTest "simple request" test_request,- unitTest "streaming http response handler" test_httpStream+ unitTest "streaming http response handler" test_httpStream,+ unitTest "cookies" test_cookies ] main :: IO ()
+ integration/Polysemy/Http/CookieTest.hs view
@@ -0,0 +1,44 @@+module Polysemy.Http.CookieTest where++import Control.Lens ((.~))+import Hedgehog (evalEither, (===))+import Polysemy (embedToFinal, runFinal)++import qualified Polysemy.Http.Data.Http as Http+import Polysemy.Http.Data.HttpError (HttpError)+import qualified Polysemy.Http.Data.Request as Request+import Polysemy.Http.Data.Request (Port(Port), Tls(Tls))+import qualified Polysemy.Http.Data.Response as Response+import Polysemy.Http.Data.Response (Response)+import Polysemy.Http.Log (interpretLogNull)+import Polysemy.Http.Native (interpretHttpNative)+import qualified Polysemy.Http.Request as Request+import Polysemy.Http.Request (addCookie)+import Polysemy.Http.Server (withServer)+import Polysemy.Http.Test (UnitTest)+import Polysemy.Resource (resourceToIOFinal)++c1, c1v, c2, c2v :: Text+c1 = "c1"+c1v = "c1v"+c2 = "c2"+c2v = "c2v"++runRequest ::+ Int ->+ IO (Either HttpError (Response LByteString))+runRequest port = do+ runFinal $ resourceToIOFinal $ embedToFinal $ interpretLogNull $ interpretHttpNative (Http.request request)+ where+ request =+ Request.get "localhost" "cookie"+ & Request.tls .~ Tls False+ & Request.port .~ Just (Port port)+ & addCookie "localhost" c2 c2v+ & addCookie "localhost" c1 c1v++test_cookies :: UnitTest+test_cookies = do+ result <- lift (withServer runRequest)+ response <- evalEither result+ [qt|#{c1}=#{c1v};#{c2}=#{c2v}|] === Response.body response
integration/Polysemy/Http/Server.hs view
@@ -19,7 +19,7 @@ withSocketsDo, ) import qualified Network.Wai.Handler.Warp as Warp-import Servant (Get, Handler, JSON, PlainText, Post, ReqBody, ServerT, serve, (:<|>)((:<|>)), (:>))+import Servant (Header, Get, Handler, JSON, PlainText, Post, ReqBody, ServerT, serve, (:<|>)((:<|>)), (:>)) import Servant.Client (BaseUrl(BaseUrl), Client, ClientEnv, ClientM, Scheme(Http), client, mkClientEnv, runClientM) freePort ::@@ -49,6 +49,8 @@ "add" :> ReqBody '[JSON] Payload :> Post '[JSON] Int :<|> "stream" :> Get '[PlainText] Text+ :<|>+ "cookie" :> Header "cookie" Text :> Get '[PlainText] Text apiClient :: Client ClientM Api apiClient =@@ -71,6 +73,8 @@ (\ (Payload a b) -> pure (a + b)) :<|> pure (Text.replicate (10 * 8192) "x")+ :<|>+ (\ cookies -> pure (fromMaybe "no cookies" cookies)) forkServer :: IO (ThreadId, Int) forkServer = do
lib/Polysemy/Http.hs view
@@ -8,7 +8,21 @@ module Polysemy.Http.Data.Request, module Polysemy.Http.Data.Response, module Polysemy.Http.Data.Header,- module Polysemy.Http.Request,+ withPort,+ withTls,+ simple,+ get,+ post,+ put,+ delete,+ fromUrl,+ getUrl,+ postUrl,+ putUrl,+ deleteUrl,+ cookie,+ addCookies,+ addCookie, HttpError(..), -- * Streaming module Polysemy.Http.Http,@@ -78,8 +92,25 @@ import Polysemy.Http.Http (streamResponse) import Polysemy.Http.Log (interpretLogNull, interpretLogStdout) import Polysemy.Http.Native (interpretHttpNative)-import Polysemy.Http.Request (delete, deleteUrl, get, getUrl, post, postUrl, put, putUrl)+import Polysemy.Http.Request (+ addCookie,+ addCookies,+ cookie,+ delete,+ deleteUrl,+ fromUrl,+ get,+ getUrl,+ post,+ postUrl,+ put,+ putUrl,+ simple,+ withPort,+ withTls,+ ) import Polysemy.Http.Strict (interpretHttpStrict)+import Prelude hiding (get, put) -- $intro -- A basic 'Polysemy' effect abstracting HTTP requests:
lib/Polysemy/Http/Data/Request.hs view
@@ -2,6 +2,7 @@ import Control.Lens (makeClassy) import qualified Data.Text as Text+import Network.HTTP.Client.Internal (CookieJar) import Polysemy.Http.Data.Header (HeaderName, HeaderValue) @@ -123,6 +124,7 @@ _tls :: Tls, _path :: Path, _headers :: [(HeaderName, HeaderValue)],+ _cookies :: CookieJar, _query :: [(QueryKey, Maybe QueryValue)], _body :: Body }
lib/Polysemy/Http/Native.hs view
@@ -2,23 +2,9 @@ import qualified Data.CaseInsensitive as CaseInsensitive import Data.CaseInsensitive (foldedCase)+import qualified Network.HTTP.Client as HTTP import Network.HTTP.Client (BodyReader, httpLbs, responseClose, responseOpen)-import qualified Network.HTTP.Client as HTTP (Manager)-import Network.HTTP.Simple (- defaultRequest,- getResponseBody,- getResponseHeaders,- getResponseStatus,- setRequestBodyLBS,- setRequestHeaders,- setRequestHost,- setRequestMethod,- setRequestPath,- setRequestPort,- setRequestQueryString,- setRequestSecure,- )-import qualified Network.HTTP.Simple as N (Request, Response)+import Network.HTTP.Client.Internal (CookieJar(CJ)) import Polysemy (Tactical, interpretH, runT) import qualified Polysemy.Http.Data.Log as Log import Polysemy.Http.Data.Log (Log)@@ -46,34 +32,32 @@ import Polysemy.Http.Manager (interpretManager) -- |Converts a 'Request' to a native 'N.Request'.-nativeRequest :: Request -> N.Request-nativeRequest (Request method (Host host) portOverride (Tls tls) (Path path) headers query (Body body)) =- cons defaultRequest+nativeRequest :: Request -> HTTP.Request+nativeRequest (Request method (Host host) portOverride (Tls tls) (Path path) headers (CJ cookies) query (Body body)) =+ HTTP.setQueryString queryParams HTTP.defaultRequest {+ HTTP.host = encodeUtf8 host,+ HTTP.port = port,+ HTTP.secure = tls,+ HTTP.method = encodeUtf8 (methodUpper method),+ HTTP.requestHeaders = encodedHeaders,+ HTTP.path = encodeUtf8 path,+ HTTP.requestBody = HTTP.RequestBodyLBS body,+ HTTP.cookieJar = CJ . toList <$> nonEmpty cookies+ } where- cons =- scheme .- setRequestMethod (encodeUtf8 $ methodUpper method) .- setRequestHeaders encodedHeaders .- setRequestHost (encodeUtf8 host) .- setRequestPort port .- setRequestPath (encodeUtf8 path) .- setRequestQueryString queryParam .- setRequestBodyLBS body- queryParam =+ queryParams = bimap (encodeUtf8 . unQueryKey) (fmap (encodeUtf8 . unQueryValue)) <$> query- scheme =- if tls then setRequestSecure True else id port = maybe (if tls then 443 else 80) unPort portOverride encodedHeaders = bimap (CaseInsensitive.mk . encodeUtf8 . unHeaderName) (encodeUtf8 . unHeaderValue) <$> headers -convertResponse :: N.Response b -> Response b+convertResponse :: HTTP.Response b -> Response b convertResponse response =- Response (getResponseStatus response) (getResponseBody response) headers+ Response (HTTP.responseStatus response) (HTTP.responseBody response) headers where headers =- header <$> getResponseHeaders response+ header <$> HTTP.responseHeaders response header (foldedCase -> decodeUtf8 -> name, decodeUtf8 -> value) = Header (fromString name) (fromString value)
lib/Polysemy/Http/Request.hs view
@@ -1,8 +1,15 @@ module Polysemy.Http.Request where +import Control.Lens ((%~)) import qualified Data.Text as Text-import Prelude hiding (get)+import Data.Time (UTCTime(UTCTime))+import Network.HTTP.Client (Cookie(Cookie))+import Network.HTTP.Client.Internal (CookieJar(CJ, expose))+import Prelude hiding (get, put) +import Data.Time.Calendar (fromGregorian)+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import qualified Polysemy.Http.Data.Request as Request import Polysemy.Http.Data.Request (Body, Host(Host), Method(..), Path(Path), Port(Port), Request(Request), Tls(Tls)) invalidScheme ::@@ -48,6 +55,7 @@ (host, port) <- parseHostPort url (split ":" hostPort) pure (tls, host, port, Path (fromMaybe "" path)) +-- |Create a request with empty headers, query and cookies. withPort :: Maybe Port -> Tls ->@@ -57,8 +65,9 @@ Body -> Request withPort port tls method host path =- Request method host port tls path [] []+ Request method host port tls path [] (CJ []) [] +-- |Create a request with default port and empty headers, query and cookies. withTls :: Tls -> Method ->@@ -69,6 +78,7 @@ withTls = withPort Nothing +-- |Create a TLS request with default port and empty headers, query and cookies. simple :: Method -> Host ->@@ -78,6 +88,7 @@ simple = withTls (Tls True) +-- |Create a TLS GET request with default port and empty headers, query and cookies. get :: Host -> Path ->@@ -85,6 +96,7 @@ get host path = simple Get host path "" +-- |Create a TLS POST request with default port and empty headers, query and cookies. post :: Host -> Path ->@@ -93,6 +105,7 @@ post host path = simple Post host path +-- |Create a TLS PUT request with default port and empty headers, query and cookies. put :: Host -> Path ->@@ -101,6 +114,7 @@ put host path = simple Put host path +-- |Create a TLS DELETE request with default port and empty headers, query and cookies. delete :: Host -> Path ->@@ -108,6 +122,7 @@ delete host path = simple Delete host path "" +-- |Parse the URL and create a request or return a parse error. fromUrl :: Method -> Body ->@@ -117,12 +132,14 @@ (tls, host, port, path) <- parseUrl url pure (withPort port tls method host path body) +-- |Parse URL for a GET. getUrl :: Text -> Either Text Request getUrl = fromUrl Get "" +-- |Parse URL for a POST. postUrl :: Body -> Text ->@@ -130,6 +147,7 @@ postUrl = fromUrl Post +-- |Parse URL for a PUT. putUrl :: Body -> Text ->@@ -137,8 +155,47 @@ putUrl = fromUrl Put +-- |Parse URL for a DELETE. deleteUrl :: Text -> Either Text Request deleteUrl = fromUrl Delete ""++neverExpire :: UTCTime+neverExpire =+ UTCTime (fromGregorian 9999 1 1) 0++epoch :: UTCTime+epoch =+ posixSecondsToUTCTime 0++-- |Create a cookie with default values.+cookie ::+ Text ->+ Text ->+ Text ->+ Cookie+cookie domain name value =+ Cookie (encodeUtf8 name) (encodeUtf8 value) neverExpire (encodeUtf8 domain) "/" epoch epoch False False False False++-- |Add multiple cookies to a request.+addCookies ::+ [Cookie] ->+ Request ->+ Request+addCookies cookies =+ Request.cookies %~ update+ where+ update =+ CJ . (cookies <>) . expose++-- |Add a cookie to a request, using default values.+addCookie ::+ Text ->+ Text ->+ Text ->+ Request ->+ Request+addCookie domain name value =+ addCookies (pure (cookie domain name value))
polysemy-http.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: polysemy-http-version: 0.2.0.4+version: 0.3.0.0 synopsis: Polysemy effect for http-client description: Please see the README on Github at <https://github.com/tek/polysemy-http> category: Network@@ -70,7 +70,6 @@ , either >=5.0.1 , http-client >=0.6.4 , http-client-tls >=0.3.5- , http-conduit >=2.3.7 , http-types >=0.12.3 , lens >=4 , polysemy >=1.3.0@@ -79,6 +78,7 @@ , string-interpolate >=0.2.1 , template-haskell >=2.14.0 , text >=1.2.3+ , time mixins: base hiding (Prelude) if impl(ghc < 8.10)@@ -89,6 +89,7 @@ type: exitcode-stdio-1.0 main-is: Main.hs other-modules:+ Polysemy.Http.CookieTest Polysemy.Http.RequestTest Polysemy.Http.Server Polysemy.Http.StreamTest@@ -113,7 +114,6 @@ , hedgehog , http-client >=0.6.4 , http-client-tls >=0.3.5- , http-conduit >=2.3.7 , http-types >=0.12.3 , lens >=4 , network@@ -129,6 +129,7 @@ , tasty-hedgehog , template-haskell >=2.14.0 , text >=1.2.3+ , time , warp mixins: base hiding (Prelude)@@ -165,7 +166,6 @@ , hedgehog , http-client >=0.6.4 , http-client-tls >=0.3.5- , http-conduit >=2.3.7 , http-types >=0.12.3 , lens >=4 , polysemy >=1.3.0@@ -177,6 +177,7 @@ , tasty-hedgehog , template-haskell >=2.14.0 , text >=1.2.3+ , time mixins: base hiding (Prelude) , polysemy-http hiding (Prelude)