github.com/motyar/up@v0.2.10/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  }