Wire through custom headers and status codes for RenderPage server responses.

This commit is contained in:
Dillon Kearns 2022-01-17 16:56:40 -08:00
parent c27377c8f7
commit 03dd16a86f
19 changed files with 80 additions and 33 deletions

View File

@ -144,7 +144,7 @@ async function render(event, context) {
); );
console.log("@@@renderResult", JSON.stringify(renderResult, null, 2)); console.log("@@@renderResult", JSON.stringify(renderResult, null, 2));
const statusCode = renderResult.is404 ? 404 : 200; const statusCode = renderResult.is404 ? 404 : renderResult.statusCode;
if (renderResult.kind === "json") { if (renderResult.kind === "json") {
return { return {
@ -152,6 +152,7 @@ async function render(event, context) {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"x-powered-by": "elm-pages", "x-powered-by": "elm-pages",
...renderResult.headers,
}, },
statusCode, statusCode,
}; };
@ -169,6 +170,7 @@ async function render(event, context) {
headers: { headers: {
"Content-Type": "text/html", "Content-Type": "text/html",
"x-powered-by": "elm-pages", "x-powered-by": "elm-pages",
...renderResult.headers,
}, },
statusCode, statusCode,
}; };

View File

@ -24,7 +24,7 @@ data _ =
DataSource.Http.get (Secrets.succeed "https://elm-pages-pokedex.netlify.app/.netlify/functions/time") DataSource.Http.get (Secrets.succeed "https://elm-pages-pokedex.netlify.app/.netlify/functions/time")
Decode.string Decode.string
|> DataSource.map Data |> DataSource.map Data
|> DataSource.map PageServerResponse.RenderPage |> DataSource.map PageServerResponse.render
head : (routeParams -> String) -> StaticPayload Data routeParams -> List Head.Tag head : (routeParams -> String) -> StaticPayload Data routeParams -> List Head.Tag

View File

@ -48,10 +48,10 @@ data routeParams =
) )
|> Request.map |> Request.map
(\file -> (\file ->
DataSource.succeed (PageServerResponse.RenderPage (Just file)) DataSource.succeed (PageServerResponse.render (Just file))
) )
, Request.succeed , Request.succeed
(DataSource.succeed (PageServerResponse.RenderPage Nothing)) (DataSource.succeed (PageServerResponse.render Nothing))
] ]

View File

@ -215,7 +215,7 @@ data routeParams =
, { user = Nothing , { user = Nothing
, errors = Form.init (form defaultUser) , errors = Form.init (form defaultUser)
} }
|> PageServerResponse.RenderPage |> PageServerResponse.render
|> DataSource.succeed |> DataSource.succeed
|> Request.succeed |> Request.succeed
] ]

View File

@ -49,6 +49,11 @@ data routeParams =
(\requestData -> (\requestData ->
requestData requestData
|> PageServerResponse.RenderPage |> PageServerResponse.RenderPage
{ statusCode = 200
, headers =
[ ( "x-greeting", "hello there " ++ requestData.username ++ "!" )
]
}
|> DataSource.succeed |> DataSource.succeed
) )
, Request.map2 Data , Request.map2 Data
@ -58,6 +63,11 @@ data routeParams =
(\requestData -> (\requestData ->
requestData requestData
|> PageServerResponse.RenderPage |> PageServerResponse.RenderPage
{ statusCode = 200
, headers =
[ ( "x-greeting", "hello " ++ requestData.username ++ "!" )
]
}
|> DataSource.succeed |> DataSource.succeed
) )
, Request.succeed , Request.succeed

View File

@ -67,7 +67,7 @@ data routeParams =
(\name -> (\name ->
name name
|> Data |> Data
|> PageServerResponse.RenderPage |> PageServerResponse.render
|> DataSource.succeed |> DataSource.succeed
) )
] ]

View File

