Pass through request from NodeJS and update the ServerRequest API.

This commit is contained in:
Dillon Kearns 2021-12-15 20:10:54 -08:00
parent 3960da425c
commit 9769e7a95c
5 changed files with 297 additions and 14 deletions

View File

@ -0,0 +1,113 @@
module Page.Time exposing (Data, Model, Msg, page)
import DataSource exposing (DataSource)
import DataSource.ServerRequest as ServerRequest exposing (ServerRequest)
import Dict exposing (Dict)
import Head
import Head.Seo as Seo
import Html
import Page exposing (Page, PageWithState, StaticPayload)
import Pages.PageUrl exposing (PageUrl)
import Pages.Url
import QueryParams exposing (QueryParams)
import Shared
import Url
import View exposing (View)
type alias Model =
{}
type alias Msg =
Never
type alias RouteParams =
{}
page : Page RouteParams Data
page =
Page.serverless
{ head = head
, data = data
, routeFound = \_ -> DataSource.succeed True
}
--{ data : (ServerRequest decodedRequest -> DataSource decodedRequest) -> routeParams -> DataSource data
--, routeFound : routeParams -> DataSource Bool
--, head : StaticPayload data routeParams -> List Head.Tag
--}
|> Page.buildNoState { view = view }
type alias Request =
{ language : Maybe String
, method : ServerRequest.Method
, queryParams : Dict String (List String)
, protocol : Url.Protocol
, allHeaders : Dict String String
}
data : (ServerRequest a -> DataSource a) -> RouteParams -> DataSource Data
data resolveServerRequest routeParams =
let
serverReq : ServerRequest Request
serverReq =
ServerRequest.init
(\language method queryParams protocol allHeaders ->
{ language = language
, method = method
, queryParams = queryParams |> QueryParams.toDict
, protocol = protocol
, allHeaders = allHeaders
}
)
|> ServerRequest.optionalHeader "accept-language"
|> ServerRequest.withMethod
|> ServerRequest.withQueryParams
|> ServerRequest.withProtocol
|> ServerRequest.withAllHeaders
in
serverReq
|> ServerRequest.toDataSource
|> DataSource.map Data
head :
StaticPayload Data RouteParams
-> List Head.Tag
head static =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = Pages.Url.external "TODO"
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "TODO"
, locale = Nothing
, title = "Time"
}
|> Seo.website
type alias Data =
{ request : Request
}
view :
Maybe PageUrl
-> Shared.Model
-> StaticPayload Data RouteParams
-> View Msg
view maybeUrl sharedModel static =
{ title = "Time"
, body =
[ Html.text (static.data.request |> Debug.toString)
]
}

View File

