Disable transactions for read-only requests
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: dbapi
|
name: dbapi
|
||||||
version: 0.2.4.4
|
version: 0.2.4.5
|
||||||
synopsis: The database is your api
|
synopsis: The database is your api
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
|
|||||||
+3
-3
@@ -4,7 +4,7 @@ import Paths_dbapi (version)
|
|||||||
|
|
||||||
import Dbapi
|
import Dbapi
|
||||||
import Middleware (inTransaction, authenticated, withSavepoint, clientErrors,
|
import Middleware (inTransaction, authenticated, withSavepoint, clientErrors,
|
||||||
redirectInsecure, withDBConnection)
|
redirectInsecure, withDBConnection, Environment(..))
|
||||||
import Network.Wai.Handler.Warp hiding (Connection)
|
import Network.Wai.Handler.Warp hiding (Connection)
|
||||||
import Data.String.Conversions (cs)
|
import Data.String.Conversions (cs)
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ main = do
|
|||||||
runSettings settings $ (if configSecure conf then redirectInsecure else id)
|
runSettings settings $ (if configSecure conf then redirectInsecure else id)
|
||||||
. gzip def . cors corsPolicy . clientErrors
|
. gzip def . cors corsPolicy . clientErrors
|
||||||
. staticPolicy (only [("favicon.ico", "static/favicon.ico")])
|
. staticPolicy (only [("favicon.ico", "static/favicon.ico")])
|
||||||
. withDBConnection pool . inTransaction
|
. withDBConnection pool . inTransaction Production
|
||||||
. authenticated (cs $ configAnonRole conf) . withSavepoint $ app
|
. authenticated (cs $ configAnonRole conf) . withSavepoint Production $ app
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
describe = progDesc "create a REST API to an existing Postgres database"
|
describe = progDesc "create a REST API to an existing Postgres database"
|
||||||
|
|||||||
+25
-10
@@ -20,26 +20,41 @@ import Network.HTTP.Types.Header (RequestHeaders, hContentType, hAuthorization,
|
|||||||
hLocation)
|
hLocation)
|
||||||
import Network.HTTP.Types.Status (status400, status401, status404, status301)
|
import Network.HTTP.Types.Status (status400, status401, status404, status301)
|
||||||
import Network.Wai (Application, requestHeaders, responseLBS, rawPathInfo,
|
import Network.Wai (Application, requestHeaders, responseLBS, rawPathInfo,
|
||||||
rawQueryString, isSecure)
|
rawQueryString, isSecure, requestMethod, Request)
|
||||||
import Network.URI (URI(..), parseURI)
|
import Network.URI (URI(..), parseURI)
|
||||||
|
|
||||||
import PgQuery(LoginAttempt(..), signInRole, setRole, resetRole)
|
import PgQuery(LoginAttempt(..), signInRole, setRole, resetRole)
|
||||||
import Codec.Binary.Base64.String (decode)
|
import Codec.Binary.Base64.String (decode)
|
||||||
|
|
||||||
|
import Debug.Trace
|
||||||
|
|
||||||
|
data Environment = Test | Production deriving (Eq)
|
||||||
|
|
||||||
withDBConnection :: Pool Connection -> (Connection -> Application) -> Application
|
withDBConnection :: Pool Connection -> (Connection -> Application) -> Application
|
||||||
withDBConnection pool app req respond =
|
withDBConnection pool app req respond =
|
||||||
withResource pool (\c -> app c req respond)
|
withResource pool (\c -> app c req respond)
|
||||||
|
|
||||||
inTransaction :: (Connection -> Application) -> Connection -> Application
|
inTransaction :: Environment -> (Connection -> Application) ->
|
||||||
inTransaction app conn req respond =
|
Connection -> Application
|
||||||
finally (runRaw conn "begin" >> app conn req respond) (runRaw conn "commit")
|
inTransaction env app conn req respond =
|
||||||
|
if env == Production && readOnlyRequest req
|
||||||
|
then
|
||||||
|
app conn req respond
|
||||||
|
else
|
||||||
|
finally (runRaw conn "begin" >> app conn req respond) (runRaw conn "commit")
|
||||||
|
|
||||||
withSavepoint :: (Connection -> Application) -> Connection -> Application
|
readOnlyRequest :: Request -> Bool
|
||||||
withSavepoint app conn req respond = do
|
readOnlyRequest req = requestMethod req `elem` ["GET", "HEAD", "OPTIONS"]
|
||||||
runRaw conn "savepoint req_sp"
|
|
||||||
catch (app conn req respond) (\e -> let _ = (e::SomeException) in
|
withSavepoint :: Environment -> (Connection -> Application) ->
|
||||||
runRaw conn "rollback to savepoint req_sp" >> throw e)
|
Connection -> Application
|
||||||
|
withSavepoint env app conn req respond =
|
||||||
|
if env == Production && readOnlyRequest req
|
||||||
|
then app conn req respond
|
||||||
|
else do
|
||||||
|
runRaw conn "savepoint req_sp"
|
||||||
|
catch (app conn req respond) (\e -> let _ = (e::SomeException) in
|
||||||
|
runRaw conn "rollback to savepoint req_sp" >> throw e)
|
||||||
|
|
||||||
authenticated :: BS.ByteString -> (Connection -> Application) ->
|
authenticated :: BS.ByteString -> (Connection -> Application) ->
|
||||||
Connection -> Application
|
Connection -> Application
|
||||||
@@ -84,7 +99,7 @@ clientErrors app req respond =
|
|||||||
|
|
||||||
where
|
where
|
||||||
isPgException :: SqlError -> Maybe SqlError
|
isPgException :: SqlError -> Maybe SqlError
|
||||||
isPgException = Just
|
isPgException x = Just (traceShow x x)
|
||||||
|
|
||||||
|
|
||||||
redirectInsecure :: Application -> Application
|
redirectInsecure :: Application -> Application
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@ import Text.Regex.TDFA ((=~))
|
|||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import Network.Wai.Middleware.Cors (cors)
|
import Network.Wai.Middleware.Cors (cors)
|
||||||
|
|
||||||
import Middleware(clientErrors, withSavepoint, authenticated)
|
import Middleware(clientErrors, withSavepoint, authenticated, Environment(..))
|
||||||
|
|
||||||
import Dbapi (app, corsPolicy, AppConfig(..))
|
import Dbapi (app, corsPolicy, AppConfig(..))
|
||||||
import PgQuery(addUser)
|
import PgQuery(addUser)
|
||||||
@@ -65,7 +65,7 @@ appWithFixture :: ActionWith Application -> IO ()
|
|||||||
appWithFixture action = withDatabaseConnection $ \c -> do
|
appWithFixture action = withDatabaseConnection $ \c -> do
|
||||||
runRaw c "begin;"
|
runRaw c "begin;"
|
||||||
action $ cors corsPolicy . clientErrors $
|
action $ cors corsPolicy . clientErrors $
|
||||||
(authenticated "dbapi_anonymous" . withSavepoint) app c
|
(authenticated "dbapi_anonymous" . withSavepoint Test) app c
|
||||||
rollback c
|
rollback c
|
||||||
|
|
||||||
rangeHdrs :: ByteRange -> [Header]
|
rangeHdrs :: ByteRange -> [Header]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Test.Hspec
|
|||||||
|
|
||||||
import Database.HDBC (runRaw, quickQuery, fromSql, SqlError)
|
import Database.HDBC (runRaw, quickQuery, fromSql, SqlError)
|
||||||
import SpecHelper (dbWithSchema)
|
import SpecHelper (dbWithSchema)
|
||||||
import Middleware (withSavepoint)
|
import Middleware (withSavepoint, Environment(..))
|
||||||
import PgQuery (insert)
|
import PgQuery (insert)
|
||||||
import Types(SqlRow(..))
|
import Types(SqlRow(..))
|
||||||
import Control.Exception(catch)
|
import Control.Exception(catch)
|
||||||
@@ -24,7 +24,7 @@ spec = let
|
|||||||
|
|
||||||
describe "withSavepoint" $
|
describe "withSavepoint" $
|
||||||
it "allows partial rollback of request" $ \c -> do
|
it "allows partial rollback of request" $ \c -> do
|
||||||
let app = withSavepoint dbErrApp c
|
let app = withSavepoint Test dbErrApp c
|
||||||
[[beforeCount]] <- quickQuery c "select count(*) from \"1\".items" []
|
[[beforeCount]] <- quickQuery c "select count(*) from \"1\".items" []
|
||||||
runRaw c "set role dbapi_anonymous"
|
runRaw c "set role dbapi_anonymous"
|
||||||
_ <- insert "1" "items" (SqlRow []) c
|
_ <- insert "1" "items" (SqlRow []) c
|
||||||
|
|||||||
Reference in New Issue
Block a user