Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print unhandled exception to stdout depending on verbosity #374

Merged
merged 4 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Web/Scotty.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
import Network.Socket (Socket)
import Network.Wai (Application, Middleware, Request, StreamingBody)
import Network.Wai.Handler.Warp (Port)
import qualified Network.Wai.Parse as W (defaultParseRequestBodyOptions)

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

The qualified import of ‘Network.Wai.Parse’ is redundant

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

The qualified import of ‘Network.Wai.Parse’ is redundant

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

The qualified import of ‘Network.Wai.Parse’ is redundant

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

The qualified import of ‘Network.Wai.Parse’ is redundant

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

The qualified import of ‘Network.Wai.Parse’ is redundant

Check warning on line 70 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

The qualified import of ‘Network.Wai.Parse’ is redundant

import Web.Scotty.Internal.Types (ScottyT, ActionT, ErrorHandler, Param, RoutePattern, Options, defaultOptions, File, Kilobytes, ScottyState, defaultScottyState, ScottyException, StatusError(..), Content(..))
import UnliftIO.Exception (Handler(..), catch)
Expand Down Expand Up @@ -121,7 +121,7 @@
-- | Turn a scotty application into a WAI 'Application', which can be
-- run with any WAI handler.
scottyApp :: ScottyM () -> IO Application
scottyApp = Trans.scottyAppT id
scottyApp = Trans.scottyAppT defaultOptions id

-- | Global handler for user-defined exceptions.
defaultHandler :: ErrorHandler IO -> ScottyM ()
Expand All @@ -144,8 +144,8 @@
nested = Trans.nested

-- | Set global size limit for the request body. Requests with body size exceeding the limit will not be
-- processed and an HTTP response 413 will be returned to the client. Size limit needs to be greater than 0,
-- otherwise the application will terminate on start.
-- processed and an HTTP response 413 will be returned to the client. Size limit needs to be greater than 0,
-- otherwise the application will terminate on start.
setMaxRequestBodySize :: Kilobytes -> ScottyM ()
setMaxRequestBodySize = Trans.setMaxRequestBodySize

Expand All @@ -153,14 +153,14 @@
--
-- Uncaught exceptions turn into HTTP 500 responses.
raise :: Text -> ActionM a
raise = Trans.raise

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

In the use of ‘raise’

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

In the use of ‘raise’

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

In the use of ‘raise’

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

In the use of ‘raise’

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

In the use of ‘raise’

Check warning on line 156 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

