on 21c052d help sign in
827 lines 31 kB history raw
1-- |
2-- Module : Network.HTTP.Req
3-- Copyright : © 2016 Mark Karpov
4-- License : BSD 3 clause
5--
6-- Maintainer : Mark Karpov <markkarpov@openmailbox.org>
7-- Stability : experimental
8-- Portability : portable
9--
10-- This is an easy-to-use, type-safe, expandable, high-level HTTP library
11-- that just works without any fooling around.
12--
13-- /(A modest intro goes here, click on 'req' to start making requests.)/
14--
15-- What does the “easy-to-use” phrase mean? It means that the library is
16-- designed to be beginner-friendly, so it's simple to add it to your monad
17-- stack, intuitive to work with, well-documented, and does not get in your
18-- way. On this path certain compromises were made. For example one cannot
19-- currently modify 'L.ManagerSettings' of default manager because the
20-- library always use the same implicit global manager for simplicity and
21-- maximal connection sharing. There is a way to use your own manager with
22-- different settings, but it requires a bit more typing. Doing HTTP
23-- requests is a common task and Haskell library for this should be very
24-- approachable and clear to beginners.
25--
26-- “Type-safe” means that the library is protective and eliminates certain
27-- class of errors compared to alternative libraries like @wreq@ or vanilla
28-- @http-client@. For example we have correct-by-construction 'Url's, it's
29-- guaranteed that user does not send request body when using methods like
30-- 'GET' or 'DELETE', amount of implicit assumptions is minimized by making
31-- user specify his\/her intentions in explicit form (for example it's not
32-- possible to avoid specifying body or method of a request). The library
33-- carefully hides underlying types from lower-level @http-client@ package
34-- because it's not type safe enough (for example 'L.Request' is an instance
35-- of 'Data.String.IsString' and if it's malformed, it will blow up at
36-- run-time).
37--
38-- “Expandable” refers to the ability of the library to be expanded without
39-- ugly hacking. For example it's possible to define your own HTTP methods,
40-- new ways to construct body of request, new authorization options, new
41-- ways to actually perform request and how to represent\/parse its
42-- response. As user extends the library to satisfy his\/her special needs,
43-- the new solutions work just like built-ins. That said, all common cases
44-- are covered by the library out-of-the-box.
45--
46-- “High-level” means that there are less details to worry about. The
47-- library is a result of my experiences as a Haskell consultant, working
48-- for several clients who have very different projects and so the library
49-- adapts easily to any particular style of writing Haskell applications.
50-- For example some people prefer throwing exceptions, while others are
51-- concerned with purity: just define 'handleHttpException' accordingly when
52-- making your monad instance of 'MonadHttp' and it will play seamlessly.
53-- Finally, the library cuts boilerplate considerably and helps write
54-- concise, easy to read code, thanks to its minimal and uniform API.
55--
56-- The documentation below is structured in such a way that most important
57-- information goes first: you learn how to do HTTP requests, then how to
58-- embed them in any monad you have, then it goes on giving you details
59-- about less-common things you may want to know about. The documentation is
60-- written with sufficient coverage of details and examples, it's designed
61-- to be a complete tutorial on its own.
62--
63-- The library uses the following well-known and mature packages under the
64-- hood to guarantee you best experience without bugs or other funny
65-- business:
66--
67-- * <https://hackage.haskell.org/package/http-client> — low level HTTP
68-- client used everywhere in Haskell.
69-- * <https://hackage.haskell.org/package/http-client-tls> — TLS (HTTPS)
70-- support for @http-client@.
71-- * <https://hackage.haskell.org/package/http-conduit> — conduit
72-- interface to @http-client@.
73--
74-- You won't need low-level interface of @http-client@ most of the time, but
75-- when you do, it's better import it qualified because it has naming
76-- conflicts with @req@.
78{-# LANGUAGE DataKinds #-}
79{-# LANGUAGE DeriveDataTypeable #-}
80{-# LANGUAGE DeriveGeneric #-}
81{-# LANGUAGE FlexibleInstances #-}
82{-# LANGUAGE GeneralizedNewtypeDeriving #-}
83{-# LANGUAGE KindSignatures #-}
84{-# LANGUAGE OverloadedStrings #-}
85{-# LANGUAGE RecordWildCards #-}
86{-# LANGUAGE ScopedTypeVariables #-}
87{-# LANGUAGE TypeFamilies #-}
88{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
90module Network.HTTP.Req
91 ( -- * Making a request
92 -- $making-a-request
93 req
94 -- * Embedding requests into your monad
95 -- $embedding-requests
96 , MonadHttp (..)
97 , HttpConfig (..)
98 -- * Request
99 -- ** Methods
100 -- $request-methods
101 , GET (..)
102 , POST (..)
103 , HEAD (..)
104 , PUT (..)
105 , DELETE (..)
106 , TRACE (..)
107 , CONNECT (..)
108 , OPTIONS (..)
109 , PATCH (..)
110 , HttpMethod (..)
111 -- ** URL
112 , Url
113 , http
114 , https
115 , (/:)
116 , parseUrlHttp
117 , parseUrlHttps
118 -- ** Body
119 , HttpBody (..) -- TODO more stuff here
120 -- ** Optional parameters
121 -- $request-optional-parameters
122 , Option
123 -- *** Query parameters
124 , (=:)
125 , queryFlag
126 , QueryParam (..)
127 -- *** Headers
128 , header
129 -- *** Cookies
130 -- *** Authentication
131 -- $request-optional-parameters-authentication
132 , basicAuth
133 , oAuth1
134 , oAuth2Bearer
135 , oAuth2Token
136 , awsAuth
137 -- *** Other
138 , port
139 , decompress
140 , responseTimeout
141 , httpVersion
142 -- * Response
143 , HttpResponse (..)
144 -- * Other
145 , CanHaveBody (..)
146 , Scheme (..) )
147where
149import Control.Applicative ((<|>))
150import Control.Arrow (first, second)
151import Control.Exception (try)
152import Control.Monad
153import Control.Monad.IO.Class
154import Data.Aeson
155import Data.ByteString (ByteString)
156import Data.Data (Data)
157import Data.Default.Class
158import Data.IORef
159import Data.List.NonEmpty (NonEmpty (..))
160import Data.Proxy
161import Data.Semigroup hiding (Option, option)
162import Data.Text (Text)
163import Data.Typeable (Typeable)
164import GHC.Generics
165import GHC.TypeLits
166import System.IO.Unsafe (unsafePerformIO)
167import qualified Data.Binary.Builder as R
168import qualified Data.ByteString as B
169import qualified Data.ByteString.Lazy as BL
170import qualified Data.CaseInsensitive as CI
171import qualified Data.List.NonEmpty as NE
172import qualified Data.Text.Encoding as T
173import qualified Network.Connection as NC
174import qualified Network.HTTP.Client as L
175import qualified Network.HTTP.Client.Internal as LI
176import qualified Network.HTTP.Client.TLS as L
177import qualified Network.HTTP.Req.AWS as AWS
178import qualified Network.HTTP.Req.OAuth1 as OAuth1
179import qualified Network.HTTP.Types as Y
181----------------------------------------------------------------------------
182-- Making a request
184-- $making-a-request
185--
186-- To make an HTTP request you need only one function: 'req'.
188-- | Make an HTTP request.
189--
190-- TODO Finish docs of this function when the package is more developed.
192req
193 :: forall m method body response scheme.
194 ( MonadHttp m
195 , HttpMethod method
196 , HttpBody body
197 , HttpResponse response
198 , AllowsBody method ~ ProvidesBody body )
199 => method -- ^ HTTP method
200 -> Url scheme -- ^ 'Url' — location of resource
201 -> body -- ^ Body of the request
202 -> Option scheme -- ^ Collection of optional parameters
203 -> m response -- ^ Response
204req method url body options = do
205 config <- getHttpConfig
206 manager <- liftIO (readIORef globalManager)
207 let request = flip appEndo L.defaultRequest $
208 -- NOTE Order of 'mappend's matters, here method is overwritten
209 -- first and 'config' takes its effect last. In particular, this
210 -- means that 'options' can overwrite things set by 'url' and
211 -- 'body', which is useful for setting port number, "Content-Type"
212 -- header, etc.
213 getRequestMod config <>
214 getRequestMod options <>
215 getRequestMod (Womb body :: Womb "body" body) <>
216 getRequestMod url <>
217 getRequestMod (Womb method :: Womb "method" method)
218 liftIO (try $ getHttpResponse manager request)
219 >>= either handleHttpException return
221-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
222-- settings, so users don't need to deal with this manager stuff at all, but
223-- when we create a request, instance 'HttpConfig' can affect the default
224-- settings via 'getHttpConfig'.
225--
226-- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
227-- The value of 'globalManager' is named and lives on top level. This means
228-- it will be shared, i.e. computed only once on first use of manager. From
229-- that moment on the 'IORef' will be just reused — exactly the behaviour we
230-- want here in order to maximize connection sharing. GHC could spoil the
231-- plan by inlining the definition, hence the @NOINLINE@ pragma.
233globalManager :: IORef L.Manager
234globalManager = unsafePerformIO $ do
235 context <- NC.initConnectionContext
236 let settings = L.mkManagerSettingsContext (Just context) def Nothing
237 manager <- L.newManager settings
238 newIORef manager
239{-# NOINLINE globalManager #-}
241----------------------------------------------------------------------------
242-- Embedding requests into your monad
244-- $embedding-requests
245--
246-- To use 'req' in your monad, all you need to do is to make the monad an
247-- instance of the 'MonadHttp' type class, which see.
249-- | A type class for monads that support performing HTTP requests.
250-- Typically, you only need to define the 'handleHttpException' method
251-- unless you want to tweak 'HttpConfig'.
253class MonadIO m => MonadHttp m where
255 {-# MINIMAL handleHttpException #-}
257 -- | This method describes how to deal with 'L.HttpException' that was
258 -- caught by the library. One option is to re-throw it if you are OK with
259 -- exceptions, but if you prefer working with something like
260 -- 'Control.Monad.Error.MonadError', this is the right place to pass it to
261 -- 'Control.Monad.Error.throwError' for example.
263 handleHttpException :: L.HttpException -> m a
265 -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
266 -- implementation returns its 'def' value, which is described in the
267 -- documentation for the type. Common usage pattern with manually defined
268 -- 'getHttpConfig' is to return some hard-coded value, or value extracted
269 -- from 'Control.Monad.Reader.MonadReader' if a more flexible approach to
270 -- configuration is desirable.
272 getHttpConfig :: m HttpConfig
273 getHttpConfig = return def
275-- | 'HttpConfig' contains general and default settings to be used when
276-- making HTTP requests.
278data HttpConfig = HttpConfig
279 { httpConfigProxy :: !(Maybe L.Proxy)
280 -- ^ Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
281 -- environment variables are respected, this setting overwrites them.
282 -- Default value: 'Nothing'.
283 , httpConfigRedirectCount :: !Word
284 -- ^ How many redirects to follow when getting a resource. Default
285 -- value: 10.
286 , httpConfigAltManager :: !(Maybe L.Manager)
287 -- ^ Alternative 'L.Manager' to use. 'Nothing' (default value) means
288 -- that default implicit manager will be used (that's what you want in
289 -- 99% of cases).
290 , httpConfigCheckResponse :: L.Request -> L.Response L.BodyReader -> IO ()
291 -- ^ Check the response immediately after receiving the status and
292 -- headers. This is used for throwing exceptions on non-success status
293 -- codes by default. Throwing is better then just returning a request
294 -- with non-2xx status code because in that case something is wrong and
295 -- we need a way to short-cut execution. The thrown exception is caught
296 -- by the library though and is available in 'handleHttpException',
297 -- which see.
298 } deriving Typeable
300instance Default HttpConfig where
301 def = HttpConfig
302 { httpConfigProxy = Nothing
303 , httpConfigRedirectCount = 10
304 , httpConfigAltManager = Nothing
305 , httpConfigCheckResponse = \_ response ->
306 let Y.Status statusCode _ = L.responseStatus response in
307 unless (200 <= statusCode && statusCode < 300) $ do
308 chunk <- BL.toStrict <$> L.brReadSome (L.responseBody response) 1024
309 LI.throwHttp (L.StatusCodeException (() <$ response) chunk) }
311instance RequestComponent HttpConfig where
312 getRequestMod HttpConfig {..} = Endo $ \x ->
313 x { L.proxy = httpConfigProxy
314 , L.redirectCount = fromIntegral httpConfigRedirectCount
315 , LI.requestManagerOverride = httpConfigAltManager
316 , LI.checkResponse = httpConfigCheckResponse }
318----------------------------------------------------------------------------
319-- Request — Methods
321-- $request-methods
322--
323-- The package provides all methods as defined by RFC 2616, and 'PATCH'
324-- which is defined by RFC 5789 — that should be enough to talk to RESTful
325-- APIs. In some cases however, you may want to add more methods (e.g. you
326-- work with WebDAV <https://en.wikipedia.org/wiki/WebDAV>); no need to
327-- compromise on type safety and hack, it only takes a couple of seconds to
328-- define a new method that will works seamlessly, see 'HttpMethod'.
330-- | 'GET' method.
332data GET = GET
334instance HttpMethod GET where
335 type AllowsBody GET = 'NoBody
336 httpMethodName Proxy = Y.methodGet
338-- | 'POST' method.
340data POST = POST
342instance HttpMethod POST where
343 type AllowsBody POST = 'CanHaveBody
344 httpMethodName Proxy = Y.methodPost
346-- | 'HEAD' method.
348data HEAD = HEAD
350instance HttpMethod HEAD where
351 type AllowsBody HEAD = 'NoBody
352 httpMethodName Proxy = Y.methodHead
354-- | 'PUT' method.
356data PUT = PUT
358instance HttpMethod PUT where
359 type AllowsBody PUT = 'CanHaveBody
360 httpMethodName Proxy = Y.methodPut
362-- | 'DELETE' method.
364data DELETE = DELETE
366instance HttpMethod DELETE where
367 type AllowsBody DELETE = 'NoBody
368 httpMethodName Proxy = Y.methodDelete
370-- | 'TRACE' method.
372data TRACE = TRACE
374instance HttpMethod TRACE where
375 type AllowsBody TRACE = 'CanHaveBody
376 httpMethodName Proxy = Y.methodTrace
378-- | 'CONNECT' method.
380data CONNECT = CONNECT
382instance HttpMethod CONNECT where
383 type AllowsBody CONNECT = 'CanHaveBody
384 httpMethodName Proxy = Y.methodConnect
386-- | 'OPTIONS' method.
388data OPTIONS = OPTIONS
390instance HttpMethod OPTIONS where
391 type AllowsBody OPTIONS = 'NoBody
392 httpMethodName Proxy = Y.methodOptions
394-- | 'PATCH' method.
396data PATCH = PATCH
398instance HttpMethod PATCH where
399 type AllowsBody PATCH = 'CanHaveBody
400 httpMethodName Proxy = Y.methodPatch
402-- | A type class for types that can be used as an HTTP method. To define a
403-- non-standard method, follow this example that defines COPY:
404--
405-- > data COPY = COPY
406-- >
407-- > instance HttpMethod COPY where
408-- > type AllowsBody COPY = 'CanHaveBody
409-- > httpMethodName Proxy = "COPY"
411class HttpMethod a where
413 -- | Type function 'AllowsBody' returns type of kind 'CanHaveBody' which
414 -- tells the rest of the library whether the method can have a body or
415 -- not. We use the special type 'CanHaveBody' “lifted” into kind instead
416 -- of 'Bool' to get more user-friendly compiler messages.
418 type AllowsBody a :: CanHaveBody
420 -- | Return name of the method as a 'ByteString'.
422 httpMethodName :: Proxy a -> Y.Method
424instance HttpMethod method => RequestComponent (Womb "method" method) where
425 getRequestMod _ = Endo $ \x ->
426 x { L.method = httpMethodName (Proxy :: Proxy method) }
428----------------------------------------------------------------------------
429-- Request — URL
431-- | Request's 'Url'. Start constructing your 'Url' with 'http' or 'https'
432-- specifying the scheme and host at the same time. Then use the @('/:')@
433-- constructor to grow path one piece at a time. Every single piece of path
434-- will be url(percent)-encoded, so @('/:')@ is the only way to have forward
435-- slashes between path segments. This approach makes working with dynamic
436-- path segments easy and safe. See examples below how to represent various
437-- 'Url's (make sure the @OverloadedStrings@ language extension is enabled).
438--
439-- ==== __Examples__
440--
441-- > http "httpbin.org"
442-- > -- http://httpbin.org
443--
444-- > https "httpbin.org"
445-- > -- https://httpbin.org
446--
447-- > https "httpbin.org" /: "encoding" /: "utf8"
448-- > -- https://httpbin.org/encoding/utf8
449--
450-- > https "httpbin.org" /: "foo" /: "bar/baz"
451-- > -- https://httpbin.org/foo/bar%2Fbaz
452--
453-- > https "юникод.рф"
454-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
456data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
457 -- NOTE The second value is path segments in reversed order.
458 deriving (Eq, Ord, Show, Data, Typeable, Generic)
460-- | Given host name, produce a 'Url' which have “http” as its scheme and
461-- empty path. This also sets port to @80@.
463http :: Text -> Url 'Http
464http = Url Http . pure
466-- | Given host name, produce a 'Url' which have “https” as its scheme and
467-- empty path. This also sets port to @443@.
469https :: Text -> Url 'Https
470https = Url Https . pure
472-- | Grow given 'Url' appending a single path segment to it.
474infixl 5 /:
475(/:) :: Url scheme -> Text -> Url scheme
476Url secure path /: segment = Url secure (NE.cons segment path)
478-- | The 'parseUrlHttp' function provides an alternative method to get 'Url'
479-- (possibly with some 'Option's) from a 'ByteString'. This is useful when
480-- you are given an URL to query dynamically and don't know it beforehand.
481-- The function parses 'ByteString' because it's a correct type to hold an
482-- URL, as 'Url' cannot contain characters outside of ASCII range, thus we
483-- can consider every character a 'Data.Word.Word8' value.
484--
485-- This function only parses 'Url' (scheme, host, path) and optional query
486-- parameters that are returned as 'Option'. It does not parse method name
487-- or authentication info from given 'ByteString'.
489parseUrlHttp :: ByteString -> Maybe (Url 'Http, Option scheme)
490parseUrlHttp url' = do
491 url <- B.stripPrefix "http://" url'
492 (host :| path, option) <- parseUrlHelper url
493 return (foldl (/:) (http host) path, option)
495-- | Just like 'parseUrlHttp', but expects “https” scheme.
497parseUrlHttps :: ByteString -> Maybe (Url 'Https, Option scheme)
498parseUrlHttps url' = do
499 url <- B.stripPrefix "https://" url'
500 (host :| path, option) <- parseUrlHelper url
501 return (foldl (/:) (https host) path, option)
503-- | Get host\/collection of path pieces and possibly query parameters
504-- already converted to 'Option'. This function is not public.
506parseUrlHelper :: ByteString -> Maybe (NonEmpty Text, Option scheme)
507parseUrlHelper url = do
508 let (path', query') = B.break (== 0x3f) url
509 query = mconcat (uncurry queryParam <$> Y.parseQueryText query')
510 path <- NE.nonEmpty (Y.decodePathSegments path')
511 return (path, query)
513instance RequestComponent (Url scheme) where
514 getRequestMod (Url scheme segments) = Endo $ \x ->
515 let (host :| path) = NE.reverse segments in
516 x { L.secure = case scheme of
517 Http -> False
518 Https -> True
519 , L.port = case scheme of
520 Http -> 80
521 Https -> 443
522 , L.host = Y.urlEncode False (T.encodeUtf8 host)
523 , L.path =
524 (BL.toStrict . R.toLazyByteString . Y.encodePathSegments) path }
526----------------------------------------------------------------------------
527-- Request — Body
529class HttpBody b where
530 type ProvidesBody b :: CanHaveBody
531 getReqestBody :: b -> ByteString -- FIXME should use a conduit here
533data FormUrlEncodedParam = FormUrlEncodedParam Text (Maybe Text)
535instance QueryParam FormUrlEncodedParam where
536 queryParam = FormUrlEncodedParam
538instance HttpBody b => RequestComponent (Womb "body" b) where
539 getRequestMod = undefined -- FIXME
541----------------------------------------------------------------------------
542-- Request — Optional parameters
544-- $request-optional-parameters
545--
546-- Optional parameters to a request include things like query parameters,
547-- headers, port number, etc. All optional parameters have the type
548-- 'Option', which is a 'Monoid'. This means that you can use 'mempty' as
549-- the last argument of 'req' to specify no optional parameters, or combine
550-- 'Option's using 'mappend' (or @('<>')@) to have several of them at once.
552-- | Opaque 'Option' type is a 'Monoid' you can use to pack collection of
553-- optional parameters like query parameters and headers. See sections below
554-- to learn which 'Option' primitives are available.
556-- TODO We need examples here.
558data Option (scheme :: Scheme) =
559 Option (Endo (Y.QueryText, L.Request)) (Maybe (Endo L.Request))
560 -- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
561 -- Request to avoid appending to existing query string in request every
562 -- time new parameter is added. Additional Maybe (Endo Request) is a
563 -- finalizer that will be applied after all over options. This is for
564 -- authentication methods that sign requests based on data in Request.
566instance Semigroup (Option scheme) where
567 Option er0 mr0 <> Option er1 mr1 = Option
568 (er0 <> er1) (mr0 <|> mr1)
570instance Monoid (Option scheme) where
571 mempty = Option mempty Nothing
572 mappend = (<>)
574-- | A helper to create an 'Option' that modifies only collection of query
575-- parameters. This helper is not a part of public API.
577withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
578withQueryParams f = Option (Endo (first f)) Nothing
580-- | A helper to create an 'Option' that modifies only 'L.Request'. This
581-- helper is not a part of public API.
583withRequest :: (L.Request -> L.Request) -> Option scheme
584withRequest f = Option (Endo (second f)) Nothing
586-- | A helper to create an 'Option' that adds a finalier (request
587-- endomorphism that is run after all other modifications).
589withFinalizer :: (L.Request -> L.Request) -> Option scheme
590withFinalizer f = Option mempty (Just (Endo f))
592instance RequestComponent (Option scheme) where
593 getRequestMod (Option f finalizer) = Endo $ \x ->
594 let (qparams, x') = appEndo f ([], x)
595 query = Y.renderQuery True (Y.queryTextToQuery qparams)
596 in maybe id appEndo finalizer x' { L.queryString = query }
598----------------------------------------------------------------------------
599-- Request — Optional parameters — Query Parameters
601-- | This operator builds a query parameter that will be included in URL of
602-- your request after question sign @?@. This is the same syntax you use
603-- with form URL encoded request bodies.
604--
605-- This operator is defined in terms of 'queryParam':
606--
607-- > name =: value = queryParam name (pure value)
609infix 7 =:
610(=:) :: QueryParam a => Text -> Text -> a
611name =: value = queryParam name (pure value)
613-- | Construct a flag, that is, valueless query parameter. For example, in
614-- the following URL @a@ is a flag, @b@ is a query parameter with a value:
615--
616-- > https://httpbin.org/foo/bar?a&b=10
617--
618-- This operator is defined in terms of 'queryParam':
619--
620-- > queryFlag name = queryParam name Nothing
622queryFlag :: QueryParam a => Text -> a
623queryFlag name = queryParam name Nothing
625-- | A type class for query-parameter-like things. The reason to have
626-- overloaded 'queryParam' is to be able to use as an 'Option' and as a
627-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
628-- Having the same syntax for these cases seems natural and user-friendly.
630class QueryParam a where
632 -- | Create a query parameter with given name and value. If value is
633 -- 'Nothing', it won't be included at all (i.e. you create a flag this
634 -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
635 -- method, because they are easier to read.
637 queryParam :: Text -> Maybe Text -> a
639instance QueryParam (Option scheme) where
640 queryParam name mvalue = withQueryParams ((:) (name, mvalue))
642----------------------------------------------------------------------------
643-- Request — Optional parameters — Headers
645-- | Create an 'Option' that adds a header.
647header
648 :: ByteString -- ^ Header name
649 -> ByteString -- ^ Header value
650 -> Option scheme
651header name value = withRequest $ \x ->
652 x { L.requestHeaders = (CI.mk name, value) : L.requestHeaders x }
654----------------------------------------------------------------------------
655-- Request — Optional parameters — Cookies
657-- TODO No idea right now.
659----------------------------------------------------------------------------
660-- Request — Optional parameters — Authentication
662-- $request-optional-parameters-authentication
663--
664-- TODO Something. You should always prefer authentication 'Option's to
665-- manual construction of headers, etc., because it's a better style and
666-- provides additional type safety that prevents leaking of credentials.
668-- | OAuth 1 authentication 'Option'.
670oAuth1
671 :: ByteString -- ^ Consumer token
672 -> ByteString -- ^ Consumer secret
673 -> ByteString -- ^ OAuth token
674 -> ByteString -- ^ OAuth token secret
675 -> Option scheme
676oAuth1 ctoken csecret otoken osecret = withFinalizer
677 (OAuth1.signRequest ctoken csecret otoken osecret)
679-- | The 'Option' adds basic authentication. The 'Text' values will be UTF-8
680-- encoded.
681--
682-- See also: <https://en.wikipedia.org/wiki/Basic_access_authentication>
684basicAuth
685 :: ByteString -- ^ Username
686 -> ByteString -- ^ Password
687 -> Option 'Https -- ^ Auth 'Option'
688basicAuth username password = withRequest
689 (L.applyBasicAuth username password)
691-- | The 'Option' adds an OAuth2 bearer token. This is treated by many
692-- services as the equivalent of a username and password.
693--
694-- The 'Option' is defined as:
695--
696-- > oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
698oAuth2Bearer
699 :: ByteString -- ^ Token
700 -> Option 'Https -- ^ Auth 'Option'
701oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
703-- | The 'Option' adds a not-quite-standard OAuth2 bearer token (that seems
704-- to be used only by GitHub). This will be treated by whatever services
705-- accept it as the equivalent of a username and password.
706--
707-- The 'Option' is defined as:
708--
709-- > oAuth2Token token = header "Authorization" ("token" <> token)
711oAuth2Token
712 :: ByteString -- ^ Token
713 -> Option 'Https -- ^ Auth 'Option'
714oAuth2Token token = header "Authorization" ("token" <> token)
716-- | The 'Option' adds AWS v4 request signature.
718awsAuth
719 :: ByteString -- ^ AWS access key
720 -> ByteString -- ^ AWS secret access key
721 -> Option 'Https -- ^ Auth 'Option'
722awsAuth accessKey secretKey = withFinalizer
723 (AWS.signRequest accessKey secretKey)
725----------------------------------------------------------------------------
726-- Request — Optional parameters — Other
728-- | Specify the port to connect to explicitly. Normally, 'Url' you use
729-- determines default port, @80@ for HTTP and @443@ for HTTPS, this 'Option'
730-- allows to choose arbitrary port overwriting the defaults.
732port :: Word -> Option scheme
733port n = withRequest $ \x ->
734 x { L.port = fromIntegral n }
736-- | This 'Option' controls whether gzipped data should be decompressed on
737-- the fly. By default everything except for @application\/x-tar@ is
738-- decompressed, i.e. we have:
739--
740-- > decompress (/= "application/x-tar")
741--
742-- You can also choose to decompress everything like this:
743--
744-- > decompress (const True)
746decompress
747 :: (ByteString -> Bool) -- ^ Predicate that is given MIME type, it
748 -- returns 'True' when content should be decompressed on the fly.
749 -> Option scheme
750decompress f = withRequest $ \x ->
751 x { L.decompress = f }
753-- | Specify number of microseconds to wait for response. Default is 30
754-- seconds.
756responseTimeout
757 :: Word -- ^ Number of microseconds to wait
758 -> Option scheme
759responseTimeout n = withRequest $ \x ->
760 x { L.responseTimeout = LI.ResponseTimeoutMicro (fromIntegral n) }
762-- | HTTP version to send to server, default is HTTP 1.1.
764httpVersion
765 :: Word -- ^ Major version number
766 -> Word -- ^ Minor version number
767 -> Option scheme
768httpVersion major minor = withRequest $ \x ->
769 x { L.requestVersion =
770 Y.HttpVersion (fromIntegral major) (fromIntegral minor) }
772----------------------------------------------------------------------------
773-- Response
775-- Here we need to provide various options how to consume responses.
777class HttpResponse response where
778 getHttpResponse :: L.Manager -> L.Request -> IO response
780-- helpers to
782----------------------------------------------------------------------------
783-- Other
785-- | The main class for things that are “parts” of 'L.Request' in the sense
786-- that if we have a 'L.Request', then we know how to apply an instance of
787-- 'RequestComponent' changing\/overwriting something in it. 'Endo' is
788-- endomorphism of functions under composition, it's used to chain different
789-- request component easier using @('<>')@.
791class RequestComponent a where
793 -- | Get a function that takes a 'L.Request' and changes it somehow
794 -- returning another 'L.Request'. For example HTTP method instance of
795 -- 'RequestComponent' just overwrites method. The function is wrapped in
796 -- 'Endo' so it's easier to chain such “modifying applications” together
797 -- building bigger and bigger 'RequestComponent's.
799 getRequestMod :: a -> Endo L.Request
801-- | This wrapper is only used to attach a type-level tag to given type.
802-- This is necessary to define instances of 'RequestComponent' for any thing
803-- that implements 'HttpMethod' or 'HttpBody'. Without the tag, GHC is not
804-- able to see difference between @'HttpMethod' method => 'RequestComponent'
805-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
806-- which instance to use (i.e. constraints are taken into account later,
807-- when instance is already chosen).
809newtype Womb (tag :: Symbol) a = Womb a
811-- | A simple 'Bool'-like type we only have for better error messages. We
812-- use it as a kind and its data constructors as type-level tags.
813--
814-- See also: 'HttpMethod' and 'HttpBody'.
816data CanHaveBody
817 = CanHaveBody -- ^ Indeed can have a body
818 | NoBody -- ^ Should not have a body
820-- | A type-level tag that specifies URL scheme used (and thus if TLS is
821-- enabled). This is used to force TLS requirement for some authentication
822-- 'Option's.
824data Scheme
825 = Http -- ^ HTTP, no TLS
826 | Https -- ^ HTTPS
827 deriving (Eq, Ord, Show, Data, Typeable, Generic)