[haskell] Prevent que networking code from triggering the API rate limiting
This commit is contained in:
parent
dcd0a7a9b2
commit
d1f0faf30f
4 changed files with 39 additions and 8 deletions
|
@ -27,6 +27,7 @@ dependencies:
|
||||||
- sqlite-simple
|
- sqlite-simple
|
||||||
- text
|
- text
|
||||||
- time
|
- time
|
||||||
|
- unbounded-delays
|
||||||
- unix
|
- unix
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
|
|
|
@ -1,28 +1,46 @@
|
||||||
|
{-# LANGUAGE NumericUnderscores #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module SpaceTraders
|
module SpaceTraders
|
||||||
( SpaceTradersT
|
( SpaceTradersT
|
||||||
|
, microSinceEpoch
|
||||||
|
, newEnv
|
||||||
, runSpaceTradersT
|
, runSpaceTradersT
|
||||||
, Env(..)
|
, Env(..)
|
||||||
, HasDatabaseConn
|
, HasDatabaseConn
|
||||||
, HasRequest
|
, HasRequest
|
||||||
, ask
|
, ask
|
||||||
, getConn
|
, getConn
|
||||||
|
, getLastAPICall
|
||||||
, getRequest
|
, getRequest
|
||||||
, liftIO
|
, liftIO
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
import Data.IORef
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Time.Clock.POSIX
|
||||||
import qualified Database.SQLite.Simple as S
|
import qualified Database.SQLite.Simple as S
|
||||||
import Network.HTTP.Simple
|
import Network.HTTP.Simple
|
||||||
|
|
||||||
type SpaceTradersT a = ReaderT Env IO a
|
type SpaceTradersT a = ReaderT Env IO a
|
||||||
|
|
||||||
|
newEnv :: S.Connection -> Request -> IO Env
|
||||||
|
newEnv conn req = do
|
||||||
|
r <- newIORef 0
|
||||||
|
return $ Env conn r req
|
||||||
|
|
||||||
runSpaceTradersT :: SpaceTradersT a -> Env -> IO a
|
runSpaceTradersT :: SpaceTradersT a -> Env -> IO a
|
||||||
runSpaceTradersT = runReaderT
|
runSpaceTradersT = runReaderT
|
||||||
|
|
||||||
|
microSinceEpoch :: IO Integer
|
||||||
|
microSinceEpoch = do
|
||||||
|
t <- getCurrentTime
|
||||||
|
return $ floor . (1e6 *) . nominalDiffTimeToSeconds $ utcTimeToPOSIXSeconds t
|
||||||
|
|
||||||
data Env = Env { envConn :: S.Connection
|
data Env = Env { envConn :: S.Connection
|
||||||
|
, envLastAPICall :: IORef Integer
|
||||||
, envRequest :: Request }
|
, envRequest :: Request }
|
||||||
|
|
||||||
class HasDatabaseConn a where
|
class HasDatabaseConn a where
|
||||||
|
@ -33,6 +51,8 @@ instance HasDatabaseConn Env where
|
||||||
getConn = envConn
|
getConn = envConn
|
||||||
|
|
||||||
class HasRequest a where
|
class HasRequest a where
|
||||||
|
getLastAPICall :: a -> IORef Integer
|
||||||
getRequest :: a -> Request
|
getRequest :: a -> Request
|
||||||
instance HasRequest Env where
|
instance HasRequest Env where
|
||||||
|
getLastAPICall = envLastAPICall
|
||||||
getRequest = envRequest
|
getRequest = envRequest
|
||||||
|
|
|
@ -11,9 +11,11 @@ module SpaceTraders.APIClient.Client
|
||||||
, tokenReq
|
, tokenReq
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Concurrent
|
import Control.Concurrent.Thread.Delay
|
||||||
|
import Control.Monad
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
|
import Data.IORef
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Text.Encoding as T
|
import qualified Data.Text.Encoding as T
|
||||||
import Network.HTTP.Simple
|
import Network.HTTP.Simple
|
||||||
|
@ -62,9 +64,17 @@ sendPaginated pagination requestBuilder = do
|
||||||
Nothing -> request
|
Nothing -> request
|
||||||
sendPaginated' request'
|
sendPaginated' request'
|
||||||
where
|
where
|
||||||
sendPaginated' :: (FromJSON a, MonadIO m) => Request -> m (APIPaginatedResponse a)
|
sendPaginated' :: (FromJSON a, HasRequest env, MonadIO m, MonadReader env m) => Request -> m (APIPaginatedResponse a)
|
||||||
sendPaginated' request = do
|
sendPaginated' request = do
|
||||||
response <- liftIO $ httpLbs request
|
env <- ask
|
||||||
|
let iref = getLastAPICall env
|
||||||
|
response <- liftIO $ do
|
||||||
|
now <- microSinceEpoch
|
||||||
|
prev <- readIORef iref
|
||||||
|
let diff = prev + 400_000 - now
|
||||||
|
when (diff > 0) (delay diff)
|
||||||
|
writeIORef iref now
|
||||||
|
httpLbs request
|
||||||
let status = statusCode $ getResponseStatus response
|
let status = statusCode $ getResponseStatus response
|
||||||
body = getResponseBody response
|
body = getResponseBody response
|
||||||
if status >= 200 && status <= 299
|
if status >= 200 && status <= 299
|
||||||
|
@ -74,6 +84,6 @@ sendPaginated pagination requestBuilder = do
|
||||||
else case eitherDecode body of
|
else case eitherDecode body of
|
||||||
Left e -> return . Left $ APIError (-status) (T.pack $ concat ["Error decoding JSON APIError: ", e, ". Got HTTP body: ", show body]) Null
|
Left e -> return . Left $ APIError (-status) (T.pack $ concat ["Error decoding JSON APIError: ", e, ". Got HTTP body: ", show body]) Null
|
||||||
Right (APIRateLimit r) -> do
|
Right (APIRateLimit r) -> do
|
||||||
liftIO $ threadDelay (1_000_000 * (round $ retryAfter r))
|
liftIO $ delay (1_000_000 * (round $ retryAfter r))
|
||||||
sendPaginated' request
|
sendPaginated' request
|
||||||
Right e -> return $ Left e
|
Right e -> return $ Left e
|
||||||
|
|
|
@ -30,7 +30,7 @@ initST :: IO Env
|
||||||
initST = do
|
initST = do
|
||||||
conn <- open
|
conn <- open
|
||||||
t <- runReaderT getToken conn `catch` handleNoToken conn
|
t <- runReaderT getToken conn `catch` handleNoToken conn
|
||||||
let env = Env conn (tokenReq t)
|
env <- newEnv conn (tokenReq t)
|
||||||
ma <- runReaderT getAgent conn -- We compare the agent state in the database
|
ma <- runReaderT getAgent conn -- We compare the agent state in the database
|
||||||
ma' <- runSpaceTradersT myAgent env -- with the one on the servers
|
ma' <- runSpaceTradersT myAgent env -- with the one on the servers
|
||||||
case ma' of
|
case ma' of
|
||||||
|
@ -44,7 +44,7 @@ initST = do
|
||||||
return $ env
|
return $ env
|
||||||
where
|
where
|
||||||
handleNoToken :: S.Connection -> SomeException -> IO T.Text
|
handleNoToken :: S.Connection -> SomeException -> IO T.Text
|
||||||
handleNoToken conn _ = runReaderT registerST (Env conn defaultReq)
|
handleNoToken conn _ = newEnv conn defaultReq >>= runReaderT registerST
|
||||||
|
|
||||||
registerST :: SpaceTradersT (T.Text)
|
registerST :: SpaceTradersT (T.Text)
|
||||||
registerST = do
|
registerST = do
|
||||||
|
@ -64,5 +64,5 @@ wipe c = do
|
||||||
close c
|
close c
|
||||||
removeFile "spacetraders.db"
|
removeFile "spacetraders.db"
|
||||||
conn' <- open
|
conn' <- open
|
||||||
t <- runReaderT registerST (Env conn' defaultReq)
|
t <- newEnv conn' defaultReq >>= runReaderT registerST
|
||||||
return $ Env conn' (tokenReq t)
|
newEnv conn' (tokenReq t)
|
||||||
|
|
Loading…
Add table
Reference in a new issue