@ -68,7 +68,7 @@ data { pokedexNumber } =
(Decode.field "types" (Decode.list (Decode.field "type" (Decode.field "name" Decode.string)))) (Decode.field "types" (Decode.list (Decode.field "type" (Decode.field "name" Decode.string))))
) )
) )
|> DataSource.map PageServerResponse.RenderPage |> DataSource.map PageServerResponse.render
notFoundResponse : String -> DataSource (PageServerResponse Data) notFoundResponse : String -> DataSource (PageServerResponse Data)

View File

@ -60,12 +60,12 @@ data routeParams =
|> DataSource.succeed |> DataSource.succeed
|> DataSource.andMap (DataSource.File.rawFile "examples/pokedex/content/secret-note.txt") |> DataSource.andMap (DataSource.File.rawFile "examples/pokedex/content/secret-note.txt")
|> DataSource.map LoggedIn |> DataSource.map LoggedIn
|> DataSource.map PageServerResponse.RenderPage |> DataSource.map PageServerResponse.render
) )
, Request.succeed , Request.succeed
(NotLoggedIn (NotLoggedIn
|> DataSource.succeed |> DataSource.succeed
|> DataSource.map PageServerResponse.RenderPage |> DataSource.map PageServerResponse.render
--"/login" --"/login"
-- |> ServerResponse.temporaryRedirect -- |> ServerResponse.temporaryRedirect
-- --|> ServerResponse.withStatusCode 404 -- --|> ServerResponse.withStatusCode 404

View File

@ -619,7 +619,7 @@ data routeParams =
, { user = Nothing , { user = Nothing
, initialForm = Form.init (form defaultUser) , initialForm = Form.init (form defaultUser)
} }
|> PageServerResponse.RenderPage |> PageServerResponse.render
|> DataSource.succeed |> DataSource.succeed
|> Request.succeed |> Request.succeed
] ]

View File

