on master help sign in
Improve the writing
727a711Mark Karpov
Network/HTTP/Req.hs+72−98
@@ -30,61 +30,40 @@
30--
30--
31-- The documentation below is structured in such a way that the most
31-- The documentation below is structured in such a way that the most
32-- important information is presented first: you learn how to do HTTP
32-- important information is presented first: you learn how to do HTTP
33−-- requests, how to embed them in any monad you have, and then it gives you
33+-- requests, how to embed them in the monad you have, and then it gives you
34-- details about less-common things you may want to know about. The
34-- details about less-common things you may want to know about. The
35-- documentation is written with sufficient coverage of details and
35-- documentation is written with sufficient coverage of details and
36-- examples, and it's designed to be a complete tutorial on its own.
36-- examples, and it's designed to be a complete tutorial on its own.
37--
37--
38−-- /(A modest intro goes here, click on 'req' to start making requests.)/
39−--
40-- === About the library
38-- === About the library
41--
39--
42−-- Req is an easy-to-use, type-safe, expandable, high-level HTTP client
40+-- Req is an HTTP client library that attempts to be easy-to-use, type-safe,
43−-- library that just works without any fooling around.
41+-- and expandable.
44−--
42+--
45−-- What does the phrase “easy-to-use” mean? It means that the library is
43+-- “Easy-to-use” means that the library is designed to be beginner-friendly
46−-- designed to be beginner-friendly so it's simple to add to your monad
44+-- so it's simple to add to your monad stack, intuitive to work with,
47−-- stack, intuitive to work with, well-documented, and does not get in your
45+-- well-documented, and does not get in your way. Doing HTTP requests is a
48−-- way. Doing HTTP requests is a common task and Haskell library for this
46+-- common task and a Haskell library for this should be approachable and
49−-- should be very approachable and clear to beginners, thus certain
50−-- compromises were made. For example, one cannot currently modify
47+-- clear to beginners, thus certain compromises were made. For example, one
51−-- 'L.ManagerSettings' of the default manager because the library always
48+-- cannot currently modify 'L.ManagerSettings' of the default manager
52−-- uses the same implicit global manager for simplicity and maximal
49+-- because the library always uses the same implicit global manager for
53−-- connection sharing. There is a way to use your own manager with different
50+-- simplicity and maximal connection sharing. There is a way to use your own
54−-- settings, but it requires a bit more typing.
51+-- manager with different settings, but it requires more typing.
55−--
52+--
56−-- “Type-safe” means that the library is protective and eliminates certain
53+-- “Type-safe” means that the library tries to eliminate certain classes of
57−-- classes of errors. For example, we have correct-by-construction 'Url's,
54+-- errors. For example, we have correct-by-construction URLs; it is
58−-- it's guaranteed that the user does not send the request body when using
55+-- guaranteed that the user does not send the request body when using
59−-- methods like 'GET' or 'OPTIONS', and the amount of implicit assumptions
56+-- methods like GET or OPTIONS, and the amount of implicit assumptions is
60−-- is minimized by making the user specify his\/her intentions in an
57+-- minimized by making the user specify their intentions in an explicit
61−-- explicit form (for example, it's not possible to avoid specifying the
58+-- form. For example, it's not possible to avoid specifying the body or the
62−-- body or method of request). Authentication methods that assume HTTPS
59+-- method of a request. Authentication methods that assume HTTPS force the
63−-- force the user to use HTTPS at the type level. The library also carefully
60+-- user to use HTTPS at the type level.
64−-- hides underlying types from the lower-level @http-client@ package because
65−-- those types are not safe enough (for example 'L.Request' is an instance
66−-- of 'Data.String.IsString' and, if it's malformed, it will blow up at
67−-- run-time).
68−--
61+--
69−-- “Expandable” refers to the ability of the library to be expanded without
62+-- “Expandable” refers to the ability to create new components without
70−-- having to resort to ugly hacking. For example, it's possible to define
63+-- having to resort to hacking. For example, it's possible to define your
71−-- your own HTTP methods, create new ways to construct the body of a
64+-- own HTTP methods, create new ways to construct the body of a request,
72−-- request, create new authorization options, perform a request in a
65+-- create new authorization options, perform a request in a different way,
73−-- different way, and create your own methods to parse and represent a
66+-- and create your own methods to parse a response.
74−-- response. As the user extends the library to satisfy his\/her special
75−-- needs, the new solutions will work just like the built-ins. However, all
76−-- of the common cases are also covered by the library out-of-the-box.
77−--
78−-- “High-level” means that there are less details to worry about. The
79−-- library is a result of my experiences as a Haskell consultant. Working
80−-- for several clients, who had very different projects, showed me that the
81−-- library should adapt easily to any particular style of writing Haskell
82−-- applications. For example, some people prefer throwing exceptions, while
83−-- others are concerned with purity. Just define 'handleHttpException'
84−-- accordingly when making your monad instance of 'MonadHttp' and it will
85−-- play together seamlessly. Finally, the library cuts down boilerplate
86−-- considerably, and helps you write concise, easy to read, and maintainable
87−-- code.
88--
67--
89-- === Using with other libraries
68-- === Using with other libraries
90--
69--
@@ -303,7 +282,7 @@ import Web.HttpApiData (ToHttpApiData (..))
303--
282--
304-- @body@ is a body option such as 'NoReqBody' or 'ReqBodyJson'. The
283-- @body@ is a body option such as 'NoReqBody' or 'ReqBodyJson'. The
305-- tutorial has a section about HTTP bodies, but usage is very
284-- tutorial has a section about HTTP bodies, but usage is very
306−-- straightforward and should be clear from the examples below.
285+-- straightforward and should be clear from the examples.
307--
286--
308-- @response@ is a type hint how to make and interpret response of an HTTP
287-- @response@ is a type hint how to make and interpret response of an HTTP
309-- request. Out-of-the-box it can be the following:
288-- request. Out-of-the-box it can be the following:
@@ -588,9 +567,9 @@ req' method url body options m = do
588 nubHeaders
567 nubHeaders
589 <> getRequestMod options
568 <> getRequestMod options
590 <> getRequestMod config
569 <> getRequestMod config
591− <> getRequestMod (Womb body :: Womb "body" body)
570+ <> getRequestMod (Tagged body :: Tagged "body" body)
592 <> getRequestMod url
571 <> getRequestMod url
593− <> getRequestMod (Womb method :: Womb "method" method)
572+ <> getRequestMod (Tagged method :: Tagged "method" method)
594 request <- finalizeRequest options request'
573 request <- finalizeRequest options request'
595 withReqManager (m request)
574 withReqManager (m request)
596
575
@@ -600,10 +579,10 @@ req' method url body options m = do
600withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
579withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
601withReqManager m = liftIO (readIORef globalManager) >>= m
580withReqManager m = liftIO (readIORef globalManager) >>= m
602
581
603−-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
582+-- | The global 'L.Manager' that 'req' uses. Here we just go with the
604−-- settings, so users don't need to deal with this manager stuff at all, but
583+-- default settings, so users don't need to deal with this manager stuff at
605−-- when we create a request, instance 'HttpConfig' can affect the default
584+-- all, but when we create a request, instance 'HttpConfig' can affect the
606−-- settings via 'getHttpConfig'.
585+-- default settings via 'getHttpConfig'.
607--
586--
608-- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
587-- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
609-- The value of 'globalManager' is named and lives on top level. This means
588-- The value of 'globalManager' is named and lives on top level. This means
@@ -634,8 +613,8 @@ globalManager = unsafePerformIO $ do
634-- When writing a library, keep your API polymorphic in terms of
613-- When writing a library, keep your API polymorphic in terms of
635-- 'MonadHttp', only define instance of 'MonadHttp' in final application.
614-- 'MonadHttp', only define instance of 'MonadHttp' in final application.
636-- Another option is to use a @newtype@-wrapped monad stack and define
615-- Another option is to use a @newtype@-wrapped monad stack and define
637−-- 'MonadHttp' for it. As of version /0.4.0/, the 'Req' monad that follows
616+-- 'MonadHttp' for it. As of the version /0.4.0/, the 'Req' monad that
638−-- this strategy is provided out-of-the-box (see below).
617+-- follows this strategy is provided out-of-the-box (see below).
639
618
640-- | A type class for monads that support performing HTTP requests.
619-- | A type class for monads that support performing HTTP requests.
641-- Typically, you only need to define the 'handleHttpException' method
620-- Typically, you only need to define the 'handleHttpException' method
@@ -648,17 +627,16 @@ class MonadIO m => MonadHttp m where
648 -- 'Control.Monad.Except.throwError'.
627 -- 'Control.Monad.Except.throwError'.
649 handleHttpException :: HttpException -> m a
628 handleHttpException :: HttpException -> m a
650
629
651− -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
630+ -- | Return the 'HttpConfig' to be used when performing HTTP requests.
652− -- implementation returns its 'def' value, which is described in the
631+ -- Default implementation returns its 'def' value, which is described in
653− -- documentation for the type. Common usage pattern with manually defined
632+ -- the documentation for the type. Common usage pattern with manually
654− -- 'getHttpConfig' is to return some hard-coded value, or a value
633+ -- defined 'getHttpConfig' is to return some hard-coded value, or a value
655 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
634 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
656 -- approach to configuration is desirable.
635 -- approach to configuration is desirable.
657 getHttpConfig :: m HttpConfig
636 getHttpConfig :: m HttpConfig
658 getHttpConfig = return defaultHttpConfig
637 getHttpConfig = return defaultHttpConfig
659
638
660−-- | 'HttpConfig' contains general and default settings to be used when
639+-- | 'HttpConfig' contains settings to be used when making HTTP requests.
661−-- making HTTP requests.
662data HttpConfig = HttpConfig
640data HttpConfig = HttpConfig
663 { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
641 { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
664 -- environment variables are respected, this setting overwrites them.
642 -- environment variables are respected, this setting overwrites them.
@@ -729,7 +707,7 @@ data HttpConfig = HttpConfig
729 }
707 }
730 deriving (Typeable)
708 deriving (Typeable)
731
709
732−-- | Default value of 'HttpConfig'.
710+-- | The default value of 'HttpConfig'.
733--
711--
734-- @since 2.0.0
712-- @since 2.0.0
735defaultHttpConfig :: HttpConfig
713defaultHttpConfig :: HttpConfig
@@ -773,7 +751,7 @@ instance RequestComponent HttpConfig where
773 LI.requestManagerOverride = httpConfigAltManager
751 LI.requestManagerOverride = httpConfigAltManager
774 }
752 }
775
753
776−-- | A monad that allows to run 'req' in any 'IO'-enabled monad without
754+-- | A monad that allows us to run 'req' in any 'IO'-enabled monad without
777-- having to define new instances.
755-- having to define new instances.
778--
756--
779-- @since 0.4.0
757-- @since 0.4.0
@@ -810,7 +788,7 @@ instance MonadHttp Req where
810 getHttpConfig = Req ask
788 getHttpConfig = Req ask
811
789
812-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
790-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
813−-- case of exceptional situation an 'HttpException' will be thrown.
791+-- the case of an exceptional situation an 'HttpException' will be thrown.
814--
792--
815-- @since 0.4.0
793-- @since 0.4.0
816runReq ::
794runReq ::
@@ -919,7 +897,7 @@ class HttpMethod a where
919 -- | Return name of the method as a 'ByteString'.
897 -- | Return name of the method as a 'ByteString'.
920 httpMethodName :: Proxy a -> ByteString
898 httpMethodName :: Proxy a -> ByteString
921
899
922−instance HttpMethod method => RequestComponent (Womb "method" method) where
900+instance HttpMethod method => RequestComponent (Tagged "method" method) where
923 getRequestMod _ = Endo $ \x ->
901 getRequestMod _ = Endo $ \x ->
924 x {L.method = httpMethodName (Proxy :: Proxy method)}
902 x {L.method = httpMethodName (Proxy :: Proxy method)}
925
903
@@ -966,7 +944,7 @@ data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
966type role Url nominal
944type role Url nominal
967
945
968-- With template-haskell >=2.15 and text >=1.2.4 Lift can be derived, however
946-- With template-haskell >=2.15 and text >=1.2.4 Lift can be derived, however
969−-- the derived lift forgets the type of scheme.
947+-- the derived lift forgets the type of the scheme.
970instance Typeable scheme => TH.Lift (Url scheme) where
948instance Typeable scheme => TH.Lift (Url scheme) where
971 lift url =
949 lift url =
972 TH.dataToExpQ (fmap liftText . cast) url `TH.sigE` case url of
950 TH.dataToExpQ (fmap liftText . cast) url `TH.sigE` case url of
@@ -989,14 +967,14 @@ http = Url Http . pure
989https :: Text -> Url 'Https
967https :: Text -> Url 'Https
990https = Url Https . pure
968https = Url Https . pure
991
969
992−-- | Grow given 'Url' appending a single path segment to it. Note that the
970+-- | Grow a given 'Url' appending a single path segment to it. Note that the
993-- path segment can be of any type that is an instance of 'ToHttpApiData'.
971-- path segment can be of any type that is an instance of 'ToHttpApiData'.
994infixl 5 /~
972infixl 5 /~
995
973
996(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
974(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
997Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
975Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
998
976
999−-- | Type-constrained version of @('/~')@ to remove ambiguity in the cases
977+-- | A type-constrained version of @('/~')@ to remove ambiguity in the cases
1000-- when next URL piece is a 'Data.Text.Text' literal.
978-- when next URL piece is a 'Data.Text.Text' literal.
1001infixl 5 /:
979infixl 5 /:
1002
980
@@ -1172,10 +1150,10 @@ data NoReqBody = NoReqBody
1172instance HttpBody NoReqBody where
1150instance HttpBody NoReqBody where
1173 getRequestBody NoReqBody = L.RequestBodyBS B.empty
1151 getRequestBody NoReqBody = L.RequestBodyBS B.empty
1174
1152
1175−-- | This body option allows to use a JSON object as request body—probably
1153+-- | This body option allows us to use a JSON object as the request
1176−-- the most popular format right now. Just wrap a data type that is an
1154+-- body—probably the most popular format right now. Just wrap a data type
1177−-- instance of 'ToJSON' type class and you are done: it will be converted to
1155+-- that is an instance of 'ToJSON' type class and you are done: it will be
1178−-- JSON and inserted as request body.
1156+-- converted to JSON and inserted as request body.
1179--
1157--
1180-- This body option sets the @Content-Type@ header to @\"application/json;
1158-- This body option sets the @Content-Type@ header to @\"application/json;
1181-- charset=utf-8\"@ value.
1159-- charset=utf-8\"@ value.
@@ -1186,7 +1164,7 @@ instance ToJSON a => HttpBody (ReqBodyJson a) where
1186 getRequestContentType _ = pure "application/json; charset=utf-8"
1164 getRequestContentType _ = pure "application/json; charset=utf-8"
1187
1165
1188-- | This body option streams request body from a file. It is expected that
1166-- | This body option streams request body from a file. It is expected that
1189−-- the file size does not change during the streaming.
1167+-- the file size does not change during streaming.
1190--
1168--
1191-- Using of this body option does not set the @Content-Type@ header.
1169-- Using of this body option does not set the @Content-Type@ header.
1192newtype ReqBodyFile = ReqBodyFile FilePath
1170newtype ReqBodyFile = ReqBodyFile FilePath
@@ -1211,12 +1189,11 @@ newtype ReqBodyLbs = ReqBodyLbs BL.ByteString
1211instance HttpBody ReqBodyLbs where
1189instance HttpBody ReqBodyLbs where
1212 getRequestBody (ReqBodyLbs bs) = L.RequestBodyLBS bs
1190 getRequestBody (ReqBodyLbs bs) = L.RequestBodyLBS bs
1213
1191
1214−-- | Form URL-encoded body. This can hold a collection of parameters which
1192+-- | URL-encoded body. This can hold a collection of parameters which are
1215−-- are encoded similarly to query parameters at the end of query string,
1193+-- encoded similarly to query parameters at the end of query string, with
1216−-- with the only difference that they are stored in request body. The
1194+-- the only difference that they are stored in request body. The similarity
1217−-- similarity is reflected in the API as well, as you can use the same
1195+-- is reflected in the API as well, as you can use the same combinators you
1218−-- combinators you would use to add query parameters: @('=:')@ and
1196+-- would use to add query parameters: @('=:')@ and 'queryFlag'.
1219−-- 'queryFlag'.
1220--
1197--
1221-- This body option sets the @Content-Type@ header to
1198-- This body option sets the @Content-Type@ header to
1222-- @\"application/x-www-form-urlencoded\"@ value.
1199-- @\"application/x-www-form-urlencoded\"@ value.
@@ -1302,9 +1279,6 @@ type family ProvidesBody body :: CanHaveBody where
1302-- | This type function allows any HTTP body if method says it
1279-- | This type function allows any HTTP body if method says it
1303-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
1280-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
1304-- body option to use is 'NoReqBody'.
1281-- body option to use is 'NoReqBody'.
1305−--
1306−-- __Note__: users of GHC 8.0.1 and later will see a slightly more friendly
1307−-- error message when method does not allow a body and body is provided.
1308type family
1282type family
1309 HttpBodyAllowed
1283 HttpBodyAllowed
1310 (allowsBody :: CanHaveBody)
1284 (allowsBody :: CanHaveBody)
@@ -1317,8 +1291,8 @@ type family
1317 TypeError
1291 TypeError
1318 ('Text "This HTTP method does not allow attaching a request body.")
1292 ('Text "This HTTP method does not allow attaching a request body.")
1319
1293
1320−instance HttpBody body => RequestComponent (Womb "body" body) where
1294+instance HttpBody body => RequestComponent (Tagged "body" body) where
1321− getRequestMod (Womb body) = Endo $ \x ->
1295+ getRequestMod (Tagged body) = Endo $ \x ->
1322 x
1296 x
1323 { L.requestBody = getRequestBody body,
1297 { L.requestBody = getRequestBody body,
1324 L.requestHeaders =
1298 L.requestHeaders =
@@ -1347,8 +1321,8 @@ data Option (scheme :: Scheme)
1347 = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1321 = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1348
1322
1349-- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1323-- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1350−-- Request to avoid appending to existing query string in request every
1324+-- Request to avoid appending to an existing query string in request every
1351−-- time new parameter is added. Additional Maybe (L.Request -> IO
1325+-- time new parameter is added. The additional Maybe (L.Request -> IO
1352-- L.Request) is a finalizer that will be applied after all other
1326-- L.Request) is a finalizer that will be applied after all other
1353-- transformations. This is for authentication methods that sign requests
1327-- transformations. This is for authentication methods that sign requests
1354-- based on data in Request.
1328-- based on data in Request.
@@ -1405,7 +1379,7 @@ infix 7 =:
1405(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1379(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1406name =: value = queryParam name (pure value)
1380name =: value = queryParam name (pure value)
1407
1381
1408−-- | Construct a flag, that is, valueless query parameter. For example, in
1382+-- | Construct a flag, that is, a valueless query parameter. For example, in
1409-- the following URL @\"a\"@ is a flag, while @\"b\"@ is a query parameter
1383-- the following URL @\"a\"@ is a flag, while @\"b\"@ is a query parameter
1410-- with a value:
1384-- with a value:
1411--
1385--
@@ -1595,7 +1569,7 @@ customAuth = Option mempty . pure
1595
1569
1596-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1570-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1597-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1571-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1598−-- 'Option' allows to choose an arbitrary port overwriting the defaults.
1572+-- 'Option' allows us to choose an arbitrary port overwriting the defaults.
1599port :: Int -> Option scheme
1573port :: Int -> Option scheme
1600port n = withRequest $ \x ->
1574port n = withRequest $ \x ->
1601 x {L.port = n}
1575 x {L.port = n}
@@ -1610,8 +1584,8 @@ port n = withRequest $ \x ->
1610--
1584--
1611-- > decompress (const True)
1585-- > decompress (const True)
1612decompress ::
1586decompress ::
1613− -- | Predicate that is given MIME type, it
1587+ -- | Predicate that is given MIME type, it returns 'True' when content
1614− -- returns 'True' when content should be decompressed on the fly.
1588+ -- should be decompressed on the fly.
1615 (ByteString -> Bool) ->
1589 (ByteString -> Bool) ->
1616 Option scheme
1590 Option scheme
1617decompress f = withRequest $ \x ->
1591decompress f = withRequest $ \x ->
@@ -1713,7 +1687,7 @@ lbsResponse = Proxy
1713----------------------------------------------------------------------------
1687----------------------------------------------------------------------------
1714-- Helpers for response interpretations
1688-- Helpers for response interpretations
1715
1689
1716−-- | Fetch beginning of response and return it together with new
1690+-- | Fetch beginning of the response and return it together with a new
1717-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1691-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1718-- such.
1692-- such.
1719grabPreview ::
1693grabPreview ::
@@ -1825,9 +1799,9 @@ responseCookieJar = L.responseCookieJar . toVanillaResponse
1825-- To create a new response interpretation you just need to make your data
1799-- To create a new response interpretation you just need to make your data
1826-- type an instance of the 'HttpResponse' type class.
1800-- type an instance of the 'HttpResponse' type class.
1827
1801
1828−-- | A type class for response interpretations. It allows to describe how to
1802+-- | A type class for response interpretations. It allows us to describe how
1829−-- consume response from a @'L.Response' 'L.BodyReader'@ and produce the
1803+-- to consume the response from a @'L.Response' 'L.BodyReader'@ and produce
1830−-- final result that is to be returned to the user.
1804+-- the final result that is to be returned to the user.
1831class HttpResponse response where
1805class HttpResponse response where
1832 -- | The associated type is the type of body that can be extracted from an
1806 -- | The associated type is the type of body that can be extracted from an
1833 -- instance of 'HttpResponse'.
1807 -- instance of 'HttpResponse'.
@@ -1891,7 +1865,7 @@ class RequestComponent a where
1891-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
1865-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
1892-- which instance to use (i.e. the constraints are taken into account later,
1866-- which instance to use (i.e. the constraints are taken into account later,
1893-- when instance is already chosen).
1867-- when instance is already chosen).
1894−newtype Womb (tag :: Symbol) a = Womb a
1868+newtype Tagged (tag :: Symbol) a = Tagged a
1895
1869
1896-- | Exceptions that this library throws.
1870-- | Exceptions that this library throws.
1897data HttpException
1871data HttpException
README.md+27−121
@@ -43,49 +43,33 @@ main = runReq defaultHttpConfig $ do
43 liftIO $ print (responseBody r :: Value)
43 liftIO $ print (responseBody r :: Value)
44```
44```
45
45
46−Req is an easy-to-use, type-safe, expandable, high-level HTTP client library
46+Req is an HTTP client library that attempts to be easy-to-use, type-safe,
47−that just works without any fooling around.
47+and expandable.
48−
48+
49−What does the phrase “easy-to-use” mean? It means that the library is
49+“Easy-to-use” means that the library is designed to be beginner-friendly so
50−designed to be beginner-friendly so it's simple to add to your monad stack,
50+it's simple to add to your monad stack, intuitive to work with,
51−intuitive to work with, well-documented, and does not get in your way. Doing
51+well-documented, and does not get in your way. Doing HTTP requests is a
52−HTTP requests is a common task and Haskell library for this should be very
52+common task and a Haskell library for this should be approachable and clear
53−approachable and clear to beginners, thus certain compromises were made. For
53+to beginners, thus certain compromises were made. For example, one cannot
54−example, one cannot currently modify `ManagerSettings` of the default
54+currently modify `ManagerSettings` of the default manager because the
55−manager because the library always uses the same implicit global manager for
55+library always uses the same implicit global manager for simplicity and
56−simplicity and maximal connection sharing. There is a way to use your own
56+maximal connection sharing. There is a way to use your own manager with
57−manager with different settings, but it requires a bit more typing.
57+different settings, but it requires more typing.
58−
58+
59−“Type-safe” means that the library is protective and eliminates certain
59+“Type-safe” means that the library tries to eliminate certain classes of
60−classes of errors. For example, we have correct-by-construction URLs, it's
60+errors. For example, we have correct-by-construction URLs; it is guaranteed
61−guaranteed that the user does not send the request body when using methods
61+that the user does not send the request body when using methods like GET or
62−like GET or OPTIONS, and the amount of implicit assumptions is minimized by
62+OPTIONS, and the amount of implicit assumptions is minimized by making the
63−making the user specify his/her intentions in an explicit form (for example,
63+user specify their intentions in an explicit form. For example, it's not
64−it's not possible to avoid specifying the body or method of request).
64+possible to avoid specifying the body or the method of a request.
65Authentication methods that assume HTTPS force the user to use HTTPS at the
65Authentication methods that assume HTTPS force the user to use HTTPS at the
66−type level. The library also carefully hides underlying types from the
66+type level.
67−lower-level `http-client` package because those types are not safe enough
67+
68−(for example `Request` is an instance of `IsString` and, if it's malformed,
69−it will blow up at run-time).
70−
71−“Expandable” refers to the ability to create new components for dealing with
68+“Expandable” refers to the ability to create new components without having
72−HTTP without having to resort to ugly hacking. For example, it's possible to
69+to resort to hacking. For example, it's possible to define your own HTTP
73−define your own HTTP methods, create new ways to construct the body of a
70+methods, create new ways to construct the body of a request, create new
74−request, create new authorization options, perform a request in a different
71+authorization options, perform a request in a different way, and create your
75−way, and create your own methods to parse and represent a response. As the
72+own methods to parse a response.
76−user extends the library to satisfy his/her special needs, the new solutions
77−will work just like the built-ins. However, all of the common cases are also
78−covered by the library out-of-the-box.
79−
80−“High-level” means that there are less details to worry about. The library
81−is a result of my experiences as a Haskell consultant. Working for several
82−clients, who had very different projects, showed me that the library should
83−adapt easily to any particular style of writing Haskell applications. For
84−example, some people prefer throwing exceptions, while others are concerned
85−with purity. Just define `handleHttpException` accordingly when making your
86−monad instance of `MonadHttp` and it will play together seamlessly. Finally,
87−the library cuts down boilerplate considerably, and helps you write concise,
88−easy to read, and maintainable code.
89
73
90The library uses the following mature packages under the hood to guarantee
74The library uses the following mature packages under the hood to guarantee
91you the best experience:
75you the best experience:
@@ -95,89 +79,11 @@ you the best experience:
95* [`http-client-tls`](https://hackage.haskell.org/package/http-client-tls)—TLS
79* [`http-client-tls`](https://hackage.haskell.org/package/http-client-tls)—TLS
96 (HTTPS) support for `http-client`.
80 (HTTPS) support for `http-client`.
97
81
98−It's important to note that since we leverage well-known libraries that the
82+It is important to note that since we leverage well-known libraries that the
99whole Haskell ecosystem uses, there is no risk in using Req. The machinery
83whole Haskell ecosystem uses, there is no risk in using Req. The machinery
100for performing requests is the same as with `http-conduit` and Wreq. The
84for performing requests is the same as with `http-conduit` and Wreq. The
101only difference is the API.
85only difference is the API.
102
86
103−## Motivation and Req vs other libraries
104−
105−*This section is my opinion and it contains criticisms of other well-known
106−libraries. If you're a user/fan of one of these libraries, please remember
107−not to react aggressively and respect the fact that I may have different
108−views on API design from yours.*
109−
110−I have spent time to write the library because sending HTTP requests is a
111−common need, but there is no high-level library for that in Haskell that I
112−could use with pleasure. I'll explain why.
113−
114−First of all, there is `http-client` and `http-client-tls`. They just work.
115−I have no issues with the libraries except that they are too low-level for
116−my taste. Indeed, even the docs say that they are low-level and “intended as
117−a base layer for more user-friendly packages”. This is exactly how I use
118−them in Req, as base level. Req is nothing but a different API to
119−`http-client`, so it only works because of the hard work put into
120−`http-client`.
121−
122−`http-conduit` definitely has its place. For one thing it allows you to
123−stream request and response bodies in constant memory, what other library
124−allows you to do that? On the other hand if you take a look at
125−`Network.HTTP.Simple`, then although it's said that it's a “higher level
126−API”, it's mostly the same as vanilla `http-client` in spirit/approach and
127−just adds `conduit`-powered functions to perform requests and allows to use
128−global implicit `Manager` (Req does the same). If I tried to frame what
129−exactly I don't like about `http-conduit` in words, then it would be “the
130−way requests are constructed”. You *set* parameters instead of *being
131−forced* to declare necessary bits and *being allowed* to declare optional
132−bits in a way that their combination is valid. Also, with `http-conduit` you
133−parse request from a string without the protection of TH that otherwise
134−saves the day as in Yesod.
135−
136−Then there is Wreq. `wreq` [doesn't see much development
137−lately](https://github.com/bos/wreq/issues/93). `wreq` is by itself a weird
138−library, IMO. You have functions per method—not very good, as there may be
139−new methods, like PATCH which is not new but still missing (well, you have
140−`customMethod`, but what is the point of having per-method functions if you
141−have a more general way to use any method? you should be able to just insert
142−methods in the “argument slot” of `customMethod` and end up with a more
143−general solution). Now, every method function has a companion that takes
144−`Options` (like you have `get` and `getWith`). Why the duplication? Where is
145−generality and flexibility? This is not all though, because you cannot
146−really use `get` you see in the main module, because you want to have
147−connection sharing. Wreq's author does not take the gift of automatic
148−connection re-use `Manager` from `http-client` provides, he invents the
149−whole new thing of “sessions”. Only inside a session your connections will
150−be shared and re-used. However with the session stuff you have yet another
151−set of per-method functions like `get` and `getWith`—these are different
152−ones, to be used with sessions! Now if you have a multi-threaded app, here
153−is a surprise for you: you can't share connections between threads as
154−connections are shared only inside of the `withSession` friend and “session
155−will no longer be valid after that function returns”. There are valid uses
156−for sessions, but the point is that they are just too inconvenient for
157−common tasks.
158−
159−I used `servant-client` a couple of times but the amount of boilerplate it
160−requires is frightening. If you have several query parameters, and you use
161−just one of them, you'll have to pass lots of `Nothing`s.
162−
163−## Unsolved problems
164−
165−AWS request signing is problematic because request body can be in the form
166−of an action to execute (and all that “popper” stuff for streaming), not
167−just a `ByteString` and so getting its digest (hash) is not trivial without
168−running the action and consuming body in its entirety before the request in
169−made. In Wreq the author chose to just use `error` when body is not a
170−(strict or lazy) `ByteString`. Maybe it's OK for Wreq, but I don't consider
171−this a proper solution for Req as we support full variety of body options.
172−For example, what if I want to upload 1 Gb file to S3? I want to stream it
173−in constant memory but at the same time I need to calculate its hash before
174−I start streaming. One solution to the problem seems to be in taking the
175−hash explicitly (as an argument of the hypothetical `awsAuth`) and making it
176−a responsibility of the user to calculate the hash correctly. I don't like
177−this because it's not user-friendly. So the question stays open, for now
178−there is no AWS signing functionality provided out-of-the-box. The best
179−solution for talking to AWS is the `amazonka` package so far.
180−
181## Related packages
87## Related packages
182
88
183The following packages are designed to be used with Req:
89The following packages are designed to be used with Req:
httpbin-tests/Network/HTTP/ReqSpec.hs+4−5
@@ -394,14 +394,13 @@ instance MonadHttp IO where
394----------------------------------------------------------------------------
394----------------------------------------------------------------------------
395-- Helpers
395-- Helpers
396
396
397−-- | Run request with such settings that it does not signal error on adverse
397+-- | Run a request with such settings that it does not signal errors.
398−-- response status codes.
399prepareForShit :: Req a -> IO a
398prepareForShit :: Req a -> IO a
400prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
399prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
401 where
400 where
402 noNoise _ _ _ = Nothing
401 noNoise _ _ _ = Nothing
403
402
404−-- | Run request with such settings that it throws on any response.
403+-- | Run a request with such settings that it throws on any response.
405blindlyThrowing :: Req a -> IO a
404blindlyThrowing :: Req a -> IO a
406blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
405blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
407 where
406 where
@@ -461,11 +460,11 @@ selector404
461 L.responseStatus response == Y.status404 && not (B.null chunk)
460 L.responseStatus response == Y.status404 && not (B.null chunk)
462selector404 _ = False
461selector404 _ = False
463
462
464−-- | Empty JSON 'Object'.
463+-- | The empty JSON 'Object'.
465emptyObject :: Value
464emptyObject :: Value
466emptyObject = Object HM.empty
465emptyObject = Object HM.empty
467
466
468−-- | Get rendered JSON value as 'Text'.
467+-- | Get a rendered JSON value as 'Text'.
469reflectJSON :: ToJSON a => a -> Text
468reflectJSON :: ToJSON a => a -> Text
470reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
469reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
471
470
pure-tests/Network/HTTP/ReqSpec.hs+2−2
@@ -562,14 +562,14 @@ uriPort def uri =
562 maybe def (fromIntegral) $
562 maybe def (fromIntegral) $
563 either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
563 either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
564
564
565−-- | Get path from 'URI'.
565+-- | Get the path from a 'URI'.
566uriPath :: URI -> ByteString
566uriPath :: URI -> ByteString
567uriPath uri = fromMaybe "" $ do
567uriPath uri = fromMaybe "" $ do
568 (trailingSlash, xs) <- URI.uriPath uri
568 (trailingSlash, xs) <- URI.uriPath uri
569 let pref = (encodePathPieces . fmap URI.unRText . NE.toList) xs
569 let pref = (encodePathPieces . fmap URI.unRText . NE.toList) xs
570 return $ if trailingSlash then pref <> "/" else pref
570 return $ if trailingSlash then pref <> "/" else pref
571
571
572−-- | Get query string from 'URI'.
572+-- | Get the query string from a 'URI'.
573uriQuery :: URI -> ByteString
573uriQuery :: URI -> ByteString
574uriQuery uri = do
574uriQuery uri = do
575 let liftQueryParam = \case
575 let liftQueryParam = \case
req.cabal+5−9
@@ -8,12 +8,8 @@ author: Mark Karpov <markkarpov92@gmail.com>
8tested-with: ghc ==8.6.5 ghc ==8.8.4 ghc ==8.10.3
8tested-with: ghc ==8.6.5 ghc ==8.8.4 ghc ==8.10.3
9homepage: https://github.com/mrkkrp/req
9homepage: https://github.com/mrkkrp/req
10bug-reports: https://github.com/mrkkrp/req/issues
10bug-reports: https://github.com/mrkkrp/req/issues
11−synopsis:
11+synopsis: HTTP client library
12− Easy-to-use, type-safe, expandable, high-level HTTP client library
13−
14−description:
12+description: HTTP client library.
15− Easy-to-use, type-safe, expandable, high-level HTTP client library.
16−
17category: Network, Web
13category: Network, Web
18build-type: Simple
14build-type: Simple
19data-files:
15data-files:
@@ -57,7 +53,7 @@ library
57 text >=0.2 && <1.3,
53 text >=0.2 && <1.3,
58 time >=1.2 && <1.10,
54 time >=1.2 && <1.10,
59 transformers >=0.4 && <0.6,
55 transformers >=0.4 && <0.6,
60− transformers-base -any,
56+ transformers-base,
61 unliftio-core >=0.1.1 && <0.3
57 unliftio-core >=0.1.1 && <0.3
62
58
63 if flag(dev)
59 if flag(dev)
@@ -91,7 +87,7 @@ test-suite pure-tests
91 http-types >=0.8 && <10.0,
87 http-types >=0.8 && <10.0,
92 modern-uri >=0.3 && <0.4,
88 modern-uri >=0.3 && <0.4,
93 mtl >=2.0 && <3.0,
89 mtl >=2.0 && <3.0,
94− req -any,
90+ req,
95 retry >=0.8 && <0.9,
91 retry >=0.8 && <0.9,
96 template-haskell >=2.14 && <2.17,
92 template-haskell >=2.14 && <2.17,
97 text >=0.2 && <1.3,
93 text >=0.2 && <1.3,
@@ -120,7 +116,7 @@ test-suite httpbin-tests
120 http-types >=0.8 && <10.0,
116 http-types >=0.8 && <10.0,
121 monad-control >=1.0 && <1.1,
117 monad-control >=1.0 && <1.1,
122 mtl >=2.0 && <3.0,
118 mtl >=2.0 && <3.0,
123− req -any,
119+ req,
124 text >=0.2 && <1.3,
120 text >=0.2 && <1.3,
125 unordered-containers >=0.2.5 && <0.3
121 unordered-containers >=0.2.5 && <0.3
126
122