Multiple static files directories - go

In go, we can handle static files, by defining their directory as static directory as shown below:
fs := http.FileServer(http.Dir("./static/"))
http.Handle("/files/", fs)
Where static files folder static should be beside the binary.
Another way, is to use the new //go embed as:
//go:embed static
var staticFiles embed.FS
// http.FS can be used to create a http Filesystem
var staticFS = http.FS(staticFiles)
fs := http.FileServer(staticFS) // embeded static files
// Serve static files
http.Handle("/static/", fs)
But what if I want to have most of my static files embedded in the binary, and some are not, that can be used alongside the binary, I tried to mix both method defined above, but did not work, only the embedded ones run smoothly, below code failed, any thought?:
//go:embed static
var staticFiles embed.FS
// http.FS can be used to create a http Filesystem
var staticFS = http.FS(staticFiles)
fs := http.FileServer(staticFS) // embeded static files
// Serve static files
http.Handle("/static/", fs)
www := http.FileServer(http.Dir("./files/")) // side static files, to be beside binary
// Serve static files
http.Handle("/files/", www)

I found it, was missing http.StripPrefix, below worked perfectly with me, and have multiple static folders:
package main
import (
"embed"
"encoding/json"
"fmt"
"log"
"net/http"
)
//go:embed static
var staticFiles embed.FS
func main() {
go func() {
http.HandleFunc("/favicon.ico", func(rw http.ResponseWriter, r *http.Request) {})
// http.FS can be used to create a http Filesystem
var staticFS = http.FS(staticFiles)
fs := http.FileServer(staticFS) // embeded static files
// Serve static files, to be embedded in the binary
http.Handle("/static/", fs)
// Serve public files, to be beside binary
http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("./files"))))
http.HandleFunc("/getSkills", getSkills)
log.Println("Listening on :3000...")
err := http.ListenAndServe(":3000", nil)
if err != nil {
log.Fatal(err)
}
}

Related

go:embed fs only works for the root with http server?

I have the following directory structure:
web/
dist/
index.html
main.go
the content of main.go:
package main
import (
"embed"
"io/fs"
"net/http"
)
//go:embed web/dist
var webfs embed.FS
func main() {
mux := http.NewServeMux()
sub, err := fs.Sub(webfs, "web/dist")
if err != nil {
panic(err)
}
mux.Handle("/", http.FileServer(http.FS(sub)))
http.ListenAndServe(":2222", mux)
}
When the code is run, I could get the content of index.html through http://127.0.0.1:2222. However, when I changed the line to:
mux.Handle("/admin/", http.FileServer(http.FS(sub)))
I get 404 when accessing http://127.0.0.1:2222/admin/.
You will need to strip the /admin/ off the request path (otherwise the file server will be looking in web/dist/admin). e.g.
mux.Handle("/admin/", http.StripPrefix("/admin/", http.FileServer(http.FS(sub))))
See the docs for StripPrefix for more info.

go 1.16 embed - strip directory name

