@@ -8,7 +8,7 @@
88-- Portability : portable
99--
1010-- This is an easy-to-use, type-safe, expandable, high-level HTTP library
11−-- that just works without any fooling around.
11+-- that just works without fooling around.
1212--
1313-- The documentation below is structured in such a way that most important
1414-- information goes first: you learn how to do HTTP requests, then how to
@@ -28,17 +28,20 @@
2828-- * <https://hackage.haskell.org/package/http-conduit> — conduit
2929-- interface to @http-client@.
3030--
31−-- You generally won't need low-level interface of @http-client@ at all
32−-- because this package covers /everything/, so don't import it or import
33−-- qualified because it has naming conflicts with @req@.
31+-- You won't need low-level interface of @http-client@ most of the time, but
32+-- when you do, it's better import it qualified because it has naming
33+-- conflicts with @req@.
3434
3535{-# LANGUAGE DataKinds #-}
36+{-# LANGUAGE DeriveDataTypeable #-}
37+{-# LANGUAGE DeriveGeneric #-}
3638{-# LANGUAGE FlexibleInstances #-}
3739{-# LANGUAGE GADTs #-}
3840{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3941{-# LANGUAGE KindSignatures #-}
4042{-# LANGUAGE MultiParamTypeClasses #-}
4143{-# LANGUAGE OverloadedStrings #-}
44+{-# LANGUAGE RecordWildCards #-}
4245{-# LANGUAGE ScopedTypeVariables #-}
4346{-# LANGUAGE TypeFamilies #-}
4447{-# LANGUAGE UndecidableInstances #-}
@@ -47,10 +50,9 @@ module Network.HTTP.Req
4750 ( -- * Making a request
4851 req
4952 -- * Embedding requests into your monad
53+ -- $embedding-requests
5054 , MonadHttp (..)
5155 , HttpConfig (..)
52− , httpConfig
53− , httpsConfig
5456 -- * Request
5557 -- ** Methods
5658 -- $request-methods
@@ -79,30 +81,38 @@ module Network.HTTP.Req
7981 , QueryParam (..)
8082 -- *** Headers
8183 , header
84+ -- *** Cookies
8285 -- *** Authorization
8386 -- *** Other
8487 , port
85− , redirectCount
8688 -- * Response
8789 , HttpResponse (..)
8890 -- * Other
89− , CanHaveBody (..)
90− , Scheme (..) )
91+ , CanHaveBody (..) )
9192where
9293
94+import Control.Monad.Error (MonadError (..))
9395import Control.Monad.IO.Class
9496import Control.Monad.Reader
9597import Data.Aeson
9698import Data.ByteString
99+import Data.Data (Data)
100+import Data.Default.Class
101+import Data.IORef
97102import Data.Proxy
98103import Data.Semigroup hiding (Option)
99104import Data.Text (Text)
105+import Data.Typeable (Typeable)
106+import GHC.Generics
100107import GHC.TypeLits
101−import Numeric.Natural
102−import qualified Data.ByteString as B
103−import qualified Data.ByteString.Lazy as BL
104−import qualified Network.HTTP.Client as L
105−import qualified Network.HTTP.Types as Y
108+-- import Numeric.Natural
109+import System.IO.Unsafe (unsafePerformIO)
110+import qualified Data.ByteString as B
111+import qualified Data.ByteString.Lazy as BL
112+import qualified Network.Connection as NC
113+import qualified Network.HTTP.Client as L
114+import qualified Network.HTTP.Client.TLS as L
115+import qualified Network.HTTP.Types as Y
106116
107117----------------------------------------------------------------------------
108118-- Making a request
@@ -110,14 +120,13 @@ import qualified Network.HTTP.Types as Y
110120-- | Perform an HTTP request.
111121
112122req
113− :: ( MonadHttp m
114− , HttpMethod method
115− , HttpBody body
116− , HttpResponse response
117− , AllowsBody method ~ ProvidesBody body
118− , ProvidesConnection m ~ scheme )
123+ :: ( MonadHttp m
124+ , HttpMethod method
125+ , HttpBody body
126+ , HttpResponse response
127+ , AllowsBody method ~ ProvidesBody body )
119128 => method
120− -> Endpoint scheme
129+ -> Endpoint
121130 -> body
122131 -> Option
123132 -> m response
@@ -127,28 +136,81 @@ req = undefined
127136-- synchronous to give user a chance to work with pure, explicit exceptions
128137-- if he wants to do so.
129138
130−-- TODO How to specify return type here?
131−-- TODO It needs a way to signal errors.
132−
133139----------------------------------------------------------------------------
134140-- Embedding requests into your monad
135141
142+-- $embedding-requests
143+--
144+-- To use 'req' in your monad, all you need to do is to make it an instance
145+-- of the 'MonadHttp' type class, which see.
146+
147+-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
148+-- settings, so users don't need to deal with this manager stuff at all, but
149+-- when we create a request, instance 'HttpConfig' can affect the default
150+-- settings via 'getHttpConfig'.
151+--
152+-- A note about safety in case 'unsafePerformIO' looks suspicious to you.
153+-- The value of 'globalManager' is named and lives on top level. This means
154+-- it will be shared, i.e. computed only once on first use of manager. From
155+-- that moment on the 'IORef' will be just reused — exactly the behaviour we
156+-- want here in order to maximize connection sharing.
157+
158+globalManager :: IORef L.Manager
159+globalManager = unsafePerformIO $ do
160+ context <- NC.initConnectionContext
161+ let settings = L.mkManagerSettingsContext (Just context) def Nothing
162+ manager <- L.newManager settings
163+ newIORef manager
164+{-# NOINLINE globalManager #-}
165+
166+-- | A type class for monads that support performing HTTP requests.
167+-- Typically, you only need to define the 'handleHttpException' method
168+-- unless you want to tweak 'HttpConfig'.
169+
136170class MonadIO m => MonadHttp m where
137− type ProvidesConnection m :: Scheme
138− getHttpConfig :: m (HttpConfig (ProvidesConnection m))
139171
140−-- | 'HttpConfig' contains general and default settings to be used when
141−-- making HTTP requests.
172+ {-# MINIMAL handleHttpException #-}
173+
174+ -- | This method describes how to deal with 'L.HttpException' that was
175+ -- caught by the library. One option is to re-throw it if you are OK with
176+ -- exceptions, but if you prefer working with something like 'MonadError',
177+ -- this is the right place to pass it to 'throwError' for example.
178+
179+ handleHttpException :: L.HttpException -> m a
142180
143−data HttpConfig (scheme :: Scheme) = HttpConfig
144− { httpConfigManager :: L.Manager
145− }
181+ -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
182+ -- implementation returns its 'def' value, which is described in the
183+ -- documentation for the type. Common usage pattern with manually defined
184+ -- 'getHttpConfig' is to return some hard-coded value, or value extracted
185+ -- from 'MonadReader' if a more flexible approach to configuration is
186+ -- desirable.
146187
147−httpConfig :: MonadIO m => m (HttpConfig 'Http)
148−httpConfig = undefined -- FIXME not final, need to pass params in
188+ getHttpConfig :: m HttpConfig
189+ getHttpConfig = return def
149190
150−httpsConfig :: MonadIO m => m (HttpConfig 'Https)
151−httpsConfig = undefined -- FIXME
191+-- | 'HttpConfig' contains general and default settings to be used when
192+-- making HTTP requests.
193+
194+data HttpConfig = HttpConfig
195+ { httpConfigProxy :: Maybe L.Proxy
196+ -- ^ Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
197+ -- environment variables are respected, this setting overwrites them.
198+ -- Default value: 'Nothing'.
199+ , httpConfigRedirectCount :: Word
200+ -- ^ How many redirects to follow when getting a resource. Default
201+ -- value: 10.
202+ } deriving (Show, Read, Eq, Ord, Typeable, Generic)
203+ -- ↑ NOTE We can't derive 'Data' here because of 'L.Proxy'.
204+
205+instance Default HttpConfig where
206+ def = HttpConfig
207+ { httpConfigProxy = Nothing
208+ , httpConfigRedirectCount = 10 }
209+
210+instance RequestComponent HttpConfig where
211+ getRequestMod HttpConfig {..} = Endo $ \x ->
212+ x { L.proxy = httpConfigProxy
213+ , L.redirectCount = fromIntegral httpConfigRedirectCount }
152214
153215----------------------------------------------------------------------------
154216-- Request — Methods
@@ -266,35 +328,35 @@ instance HttpMethod method => RequestComponent (Womb "method" method) where
266328----------------------------------------------------------------------------
267329-- Request — URL
268330
269−newtype Endpoint (scheme :: Scheme) = Endpoint { unEndpoint :: Text }
331+newtype Endpoint = Endpoint { unEndpoint :: Text }
332+
333+instance RequestComponent Endpoint where
334+ getRequestMod _ = undefined -- TODO
270335
271336-- data Endpoint (s :: Scheme) where
272337-- Http' :: Text -> Endpoint 'Http
273338-- Https' :: Text -> Endpoint 'Https
274339-- (:/) :: Endpoint s -> Text -> Endpoint s
275340
276−http :: Text -> Endpoint 'Http
341+http :: Text -> Endpoint
277342http = Endpoint . percentEncode
278343
279−https :: Text -> Endpoint 'Https
344+https :: Text -> Endpoint
280345https = Endpoint . percentEncode
281346
282−infixr 5 /:
347+infixl 5 /:
283348
284−(/:) :: Endpoint scheme -> Text -> Endpoint scheme
349+(/:) :: Endpoint -> Text -> Endpoint
285350(/:) = undefined -- TODO
286351
287352-- TODO Escape things in endpoint and query parameters, only :/ should work
288353-- as real /
289354
290−parseUrlHttp :: Text -> Endpoint 'Http
291−parseUrlHttp = undefined
292−
293−parseUrlHttps :: Text -> Endpoint 'Https
294−parseUrlHttps = undefined
355+parseUrl :: Text -> Endpoint
356+parseUrl = undefined
295357
296358----------------------------------------------------------------------------
297−-- Request body
359+-- Request — Body
298360
299361class HttpBody b where
300362 type ProvidesBody b :: CanHaveBody
@@ -359,6 +421,11 @@ instance QueryParam Option where
359421header :: Text -> Text -> Option
360422header = undefined
361423
424+----------------------------------------------------------------------------
425+-- Request — Optional parameters — Cookies
426+
427+-- TODO No idea right now.
428+
362429----------------------------------------------------------------------------
363430-- Request — Optional parameters — Authorization
364431
@@ -367,11 +434,11 @@ header = undefined
367434----------------------------------------------------------------------------
368435-- Request — Optional parameters — Other
369436
370−port :: Natural -> Option
437+port :: Word -> Option
371438port = undefined -- TODO
372439
373−redirectCount :: Natural -> Option
374−redirectCount = undefined -- TODO
440+-- redirectCount :: Word -> Option
441+-- redirectCount = undefined -- TODO
375442
376443-- etc.
377444
@@ -393,7 +460,5 @@ class RequestComponent a where
393460
394461data CanHaveBody = CanHaveBody | NoBody
395462
396−data Scheme = Http | Https
397−
398463percentEncode :: Text -> Text
399464percentEncode = undefined