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

Request logger static files #1926

Merged
merged 4 commits into from
Mar 2, 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
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
use flake . --impure
export IHP_LIB=lib/IHP/
14 changes: 8 additions & 6 deletions IHP/ControllerSupport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class InitControllerContext application where
initContext = pure ()
{-# INLINABLE initContext #-}

instance InitControllerContext () where
initContext = pure ()

{-# INLINE runAction #-}
runAction :: forall controller. (Controller controller, ?context :: ControllerContext, ?modelContext :: ModelContext, ?applicationContext :: ApplicationContext, ?requestContext :: RequestContext) => controller -> IO ResponseReceived
runAction controller = do
Expand Down Expand Up @@ -149,12 +152,11 @@ prepareRLSIfNeeded modelContext = do
Nothing -> pure modelContext

{-# INLINE startWebSocketApp #-}
startWebSocketApp :: forall webSocketApp application. (?applicationContext :: ApplicationContext, ?context :: RequestContext, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => IO ResponseReceived -> IO ResponseReceived
startWebSocketApp onHTTP = do
startWebSocketApp :: forall webSocketApp application. (?applicationContext :: ApplicationContext, ?context :: RequestContext, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => IO ResponseReceived -> Network.Wai.Application
startWebSocketApp onHTTP request respond = do
let ?modelContext = ?applicationContext.modelContext
let ?requestContext = ?context
let respond = ?context.respond
let request = ?context.request
requestContext <- createRequestContext ?applicationContext request respond
let ?requestContext = requestContext

let handleConnection pendingConnection = do
connection <- WebSockets.acceptRequest pendingConnection
Expand All @@ -177,7 +179,7 @@ startWebSocketApp onHTTP = do
Just response -> respond response
Nothing -> onHTTP
{-# INLINE startWebSocketAppAndFailOnHTTP #-}
startWebSocketAppAndFailOnHTTP :: forall webSocketApp application. (?applicationContext :: ApplicationContext, ?context :: RequestContext, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => IO ResponseReceived
startWebSocketAppAndFailOnHTTP :: forall webSocketApp application. (?applicationContext :: ApplicationContext, ?context :: RequestContext, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => Network.Wai.Application
startWebSocketAppAndFailOnHTTP = startWebSocketApp @webSocketApp @application (respond $ responseLBS HTTP.status400 [(hContentType, "text/plain")] "This endpoint is only available via a WebSocket")
where
respond = ?context.respond
Expand Down
13 changes: 4 additions & 9 deletions IHP/ErrorController.hs
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,10 @@ recordNotFoundExceptionHandlerProd exception controller additionalInfo =
in Just (handleNotFound ?context.request ?context.respond)
Nothing -> Nothing

handleRouterException :: (?context :: RequestContext) => SomeException -> IO ResponseReceived
handleRouterException exception =
case fromException exception of
handleRouterException :: (?applicationContext :: ApplicationContext) => SomeException -> Application
handleRouterException exception request respond =
let ?context = ?applicationContext
in case fromException exception of
Just Router.NoConstructorMatched { expectedType, value, field } -> do
let errorMessage = [hsx|
<p>Routing failed with: {tshow exception}</p>
Expand All @@ -329,14 +330,12 @@ handleRouterException exception =
let title = case value of
Just value -> [hsx|Expected <strong>{expectedType}</strong> for field <strong>{field}</strong> but got <q>{value}</q>|]
Nothing -> [hsx|The action was called without the required <q>{field}</q> parameter|]
let RequestContext { respond } = ?context
respond $ responseBuilder status400 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))
Just Router.BadType { expectedType, value = Just value, field } -> do
let errorMessage = [hsx|
<p>Routing failed with: {tshow exception}</p>
|]
let title = [hsx|Query parameter <q>{field}</q> needs to be a <q>{expectedType}</q> but got <q>{value}</q>|]
let RequestContext { respond } = ?context
respond $ responseBuilder status400 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))
_ -> case fromException exception of
Just Router.UnexpectedMethodException { allowedMethods = [Router.DELETE], method = Router.GET } -> do
Expand Down Expand Up @@ -371,7 +370,6 @@ handleRouterException exception =
</p>
|]
let title = [hsx|Action was called from a GET request, but needs to be called as a DELETE request|]
let RequestContext { respond } = ?context
respond $ responseBuilder status400 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))
Just Router.UnexpectedMethodException { allowedMethods = [Router.POST], method = Router.GET } -> do
let errorMessage = [hsx|
Expand All @@ -386,7 +384,6 @@ handleRouterException exception =
</p>
|]
let title = [hsx|Action was called from a GET request, but needs to be called as a POST request|]
let RequestContext { respond } = ?context
respond $ responseBuilder status400 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))
Just Router.UnexpectedMethodException { allowedMethods, method } -> do
let errorMessage = [hsx|
Expand All @@ -397,7 +394,6 @@ handleRouterException exception =
</p>
|]
let title = [hsx|Action was called with a {method} request, but needs to be called with one of these request methods: <q>{allowedMethods}</q>|]
let RequestContext { respond } = ?context
respond $ responseBuilder status400 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))
_ -> do
let errorMessage = [hsx|
Expand All @@ -407,7 +403,6 @@ handleRouterException exception =
<p>Are you trying to do a DELETE action, but your link is missing class="js-delete"?</p>
|]
let title = H.text "Routing failed"
let RequestContext { respond } = ?context
respond $ responseBuilder status500 [(hContentType, "text/html")] (Blaze.renderHtmlBuilder (renderError title errorMessage))