@ -266,7 +266,7 @@ single :
-> Builder {} data -> Builder {} data
single { data, head } = single { data, head } =
WithData WithData
{ data = \_ -> data |> DataSource.map PageServerResponse.RenderPage { data = \_ -> data |> DataSource.map PageServerResponse.render
, staticRoutes = DataSource.succeed [ {} ] , staticRoutes = DataSource.succeed [ {} ]
, head = head , head = head
, serverless = False , serverless = False
@ -284,7 +284,7 @@ preRender :
-> Builder routeParams data -> Builder routeParams data
preRender { data, head, pages } = preRender { data, head, pages } =
WithData WithData
{ data = data >> DataSource.map PageServerResponse.RenderPage { data = data >> DataSource.map PageServerResponse.render
, staticRoutes = pages , staticRoutes = pages
, head = head , head = head
, serverless = False , serverless = False

View File

@ -192,7 +192,7 @@ init config flags url key =
case Result.map2 Tuple.pair sharedDataResult pageDataResult of case Result.map2 Tuple.pair sharedDataResult pageDataResult of
Ok ( sharedData, pageData_ ) -> Ok ( sharedData, pageData_ ) ->
case pageData_ of case pageData_ of
PageServerResponse.RenderPage pageData -> PageServerResponse.RenderPage responseInfo pageData ->
let let
userFlags : Pages.Flags.Flags userFlags : Pages.Flags.Flags
userFlags = userFlags =
@ -477,7 +477,7 @@ update config appMsg model =
|> Result.andThen |> Result.andThen
(\pageResponse -> (\pageResponse ->
case pageResponse of case pageResponse of
PageServerResponse.RenderPage renderPagePageData -> PageServerResponse.RenderPage responseInfo renderPagePageData ->
Ok renderPagePageData Ok renderPagePageData
PageServerResponse.ServerResponse _ -> PageServerResponse.ServerResponse _ ->
@ -575,7 +575,7 @@ update config appMsg model =
case Result.map2 Tuple.pair sharedDataResult pageDataResult of case Result.map2 Tuple.pair sharedDataResult pageDataResult of
Ok ( sharedData, pageData_ ) -> Ok ( sharedData, pageData_ ) ->
case pageData_ of case pageData_ of
PageServerResponse.RenderPage pageData -> PageServerResponse.RenderPage responseInfo pageData ->
let let
updateResult : Maybe ( userModel, Cmd userMsg ) updateResult : Maybe ( userModel, Cmd userMsg )
updateResult = updateResult =

View File

@ -726,7 +726,7 @@ nextStepToEffect site contentCache config model ( updatedStaticResponsesModel, n
case includeHtml of case includeHtml of
RenderRequest.OnlyJson -> RenderRequest.OnlyJson ->
Ok Ok
(PageServerResponse.RenderPage (PageServerResponse.render
{ head = [] { head = []
, view = "This page was not rendered because it is a JSON-only request." , view = "This page was not rendered because it is a JSON-only request."
, title = "This page was not rendered because it is a JSON-only request." , title = "This page was not rendered because it is a JSON-only request."
@ -738,7 +738,7 @@ nextStepToEffect site contentCache config model ( updatedStaticResponsesModel, n
|> Result.map |> Result.map
(\( pageData_, sharedData ) -> (\( pageData_, sharedData ) ->
case pageData_ of case pageData_ of
PageServerResponse.RenderPage pageData -> PageServerResponse.RenderPage responseInfo pageData ->
let let
pageModel : userModel pageModel : userModel
pageModel = pageModel =
@ -763,7 +763,7 @@ nextStepToEffect site contentCache config model ( updatedStaticResponsesModel, n
viewValue = viewValue =
(config.view currentPage Nothing sharedData pageData |> .view) pageModel (config.view currentPage Nothing sharedData pageData |> .view) pageModel
in in
PageServerResponse.RenderPage PageServerResponse.RenderPage responseInfo
{ head = config.view currentPage Nothing sharedData pageData |> .head { head = config.view currentPage Nothing sharedData pageData |> .head
, view = viewValue.body |> HtmlPrinter.htmlToString , view = viewValue.body |> HtmlPrinter.htmlToString
, title = viewValue.title , title = viewValue.title
@ -808,7 +808,7 @@ nextStepToEffect site contentCache config model ( updatedStaticResponsesModel, n
case Result.map3 (\a b c -> ( a, b, c )) pageFoundResult renderedResult siteDataResult of case Result.map3 (\a b c -> ( a, b, c )) pageFoundResult renderedResult siteDataResult of
Ok ( pageFound, renderedOrApiResponse, siteData ) -> Ok ( pageFound, renderedOrApiResponse, siteData ) ->
case renderedOrApiResponse of case renderedOrApiResponse of
PageServerResponse.RenderPage rendered -> PageServerResponse.RenderPage responseInfo rendered ->
{ route = payload.path |> Path.toRelative { route = payload.path |> Path.toRelative
, contentJson = , contentJson =
--toJsPayload.pages --toJsPayload.pages
@ -821,6 +821,8 @@ nextStepToEffect site contentCache config model ( updatedStaticResponsesModel, n
, title = rendered.title , title = rendered.title
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v) , staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
, is404 = False , is404 = False
, statusCode = responseInfo.statusCode
, headers = responseInfo.headers
} }
|> ToJsPayload.PageProgress |> ToJsPayload.PageProgress
|> Effect.SendSinglePage False |> Effect.SendSinglePage False
@ -896,7 +898,7 @@ sendSinglePageProgress site contentJson config model =
case includeHtml of case includeHtml of
RenderRequest.OnlyJson -> RenderRequest.OnlyJson ->
Ok Ok
(PageServerResponse.RenderPage (PageServerResponse.render
{ head = [] { head = []
, view = "This page was not rendered because it is a JSON-only request." , view = "This page was not rendered because it is a JSON-only request."
, title = "This page was not rendered because it is a JSON-only request." , title = "This page was not rendered because it is a JSON-only request."
@ -908,7 +910,7 @@ sendSinglePageProgress site contentJson config model =
|> Result.map |> Result.map
(\( pageData_, sharedData ) -> (\( pageData_, sharedData ) ->
case pageData_ of case pageData_ of
PageServerResponse.RenderPage pageData -> PageServerResponse.RenderPage responseInfo pageData ->
let let
pageModel : userModel pageModel : userModel
pageModel = pageModel =
@ -933,7 +935,7 @@ sendSinglePageProgress site contentJson config model =
viewValue = viewValue =
(config.view currentPage Nothing sharedData pageData |> .view) pageModel (config.view currentPage Nothing sharedData pageData |> .view) pageModel
in in
PageServerResponse.RenderPage PageServerResponse.RenderPage responseInfo
{ head = config.view currentPage Nothing sharedData pageData |> .head { head = config.view currentPage Nothing sharedData pageData |> .head
, view = viewValue.body |> HtmlPrinter.htmlToString , view = viewValue.body |> HtmlPrinter.htmlToString
, title = viewValue.title , title = viewValue.title
@ -983,7 +985,7 @@ sendSinglePageProgress site contentJson config model =
case maybeNotFoundReason of case maybeNotFoundReason of
Nothing -> Nothing ->
case renderedOrApiResponse of case renderedOrApiResponse of
PageServerResponse.RenderPage rendered -> PageServerResponse.RenderPage responseInfo rendered ->
{ route = page |> Path.toRelative { route = page |> Path.toRelative
, contentJson = contentJson , contentJson = contentJson
, html = rendered.view , html = rendered.view
@ -992,6 +994,8 @@ sendSinglePageProgress site contentJson config model =
, title = rendered.title , title = rendered.title
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v) , staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
, is404 = False , is404 = False
, statusCode = responseInfo.statusCode
, headers = responseInfo.headers
} }
|> ToJsPayload.PageProgress |> ToJsPayload.PageProgress
|> Effect.SendSinglePage True |> Effect.SendSinglePage True
@ -1049,6 +1053,8 @@ render404Page config model path notFoundReason =
, title = notFoundDocument.title , title = notFoundDocument.title
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v) , staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
, is404 = True , is404 = True
, statusCode = 404
, headers = []
} }
|> ToJsPayload.PageProgress |> ToJsPayload.PageProgress
|> Effect.SendSinglePage True |> Effect.SendSinglePage True

View File

@ -22,6 +22,8 @@ type alias ToJsSuccessPayloadNew =
, title : String , title : String
, staticHttpCache : Dict String String , staticHttpCache : Dict String String
, is404 : Bool , is404 : Bool
, statusCode : Int
, headers : List ( String, String )
} }
@ -63,6 +65,10 @@ successCodecNew canonicalSiteUrl currentPagePath =
.staticHttpCache .staticHttpCache
(Codec.dict Codec.string) (Codec.dict Codec.string)
|> Codec.field "is404" .is404 Codec.bool |> Codec.field "is404" .is404 Codec.bool
|> Codec.field "statusCode" .statusCode Codec.int
|> Codec.field "headers"
.headers
(Codec.dict Codec.string |> Codec.map Dict.toList Dict.fromList)
|> Codec.buildObject |> Codec.buildObject

View File

@ -198,13 +198,13 @@ data =`
? `Request.succeed () ? `Request.succeed ()
|> Request.thenRespond |> Request.thenRespond
(\\() -> (\\() ->
DataSource.succeed (PageServerResponse.RenderPage {}) DataSource.succeed (PageServerResponse.render {})
) )
` `
: withFallback : withFallback
? ` Data ? ` Data
|> DataSource.succeed |> DataSource.succeed
|> DataSource.map PageServerResponse.RenderPage |> DataSource.map PageServerResponse.render
` `
: `DataSource.succeed {}` : `DataSource.succeed {}`
} }

View File

@ -367,15 +367,17 @@ async function start(options) {
const is404 = renderResult.is404; const is404 = renderResult.is404;
switch (renderResult.kind) { switch (renderResult.kind) {
case "json": { case "json": {
res.writeHead(is404 ? 404 : 200, { res.writeHead(is404 ? 404 : renderResult.statusCode, {
"Content-Type": "application/json", "Content-Type": "application/json",
...renderResult.headers,
}); });
res.end(renderResult.contentJson); res.end(renderResult.contentJson);
break; break;
} }
case "html": { case "html": {
res.writeHead(is404 ? 404 : 200, { res.writeHead(is404 ? 404 : renderResult.statusCode, {
"Content-Type": "text/html", "Content-Type": "text/html",
...renderResult.headers,
}); });
res.end(renderResult.htmlString); res.end(renderResult.htmlString);
break; break;

View File

@ -459,7 +459,7 @@ dataForRoute : Maybe Route -> DataSource (PageServerResponse PageData)
dataForRoute route = dataForRoute route =
case route of case route of
Nothing -> Nothing ->
DataSource.succeed (PageServerResponse.RenderPage Data404NotFoundPage____) DataSource.succeed (PageServerResponse.RenderPage { statusCode = 400, headers = [] } Data404NotFoundPage____)
${templates ${templates
.map( .map(
(name) => (name) =>

View File

@ -138,6 +138,8 @@ function runElmApp(
staticData: args.contentJson, staticData: args.contentJson,
is404: args.is404, is404: args.is404,
}), }),
statusCode: args.statusCode,
headers: args.headers,
}); });
} else { } else {
resolve(outputString(basePath, fromElm, isDevServer)); resolve(outputString(basePath, fromElm, isDevServer));
@ -192,6 +194,8 @@ async function outputString(
contentJson["staticData"] = args.contentJson; contentJson["staticData"] = args.contentJson;
contentJson["is404"] = args.is404; contentJson["is404"] = args.is404;
contentJson["path"] = args.route; contentJson["path"] = args.route;
contentJson["statusCode"] = args.statusCode;
contentJson["headers"] = args.headers;
const normalizedRoute = args.route.replace(/index$/, ""); const normalizedRoute = args.route.replace(/index$/, "");
return { return {
@ -199,6 +203,8 @@ async function outputString(
route: normalizedRoute, route: normalizedRoute,
htmlString: preRenderHtml(basePath, args, contentJson, isDevServer), htmlString: preRenderHtml(basePath, args, contentJson, isDevServer),
contentJson: args.contentJson, contentJson: args.contentJson,
statusCode: args.statusCode,
headers: args.headers,
kind: "html", kind: "html",
}; };
} }

View File

@ -2002,7 +2002,8 @@ submitHandlers myForm toDataSource =
Err model -> Err model ->
Err () |> toDataSource model Err () |> toDataSource model
) )
|> DataSource.map PageServerResponse.RenderPage -- TODO allow customizing headers or status code, or not?
|> DataSource.map PageServerResponse.render
) )
] ]

View File

@ -1,4 +1,7 @@
module PageServerResponse exposing (map, PageServerResponse(..)) module PageServerResponse exposing
( map, PageServerResponse(..)
, render
)
{-| {-|
@ -11,16 +14,27 @@ import Server.Response exposing (Response)
{-| -} {-| -}
type PageServerResponse data type PageServerResponse data
= RenderPage data = RenderPage
{ statusCode : Int
, headers : List ( String, String )
}
data
| ServerResponse Response | ServerResponse Response
render : data -> PageServerResponse data
render data =
RenderPage
{ statusCode = 200, headers = [] }
data
{-| -} {-| -}
map : (data -> mappedData) -> PageServerResponse data -> PageServerResponse mappedData map : (data -> mappedData) -> PageServerResponse data -> PageServerResponse mappedData
map mapFn pageServerResponse = map mapFn pageServerResponse =
case pageServerResponse of case pageServerResponse of
RenderPage data -> RenderPage response data ->
RenderPage (mapFn data) RenderPage response (mapFn data)
ServerResponse serverResponse -> ServerResponse serverResponse ->
ServerResponse serverResponse ServerResponse serverResponse