github.com/orangenpresse/up@v0.6.0/http/redirects/redirects.go (about) 1 // Package redirects provides redirection and URL rewriting. 2 package redirects 3 4 import ( 5 "fmt" 6 "net/http" 7 8 "github.com/apex/log" 9 "github.com/apex/up" 10 "github.com/apex/up/internal/logs" 11 "github.com/apex/up/internal/redirect" 12 ) 13 14 // TODO: tests for popagating 4xx / 5xx, dont mask all these 15 // TODO: load _redirects relative to .Static.Dir? 16 // TODO: add list of methods to match on 17 18 // log context. 19 var ctx = logs.Plugin("redirects") 20 21 type rewrite struct { 22 http.ResponseWriter 23 header bool 24 isNotFound bool 25 } 26 27 // WriteHeader implementation. 28 func (r *rewrite) WriteHeader(code int) { 29 r.header = true 30 r.isNotFound = code == 404 31 32 if r.isNotFound { 33 return 34 } 35 36 r.ResponseWriter.WriteHeader(code) 37 } 38 39 // Write implementation. 40 func (r *rewrite) Write(b []byte) (int, error) { 41 if r.isNotFound { 42 return len(b), nil 43 } 44 45 if !r.header { 46 r.WriteHeader(200) 47 return r.Write(b) 48 } 49 50 return r.ResponseWriter.Write(b) 51 } 52 53 // New redirects handler. 54 func New(c *up.Config, next http.Handler) (http.Handler, error) { 55 if len(c.Redirects) == 0 { 56 return next, nil 57 } 58 59 rules, err := redirect.Compile(c.Redirects) 60 if err != nil { 61 return nil, err 62 } 63 64 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 65 rule := rules.Lookup(r.URL.Path) 66 67 ctx := ctx.WithFields(log.Fields{ 68 "path": r.URL.Path, 69 }) 70 71 // pass-through 72 if rule == nil { 73 ctx.Debug("no match") 74 next.ServeHTTP(w, r) 75 return 76 } 77 78 // destination path 79 path := rule.URL(r.URL.Path) 80 81 // forced rewrite 82 if rule.IsRewrite() && rule.Force { 83 ctx.WithField("dest", path).Info("forced rewrite") 84 r.Header.Set("X-Original-Path", r.URL.Path) 85 r.URL.Path = path 86 next.ServeHTTP(w, r) 87 return 88 } 89 90 // rewrite 91 if rule.IsRewrite() { 92 res := &rewrite{ResponseWriter: w} 93 next.ServeHTTP(res, r) 94 95 if res.isNotFound { 96 ctx.WithField("dest", path).Info("rewrite") 97 r.Header.Set("X-Original-Path", r.URL.Path) 98 r.URL.Path = path 99 // This hack is necessary for SPAs because the Go 100 // static file server uses .html to set the correct mime, 101 // ideally it uses the file's extension or magic number etc. 102 w.Header().Set("Content-Type", "text/html; charset=utf-8") 103 next.ServeHTTP(w, r) 104 } 105 return 106 } 107 108 // redirect 109 ctx.WithField("dest", path).Info("redirect") 110 w.Header().Set("Location", path) 111 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 112 w.WriteHeader(rule.Status) 113 fmt.Fprintln(w, http.StatusText(rule.Status)) 114 }) 115 116 return h, nil 117 }