SAFE-Stack / SAFE-BookStore

Working sample of a SAFE-Stack project with hot reloading
https://safebookstore.azurewebsites.net/
The Unlicense
494 stars 147 forks source link

SAFE apps can't be hosted as sub-applications #377

Closed 0x53A closed 8 months ago

0x53A commented 5 years ago

We need to be able to host our app as a subapplication in IIS. By that I mean something like http://my.domain.test/customer1/location1/SAFE-BookStore.

The two biggest issues I had were the pageParser and links.


1) Navigation must be relative

All links should be of the format href="target", NOT href="/target".


2) Server should redirect from "" to "/".

If my page loads as http://my.domain.test/customer1/location1/SAFE-BookStore and I click on href="login", then the browser navigates to http://my.domain.test/customer1/location1/login instead of http://my.domain.test/customer1/location1/SAFE-BookStore/login.

I fixed this by adding


let redirectToSlash : HttpHandler = fun next ctx ->
    redirectTo false (ctx.Request.PathBase + "/") next ctx

// ...

let webApp =
  choose [
    route "" >=> redirectToSlash
    route "/" >=> SSRviewHome
    // ...

3) The elmish urlparser doesn't handle the path correctly.

The path is suddenly /customer1/location1/SAFE-BookStore/ instead of /. So I need to strip the prefix.

This is the part where I am not sure what the best solution is and would like some opinions.

I hacked it this way:

// ---------------------------------------------
// in app.fs
// ---------------------------------------------

// My application is relatively simple and only has 1-level deep urls.
// So I know that the first slash seperates root and relative.
// If you have a more complex uri scheme, then you need a different solution.

let rootPath =
    let currentFullUrl = Browser.location.pathname
    currentFullUrl.Substring(0, currentFullUrl.LastIndexOf('/'))
Browser.console.log("ROOT: " + rootPath)

// ...

// pass the rootPath to the urlParser
|> Program.toNavigable (Pages.urlParser rootPath) Client.TopLevel.urlUpdate

// ---------------------------------------------
// in pages.fs
// ---------------------------------------------

let pageParser (basePath:string) : Parser<Page -> Page,_> =
    let basePath = basePath.TrimStart('/')
    let coreParser =
        oneOf
            [ map Page.Home (s "")
              map Page.OtherPage (s "otherPage") ]
    if basePath = "" then coreParser else

    let basePathParts = basePath.Split('/')
    let basePathParser = basePathParts |> Seq.map s |> Seq.reduce (</>)
    basePathParser </> coreParser

let urlParser basePath location = parsePath (pageParser basePath) location

After these changes, I could successfully host my app as an IIS sub application. I'll prepare a PR to Bookstore when I have some time.

forki commented 5 years ago

Cool thanks

Am Fr., 23. Nov. 2018, 13:46 hat Lukas Rieger notifications@github.com geschrieben:

We need to be able to host our app as a subapplication in IIS. By that I mean something like http://my.domain.test/customer1/location1/SAFE-BookStore.

The two biggest issues I had were the pageParser and links.

  1. Navigation must be relative

All links should be of the format href="target", NOT href="/target".

  1. Server should redirect from "" to "/".

If my page loads as http://my.domain.test/customer1/location1/SAFE-BookStore and I click on href="login", then the browser navigates to http://my.domain.test/customer1/location1/login instead of http://my.domain.test/customer1/location1/SAFE-BookStore/login.

I fixed this by adding

let redirectToSlash : HttpHandler = fun next ctx -> redirectTo false (ctx.Request.PathBase + "/") next ctx // ... let webApp = choose [ route "" >=> redirectToSlash route "/" >=> SSRviewHome // ...


  1. The elmish urlparser doesn't handle the path correctly.

The path is suddenly /customer1/location1/SAFE-BookStore/ instead of /. So I need to strip the prefix.

This is the part where I am not sure what the best solution is and would like some opinions.

I hacked it this way:

// ---------------------------------------------// in app.fs// --------------------------------------------- // My application is relatively simple and only has 1-level deep urls.// So I know that the first slash seperates root and relative.// If you have a more complex uri scheme, then you need a different solution. let rootPath = let currentFullUrl = Browser.location.pathname currentFullUrl.Substring(0, currentFullUrl.LastIndexOf('/')) Browser.console.log("ROOT: " + rootPath) // ... // pass the rootPath to the urlParser|> Program.toNavigable (Pages.urlParser rootPath) Client.TopLevel.urlUpdate

// ---------------------------------------------// in pages.fs// --------------------------------------------- let pageParser (basePath:string) : Parser<Page -> Page,_> = let basePath = basePath.TrimStart('/') let coreParser = oneOf [ map Page.Home (s "") map Page.OtherPage (s "otherPage") ] if basePath = "" then coreParser else

let basePathParts = basePath.Split('/')
let basePathParser = basePathParts |> Seq.map s |> Seq.reduce (</>)
basePathParser </> coreParser

let urlParser basePath location = parsePath (pageParser basePath) location


After these changes, I could successfully host my app as an IIS sub application. I'll prepare a PR to Bookstore when I have some time.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/SAFE-Stack/SAFE-BookStore/issues/377, or mute the thread https://github.com/notifications/unsubscribe-auth/AADgNLJigA5wLz-PJoq26hdQrTJ3Ekz9ks5ux-4qgaJpZM4YwmrQ .