65

I am attempting to use the Gorilla toolkit's mux package to route URLs in a Go web server. Using this question as a guide I have the following Go code:

func main() {
    r := mux.NewRouter()
    r.Handle("/", http.FileServer(http.Dir("./static/")))
    r.HandleFunc("/search/{searchTerm}", Search)
    r.HandleFunc("/load/{dataId}", Load)
    http.Handle("/", r)
    http.ListenAndServe(":8100", nil)
}

The directory structure is:

...
main.go
static\
  | index.html
  | js\
     | <js files>
  | css\
     | <css files>

The Javascript and CSS files are referenced in index.html like this:

...
<link rel="stylesheet" href="css/redmond/jquery-ui.min.css"/>
<script src="js/jquery.min.js"></script>
...

When I access http://localhost:8100 in my web browser the index.html content is delivered successfully, however, all the js and css URLs return 404s.

How can I get the program to serve files out of static sub-directories?

Community
  • 1
  • 1
jason
  • 1,157
  • 1
  • 7
  • 23
  • You might want to see this discussion (not using Gorilla though) about serving static files from root or subdirectories http://stackoverflow.com/questions/14086063/serve-homepage-and-static-content-from-root/14187941#14187941 – Deleplace Apr 05 '13 at 15:27
  • @Ripounet, I did see that question during my research, however, since it was not using Gorilla I was never able to get the ideas to work with my setup where one of my goals was to not have any static files in the root directory of my project (next to `main.go`). Also, it seems very similar to [@Joe's answer](http://stackoverflow.com/a/15835001/971556) below, which also will not work with my setup. – jason Apr 05 '13 at 17:12

5 Answers5

91

I think you might be looking for PathPrefix...

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/search/{searchTerm}", Search)
    r.HandleFunc("/load/{dataId}", Load)
    r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
    http.ListenAndServe(":8100", r)
}
AhmetB - Google
  • 35,086
  • 32
  • 117
  • 191
Chris Farmiloe
  • 13,103
  • 4
  • 45
  • 57
  • This was helpful, thank you. I did have a problem trying to get two aliases working though. eg `r.PathPrefix("/a/").Handler(http.FileServer(http.Dir("b/"))) r.PathPrefix("/").Handler(http.FileServer(http.Dir("c/")))` In that case, everything in `c/` is served, but not `b/`. Tried a few different subtle variations but to no success. Any ideas? – markdsievers Apr 10 '14 at 20:33
  • 4
    @markdsievers, you might need to strip "/a/" part from the URL. Example: `r.PathPrefix("/a/").Handler(http.StripPrefix("/a/", http.FileServer(http.Dir("b"))))`. – Roman Nov 15 '14 at 09:38
  • 1
    Is it possible to add a NotFound handler? – Cloom Magoo Jun 20 '15 at 16:01
  • 1
    the code seems to be similar to my current project code, just a note: the static handler need to be put as last routes, otherwise the `other` routes's GET will also be covered by that static handler. – Hoang Tran Jun 21 '18 at 13:03
  • This fixed it for me! As @HoangTran mentioned you'll want to set this as the last route. Basically like a "catch-all" if all else fails. – Christian Gossain Feb 23 '21 at 20:47
46

After a lot of trial and error, both above answers helped me in coming up with what worked for me. I have static folder in web app's root directory.

Along with PathPrefix I had to use StripPrefix for getting route to work recursively.

package main

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

func main() {
    r := mux.NewRouter()
    s := http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))
    r.PathPrefix("/static/").Handler(s)
    http.Handle("/", r)
    err := http.ListenAndServe(":8081", nil)
}

I hope it helps somebody else having problems.

codefreak
  • 6,322
  • 3
  • 35
  • 49
  • For anyone using the [golang workspace](https://golang.org/doc/code.html) `s := ...` should read as follows when your working directory is `[workspace]/src` ... `s := http.StripPrefix("/static/", httpFileServer(http.Dir("./web/static/")))` – Frito Sep 13 '17 at 01:00
  • I think, you could also gZip static content there by using some "github.com/lpar/gzipped" or similar library. `gzipped.FileServer(http.Dir("./static/"))` – Dzintars Aug 11 '19 at 17:11
  • it's not working for me, i am getting 404 page not found – deeptimancode Mar 12 '21 at 20:47
10

I have this code here that works quite nice and is re-usable.

func ServeStatic(router *mux.Router, staticDirectory string) {
    staticPaths := map[string]string{
        "styles":           staticDirectory + "/styles/",
        "bower_components": staticDirectory + "/bower_components/",
        "images":           staticDirectory + "/images/",
        "scripts":          staticDirectory + "/scripts/",
    }
    for pathName, pathValue := range staticPaths {
        pathPrefix := "/" + pathName + "/"
        router.PathPrefix(pathPrefix).Handler(http.StripPrefix(pathPrefix,
            http.FileServer(http.Dir(pathValue))))
    }
}
router := mux.NewRouter()
ServeStatic(router, "/static/")
Thomas Modeneis
  • 517
  • 6
  • 10
4

Try this:

fileHandler := http.StripPrefix("/static/", http.FileServer(http.Dir("/absolute/path/static")))
http.Handle("/static/", fileHandler)
Joe
  • 5,834
  • 1
  • 15
  • 14
  • This would mean changing all the `src`, `href` etc attributes to the form `"/static/js/jquery.min.js"`. Although technically *would* work. – Chris Farmiloe Apr 05 '13 at 13:18
  • Which will allow the JS and CSS files to be loaded, but the `index.html` file would no longer be available at `http://localhost:8100/` – jason Apr 05 '13 at 13:30
  • I usually put all `images`,`css`,`js` etc in `static` folder. – Joe Apr 06 '13 at 08:46
  • The content of the homepage is usually generated dynamically. – Joe Apr 06 '13 at 08:53
  • Not if the homepage pulls all dynamic content via JavaScript, then the index.html itself is often completely static – Niklas Schnelle Oct 07 '15 at 19:08
-1

This serve all files inside the folder flag, as well as serving index.html at the root.

Usage

   //port default values is 8500
   //folder defaults to the current directory
   go run main.go 

   //your case, dont forget the last slash
   go run main.go -folder static/

   //dont
   go run main.go -folder ./

Code

    package main

import (
    "flag"
    "fmt"
    "net/http"
    "os"
    "strconv"
    "strings"

    "github.com/gorilla/handlers"
    "github.com/gorilla/mux"
    "github.com/kr/fs"
)

func main() {
    mux := mux.NewRouter()

    var port int
    var folder string
    flag.IntVar(&port, "port", 8500, "help message for port")
    flag.StringVar(&folder, "folder", "", "help message for folder")

    flag.Parse()

    walker := fs.Walk("./" + folder)
    for walker.Step() {
        var www string

        if err := walker.Err(); err != nil {
            fmt.Fprintln(os.Stderr, "eroooooo")
            continue
        }
        www = walker.Path()
        if info, err := os.Stat(www); err == nil && !info.IsDir() {
            mux.HandleFunc("/"+strings.Replace(www, folder, "", -1), func(w http.ResponseWriter, r *http.Request) {
                http.ServeFile(w, r, www)
            })
        }
    }
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, folder+"index.html")
    })
    http.ListenAndServe(":"+strconv.Itoa(port), handlers.LoggingHandler(os.Stdout, mux))
}
CESCO
  • 5,396
  • 4
  • 41
  • 73