Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to do static assets middleware? #26

Closed
sporto opened this issue Jun 1, 2014 · 9 comments
Closed

How to do static assets middleware? #26

sporto opened this issue Jun 1, 2014 · 9 comments

Comments

@sporto
Copy link

sporto commented Jun 1, 2014

Hi can you please give me an example on how to use with http.FileServer for serving static assets?

@zenazn
Copy link
Owner

zenazn commented Jun 1, 2014

I'd probably use it as an endpoint, not a middleware:

goji.Get("/assets/*", http.FileServer(http.Dir("/path/to/assets")))

@sporto
Copy link
Author

sporto commented Jun 2, 2014

Thanks

@sporto sporto closed this as completed Jun 2, 2014
@bernhardw
Copy link

How would you do it if files like index.html or favicon.ico should be available under /?

Maybe it makes sense - performance wise - to attach it through goji.NotFound(), in order to try the routes first and only then access the file system.

@elcct
Copy link

elcct commented Sep 3, 2014

I did it this way:

https://github.com/elcct/defaultproject/blob/master/server.go#L43-L44

But ideally, I would serve those files directly via nginx

@bernhardw
Copy link

Those lines are actually what reminded me to take these "root files" into account :) But I would prefer a way without specifying a separate route each.

What do you think of attaching a FileServer to goji.NotFound() if the file requested exists, and otherwise handle the 404?

@zenazn
Copy link
Owner

zenazn commented Sep 4, 2014

If there are only a few of them and they change infrequently, I'd recommend adding them by hand (or maybe writing yourself a short script that codegens them for you). Don't overengineer a solution to a problem you don't have.

If you can't do that or otherwise don't want to, I'd write a middleware that goes something like this (entirely untested!)

type tryFiles struct {
    dir   string
    inner http.Handler
}

func (t tryFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    clean := filepath.Clean(r.URL.Path)
    fname := filepath.Join(t.dir, clean)
    f, err := os.Open(fname)
    if err != nil {
        t.inner.ServeHTTP(w, r)
        return
    }
    defer f.Close()

    s, err := f.Stat()
    if err != nil {
        t.inner.ServeHTTP(w, r)
        return
    }

    http.ServeContent(w, r, fname, s.ModTime(), f)
}

func TryFiles(dir string) func(http.Handler) http.Handler {
    return func(h http.Handler) http.Handler {
        return tryFiles{dir, h}
    }
}

which can be used like this: goji.Use(TryFiles("path/to/public"))

Maybe I'll make a real repo out of this :)

@bernhardw
Copy link

True, but I prefer a static file serving middleware. Here's what I use:

type static struct {
    dir string
    h   http.Handler
}

func (s static) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fname := filepath.Join(s.dir, r.URL.Path)

    _, err := os.Stat(fname)
    if err != nil {
        s.h.ServeHTTP(w, r)
        return
    }

    server := http.FileServer(http.Dir(s.dir))
    server.ServeHTTP(w, r)
}

func Static(dir string) func(http.Handler) http.Handler {
    return func(h http.Handler) http.Handler {
        return static{dir, h}
    }
}

I'm using FileServer() because it already Clean()s, Open()s, serves /index.html under / and probably does some more.

It would be great to have an official one added to https://github.com/goji :)

@colthreepv
Copy link

👍 +1 to this Issue

The use case I'm interested is:

  • goji serve static files in development (using env variables), on /* and api on /api/v1/*
  • in production static file serving gets handled by nginx/apache/whatever

Would be useful to only register API endpoints, and put on a clever NotFound handler like @zenazn posted before.
If I succeed in creating a decent middleware I'll post it

@colthreepv
Copy link

I don't think I'm able to make a plugin out of it.. yet (my skills in go + goji are like... well.. forget it!).
But in the meantime I've solved implementing a reverse string pattern

It's very simple. Posting it here so if you have ideas, I may also benefit!

// ReverseStringPattern is a simple struct keeping the raw string
// and the prefix to skip in requests
type ReverseStringPattern struct {
    raw    string
    prefix string
}

// prefix returns "" so goji doesn't ever skip the matching function
func (s ReverseStringPattern) Prefix() string {
    return ""
}
func (s ReverseStringPattern) Match(r *http.Request, c *web.C) bool {
    path := r.URL.Path
    return !strings.HasPrefix(path, s.prefix)
}
func (s ReverseStringPattern) Run(r *http.Request, c *web.C) {}

func NewReverseStringPattern(s string) ReverseStringPattern {
    var prefix string = s
    if strings.HasSuffix(s, "*") {
        prefix = s[:len(s)-1]
    }
    return ReverseStringPattern{raw: s, prefix: prefix}
}

// serveStatic makes sure that if running for development
// purposes, it serves static content from /static
func serveStatic() {
    buildMode := os.Getenv("mode")
    switch buildMode {
    case "development", "":
        reverseApiPattern := NewReverseStringPattern("/api/*")
        goji.Handle(reverseApiPattern, http.FileServer(http.Dir("static")))
    case "production":
        return
    default:
        return
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants