packages feed

launchdarkly-server-sdk 3.1.0 → 3.1.1

raw patch · 5 files changed

+56/−18 lines, 5 files

Files

CHANGELOG.md view
@@ -2,6 +2,11 @@  All notable changes to the LaunchDarkly Haskell Server-side SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [3.1.1] - 2023-02-17+### Fixed:+- The polling thread will be shutdown if the LaunchDarkly polling API returns an unrecoverable response code.+- Updated dependency versions in the Redis store package to mirror the SDK requirements.+ ## [3.1.0] - 2023-01-27 ### Added: - New `ApplicationInfo` type, for configuration of application metadata that may be used in LaunchDarkly analytics or other product features. This does not affect feature flag evaluations.
launchdarkly-server-sdk.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           launchdarkly-server-sdk-version:        3.1.0+version:        3.1.1 synopsis:       Server-side SDK for integrating with LaunchDarkly description:    Please see the README on GitHub at <https://github.com/launchdarkly/haskell-server-sdk#readme> category:       Web
src/LaunchDarkly/Server/Client/Internal.hs view
@@ -27,7 +27,7 @@  -- | The version string for this library. clientVersion :: Text-clientVersion = "3.1.0"+clientVersion = "3.1.1"  setStatus :: ClientI -> Status -> IO () setStatus client status' =
src/LaunchDarkly/Server/Network/Common.hs view
@@ -6,6 +6,7 @@     , tryHTTP     , addToAL     , handleUnauthorized+    , isHttpUnrecoverable     ) where  import Data.ByteString.Internal            (unpackChars)@@ -56,3 +57,9 @@     where headers = responseHeaders response           date = fromMaybe "" $ lookup hDate headers           parsedTime = parseTimeM True defaultTimeLocale rfc822DateFormat (unpackChars date)++isHttpUnrecoverable :: Int -> Bool+isHttpUnrecoverable status+    | status < 400 || status >= 500 = False+    | status `elem` [400, 408, 429] = False+    | otherwise = True
src/LaunchDarkly/Server/Network/Polling.hs view
@@ -5,15 +5,14 @@ import qualified Data.Text as                            T import           Network.HTTP.Client                     (Manager, Request(..), Response(..), httpLbs) import           Data.Generics.Product                   (getField)-import           Control.Monad                           (forever) import           Control.Concurrent                      (threadDelay) import           Data.Aeson                              (eitherDecode, FromJSON(..)) import           Control.Monad.Logger                    (MonadLogger, logInfo, logError) import           Control.Monad.IO.Class                  (MonadIO, liftIO) import           Control.Monad.Catch                     (MonadMask, MonadThrow)-import           Network.HTTP.Types.Status               (ok200)+import           Network.HTTP.Types.Status               (ok200, Status (statusCode)) -import           LaunchDarkly.Server.Network.Common      (checkAuthorization, tryHTTP, handleUnauthorized)+import           LaunchDarkly.Server.Network.Common      (checkAuthorization, tryHTTP, handleUnauthorized, isHttpUnrecoverable) import           LaunchDarkly.Server.Features            (Flag, Segment) import           LaunchDarkly.AesonCompat                (KeyMap) @@ -23,33 +22,60 @@ import LaunchDarkly.Server.Config.ClientContext import LaunchDarkly.Server.Client.Internal (Status(..)) import LaunchDarkly.Server.Config.HttpConfiguration (HttpConfiguration(..), prepareRequest)+import Data.ByteString.Lazy (ByteString)  data PollingResponse = PollingResponse     { flags    :: !(KeyMap Flag)     , segments :: !(KeyMap Segment)     } deriving (Generic, FromJSON, Show) -processPoll :: (MonadIO m, MonadLogger m, MonadMask m, MonadThrow m) => Manager -> DataSourceUpdates -> Request -> m ()+processPoll :: (MonadIO m, MonadLogger m, MonadMask m, MonadThrow m) => Manager -> DataSourceUpdates -> Request -> m Bool processPoll manager dataSourceUpdates request = liftIO (tryHTTP $ httpLbs request manager) >>= \case-    (Left err)       -> $(logError) (T.pack $ show err)-    (Right response) -> checkAuthorization response >> if responseStatus response /= ok200-        then $(logError) "unexpected polling status code"-        else case (eitherDecode (responseBody response) :: Either String PollingResponse) of-            (Left err)   -> $(logError) (T.pack $ show err)+    (Left err)       -> do+        $(logError) (T.pack $ show err)+        pure True+    (Right response) -> checkAuthorization response >> processResponse response++    where++    processResponse :: (MonadIO m, MonadLogger m, MonadMask m, MonadThrow m) => Response ByteString -> m Bool+    processResponse response+        | isHttpUnrecoverable $ statusCode $ responseStatus response = do+            $(logError) "polling stopping after receiving unrecoverable error"+            pure False+        | responseStatus response /= ok200 = do+            $(logError) "unexpected polling status code"+            pure True+        | otherwise = case (eitherDecode (responseBody response) :: Either String PollingResponse) of+            (Left err)   -> do+                $(logError) (T.pack $ show err)+                pure $ True             (Right body) -> do                 status <- liftIO $ dataSourceUpdatesInit dataSourceUpdates (getField @"flags" body) (getField @"segments" body)                 case status of-                  Right () -> liftIO $ dataSourceUpdatesSetStatus dataSourceUpdates Initialized-                  Left err ->+                  Right () -> do+                    liftIO $ dataSourceUpdatesSetStatus dataSourceUpdates Initialized+                    pure $ True+                  Left err -> do                     $(logError) $ T.append "store failed put: " err+                    pure $ True  ++ pollingThread :: (MonadIO m, MonadLogger m, MonadMask m) => Text -> Natural -> ClientContext -> DataSourceUpdates -> m () pollingThread baseURI pollingIntervalSeconds clientContext dataSourceUpdates = do     let pollingMicroseconds = fromIntegral pollingIntervalSeconds * 1000000-    req <- liftIO $ prepareRequest (httpConfiguration clientContext) (T.unpack baseURI ++ "/sdk/latest-all") -    handleUnauthorized dataSourceUpdates $ forever $ do+    req <- liftIO $ prepareRequest (httpConfiguration clientContext) (T.unpack baseURI ++ "/sdk/latest-all")+    handleUnauthorized dataSourceUpdates $ (poll req pollingMicroseconds)++    where++    poll :: (MonadIO m, MonadLogger m, MonadMask m) => Request -> Int -> m ()+    poll req pollingMicroseconds = do         $(logInfo) "starting poll"-        processPoll (tlsManager $ httpConfiguration clientContext) dataSourceUpdates req-        $(logInfo) "finished poll"-        liftIO $ threadDelay pollingMicroseconds+        processPoll (tlsManager $ httpConfiguration clientContext) dataSourceUpdates req >>= \case+            True -> do+                liftIO $ threadDelay pollingMicroseconds+                poll req pollingMicroseconds+            False -> pure ()