404 error when serving files in paths not root

311 views Asked by At

I am running this command "go run webapp/main.go." The reason is that app engine will call my app from the root directory, so I changed the paths to work from calling the file from root. I also do not mind if you have Go best practices tips.

└── webapp
    ├── app.yaml
    ├── assets
    │   ├── css
    │   │   └── index.css
    │   └── img
    ├── main.go
    ├── main_test.go
    └── templates
        └── index.html

Confused on how something so trivial can be wrong. localhost:8080/css/index.css works fine. I also have another handler function to serve localhost:8080/static/css/index.css, but I get a 404 error. Everything was working smoothly when I was using the command "go run main.go" and remove "webapp" from the code. Still, how can it work with / and not /static/. As can be seen in this https://stackoverflow.com/a/47997908/6828897 answer, it should serve ./webapp/assets/static as a dir. I also have tried http.StripPrefix but no luck either.

package main

import (
    "flag"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "sync"
    "text/template"
)

type templateHandler struct {
    once     sync.Once
    filename string
    templ    *template.Template
}

// ServeHTTP handles the HTTP request.
func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.templ = template.Must(template.ParseFiles(filepath.Join("webapp", "templates", t.filename)))
    })
    if err := t.templ.Execute(w, r); err != nil {
        log.Printf("Error executing template: %v", err)
        http.Error(w, "Internal server error", http.StatusInternalServerError)
    }
}

func main() {
    dir, err := os.Getwd()
    if err != nil {
        log.Printf(err.Error())
    }
    log.Printf("dir: %s", dir)

    // command flags
    var addr = flag.String("addr", ":8080", "The addr of the application.")
    flag.Parse()

    // env variables
    envPort := os.Getenv("PORT")
    if envPort != "" {
        envPort = ":" + envPort
        addr = &envPort
    }

    fs := http.FileServer(http.Dir("./webapp/assets"))
    http.Handle("/static/", fs)

    log.Printf("Listening on port %s", *addr)

    // http.Handle("/", &templateHandler{filename: "index.html"})

    if err := http.ListenAndServe(*addr, fs); err != nil {
        log.Fatal(err)
    }
}
1

There are 1 answers

0
mkopriva On

"how can it work with / and not /static/"

Because you are passing fs directly to ListenAndServe, that means that the DefaultServeMux which is used by http.Handle("/static/", fs) is being ignored / overridden.

http.Handle:

Handle registers the handler for the given pattern in the DefaultServeMux. The documentation for ServeMux explains how patterns are matched.

http.ListenAndServe

ListenAndServe listens on the TCP network address addr and then calls Serve with handler to handle requests on incoming connections. Accepted connections are configured to enable TCP keep-alives.

The handler is typically nil, in which case the DefaultServeMux is used.

ListenAndServe always returns a non-nil error.

So in general what you should do is something like this:

fs := http.FileServer(http.Dir("./webapp/assets"))
// register fs for "/static/" in DefaultServeMux
http.Handle("/static/", fs)
// start listening at addr and serve incoming requests
// using DefaultServeMux as the router (because nil).
if err := http.ListenAndServe(*addr, nil); err != nil {
    log.Fatal(err)
}

If there are other issues in your setup it is possible that your app may not work as desired right away, however this change is certainly a requirement to get closer to that goal.