i was previously using statik to embed files into a Go application.
with Go 1.16 i can remove that deps
for example:
//go:embed static
var static embed.FS
fs := http.FileServer(http.FS(static))
http.Handle("/", fs)
this will serve the ./static/ directory from http://.../static/
Is there a way I can serve that directory from / root path, without /static?
Use fs.Sub:
Sub returns an FS corresponding to the subtree rooted at fsys's dir.
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static
var static embed.FS
func main() {
subFS, _ := fs.Sub(static, "static")
http.Handle("/", http.FileServer(http.FS(subFS)))
log.Fatal(http.ListenAndServe(":4000", nil))
}
fs.Sub is also useful in combination with http.StripPrefix to "rename" a directory. For instance, to "rename" the directory static to public, such that a request for /public/index.html serves static/index.html:
//go:embed static
var static embed.FS
subFS, _ := fs.Sub(static, "static")
http.Handle("/", http.StripPrefix("/public", http.FileServer(http.FS(subFS))))
Alternatively, create a .go file in the static directory and move the embed directive there (//go:embed *). That matches a little more closely what the statik tool does (it creates a whole new package), but is usually unnecessary thanks to fs.Sub.
// main.go
package main
import (
"my.module/static"
"log"
"net/http"
)
func main() {
http.Handle("/", http.FileServer(http.FS(static.FS)))
log.Fatal(http.ListenAndServe(":4000", nil))
}
// static/static.go
package static
import "embed"
//go:embed *
var FS embed.FS

Serve embedded filesystem from root path of URL

Go 1.16 added the new embed package. I would like to use this package to embed a directory and serve it over HTTP. Consider the following setup:
myproject/
|-- main.go
|-- static/
| |-- index.html
| |-- styles.css
| |-- scripts.js
package main
import (
"embed"
"log"
"net/http"
)
//go:embed static
var staticFS embed.FS
func main() {
http.Handle("/", http.FileServer(http.FS(staticFS)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
With this setup, my expectation is that I can point my browser to localhost:8080 and have it load index.html. What I am observing instead, is that I need to point my browser to localhost:8080/static to have it load index.html.
How can an embedded filesystem be served from the root path of the URL?
When declaring a variable of type embed.FS, that variable represents a filesystem that already contains a root directory. All resources from the //go:embed directive are copied into this root directory of the filesystem. That means that the staticFS variable does not refer to the static folder that was being embedded directly, but to the root directory that contains the static folder. With that in mind, to achieve the desired result of being able to access the static folder from localhost:8080, Go's fs.Sub can be used to pass a filesystem to the server where the static folder is the root:
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static
var embeddedFS embed.FS
func main() {
serverRoot, err := fs.Sub(embeddedFS, "static")
if err != nil {
log.Fatal(err)
}
http.Handle("/", http.FileServer(http.FS(serverRoot)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Alternatively to fs.Sub you can declare embedding inside the static directory.
package static
//go:embed *.html *.css *.js
var FS embed.FS
And then import it.
package main
import (
"embed"
"log"
"net/http"
"import/path/to/static"
)
func main() {
http.Handle("/", http.FileServer(http.FS(static.FS)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
The downside of adding embedding into static directory is that *.go file may be also added as embedded unless strict file masks are used (e.g. //go:embed *.html *js instead of //go:embed *).
Edit: Removed additionally proposed http.StripPrefix because it does not actually help with issue.

How to serve a WASM file

I have a small WASM program that I would like to test on my private LAN (mostly mobile devices). I am able to serve it on local loopback. I was hoping to create a simple page route in go that would handle the WASM and serve it to devices on my network. Unfortunately the application/wasm content type isn't recognized (I think).
Is there an easy way to serve the index.html that has embedded WASM?
I am not sure how to modify this to allow for the Content-Type:
package main
import (
"io/ioutil"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "5000"
}
f, _ := os.Create("/var/log/golang/golang-server.log")
defer f.Close()
log.SetOutput(f)
const indexPage = "public/index.html"
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Printf("Serving %s to %s...\n", indexPage, r.RemoteAddr)
http.ServeFile(w, r, indexPage)
})
log.Printf("Listening on port %s\n\n", port)
http.ListenAndServe(":"+port, nil)
}
What does it mean "index.html that has embedded WASM"?
I suppose you have separate WASM file, located somewhere in your filesystem.
In go documentation it is recommended to use WASM files like this:
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).
then((result) => {
go.run(result.instance);
});
so browser when loads index.html also asks your webserver for main.wasm file. You have to handle it explicitly (add this call after your previous call to http.HandleFunc):
http.HandleFunc("/main.wasm", func(w http.ResponseWriter, r *http.Request) {
log.Printf("Serving WASM to %s...\n", r.RemoteAddr)
http.ServeFile(w, r, "path/to/wasm/file")
})
But much more simple way of serving static content (including .wasm files) is serving hole directory with assets (usually I place all required resources into this folder - js, css, images, etc):
http.Handle(
"/assets/",
http.StripPrefix("/assets/", http.FileServer(http.Dir("path/to/assets/folder")))
)
In this case replace JavaScript code instantiateStreaming(fetch("main.wasm"), go.importObject) with instantiateStreaming(fetch("/assets/main.wasm"), go.importObject) and place your wasm file into assets folder.

Serving static content with GoLang Webserver

I'm exploring the depths of Go, and I've been trying to write a simple web application to wrap my head around everything. I'm trying to serve a React.js application.
Below is the code of the Go server. I've got the default route of / serving the index.html which is working fine. I'm struggling to allow static files to be served to that index file. I'm allowing the React App to do it's own client side routing, although I need to statically serve the JavaScript / CSS / Media files.
For example, I need to be able to serve the bundle.js file into the index.html for the React application to run. Currently, when I route to localhost:8000/dist/ I see the files being listed, but every file/folder that I click from there is throwing a 404 Page Not Found. Is there something that I'm missing? A push in the right direction would be greatly appreciated.
Webserver.go
package main
import (
"net/http"
"log"
"fmt"
"os"
"github.com/BurntSushi/toml"
"github.com/gorilla/mux"
)
type ServerConfig struct {
Environment string
Host string
HttpPort int
HttpsPort int
ServerRoot string
StaticDirectories []string
}
func ConfigureServer () ServerConfig {
_, err := os.Stat("env.toml")
if err != nil {
log.Fatal("Config file is missing: env.toml")
}
var config ServerConfig
if _, err := toml.DecodeFile("env.toml", &config); err != nil {
log.Fatal(err)
}
return config
}
func IndexHandler (w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./src/index.html")
}
func main () {
Config := ConfigureServer()
router := mux.NewRouter()
// Configuring static content to be served.
router.Handle("/dist/", http.StripPrefix("/dist/", http.FileServer(http.Dir("dist"))))
// Routing to the Client-Side Application.
router.HandleFunc("/", IndexHandler).Methods("GET")
log.Printf(fmt.Sprintf("Starting HTTP Server on Host %s:%d.", Config.Host, Config.HttpPort))
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", Config.Host, Config.HttpPort), router); err != nil {
log.Fatal(err)
}
}
Per the gorilla mux docs, the proper way to do this would be a handler registered with PathPrefix, like this:
router.PathPrefix("/dist/").Handler(http.StripPrefix("/dist/", http.FileServer(http.Dir("dist"))))
An example can be found if you search the docs for something like PathPrefix("/static/").
This wildcard behavior actually comes by default with the pattern matching mechanism in net/http, so if you weren't using gorilla, but just the default net/http, you could do the following:
http.Handle("/dist/", http.StripPrefix("/dist/", http.FileServer(http.Dir("dist"))))
There could be an issue with file access path. Try:
// Strip away "/dist" instead of "/dist/"
router.Handle("/dist/", http.StripPrefix("/dist", http.FileServer(http.Dir("dist"))))

Resources