Where is index.html in an embedded directory?
Asked Answered
P

2

6

I am trying to embed a static site (and SPA) into my Go code. The high level structure of my project is

.
├── web.go
└── spa/
    └── index.html

My intent is to have http://localhost:8090/ serving index.html.

The relevant code to do that is

//go:embed spa
var spa embed.FS

log.Info("starting api server")
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.FS(spa)))
log.Fatal(http.ListenAndServe(":8090", r))

When accessing http://localhost:8090/, I get

  • a directory listing page with a single link spa
  • upon clicking on this link I get a 404 page not found

How should I set this up?

Prau answered 23/4, 2021 at 15:11 Comment(0)
Y
1

With Gorilla Mux you need to specify a path prefix:

package main

import (
    "embed"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

//go:embed spa
var spa embed.FS

func main() {
    log.Println("starting api server")
    r := mux.NewRouter()
    r.PathPrefix("/spa/").Handler(http.StripPrefix("/", http.FileServer(http.FS(spa))))
    log.Fatal(http.ListenAndServe(":8090", r))
}

This will route requests to <host>/spa/* to the handler. Then you must strip the prefix / as the contents of spa directory have the spa/ prefix without the leading /:

    b, _ := spa.ReadFile("spa/index.html")
    fmt.Println(string(b)) // file contents

To wrap it up:

http://localhost:8090/spa/index.html is routed to the r.PathPrefix("/spa/") handler. The route though is /spa/index.html, strip the first / resulting in spa/index.html and this finally matches the file path in the embedded variable.

Yezd answered 23/4, 2021 at 16:19 Comment(1)
Thank you very much for the detailed explanation. It works in the sense that http://localhost:8090/spa/ ultimately reaches the index.html file and the URI is http://localhost:8090/spa/#/ (and the content is fully visible). Thanks a lot for that. I will read about how to redirect / to /spa/ in order to have the root serving the SPA.Prau
F
13

File paths in an embedded directory are prefixed by the path used in the //go:embed directive. The embedded file system path for index.html is ​spa/index.html.

Create a sub file system rooted at the spa directory and serve that file system. The path for index.html in the sub file system is index.html.

sub, _ := fs.Sub(spa, "spa")
r.Handle("/", http.FileServer(http.FS(sub)))

https://pkg.go.dev/io/fs#Sub

Run an example on the playground.

This approach works with any mux including the Gorilla Mux. Here's the code for Gorilla where r is a *mux.Router:

sub, _ := fs.Sub(spa, "spa")
r.PathPrefix("/").Handler(http.FileServer(http.FS(sub)))

Run an example on the playground

Fresh answered 23/4, 2021 at 15:35 Comment(5)
Thank you. I must investigate that because when going from @blackgreen's solution to yours (r.PathPrefix("/spa/").Handler(http.StripPrefix("/", http.FileServer(http.FS(spa))))r.Handle("/", http.FileServer(http.FS(sub)))), the call to index.html is correct, but then all consecutive call fail (either with a 404 or with (canceled)), which is really strange (on the same spa directory)Prau
What do you mean by "all consecutive call"? Are you saying that the second request to /index.html fails, the request to some other url fails?Snodgrass
Sorry, I was not clear. What I meant is that index.html calls other URLs (it is an SPA), and these calls (to files in the created FS, next to index.html) fail with 404 or (cancelled) (per Chrome DevTools). What is weird is that the other solution works fine with the same files, at the same place (but of course called with the /spa suffix)Prau
@Prau I updated the answer to show how to match the tree rooted at "/" using Gorilla Mux.Snodgrass
Does it depend on the spa directory at run time?Covetous
Y
1

With Gorilla Mux you need to specify a path prefix:

package main

import (
    "embed"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

//go:embed spa
var spa embed.FS

func main() {
    log.Println("starting api server")
    r := mux.NewRouter()
    r.PathPrefix("/spa/").Handler(http.StripPrefix("/", http.FileServer(http.FS(spa))))
    log.Fatal(http.ListenAndServe(":8090", r))
}

This will route requests to <host>/spa/* to the handler. Then you must strip the prefix / as the contents of spa directory have the spa/ prefix without the leading /:

    b, _ := spa.ReadFile("spa/index.html")
    fmt.Println(string(b)) // file contents

To wrap it up:

http://localhost:8090/spa/index.html is routed to the r.PathPrefix("/spa/") handler. The route though is /spa/index.html, strip the first / resulting in spa/index.html and this finally matches the file path in the embedded variable.

Yezd answered 23/4, 2021 at 16:19 Comment(1)
Thank you very much for the detailed explanation. It works in the sense that http://localhost:8090/spa/ ultimately reaches the index.html file and the URI is http://localhost:8090/spa/#/ (and the content is fully visible). Thanks a lot for that. I will read about how to redirect / to /spa/ in order to have the root serving the SPA.Prau

© 2022 - 2024 — McMap. All rights reserved.