From 16997d85eb6403c6cf39f7f92af936da532b1bc4 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 30 Jan 2015 11:09:36 -0700 Subject: [PATCH] Made 'extensionless' middleware more modular/useful --- config/parsing.go | 2 +- middleware/extensionless/extensionless.go | 98 +++++++++++++++-------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/config/parsing.go b/config/parsing.go index 3c09b9e75..4e8b99f6b 100644 --- a/config/parsing.go +++ b/config/parsing.go @@ -24,7 +24,7 @@ func (p *parser) begin() error { // combination. func (p *parser) address() error { if p.tkn() == "}" || p.tkn() == "{" { - return p.err("Syntax", "'"+p.tkn()+"' is not a listening address or EOF") + return p.err("Syntax", "'"+p.tkn()+"' is not EOF or address") } p.cfg.Host, p.cfg.Port = parseAddress(p.tkn()) return nil diff --git a/middleware/extensionless/extensionless.go b/middleware/extensionless/extensionless.go index eb5a7c9ec..ef629d9d2 100644 --- a/middleware/extensionless/extensionless.go +++ b/middleware/extensionless/extensionless.go @@ -15,47 +15,81 @@ import ( // New creates a new instance of middleware that assumes extensions // so the site can use cleaner, extensionless URLs func New(c middleware.Controller) (middleware.Middleware, error) { + root := c.Root() + + extensions, err := parse(c) + if err != nil { + return nil, err + } + + return func(next http.HandlerFunc) http.HandlerFunc { + return Extensionless{ + Next: next, + Extensions: extensions, + Root: root, + }.ServeHTTP + }, nil +} + +// Extensionless is an http.Handler that can assume an extension from clean URLs. +// It tries extensions in the order listed in Extensions. +type Extensionless struct { + Next http.HandlerFunc + Extensions []string + Root string +} + +// ServeHTTP implements the http.Handler interface. +func (e Extensionless) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !hasExt(r) { + for _, ext := range e.Extensions { + if resourceExists(e.Root, r.URL.Path+ext) { + r.URL.Path = r.URL.Path + ext + break + } + } + } + e.Next(w, r) +} + +// parse sets up an instance of Extensionless middleware +// from a middleware controller and returns a list of extensions. +func parse(c middleware.Controller) ([]string, error) { var extensions []string - var root = c.Root() // TODO: Big gotcha! Save this now before it goes away! We can't get this later during a request! for c.Next() { + // At least one extension is required if !c.NextArg() { - return nil, c.ArgErr() + return extensions, c.ArgErr() } extensions = append(extensions, c.Val()) + + // Tack on any other extensions that may have been listed for c.NextArg() { extensions = append(extensions, c.Val()) } } - resourceExists := func(path string) bool { - _, err := os.Stat(root + path) - // technically we should use os.IsNotExist(err) - // but we don't handle any other kinds of errors anyway - return err == nil - } - - hasExt := func(r *http.Request) bool { - if r.URL.Path[len(r.URL.Path)-1] == '/' { - // directory - return true - } - lastSep := strings.LastIndex(r.URL.Path, "/") - lastDot := strings.LastIndex(r.URL.Path, ".") - return lastDot > lastSep - } - - return func(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if !hasExt(r) { - for _, ext := range extensions { - if resourceExists(r.URL.Path + ext) { - r.URL.Path = r.URL.Path + ext - break - } - } - } - next(w, r) - } - }, nil + return extensions, nil +} + +// resourceExists returns true if the file specified at +// root + path exists; false otherwise. +func resourceExists(root, path string) bool { + _, err := os.Stat(root + path) + // technically we should use os.IsNotExist(err) + // but we don't handle any other kinds of errors anyway + return err == nil +} + +// hasExt returns true if the HTTP request r has an extension, +// false otherwise. +func hasExt(r *http.Request) bool { + if r.URL.Path[len(r.URL.Path)-1] == '/' { + // directory + return true + } + lastSep := strings.LastIndex(r.URL.Path, "/") + lastDot := strings.LastIndex(r.URL.Path, ".") + return lastDot > lastSep }