craze 0.1.2.0 → 0.1.3.0
raw patch · 4 files changed
+449/−228 lines, 4 filesdep +lensdep +lifted-asyncdep +lifted-base
Dependencies added: lens, lifted-async, lifted-base, monad-control, mtl, transformers-base
Files
- craze.cabal +41/−15
- src/Network/Craze.hs +97/−213
- src/Network/Craze/Internal.hs +201/−0
- src/Network/Craze/Types.hs +110/−0
craze.cabal view
@@ -1,9 +1,9 @@--- This file has been generated from package.yaml by hpack version 0.9.0.+-- This file has been generated from package.yaml by hpack version 0.14.0. -- -- see: https://github.com/sol/hpack name: craze-version: 0.1.2.0+version: 0.1.3.0 synopsis: HTTP Racing Library description: A micro-library for racing HTTP GET requests homepage: https://github.com/etcinit/craze#readme@@ -29,11 +29,19 @@ , bytestring , transformers , text+ , mtl+ , lifted-async+ , lifted-base+ , monad-control+ , transformers-base+ , lens , async , data-default-class , containers exposed-modules: Network.Craze+ Network.Craze.Internal+ Network.Craze.Types default-language: Haskell2010 executable craze-example@@ -47,13 +55,19 @@ , bytestring , transformers , text+ , mtl+ , lifted-async+ , lifted-base+ , monad-control+ , transformers-base+ , lens , craze , optparse-generic default-language: Haskell2010 -test-suite craze-spec+test-suite craze-doctest type: exitcode-stdio-1.0- main-is: Spec.hs+ main-is: Doctest.hs hs-source-dirs: test ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -Wall -fno-warn-unused-do-bind@@ -63,20 +77,23 @@ , bytestring , transformers , text+ , mtl+ , lifted-async+ , lifted-base+ , monad-control+ , transformers-base+ , lens , craze- , hspec- , hspec-discover- , http-types- , haxy- , HTTP+ , doctest+ , doctest-discover other-modules:- Doctest Network.CrazeSpec+ Spec default-language: Haskell2010 -test-suite craze-doctest+test-suite craze-spec type: exitcode-stdio-1.0- main-is: Doctest.hs+ main-is: Spec.hs hs-source-dirs: test ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -Wall -fno-warn-unused-do-bind@@ -86,10 +103,19 @@ , bytestring , transformers , text+ , mtl+ , lifted-async+ , lifted-base+ , monad-control+ , transformers-base+ , lens , craze- , doctest- , doctest-discover+ , hspec+ , hspec-discover+ , http-types+ , haxy+ , HTTP other-modules:+ Doctest Network.CrazeSpec- Spec default-language: Haskell2010
src/Network/Craze.hs view
@@ -1,22 +1,25 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE ImpredicativeTypes #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-} -- | Craze is a small module for performing multiple similar HTTP GET requests -- in parallel. This is performed through the `raceGet` function, which will -- perform all the requests and pick the first successful response that passes--- a certain check, menaing that the parallel requests are essentially racing+-- a certain check, meaning that the parallel requests are essentially racing -- against each other. -- -- __What is the usefulness of this?__ -- -- If you are dealing with data source or API that is very unreliable (high--- latency, random failures) and there are no limitations or consequences on--- perfoming significantly more requests, then performing multiple requests--- (through direct connections, proxies, VPNs) may increase the chances of--- getting a successful response faster and more reliably.+-- latency, random failures) and there are no limitations on performing+-- significantly more requests, then performing multiple requests (through+-- direct connections, proxies, VPNs) may increase the chances of getting a+-- successful response faster and more reliably. -- -- However, if using a different data source or transport is a possibility, it -- is potentially a better option that this approach.@@ -26,11 +29,17 @@ -- Performing two parallel GET requests against https://chromabits.com and -- returning the status code of the first successful one: --+-- The providers generate two client configurations. The handler "parses" the+-- response (in this case it just gets the status code). Finally, the checker+-- filters out responses that we don't consider valid (anything that is not+-- HTTP 200 in this case).+--+-- >>> :set -XOverloadedStrings -- >>> :{ -- let racer = (Racer -- { racerProviders =--- [ return defaultProviderOptions--- , return defaultProviderOptions+-- [ simpleTagged [] "Client A"+-- , simpleTagged [] "Client B" -- ] -- , racerHandler = return . respStatus -- , racerChecker = (200 ==)@@ -46,150 +55,39 @@ RacerHandler , RacerChecker , Racer(..)+ , RacerProvider , ProviderOptions(..)- , RacerResult+ , RacerResult(..)+ , ClientStatus(..) -- * Functions- , defaultRacer- , defaultProviderOptions , raceGet , raceGetResult -- * Providers+ -- $providers , simple , simpleTagged , delayed , delayedTagged- -- * Results- , rrResponse- , rrWinner- , rrProviders+ -- * Deprecated+ , defaultRacer+ , defaultProviderOptions ) where -import Control.Concurrent (threadDelay)+import Control.Monad (when)+import Data.Map.Lazy (keys, lookup)+import Prelude hiding (lookup)+ import Control.Concurrent.Async-import Control.Monad (when)+import Control.Monad.State (runStateT)+import Control.Monad.Trans (MonadIO, liftIO) import Data.ByteString (ByteString)-import Data.Default.Class (Default, def)-import Data.Map.Lazy (Map, delete, elems, fromList, keys,- lookup, mapWithKey)-import Data.Monoid ((<>))+import Data.Default.Class (def) import Data.Text (Text, pack) import qualified Data.Text.IO as TIO import Network.Curl-import Prelude hiding (lookup) --- | A `RacerHandler` is simply a function for transforming a response after it--- is received. The handler is only applied to successful requests before they--- are checked by the `RacerChecker`.------ This is primarily for extracting or parsing a `CurlResponse_` before doing--- any further work. The type returned by the handler will be used as the--- input of the checker and will be the return type of `raceGet`.-type RacerHandler headerTy bodyTy a = CurlResponse_ headerTy bodyTy -> IO a---- | A function that computes whether or not a result is valid or not.--- Successful responses that do not pass the checker are discarded.------ This should help filtering out successful responses that do not, for some--- reason, have the expected result (e.g. Random content changes, Rate--- Limitting, etc).-type RacerChecker a = a -> Bool---- | A function that returns the @ProviderOptions@ to use for making a request.-type RacerProvider = IO ProviderOptions---- | Options for a specific provider.-data ProviderOptions = ProviderOptions- { -- | Options to pass down to Curl.- poOptions :: [CurlOption]- -- | Number of microseconds to delay the request by.- , poDelay :: Maybe Int- -- | A tag to identify this type provider.- , poTag :: Text- } deriving (Show)--instance Default ProviderOptions where- def = ProviderOptions- { poOptions = []- , poDelay = Nothing- , poTag = "default"- }---- | The result of a racing operation. This can be used to collect statistics--- on which providers win more often, etc.-data RacerResult a = RacerResult- { rrResponse :: Maybe a- , rrWinner :: Maybe ProviderOptions- , rrProviders :: [RacerProvider]- }---- | A record describing the rules for racing requests.-data Racer headerTy bodyTy a = Racer- { racerHandler :: RacerHandler headerTy bodyTy a- , racerChecker :: RacerChecker a- -- | On a `Racer`, each `RaceProvider` represents a separate client- -- configuration. When performing a race, each provider will be used to spwan- -- a client and perform a request. This allows one to control the number of- -- requests performed and with which `CurlOption`s.- , racerProviders :: [RacerProvider]- -- | When set to `True`, debugging messages will be written to stdout.- , racerDebug :: Bool- -- | When set to `True`, the Racer will attempt to return the last response- -- in the event that all responses failed to pass the checker. This can be- -- used for identifying error conditions.- , racerReturnLast :: Bool- }--instance Default (Racer [(String,String)] ByteString ByteString) where- def = Racer- { racerHandler = return . respBody- , racerChecker = const True- , racerProviders = []- , racerDebug = False- , racerReturnLast = False- }---- | A `Racer` with some default values.------ __Note:__ The handler will extract the response body as a `ByteString` and--- ignore everything else, hence the type:------ @--- Racer [(String, String)] ByteString ByteString--- @------ If this is not the desired behavior, or if the response should be parsed or--- processed, you should use the `Racer` constructor directly and provide all--- fields.-defaultRacer :: Racer [(String,String)] ByteString ByteString-defaultRacer = def---- | A default set of options for a provider.-defaultProviderOptions :: ProviderOptions-defaultProviderOptions = def---- | A simple provider. It does not delay requests.-simple :: [CurlOption] -> IO ProviderOptions-simple xs = pure $ def { poOptions = xs }---- | Like @simple@, but with a tag for identification.-simpleTagged :: [CurlOption] -> Text -> IO ProviderOptions-simpleTagged xs t = do- opts <- simple xs- pure $ opts { poTag = t }---- | A provider which will delay a request by the provided number of--- microseconds.-delayed :: [CurlOption] -> Int -> IO ProviderOptions-delayed xs d = pure $ def- { poOptions = xs- , poDelay = Just d- }---- | Like @delayed@, but with a tag for identification.-delayedTagged :: [CurlOption] -> Int -> Text -> IO ProviderOptions-delayedTagged xs d t = do- opts <- delayed xs d- pure $ opts { poTag = t }+import Network.Craze.Internal+import Network.Craze.Types -- | Perform a GET request on the provided URL using all providers in -- parallel.@@ -210,105 +108,91 @@ -- finish. -- raceGet- :: (Eq a, CurlHeader ht, CurlBuffer bt)+ :: (Eq a, CurlHeader ht, CurlBuffer bt, MonadIO m) => Racer ht bt a -> URLString- -> IO (Maybe a)+ -> m (Maybe a) raceGet r url = rrResponse <$> raceGetResult r url -- | Same as @raceGet@, but returns a @RacerResult@ which contains more -- information about the race performed. raceGetResult- :: (Eq a, CurlHeader ht, CurlBuffer bt)+ :: (Eq a, CurlHeader ht, CurlBuffer bt, MonadIO m) => Racer ht bt a -> URLString- -> IO (RacerResult a)-raceGetResult r url = do- asyncs <- fromList <$> mapM performGetAsync_ (racerProviders r)+ -> m (RacerResult a)+raceGetResult r@Racer{..} url = do+ initialState@RaceState{..} <- makeRaceState (pack url) r - when (racerDebug r) $ do+ let asyncs = keys _rsClientMap++ when racerDebug . liftIO $ do TIO.putStr "[racer] Created Asyncs: "- print . elems $ mapWithKey identifier asyncs+ print $ asyncThreadId <$> asyncs - maybeResponse <- waitForOne- asyncs (racerHandler r) (racerChecker r) (racerDebug r) (racerReturnLast r)+ (maybeResponse, finalState) <- runStateT waitForOne initialState pure $ case maybeResponse of Nothing -> RacerResult { rrResponse = Nothing , rrWinner = Nothing- , rrProviders = racerProviders r+ , rrProviders = racerProviders+ , rrStatuses = extractStatuses finalState } Just (as, response) -> RacerResult { rrResponse = Just response- , rrWinner = lookup as asyncs- , rrProviders = racerProviders r+ , rrWinner = _csOptions <$> lookup as _rsClientMap+ , rrProviders = racerProviders+ , rrStatuses = extractStatuses finalState }- where- performGetAsync_- :: (CurlHeader ht, CurlBuffer bt)- => RacerProvider- -> IO (Async (CurlResponse_ ht bt), ProviderOptions)- performGetAsync_ = performGetAsync url -waitForOne- :: (Eq a)- => Map (Async (CurlResponse_ ht bt)) ProviderOptions- -> RacerHandler ht bt a- -> RacerChecker a- -> Bool- -> Bool- -> IO (Maybe (Async (CurlResponse_ ht bt), a))-waitForOne asyncs handler check debug returnLast- = if null asyncs then pure Nothing else do- winner <- waitAnyCatch (keys asyncs)-- case winner of- (as, Right a) -> do- result <- handler a-- let remaining = delete as asyncs-- if check result then do- cancelAll (keys remaining)-- when debug $ do- TIO.putStr "[racer] Winner: "- print (asyncThreadId as)-- pure $ Just (as, result)- else (if returnLast && null remaining- then do- when debug $ do- TIO.putStr "[racer] Reached last. Returning: "- print (asyncThreadId as)-- pure $ Just (as, result)- else waitForOne remaining handler check debug returnLast- )- (as, Left _) -> waitForOne- (delete as asyncs) handler check debug returnLast--cancelAll :: [Async a] -> IO ()-cancelAll = mapM_ (async . cancel)+-- $providers+--+-- 'RacerProvider' provide client configurations. Craze comes bundled with a+-- few built-in providers which can be used for quickly building client+-- configurations. -identifier :: Async (CurlResponse_ ht bt) -> ProviderOptions -> Text-identifier a o = poTag o <> ":" <> (pack . show . asyncThreadId $ a)+-- | A simple provider. It does not delay requests.+simple :: Monad m => [CurlOption] -> m ProviderOptions+simple xs = pure $ def { poOptions = xs } -performGetAsync- :: (CurlHeader ht, CurlBuffer bt)- => URLString- -> RacerProvider- -> IO (Async (CurlResponse_ ht bt), ProviderOptions)-performGetAsync url provider = do- options <- provider+-- | Like @simple@, but with a tag for identification.+simpleTagged :: Monad m => [CurlOption] -> Text -> m ProviderOptions+simpleTagged xs t = do+ opts <- simple xs+ pure $ opts { poTag = t } - responseAsync <- async $ do- case poDelay options of- Nothing -> pure ()- Just delay -> threadDelay delay+-- | A provider which will delay a request by the provided number of+-- microseconds.+delayed :: Monad m => [CurlOption] -> Int -> m ProviderOptions+delayed xs d = pure $ def+ { poOptions = xs+ , poDelay = Just d+ } - curlGetResponse_ url (poOptions options)+-- | Like @delayed@, but with a tag for identification.+delayedTagged :: Monad m => [CurlOption] -> Int -> Text -> m ProviderOptions+delayedTagged xs d t = do+ opts <- delayed xs d+ pure $ opts { poTag = t } - pure (responseAsync, options)+-- | A `Racer` with some default values.+--+-- __Note:__ The handler will extract the response body as a `ByteString` and+-- ignore everything else, hence the type:+--+-- @+-- Racer [(String, String)] ByteString ByteString+-- @+--+-- If this is not the desired behavior, or if the response should be parsed or+-- processed, you should use the `Racer` constructor directly and provide all+-- fields.+defaultRacer :: Racer [(String,String)] ByteString ByteString+defaultRacer = def+{-# DEPRECATED defaultRacer "Use Data.Default.Class.def instead" #-} +-- | A default set of options for a provider.+defaultProviderOptions :: ProviderOptions+defaultProviderOptions = def+{-# DEPRECATED defaultProviderOptions "Use Data.Default.Class.def instead" #-}
+ src/Network/Craze/Internal.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}++module Network.Craze.Internal where++import Control.Exception (SomeException)+import Control.Monad (forM, when)+import Data.Map.Lazy (Map)+import qualified Data.Map.Lazy as M+import Data.Monoid ((<>))++import Control.Concurrent.Async.Lifted (Async, async, asyncThreadId,+ cancel, waitAnyCatch)+import Control.Concurrent.Lifted (threadDelay)+import Control.Lens (at, makeLenses, use, (&),+ (.~), (?=))+import Control.Monad.State (MonadState)+import Control.Monad.Trans (MonadIO, liftIO)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Network.Curl (CurlBuffer, CurlHeader,+ CurlResponse_,+ curlGetResponse_)++import Network.Craze.Types++type ClientMap ht bt a = Map (Async (CurlResponse_ ht bt)) (ClientState a)++data ClientState a = ClientState+ { _csOptions :: ProviderOptions+ , _csStatus :: ClientStatus a+ }++data RaceState ht bt a = RaceState+ { _rsClientMap :: ClientMap ht bt a+ , _rsChecker :: RacerChecker a+ , _rsHandler :: RacerHandler ht bt a+ , _rsDebug :: Bool+ , _rsReturnLast :: Bool+ }++makeLenses ''ClientState+makeLenses ''RaceState++extractStatuses :: RaceState ht bt a -> [(Text, ClientStatus a)]+extractStatuses RaceState{..} = M.elems $ makeTuple <$> _rsClientMap+ where+ makeTuple :: ClientState a -> (Text, ClientStatus a)+ makeTuple ClientState{..} = (poTag _csOptions, _csStatus)++makeRaceState+ :: (CurlHeader ht, CurlBuffer bt, MonadIO m)+ => Text+ -> Racer ht bt a+ -> m (RaceState ht bt a)+makeRaceState url Racer{..} = do+ providerMap <- makeClientMap url racerProviders++ pure $ RaceState+ providerMap+ racerChecker+ racerHandler+ racerDebug+ racerReturnLast++makeClientMap+ :: (CurlHeader ht, CurlBuffer bt, MonadIO m)+ => Text+ -> [RacerProvider]+ -> m (ClientMap ht bt a)+makeClientMap url providers = M.fromList <$> forM providers (makeClient url)++makeClient+ :: (CurlHeader ht, CurlBuffer bt, MonadIO m)+ => Text+ -> RacerProvider+ -> m (Async (CurlResponse_ ht bt), ClientState a)+makeClient url provider = liftIO $ do+ options <- provider+ future <- async $ performGet url options++ pure (future, ClientState options Pending)++performGet+ :: (CurlHeader ht, CurlBuffer bt)+ => Text+ -> ProviderOptions+ -> IO (CurlResponse_ ht bt)+performGet url ProviderOptions{..} = do+ case poDelay of+ Nothing -> pure ()+ Just delay -> threadDelay delay++ curlGetResponse_ (T.unpack url) poOptions++cancelAll :: MonadIO m => [Async a] -> m ()+cancelAll = liftIO . mapM_ (async . cancel)++cancelRemaining+ :: (MonadIO m, MonadState (RaceState ht bt a) m)+ => m ()+cancelRemaining = do+ remaining <- onlyPending <$> use rsClientMap++ cancelAll $ M.keys remaining++identifier :: Async (CurlResponse_ ht bt) -> ProviderOptions -> Text+identifier a o = poTag o <> ":" <> (T.pack . show . asyncThreadId $ a)++onlyPending :: ClientMap ht bt a -> ClientMap ht bt a+onlyPending = M.filter (isPending . _csStatus)++isPending :: ClientStatus a -> Bool+isPending Pending = True+isPending _ = False++markAsSuccessful+ :: (MonadState (RaceState ht bt a) m)+ => Async (CurlResponse_ ht bt)+ -> a+ -> m ()+markAsSuccessful key result = do+ maybePrevious <- use $ rsClientMap . at key++ case maybePrevious of+ Just previous -> (rsClientMap . at key)+ ?= (previous & csStatus .~ Successful result)+ Nothing -> pure ()++markAsFailure+ :: (MonadState (RaceState ht bt a) m)+ => Async (CurlResponse_ ht bt)+ -> a+ -> m ()+markAsFailure key result = do+ maybePrevious <- use $ rsClientMap . at key++ case maybePrevious of+ Just previous -> (rsClientMap . at key)+ ?= (previous & csStatus .~ Failed result)+ Nothing -> pure ()++markAsErrored+ :: (MonadState (RaceState ht bt a) m)+ => Async (CurlResponse_ ht bt)+ -> SomeException+ -> m ()+markAsErrored key result = do+ maybePrevious <- use $ rsClientMap . at key++ case maybePrevious of+ Just previous -> (rsClientMap . at key)+ ?= (previous & csStatus .~ Errored result)+ Nothing -> pure ()++waitForOne+ :: (Eq a, MonadIO m, MonadState (RaceState ht bt a) m)+ => m (Maybe (Async (CurlResponse_ ht bt), a))+waitForOne = do+ debug <- use rsDebug+ providerMap <- use rsClientMap++ let asyncs = _csOptions <$> onlyPending providerMap++ if null asyncs then pure Nothing else do+ winner <- liftIO $ waitAnyCatch (M.keys asyncs)++ case winner of+ (as, Right a) -> do+ handler <- use rsHandler+ check <- use rsChecker+ returnLast <- use rsReturnLast+ result <- liftIO $ handler a++ if check result then do+ markAsSuccessful as result+ cancelRemaining++ when debug . liftIO $ do+ TIO.putStr "[racer] Winner: "+ print (asyncThreadId as)++ pure $ Just (as, result)+ else do+ markAsFailure as result++ remaining <- M.keys . onlyPending <$> use rsClientMap++ if returnLast && null remaining+ then do+ when debug . liftIO $ do+ TIO.putStr "[racer] Reached last. Returning: "+ print (asyncThreadId as)++ pure $ Just (as, result)+ else waitForOne+ (as, Left ex) -> markAsErrored as ex >> waitForOne
+ src/Network/Craze/Types.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++module Network.Craze.Types where++import Control.Exception (SomeException)++import Data.ByteString (ByteString)+import Data.Default.Class (Default, def)+import Data.Text (Text)+import Network.Curl (CurlOption, CurlResponse_, respBody)++-- | A 'RacerHandler' is simply a function for transforming a response after it+-- is received. The handler is only applied to successful requests before they+-- are checked by the 'RacerChecker'.+--+-- This is primarily for extracting or parsing a 'CurlResponse_' before doing+-- any further work. The type returned by the handler will be used as the+-- input of the checker and will be the return type of functions like+-- 'raceGet'.+--+type RacerHandler headerTy bodyTy a = CurlResponse_ headerTy bodyTy -> IO a++-- | A function that computes whether or not a result is valid or not.+--+-- A racer will discard successful responses it get from its clients if they do+-- not pass the checker.+--+-- This step allows the racer to potentially discard responses that, while+-- technically successful, do not contain the expected result (e.g. APIs that+-- return errors as HTTP 200s, rate limitting messages, or unexpected formats).+--+type RacerChecker a = a -> Bool++-- | A provider is simply a factory function for 'ProviderOptions', which are+-- used to configure a client.+type RacerProvider = IO ProviderOptions++-- | Configuration used to set up an individual client in the race.+data ProviderOptions = ProviderOptions+ { -- | Options to pass down to Curl.+ poOptions :: [CurlOption]+ -- | Number of microseconds to delay the request by.+ --+ -- Delays can be used to give other clients a headstart. This is useful+ -- in cases were some clients are more costly to use than others (e.g.+ -- Bandwidth costs, resource usage, etc).+ --+ , poDelay :: Maybe Int+ -- | A tag to identify this type provider.+ --+ -- Tags are not required to be unique, but they are generally more helpful+ -- if they are.+ , poTag :: Text+ } deriving (Show)++instance Default ProviderOptions where+ def = ProviderOptions+ { poOptions = []+ , poDelay = Nothing+ , poTag = "default"+ }++-- | The status of running a single client.+data ClientStatus a+ -- | A successful response (passed the checker). A race will usually only+ -- have one successful response.+ = Successful a+ -- | A response that was received but failed to pass the checker.+ | Failed a+ -- | An exception thrown while using the client.+ | Errored SomeException+ -- | The operation is still pending, was cancelled, or was never started.+ | Pending+ deriving (Show)++-- | The result of a racing operation. This can be used to collect statistics+-- on which providers win more often, etc.+data RacerResult a = RacerResult+ { rrResponse :: Maybe a+ , rrWinner :: Maybe ProviderOptions+ , rrProviders :: [RacerProvider]+ , rrStatuses :: [(Text, ClientStatus a)]+ }++-- | A record describing the rules for racing requests.+data Racer headerTy bodyTy a = Racer+ { racerHandler :: RacerHandler headerTy bodyTy a+ , racerChecker :: RacerChecker a+ -- | On a `Racer`, each `RaceProvider` represents a separate client+ -- configuration. When performing a race, each provider will be used to spwan+ -- a client and perform a request. This allows one to control the number of+ -- requests performed and with which `CurlOption`s.+ , racerProviders :: [RacerProvider]+ -- | When set to `True`, debugging messages will be written to stdout.+ , racerDebug :: Bool+ -- | When set to `True`, the Racer will attempt to return the last response+ -- in the event that all responses failed to pass the checker. This can be+ -- used for identifying error conditions.+ , racerReturnLast :: Bool+ }++instance Default (Racer [(String,String)] ByteString ByteString) where+ def = Racer+ { racerHandler = pure . respBody+ , racerChecker = const True+ , racerProviders = []+ , racerDebug = False+ , racerReturnLast = False+ }