Expand Down
5 changes: 3 additions & 2 deletions IHP/IDE/ToolServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import qualified System.Process as Process
import System.Info
import qualified IHP.EnvVar as EnvVar
import qualified IHP.AutoRefresh.Types as AutoRefresh
import qualified IHP.AutoRefresh as AutoRefresh
import IHP.Controller.Context
import qualified IHP.IDE.ToolServer.Layout as Layout
import IHP.Controller.Layout
Expand Down Expand Up @@ -85,7 +86,7 @@ startToolServer' port isDebugMode = do
let ?applicationContext = applicationContext
requestContext <- ControllerSupport.createRequestContext applicationContext request respond
let ?context = requestContext
frontControllerToWAIApp toolServerApplication [] (staticApp request respond)
frontControllerToWAIApp @ToolServerApplication @AutoRefresh.AutoRefreshWSApp (\app -> app) toolServerApplication staticApp request respond

let openAppUrl = openUrl ("http://localhost:" <> tshow port <> "/")
let warpSettings = Warp.defaultSettings
Expand Down Expand Up @@ -157,4 +158,4 @@ readDatabaseNeedsMigration :: (?context :: ControllerContext) => IO Bool
readDatabaseNeedsMigration = do
context <- theDevServerContext
state <- readIORef (context.appStateRef)
readIORef (state.databaseNeedsMigration)
readIORef (state.databaseNeedsMigration)
51 changes: 26 additions & 25 deletions IHP/RouterSupport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -84,37 +84,38 @@ runAction'
:: forall application controller
. ( Controller controller
, ?applicationContext :: ApplicationContext
, ?context :: RequestContext
, InitControllerContext application
, ?application :: application
, Typeable application
, Typeable controller
)
=> controller -> (TMap.TMap -> TMap.TMap) -> IO ResponseReceived
runAction' controller contextSetter = do
=> controller -> (TMap.TMap -> TMap.TMap) -> Application
runAction' controller contextSetter request respond = do
let ?modelContext = ApplicationContext.modelContext ?applicationContext
let ?requestContext = ?context
requestContext <- createRequestContext ?applicationContext request respond
let ?context = requestContext
let ?requestContext = requestContext
contextOrErrorResponse <- newContextForAction contextSetter controller
case contextOrErrorResponse of
Left res -> res
Right context -> let ?context = context in runAction controller
{-# INLINABLE runAction' #-}

type RouteParseResult = IO (TMap.TMap -> TMap.TMap, (TMap.TMap -> TMap.TMap) -> IO ResponseReceived)
type RouteParseResult = IO (TMap.TMap -> TMap.TMap, (TMap.TMap -> TMap.TMap) -> Application)
type RouteParser = Parser (RouteParseResult)

toRouteParser :: Parser (IO ResponseReceived) -> RouteParser
toRouteParser :: Parser Application -> RouteParser
toRouteParser parser = do
controller <- parser
pure $ pure (\t -> t, \_ -> controller)

toRouteParser' :: Parser ((TMap.TMap -> TMap.TMap) -> IO ResponseReceived) -> RouteParser
toRouteParser' :: Parser ((TMap.TMap -> TMap.TMap) -> Application) -> RouteParser
toRouteParser' parser = do
controller <- parser
pure $ pure (\t -> t, controller)

toRouteParseResult :: IO ResponseReceived -> RouteParseResult
toRouteParseResult ioResponseReceived = pure (\t -> t, \_ -> ioResponseReceived)
toRouteParseResult :: Application -> RouteParseResult
toRouteParseResult application = pure (\t -> t, \_ -> application)

class FrontController application where
controllers
Expand All @@ -132,8 +133,8 @@ defaultRouter
=> [RouteParser] -> RouteParser
defaultRouter additionalControllers = do
let allControllers = controllers <> additionalControllers
ioResponseReceived <- choice $ map (\r -> r <* endOfInput) allControllers
pure ioResponseReceived
applications <- choice $ map (\r -> r <* endOfInput) allControllers
pure applications
{-# INLINABLE defaultRouter #-}

class HasPath controller where
Expand Down Expand Up @@ -835,13 +836,20 @@ startPage action = get (ByteString.pack (actionPrefix @action)) action
withPrefix prefix routes = string prefix >> choice (map (\r -> r <* endOfInput) routes)
{-# INLINABLE withPrefix #-}

runApp :: (?applicationContext :: ApplicationContext, ?context :: RequestContext) => RouteParser -> IO ResponseReceived -> IO ResponseReceived
runApp routes notFoundAction = do
let path = ?context.request.rawPathInfo
handleException :: SomeException -> IO (Either String (IO ResponseReceived))
frontControllerToWAIApp :: forall app (autoRefreshApp :: Type). (?applicationContext :: ApplicationContext, FrontController app, WSApp autoRefreshApp, Typeable autoRefreshApp, InitControllerContext ()) => Middleware -> app -> Application -> Application
frontControllerToWAIApp middleware application notFoundAction request respond = do
let requestContext = RequestContext { request, respond, requestBody = FormBody { params = [], files = [] }, vault = ?applicationContext.session, frameworkConfig = ?applicationContext.frameworkConfig }

let ?context = requestContext

let
path = request.rawPathInfo
handleException :: SomeException -> IO (Either String Application)
handleException exception = pure $ Right $ ErrorController.handleRouterException exception

routedAction :: Either String (IO ResponseReceived) <-
routes = let ?application = application in router [let ?application = () in webSocketApp @autoRefreshApp]

routedAction :: Either String Application <-
(do
res <- evaluate $ parseOnly (routes <* endOfInput) path
case res of
Expand All @@ -850,17 +858,10 @@ runApp routes notFoundAction = do
(tmapSetter, controllerFn) <- io
pure $ Right $ controllerFn $ tmapSetter
)
-- pure (undefined::IO ResponseReceived)))
`Exception.catch` handleException
case routedAction of
Left message -> notFoundAction
Right action -> action
{-# INLINABLE runApp #-}

frontControllerToWAIApp :: forall app. (?applicationContext :: ApplicationContext, ?context :: RequestContext, FrontController app) => app -> [RouteParser] -> IO ResponseReceived -> IO ResponseReceived
frontControllerToWAIApp application additionalControllers notFoundAction = runApp defaultRouter notFoundAction
where
defaultRouter :: RouteParser = (let ?application = application in router additionalControllers)
Left message -> notFoundAction request respond
Right action -> (middleware action) request respond
{-# INLINABLE frontControllerToWAIApp #-}

mountFrontController :: forall frontController. (?applicationContext :: ApplicationContext, ?context :: RequestContext, FrontController frontController) => frontController -> RouteParser
Expand Down
23 changes: 6 additions & 17 deletions IHP/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ run configBuilder = do
. runServer frameworkConfig
. customMiddleware
. corsMiddleware
. sessionMiddleware
. requestLoggerMiddleware
. methodOverridePost
$ application staticApp
. sessionMiddleware
$ application staticApp requestLoggerMiddleware

{-# INLINABLE run #-}

Expand Down Expand Up @@ -99,7 +98,7 @@ initStaticApp frameworkConfig = do

frameworkStaticDir = libDir <> "/static/"
frameworkSettings = (Static.defaultWebAppSettings frameworkStaticDir)
{ Static.ss404Handler = Just handleNotFound
{ Static.ss404Handler = Just (frameworkConfig.requestLoggerMiddleware handleNotFound)
, Static.ssMaxAge = maxAge
}
appSettings = (Static.defaultWebAppSettings "static/")
Expand Down Expand Up @@ -127,16 +126,9 @@ initCorsMiddleware FrameworkConfig { corsResourcePolicy } = case corsResourcePol
Just corsResourcePolicy -> Cors.cors (const (Just corsResourcePolicy))
Nothing -> id

application :: (FrontController RootApplication, ?applicationContext :: ApplicationContext) => Application -> Application
application staticApp request respond = do
requestContext <- ControllerSupport.createRequestContext ?applicationContext request respond
let ?context = requestContext
let builtinControllers = let ?application = () in
[ webSocketApp @AutoRefresh.AutoRefreshWSApp
, webSocketAppWithCustomPath @AutoRefresh.AutoRefreshWSApp "" -- For b.c. with older versions of ihp-auto-refresh.js
]

frontControllerToWAIApp RootApplication builtinControllers (staticApp request respond)
application :: (FrontController RootApplication, ?applicationContext :: ApplicationContext) => Application -> Middleware -> Application
application staticApp middleware request respond = do
frontControllerToWAIApp @RootApplication @AutoRefresh.AutoRefreshWSApp middleware RootApplication staticApp request respond
{-# INLINABLE application #-}

runServer :: (?applicationContext :: ApplicationContext) => FrameworkConfig -> Application -> IO ()
Expand All @@ -152,9 +144,6 @@ runServer FrameworkConfig { environment = Env.Production, appPort, exceptionTrac
|> Warp.setPort appPort
|> Warp.setOnException exceptionTracker.onException

instance ControllerSupport.InitControllerContext () where
initContext = pure ()

withInitalizers :: FrameworkConfig -> ModelContext -> IO () -> IO ()
withInitalizers frameworkConfig modelContext continue = do
let ?context = frameworkConfig
Expand Down
13 changes: 10 additions & 3 deletions Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ data DemoController = DemoAction deriving (Eq, Show, Data)

instance AutoRoute DemoController
instance InitControllerContext RootApplication
instance FrontController RootApplication where
data WebApplication = WebApplication deriving (Eq, Show)

instance InitControllerContext WebApplication where
initContext = pure ()

instance FrontController WebApplication where
controllers =
[ parseRoute @DemoController
, startPage DemoAction
[ startPage DemoAction
]

instance FrontController RootApplication where
controllers = [ mountFrontController WebApplication ]

instance Controller DemoController where
action DemoAction = renderPlain "Hello World!"

Expand Down
2 changes: 1 addition & 1 deletion Test/Controller/AccessDeniedSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ makeApplication :: (?applicationContext :: ApplicationContext) => IO Application
makeApplication = do
store <- Session.mapStore_
let sessionMiddleware :: Middleware = Session.withSession store "SESSION" ?applicationContext.frameworkConfig.sessionCookie ?applicationContext.session
pure (sessionMiddleware (Server.application handleNotFound))
pure (sessionMiddleware $ (Server.application handleNotFound) (\app -> app))

assertAccessDenied :: SResponse -> IO ()
assertAccessDenied response = do
Expand Down
2 changes: 1 addition & 1 deletion Test/Controller/NotFoundSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ makeApplication :: (?applicationContext :: ApplicationContext) => IO Application
makeApplication = do
store <- Session.mapStore_
let sessionMiddleware :: Middleware = Session.withSession store "SESSION" ?applicationContext.frameworkConfig.sessionCookie ?applicationContext.session
pure (sessionMiddleware (Server.application handleNotFound))
pure (sessionMiddleware $ (Server.application handleNotFound) (\app -> app))

assertNotFound :: SResponse -> IO ()
assertNotFound response = do
Expand Down
2 changes: 1 addition & 1 deletion Test/RouterSupportSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ config = do
option (AppPort 8000)

application :: (?applicationContext :: ApplicationContext) => Application
application = Server.application handleNotFound
application = Server.application handleNotFound (\app -> app)

tests :: Spec
tests = beforeAll (mockContextNoDatabase WebApplication config) do
Expand Down
2 changes: 1 addition & 1 deletion Test/SEO/Sitemap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,5 @@ tests = beforeAll (mockContextNoDatabase WebApplication config) do
describe "SEO" do
describe "Sitemap" do
it "should render a XML Sitemap" $ withContext do
runSession (testGet "/sitemap.xml") (Server.application handleNotFound)
runSession (testGet "/sitemap.xml") (Server.application handleNotFound (\app -> app))
>>= assertSuccess "<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"><url><loc>http://localhost:8000/test/ShowPost?postId=00000000-0000-0000-0000-000000000000</loc><lastmod>2105-04-16</lastmod><changefreq>hourly</changefreq></url></urlset>"
2 changes: 1 addition & 1 deletion Test/ViewSupportSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ makeApplication :: (?applicationContext :: ApplicationContext) => IO Application
makeApplication = do
store <- Session.mapStore_
let sessionMiddleware :: Middleware = Session.withSession store "SESSION" ?applicationContext.frameworkConfig.sessionCookie ?applicationContext.session
pure (sessionMiddleware (Server.application handleNotFound))
pure (sessionMiddleware $ (Server.application handleNotFound (\app -> app)))

tests :: Spec
tests = beforeAll (mockContextNoDatabase WebApplication config) do
Expand Down
Loading