@ -5,7 +5,7 @@ module Page exposing
, prerender, single
, Builder(..)
, PageWithState
, serverless, prerenderWithFallback
, prerenderWithFallback, serverless
)
{-|
@ -77,13 +77,15 @@ When there are Dynamic Route Segments, you need to tell `elm-pages` which pages
-}
import DataSource.ServerRequest as ServerRequest exposing (ServerRequest)
import Browser.Navigation
import DataSource exposing (DataSource)
import DataSource.Http
import DataSource.ServerRequest exposing (ServerRequest(..))
import Head
import Pages.Internal.NotFoundReason exposing (NotFoundReason)
import Pages.Internal.RoutePattern exposing (RoutePattern)
import Pages.PageUrl exposing (PageUrl)
import Pages.Secrets as Secrets
import Path exposing (Path)
import Shared
import View exposing (View)
@ -304,7 +306,6 @@ prerender { data, head, pages } =
}
{-| -}
prerenderWithFallback :
{ data : routeParams -> DataSource data
@ -365,7 +366,7 @@ serverless :
-> Builder routeParams data
serverless { data, head, routeFound } =
WithData
{ data = data ServerRequest.toStaticHttp
{ data = data DataSource.ServerRequest.toDataSource
, staticRoutes = DataSource.succeed []
, head = head
, serverless = True

View File

@ -2,6 +2,7 @@ const path = require("path");
const fs = require("fs");
const which = require("which");
const chokidar = require("chokidar");
const { URL } = require("url");
const {
spawnElmMake,
compileElmForBrowser,
@ -252,7 +253,7 @@ async function start(options) {
* @param {((value: any) => any) | null | undefined} onOk
* @param {((reason: any) => PromiseLike<never>) | null | undefined} onErr
*/
function runRenderThread(pathname, onOk, onErr) {
function runRenderThread(serverRequest, pathname, onOk, onErr) {
let cleanUpThread = () => {};
return new Promise(async (resolve, reject) => {
const readyThread = await waitForThread();
@ -265,6 +266,7 @@ async function start(options) {
readyThread.worker.postMessage({
mode: "dev-server",
pathname,
serverRequest,
});
readyThread.worker.on("message", (message) => {
if (message.tag === "done") {
@ -344,6 +346,7 @@ async function start(options) {
}
await runRenderThread(
reqToJson(req),
pathname,
function (renderResult) {
const is404 = renderResult.is404;
@ -562,4 +565,19 @@ async function ensureRequiredExecutables() {
}
}
function reqToJson(req) {
const url = new URL(req.url, "http://localhost:1234");
return {
method: req.method,
hostname: req.hostname,
query: url.search ? url.search.substring(1) : "",
headers: req.headers,
host: url.host,
pathname: url.pathname,
port: url.port,
protocol: url.protocol,
rawUrl: req.url,
};
}
module.exports = { start };

View File

@ -7,16 +7,15 @@ let Elm;
global.staticHttpCache = {};
async function run({ mode, pathname }) {
async function run({ mode, pathname, serverRequest }) {
console.time(`${threadId} ${pathname}`);
try {
const req = null;
const renderResult = await renderer(
workerData.basePath,
requireElm(mode),
mode,
pathname,
req,
serverRequest,
function (patterns) {
if (mode === "dev-server" && patterns.size > 0) {
parentPort.postMessage({ tag: "watch", data: [...patterns] });

View File

@ -1,15 +1,21 @@
module DataSource.ServerRequest exposing (ServerRequest, expectHeader, init, optionalHeader, staticData, toStaticHttp)
module DataSource.ServerRequest exposing
( ServerRequest, expectHeader, init, optionalHeader, staticData, toDataSource
, Method(..), withAllHeaders, withHost, withMethod, withProtocol, withQueryParams
)
{-|
@docs ServerRequest, expectHeader, init, optionalHeader, staticData, toStaticHttp
@docs ServerRequest, expectHeader, init, optionalHeader, staticData, toDataSource
-}
import DataSource
import DataSource.Http
import Dict exposing (Dict)
import OptimizedDecoder
import QueryParams exposing (QueryParams)
import Secrets
import Url
{-| -}
@ -33,8 +39,8 @@ staticData =
{-| -}
toStaticHttp : ServerRequest decodesTo -> DataSource.DataSource decodesTo
toStaticHttp (ServerRequest decoder) =
toDataSource : ServerRequest decodesTo -> DataSource.DataSource decodesTo
toDataSource (ServerRequest decoder) =
DataSource.Http.get (Secrets.succeed "$$elm-pages$$headers") decoder
@ -43,18 +49,164 @@ expectHeader : String -> ServerRequest (String -> value) -> ServerRequest value
expectHeader headerName (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.field headerName OptimizedDecoder.string
(OptimizedDecoder.field (headerName |> String.toLower) OptimizedDecoder.string
|> OptimizedDecoder.field "headers"
)
|> ServerRequest
{-| -}
withAllHeaders : ServerRequest (Dict String String -> value) -> ServerRequest value
withAllHeaders (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.dict OptimizedDecoder.string
|> OptimizedDecoder.field "headers"
)
|> ServerRequest
{-| -}
withMethod : ServerRequest (Method -> value) -> ServerRequest value
withMethod (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.field "method" OptimizedDecoder.string
|> OptimizedDecoder.map methodFromString
)
|> ServerRequest
{-| -}
withHost : ServerRequest (String -> value) -> ServerRequest value
withHost (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.field "host" OptimizedDecoder.string)
|> ServerRequest
{-| -}
withProtocol : ServerRequest (Url.Protocol -> value) -> ServerRequest value
withProtocol (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.field "protocol" OptimizedDecoder.string
|> OptimizedDecoder.andThen
(\protocol ->
if protocol |> String.startsWith "https" then
OptimizedDecoder.succeed Url.Https
else if protocol |> String.startsWith "http" then
OptimizedDecoder.succeed Url.Http
else
OptimizedDecoder.fail <| "Unexpected protocol: " ++ protocol
)
)
|> ServerRequest
{-| -}
withQueryParams : ServerRequest (QueryParams -> value) -> ServerRequest value
withQueryParams (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.field "query" OptimizedDecoder.string
|> OptimizedDecoder.map QueryParams.fromString
)
|> ServerRequest
{-| -}
optionalHeader : String -> ServerRequest (Maybe String -> value) -> ServerRequest value
optionalHeader headerName (ServerRequest decoder) =
decoder
|> OptimizedDecoder.andMap
(OptimizedDecoder.optionalField headerName OptimizedDecoder.string
(OptimizedDecoder.optionalField (headerName |> String.toLower) OptimizedDecoder.string
|> OptimizedDecoder.field "headers"
)
|> ServerRequest
type Method
= Connect
| Delete
| Get
| Head
| Options
| Patch
| Post
| Put
| Trace
| NonStandard String
methodFromString : String -> Method
methodFromString rawMethod =
case rawMethod |> String.toLower of
"connect" ->
Connect
"delete" ->
Delete
"get" ->
Get
"head" ->
Head
"options" ->
Options
"patch" ->
Patch
"post" ->
Post
"put" ->
Put
"trace" ->
Trace
_ ->
NonStandard rawMethod
{-| Gets the HTTP Method as a String, like 'GET', 'PUT', etc.
-}
methodToString : Method -> String
methodToString method =
case method of
Connect ->
"CONNECT"
Delete ->
"DELETE"
Get ->
"GET"
Head ->
"HEAD"
Options ->
"OPTIONS"
Patch ->
"PATCH"
Post ->
"POST"
Put ->
"PUT"
Trace ->
"TRACE"
NonStandard nonStandardMethod ->
nonStandardMethod