In the use of ‘raise’
{-# DEPRECATED raise "Throw an exception instead" #-}

-- | Throw a 'StatusError' exception that has an associated HTTP error code and can be caught with 'catch'.
--
-- Uncaught exceptions turn into HTTP responses corresponding to the given status.
raiseStatus :: Status -> Text -> ActionM a
raiseStatus = Trans.raiseStatus

Check warning on line 163 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

In the use of ‘raiseStatus’

Check warning on line 163 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

In the use of ‘raiseStatus’

Check warning on line 163 in Web/Scotty.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

In the use of ‘raiseStatus’
{-# DEPRECATED raiseStatus "Use status, text, and finish instead" #-}

-- | Throw an exception which can be caught within the scope of the current Action with 'catch'.
Expand Down
22 changes: 15 additions & 7 deletions Web/Scotty/Action.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

@cblp cblp Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows to read record fields in the most natural way (in my taste):

-- line 179
someExceptionHandler Options{verbose} =
    when (verbose > 0) ...

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
Expand Down Expand Up @@ -86,6 +87,7 @@
import qualified Data.Text.Lazy.Encoding as TLE
import Data.Time (UTCTime)
import Data.Time.Format (parseTimeM, defaultTimeLocale)
import Data.Typeable (typeOf)
import Data.Word

import Network.HTTP.Types
Expand All @@ -100,8 +102,9 @@
import Numeric.Natural

import Web.Scotty.Internal.Types
import Web.Scotty.Util (mkResponse, addIfNotPresent, add, replace, lazyTextToStrictByteString, decodeUtf8Lenient)

Check warning on line 105 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

The import of ‘decodeUtf8Lenient’

Check warning on line 105 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

The import of ‘decodeUtf8Lenient’

Check warning on line 105 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

The import of ‘decodeUtf8Lenient’
import UnliftIO.Exception (Handler(..), catch, catches, throwIO)
import System.IO (hPutStrLn, stderr)

import Network.Wai.Internal (ResponseReceived(..))

Expand All @@ -112,15 +115,16 @@
-- 'Nothing' indicates route failed (due to Next) and pattern matching should try the next available route.
-- 'Just' indicates a successful response.
runAction :: MonadUnliftIO m =>
Maybe (ErrorHandler m) -- ^ this handler (if present) is in charge of user-defined exceptions
Options
-> Maybe (ErrorHandler m) -- ^ this handler (if present) is in charge of user-defined exceptions
-> ActionEnv
-> ActionT m () -- ^ Route action to be evaluated
-> m (Maybe Response)
runAction mh env action = do
runAction options mh env action = do
ok <- flip runReaderT env $ runAM $ tryNext $ action `catches` concat
[ [actionErrorHandler]
, maybeToList mh
, [statusErrorHandler, scottyExceptionHandler, someExceptionHandler]
, [statusErrorHandler, scottyExceptionHandler, someExceptionHandler options]
]
res <- getResponse env
return $ bool Nothing (Just $ mkResponse res) ok
Expand All @@ -128,7 +132,7 @@
-- | Catches 'StatusError' and produces an appropriate HTTP response.
statusErrorHandler :: MonadIO m => ErrorHandler m
statusErrorHandler = Handler $ \case
StatusError s e -> do

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

In the use of data constructor ‘StatusError’

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

In the use of data constructor ‘StatusError’

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

In the use of data constructor ‘StatusError’

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

In the use of data constructor ‘StatusError’

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

In the use of data constructor ‘StatusError’

Check warning on line 135 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

In the use of data constructor ‘StatusError’
status s
let code = T.pack $ show $ statusCode s
let msg = decodeUtf8Lenient $ statusMessage s
Expand Down Expand Up @@ -190,11 +194,15 @@
text $ T.unwords ["resourcet Exception:", T.pack (show rte)]

-- | Uncaught exceptions turn into HTTP 500 Server Error codes
someExceptionHandler :: MonadIO m => ErrorHandler m
someExceptionHandler = Handler $ \case
(e :: E.SomeException) -> do
someExceptionHandler :: MonadIO m => Options -> ErrorHandler m
someExceptionHandler Options{verbose} =
Handler $ \(E.SomeException e) -> do
when (verbose > 0) $
liftIO $
hPutStrLn stderr $
"Unhandled exception of " <> show (typeOf e) <> ": " <> show e
status status500
text $ T.unwords ["Uncaught server exception:", T.pack (show e)]


-- | Throw a "500 Server Error" 'StatusError', which can be caught with 'catch'.
--
Expand All @@ -209,7 +217,7 @@
--
-- Uncaught exceptions turn into HTTP responses corresponding to the given status.
raiseStatus :: Monad m => Status -> T.Text -> ActionT m a
raiseStatus s = E.throw . StatusError s

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

In the use of data constructor ‘StatusError’

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

In the use of data constructor ‘StatusError’

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

In the use of data constructor ‘StatusError’

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

In the use of data constructor ‘StatusError’

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

In the use of data constructor ‘StatusError’

Check warning on line 220 in Web/Scotty/Action.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

In the use of data constructor ‘StatusError’
{-# DEPRECATED raiseStatus "Use status, text, and finish instead" #-}

-- | Throw an exception which can be caught within the scope of the current Action with 'catch'.
Expand Down
3 changes: 2 additions & 1 deletion Web/Scotty/Internal/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import Control.Monad.IO.Class (MonadIO(..))
import UnliftIO (MonadUnliftIO(..))
import Control.Monad.Reader (MonadReader(..), ReaderT, asks, mapReaderT)
import Control.Monad.State.Strict (State, StateT(..))

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

The import of ‘StateT’

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

The import of ‘StateT’

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

The import of ‘StateT’

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

The import of ‘StateT’

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

The import of ‘StateT’

Check warning on line 25 in Web/Scotty/Internal/Types.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

The import of ‘StateT’
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Control (MonadBaseControl, MonadTransControl)
import qualified Control.Monad.Trans.Resource as RT (InternalState, InvalidAccess)
Expand Down Expand Up @@ -120,7 +120,8 @@
let ro' = ro { maxRequestBodySize = maxRequestBodySize }
in s { routeOptions = ro' }

newtype ScottyT m a = ScottyT { runS :: State (ScottyState m) a }
newtype ScottyT m a =
ScottyT { runS :: ReaderT Options (State (ScottyState m)) a }
deriving ( Functor, Applicative, Monad )


Expand Down
31 changes: 24 additions & 7 deletions Web/Scotty/Route.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Control.Concurrent.STM (newTVarIO)
import Control.Monad.IO.Class (MonadIO(..))
import UnliftIO (MonadUnliftIO(..))
import qualified Control.Monad.Reader as MR
import qualified Control.Monad.State as MS
import Control.Monad.Trans.Resource (InternalState)

Expand All @@ -21,7 +22,9 @@
import qualified Text.Regex as Regex

import Web.Scotty.Action
import Web.Scotty.Internal.Types (RoutePattern(..), RouteOptions, ActionEnv(..), ScottyState(..), ScottyT(..), File, ErrorHandler, Middleware, BodyInfo, handler, addRoute, defaultScottyResponse)

import Web.Scotty.Internal.Types (Options, RoutePattern(..), RouteOptions, ActionEnv(..), ActionT, ScottyState(..), ScottyT(..), ErrorHandler, Middleware, BodyInfo, File, handler, addRoute, defaultScottyResponse)

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

The import of ‘ActionT’

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

The import of ‘ActionT’

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

The import of ‘ActionT’

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

The import of ‘ActionT’

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

The import of ‘ActionT’

Check warning on line 26 in Web/Scotty/Route.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

The import of ‘ActionT’

import Web.Scotty.Util (decodeUtf8Lenient)
import Web.Scotty.Body (cloneBodyInfo, getBodyAction, getBodyChunkAction, getFormParamsAndFilesAction)

Expand Down Expand Up @@ -80,7 +83,13 @@

-- | Add a route that matches regardless of the HTTP verb.
matchAny :: (MonadUnliftIO m) => RoutePattern -> ActionT m () -> ScottyT m ()
matchAny pat action = ScottyT $ MS.modify $ \s -> addRoute (route (routeOptions s) (handler s) Nothing pat action) s
matchAny pat action =
ScottyT $ do
serverOptions <- MR.ask
MS.modify $ \s ->
addRoute
(route serverOptions (routeOptions s) (handler s) Nothing pat action)
s

-- | Specify an action to take if nothing else is found. Note: this _always_ matches,
-- so should generally be the last route specified.
Expand All @@ -103,13 +112,20 @@
"something"
-}
addroute :: (MonadUnliftIO m) => StdMethod -> RoutePattern -> ActionT m () -> ScottyT m ()
addroute method pat action = ScottyT $ MS.modify $ \s ->
addRoute (route (routeOptions s) (handler s) (Just method) pat action) s
addroute method pat action =
ScottyT $ do
serverOptions <- MR.ask
MS.modify $ \s ->
addRoute
(route serverOptions (routeOptions s) (handler s) (Just method) pat action)
s


route :: (MonadUnliftIO m) =>
RouteOptions
Options
-> RouteOptions
-> Maybe (ErrorHandler m) -> Maybe StdMethod -> RoutePattern -> ActionT m () -> BodyInfo -> Middleware m
route opts h method pat action bodyInfo app req =
route serverOpts opts h method pat action bodyInfo app req =
let tryNext = app req
-- We match all methods in the case where 'method' is 'Nothing'.
-- See https://github.com/scotty-web/scotty/issues/196 and 'matchAny'
Expand All @@ -128,7 +144,8 @@
cbi <- cloneBodyInfo bodyInfo

env <- mkEnv cbi req captures opts
res <- runAction h env action
res <- runAction serverOpts h env action

maybe tryNext return res
Nothing -> tryNext
else tryNext
Expand Down
14 changes: 8 additions & 6 deletions Web/Scotty/Trans.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@

import Control.Exception (assert)
import Control.Monad (when)
import Control.Monad.Reader (runReaderT)
import Control.Monad.State.Strict (execState, modify)
import Control.Monad.IO.Class

Expand Down Expand Up @@ -104,7 +105,7 @@
scottyOptsT opts runActionToIO s = do
when (verbose opts > 0) $
liftIO $ putStrLn $ "Setting phasers to stun... (port " ++ show (getPort (settings opts)) ++ ") (ctrl-c to quit)"
liftIO . runSettings (settings opts) =<< scottyAppT runActionToIO s
liftIO . runSettings (settings opts) =<< scottyAppT opts runActionToIO s

-- | Run a scotty application using the warp server, passing extra options, and
-- listening on the provided socket.
Expand All @@ -119,17 +120,18 @@
when (verbose opts > 0) $ do
d <- liftIO $ socketDescription sock
liftIO $ putStrLn $ "Setting phasers to stun... (" ++ d ++ ") (ctrl-c to quit)"
liftIO . runSettingsSocket (settings opts) sock =<< scottyAppT runActionToIO s
liftIO . runSettingsSocket (settings opts) sock =<< scottyAppT opts runActionToIO s

-- | Turn a scotty application into a WAI 'Application', which can be
-- run with any WAI handler.
-- NB: scottyApp === scottyAppT id
scottyAppT :: (Monad m, Monad n)
=> (m W.Response -> IO W.Response) -- ^ Run monad 'm' into 'IO', called at each action.
=> Options
-> (m W.Response -> IO W.Response) -- ^ Run monad 'm' into 'IO', called at each action.
-> ScottyT m ()
-> n W.Application
scottyAppT runActionToIO defs = do
let s = execState (runS defs) defaultScottyState
scottyAppT options runActionToIO defs = do

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.6.4

This binding for ‘options’ shadows the existing binding

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.8.2

This binding for ‘options’ shadows the existing binding

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.0.2

This binding for ‘options’ shadows the existing binding

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 8.10.7

This binding for ‘options’ shadows the existing binding

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.4.6

This binding for ‘options’ shadows the existing binding

Check warning on line 133 in Web/Scotty/Trans.hs

View workflow job for this annotation

GitHub Actions / ubuntu-latest / ghc 9.2.8

This binding for ‘options’ shadows the existing binding
let s = execState (runReaderT (runS defs) options) defaultScottyState
let rapp req callback = do
bodyInfo <- newBodyInfo req
resp <- runActionToIO (applyAll notFoundApp ([midd bodyInfo | midd <- routes s]) req)
Expand Down Expand Up @@ -163,7 +165,7 @@
middleware = ScottyT . modify . addMiddleware

-- | Set global size limit for the request body. Requests with body size exceeding the limit will not be
-- processed and an HTTP response 413 will be returned to the client. Size limit needs to be greater than 0,
-- processed and an HTTP response 413 will be returned to the client. Size limit needs to be greater than 0,
-- otherwise the application will terminate on start.
setMaxRequestBodySize :: Kilobytes -- ^ Request size limit
-> ScottyT m ()
Expand Down
21 changes: 12 additions & 9 deletions bench/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Lucid.Base
import Lucid.Html5
import Web.Scotty
import Web.Scotty.Internal.Types
import qualified Control.Monad.Reader as R
import qualified Control.Monad.State.Lazy as SL
import qualified Control.Monad.State.Strict as SS
import qualified Data.ByteString.Lazy as BL
Expand All @@ -19,15 +20,17 @@ import Weigh

main :: IO ()
main = do
mainWith $ do
setColumns [Case,Allocated,GCs,Live,Check,Max,MaxOS]
setFormat Markdown
io "ScottyM Strict" BL.putStr
(SS.evalState (runS $ renderBST htmlScotty) defaultScottyState)
io "ScottyM Lazy" BL.putStr
(SL.evalState (runScottyLazy $ renderBST htmlScottyLazy) defaultScottyState)
io "Identity" BL.putStr
(runIdentity $ renderBST htmlIdentity)
mainWith $ do
setColumns [Case,Allocated,GCs,Live,Check,Max,MaxOS]
setFormat Markdown
io "ScottyM Strict" BL.putStr
(SS.evalState
(R.runReaderT (runS $ renderBST htmlScotty) defaultOptions)
defaultScottyState)
io "ScottyM Lazy" BL.putStr
(SL.evalState (runScottyLazy $ renderBST htmlScottyLazy) defaultScottyState)
io "Identity" BL.putStr
(runIdentity $ renderBST htmlIdentity)


htmlTest :: Monad m => HtmlT m ()
Expand Down
Loading