I followed this example for serving a NextJs front end single-page application using Golang and the native net/http package:
import (
"embed"
"io/fs"
"log"
"net/http"
"runtime/pprof"
)
//go:embed nextjs/dist
//go:embed nextjs/dist/_next
//go:embed nextjs/dist/_next/static/chunks/pages/*.js
//go:embed nextjs/dist/_next/static/*/*.js
var nextFS embed.FS
func main() {
// Root at the `dist` folder generated by the Next.js app.
distFS, err := fs.Sub(nextFS, "nextjs/dist")
if err != nil {
log.Fatal(err)
}
// The static Next.js app will be served under `/`.
http.Handle("/", http.FileServer(http.FS(distFS)))
// The API will be served under `/api`.
http.HandleFunc("/api", handleAPI)
// Start HTTP server at :8080.
log.Println("Starting HTTP server at http://localhost:8080 ...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
and it works. Now I want to use gorilla/mux instead of the native net/http package. So now my main function looks like this:
func main() {
// Root at the `dist` folder generated by the Next.js app.
distFS, err := fs.Sub(nextFS, "nextjs/dist")
if err != nil {
log.Fatal(err)
}
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.FS(distFS)))
srv := &http.Server{
Handler: r,
Addr: "0.0.0.0:8080",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
This works for serving the index.html file when I navigate to localhost:8080 in the browser, but the page has no styling, no images, and no JavaScript.
I tried using the instructions at gorilla/mux for serving SPAs, but for this Next.js application, it could not find the files and the browser would error out with a connection reset error.
What else do I need to do to for the CSS, JavaScript, and images to be available when the page is loaded?
Please, try
r.PathPrefix("/").Handler(http.FileServer(http.FS(distFS)))
gorilla/mux interprets the first argument of the Handle function as a template: https://pkg.go.dev/github.com/gorilla/mux#Route.Path.
Please, mind the order when adding routes: when two routes match the same path, the first added one wins.
I have reverse proxies in my main web-server that are dedicated to a certain micro-service and handle forward requests to their appropriate micro-services.
func newTrimPrefixReverseProxy(target *url.URL, prefix string) *httputil.ReverseProxy {
director := func(req *http.Request) {
// ... trims prefix from request path and prepends the path of the target url
}
return &httputil.ReverseProxy{Director: director}
}
This has worked perfectly for pure JSON responses, but I have ran into issues recently when trying to serve content (stream responses) through the reverse proxy. The means for serving the content is irrelevant, the (video) content is served as intended when the service is accessed directly and not through the reverse proxy.
Serving the content:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "video.mp4", time.Now().Add(time.Hour*24*365*12*9*-1), videoReadSeeker)
})
Again, the videoReadSeeker and how the content is served is not the issue, the issue is having my response relayed as intended to the requester through the reverse proxy; when accessing the service directly, the video shows up and I can scrub it to my heart's content.
Note that the response for data the content is received (http status, headers), but the content stream in the response body is not.
How can I make sure that the reverse proxy handles streamed responses as intended for the content?
Do you get the same results when using:
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, err := url.Parse("http://localhost:8080/asdfasdf")
if err != nil {
log.Fatal("url.Parse: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(u)
log.Printf("Listening at :8081")
if err := http.ListenAndServe(":8081", proxy); err != nil {
log.Fatal("ListenAndServe: %v", err)
}
}
Ultimately these are the same implementation under the hood, but the director provided here ensures that some of the expected headers exist that you will need for some proxy features to function as expected.
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"))))
How do you serve index.html (or some other static HTML file) using a go web server?
I just want a basic, static HTML file (like an article, for example) which I can serve from a go web server. The HTML should be modifiable outside of the go program, as it would be in the case while using HTML templates.
This is my web server which only hosts hard-coded text ("Hello world!").
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":3000", nil)
}
That task is very easy with Golang net/http package.
All You need to do is:
package main
import (
"net/http"
)
func main() {
http.Handle("/", http.FileServer(http.Dir("./static")))
http.ListenAndServe(":3000", nil)
}
assuming that static files are in folder named static in the root directory of the project.
If it's in folder static, you'll have index.html file calling http://localhost:3000/ which will result in rendering that index file instead of listing all the files availible.
Additionally, calling any other file in that folder (for example http://localhost:3000/clients.html) will show that file, properly rendered by the browser (at least Chrome, Firefox and Safari :))
UPDATE: serving files from url different than "/"
If You want to serve files, say from folder ./public under url: localhost:3000/static You have to use additional function: func StripPrefix(prefix string, h Handler) Handler like this:
package main
import (
"net/http"
)
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./public"))))
http.ListenAndServe(":3000", nil)
}
Thanks to that, all your files from ./public are avalible under localhost:3000/static
Without http.StripPrefix function, if you would try to access file localhost:3000/static/test.html, the server would look for it in ./public/static/test.html
This is because the server treats the whole URI as a relative path to the file.
Fortunately, it's easily solved with the built-in function.
I prefer using http.ServeFile for this over http.FileServer. I wanted directory browsing disabled, a proper 404 if files are missing and an easy way to special case the index file. This way, you can just drop the built binary into a folder and it will serve everything relative to that binary. Of course, you can use strings.Replace on p if you have the files stored in another directory.
func main() {
fmt.Println("Now Listening on 80")
http.HandleFunc("/", serveFiles)
log.Fatal(http.ListenAndServe(":80", nil))
}
func serveFiles(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path)
p := "." + r.URL.Path
if p == "./" {
p = "./static/index.html"
}
http.ServeFile(w, r, p)
}
If you only want to serve 1 file and not a full directory, you can use http.ServeFile
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "index.html")
})
NOT a FTP server: That is something different than what I intended, which would be to serve the index.html homepage, like a normal web server would. Like, when I go to mydomain.com in my browser, I want index.html rendered.
That is mainly what "Writing Web Applications" describes, and what a project like hugo (static html site generator) does.
It is about reading a file, and responsing with a ContentType "text/html":
func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := server.renderFile(w, r.URL.Path)
if err != nil {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
server.fn404(w, r)
}
}
with renderFile() essentially reading and setting the right type:
file, err = ioutil.ReadFile(server.MediaPath + filename)
if ext != "" {
w.Header().Set("Content-Type", mime.TypeByExtension(ext))
}
You can also use the Gorilla Mux Router to server static files.
Assuming you have a static folder and an index.html file in the root.
import "github.com/gorilla/mux"
func main(){
router := mux.NewRouter()
fs := http.FileServer(http.Dir("./static/"))
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs))
log.Fatal(http.ListenAndServe(":8080", router))
}
Example how custom serve mp3 file:
r := http.NewServeMux()
r.HandleFunc("/file/*", func(w http.ResponseWriter, r *http.Request) {
// Prepare file path
pathFile := strings.ReplaceAll(r.RequestURI, "/file/", "./my_path/")
f, err := os.Open(pathFile)
if f == nil || err != nil {
return
}
// Read file into memory
fileBytes, err := ioutil.ReadAll(f)
if err != nil {
log.Println(err)
_, _ = fmt.Fprintf(w, "Error file bytes")
return
}
// Check mime type
mime := http.DetectContentType(fileBytes)
if mime != "audio/mpeg" {
log.Println("Error file type")
_, _ = fmt.Fprintf(w, "Error file type")
return
}
// Custom headers
r.Header.Add("Content-Type", "audio/mpeg")
r.Header.Add("Cache-Control", "must-revalidate, post-check=0, pre-check=0")
r.Header.Add("Content-Description", "File Transfer")
r.Header.Add("Content-Disposition", "attachment; filename=file.mp3")
r.Header.Add("Content-Transfer-Encoding", "binary")
r.Header.Add("Expires", "0")
r.Header.Add("Pragma", "public")
r.Header.Add("Content-Length", strconv.Itoa(len(fileBytes)))
http.ServeFile(w, r, pathFile)
})
log.Fatal(http.ListenAndServe(":80", r))
This is easy in golang as:
package main
import (
"log"
"net/http"
)
func main() {
log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("."))))
}
`
You can just do this and make sure to keep your HTML file as index.html
This will serve the index.html file (if you have in the root) to the browser on localhost:8080
func main() {
port := flag.String("p", "8080", "port to serve on")
directory := flag.String("d", ".", "static file folder")
flag.Parse()
http.Handle("/", http.FileServer(http.Dir(*directory)))
log.Printf("Serving %s on HTTP port: %s\n", *directory, *port)
log.Fatal(http.ListenAndServe(":"+*port, nil))
}
I want to write a simple webserver in go that does the following: when i go to http://example.go:8080/image, it returns a static image.
I'm following an example i found here. In this example they implement this method:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
and then refer to it here :
...
...
http.HandleFunc("/", handler)
Now, what i wanna do is serve an image instead of writing to the string.
How would i go about that?
You can serve static files using the http.FileServer function.
package main
import (
"log"
"net/http"
)
func main() {
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("path/to/file"))))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
EDIT: More idiomatic code.
EDIT 2: This code above will return an image image.png when the browser requests http://example.go/image.png
The http.StripPrefix function here is strictly unnecessary in this case as the path being handled is the web root. If the images were to be served from the path http://example.go/images/image.png then the line above would need to be http.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("path/to/file")))).
Playground