func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { // Record the response responseRecorder := middleware.NewResponseRecorder(w) // Attach the Replacer we'll use so that other middlewares can // set their own placeholders if they want to. rep := middleware.NewReplacer(r, responseRecorder, CommonLogEmptyValue) responseRecorder.Replacer = rep // Bon voyage, request! status, err := l.Next.ServeHTTP(responseRecorder, r) if status >= 400 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, status) } else { // Default failover error handler responseRecorder.WriteHeader(status) fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status)) } status = 0 } // Write log entry rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { responseRecorder := middleware.NewResponseRecorder(w) status, err := l.Next.ServeHTTP(responseRecorder, r) rep := middleware.NewReplacer(r, responseRecorder) rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (s SimpleRule) Rewrite(r *http.Request) bool { if s.From == r.URL.Path { // take note of this rewrite for internal use by fastcgi // all we need is the URI, not full URL r.Header.Set(headerFieldName, r.URL.RequestURI()) // replace variables to := path.Clean(middleware.NewReplacer(r, nil, "").Replace(s.To)) r.URL.Path = to return true } return false }
// ServeHTTP implements the middleware.Handler interface. func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range rd.Rules { if (rule.FromPath == "/" || r.URL.Path == rule.FromPath) && schemeMatches(rule, r) { to := middleware.NewReplacer(r, nil, "").Replace(rule.To) if rule.Meta { safeTo := html.EscapeString(to) fmt.Fprintf(w, metaRedir, safeTo, safeTo) } else { http.Redirect(w, r, to, rule.Code) } return 0, nil } } return rd.Next.ServeHTTP(w, r) }
// ServeHTTP implements the middleware.Handler interface and serves requests, // setting headers on the response according to the configured rules. func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { replacer := middleware.NewReplacer(r, nil, "") for _, rule := range h.Rules { if middleware.Path(r.URL.Path).Matches(rule.Path) { for _, header := range rule.Headers { if strings.HasPrefix(header.Name, "-") { w.Header().Del(strings.TrimLeft(header.Name, "-")) } else { w.Header().Set(header.Name, replacer.Replace(header.Value)) } } } } return h.Next.ServeHTTP(w, r) }
// Rewrite rewrites the internal location of the current request. func (r *RegexpRule) Rewrite(req *http.Request) bool { rPath := req.URL.Path // validate base if !middleware.Path(rPath).Matches(r.Base) { return false } // validate extensions if !r.matchExt(rPath) { return false } // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start -= 1 } // validate regexp if !r.MatchString(rPath[start:]) { return false } // replace variables to := path.Clean(middleware.NewReplacer(req, nil, "").Replace(r.To)) // validate resulting path url, err := url.Parse(to) if err != nil { return false } // take note of this rewrite for internal use by fastcgi // all we need is the URI, not full URL req.Header.Set("Caddy-Rewrite-Original-URI", req.URL.RequestURI()) // perform rewrite req.URL.Path = url.Path if url.RawQuery != "" { // overwrite query string if present req.URL.RawQuery = url.RawQuery } return true }
// Rewrite rewrites the internal location of the current request. func (r *RegexpRule) Rewrite(req *http.Request) bool { rPath := req.URL.Path // validate base if !middleware.Path(rPath).Matches(r.Base) { return false } // validate extensions if !r.matchExt(rPath) { return false } // include trailing slash in regexp if present start := len(r.Base) if strings.HasSuffix(r.Base, "/") { start -= 1 } // validate regexp if !r.MatchString(rPath[start:]) { return false } // replace variables to := path.Clean(middleware.NewReplacer(req, nil, "").Replace(r.To)) // validate resulting path url, err := url.Parse(to) if err != nil { return false } // perform rewrite req.URL.Path = url.Path if url.RawQuery != "" { // overwrite query string if present req.URL.RawQuery = url.RawQuery } return true }
func TestReverseProxy(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stderr) var requestReceived bool backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestReceived = true w.Write([]byte("Hello, client")) })) defer backend.Close() // set up proxy p := &Proxy{ Upstreams: []Upstream{newFakeUpstream(backend.URL, false)}, } // create request and response recorder r, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } w := httptest.NewRecorder() p.ServeHTTP(w, r) if !requestReceived { t.Error("Expected backend to receive request, but it didn't") } // Make sure {upstream} placeholder is set rr := middleware.NewResponseRecorder(httptest.NewRecorder()) rr.Replacer = middleware.NewReplacer(r, rr, "-") p.ServeHTTP(rr, r) if got, want := rr.Replacer.Replace("{upstream}"), backend.URL; got != want { t.Errorf("Expected custom placeholder {upstream} to be set (%s), but it wasn't; got: %s", want, got) } }
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range l.Rules { if middleware.Path(r.URL.Path).Matches(rule.PathScope) { responseRecorder := middleware.NewResponseRecorder(w) status, err := l.Next.ServeHTTP(responseRecorder, r) if status >= 400 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. if l.ErrorFunc != nil { l.ErrorFunc(responseRecorder, r, status) } else { // Default failover error handler responseRecorder.WriteHeader(status) fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status)) } status = 0 } rep := middleware.NewReplacer(r, responseRecorder) rule.Log.Println(rep.Replace(rule.Format)) return status, err } } return l.Next.ServeHTTP(w, r) }
// ServeHTTP satisfies the middleware.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, upstream := range p.Upstreams { if middleware.Path(r.URL.Path).Matches(upstream.From()) && upstream.IsAllowedPath(r.URL.Path) { var replacer middleware.Replacer start := time.Now() requestHost := r.Host // Since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return http.StatusBadGateway, errUnreachable } proxy := host.ReverseProxy r.Host = host.Name if baseURL, err := url.Parse(host.Name); err == nil { r.Host = baseURL.Host if proxy == nil { proxy = NewSingleHostReverseProxy(baseURL, host.WithoutPathPrefix) } } else if proxy == nil { return http.StatusInternalServerError, err } var extraHeaders http.Header if host.ExtraHeaders != nil { extraHeaders = make(http.Header) if replacer == nil { rHost := r.Host r.Host = requestHost replacer = middleware.NewReplacer(r, nil, "") r.Host = rHost } for header, values := range host.ExtraHeaders { for _, value := range values { extraHeaders.Add(header, replacer.Replace(value)) if header == "Host" { r.Host = replacer.Replace(value) } } } } atomic.AddInt64(&host.Conns, 1) backendErr := proxy.ServeHTTP(w, r, extraHeaders) atomic.AddInt64(&host.Conns, -1) if backendErr == nil { return 0, nil } timeout := host.FailTimeout if timeout == 0 { timeout = 10 * time.Second } atomic.AddInt32(&host.Fails, 1) go func(host *UpstreamHost, timeout time.Duration) { time.Sleep(timeout) atomic.AddInt32(&host.Fails, -1) }(host, timeout) } return http.StatusBadGateway, errUnreachable } } return p.Next.ServeHTTP(w, r) }
func newReplacer(r *http.Request) middleware.Replacer { return middleware.NewReplacer(r, nil, "") }
func TestDownstreamHeadersUpdate(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stderr) backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Merge-Me", "Initial") w.Header().Add("Remove-Me", "Remove-Value") w.Header().Add("Replace-Me", "Replace-Value") w.Write([]byte("Hello, client")) })) defer backend.Close() upstream := newFakeUpstream(backend.URL, false) upstream.host.DownstreamHeaders = http.Header{ "+Merge-Me": {"Merge-Value"}, "+Add-Me": {"Add-Value"}, "-Remove-Me": {""}, "Replace-Me": {"{hostname}"}, } // set up proxy p := &Proxy{ Upstreams: []Upstream{upstream}, } // create request and response recorder r, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } w := httptest.NewRecorder() p.ServeHTTP(w, r) replacer := middleware.NewReplacer(r, nil, "") actualHeaders := w.Header() headerKey := "Merge-Me" values, ok := actualHeaders[headerKey] if !ok { t.Errorf("Downstream response does not contain expected %v header. Expected header should be added", headerKey) } else if len(values) < 2 && (values[0] != "Initial" || values[1] != replacer.Replace("{hostname}")) { t.Errorf("Values for header `+Merge-Me` should be merged. Got %v", values) } headerKey = "Add-Me" if _, ok := actualHeaders[headerKey]; !ok { t.Errorf("Downstream response does not contain expected %v header", headerKey) } headerKey = "Remove-Me" if _, ok := actualHeaders[headerKey]; ok { t.Errorf("Downstream response should not contain %v header received from upstream", headerKey) } headerKey = "Replace-Me" headerValue := replacer.Replace("{hostname}") value, ok := actualHeaders[headerKey] if !ok { t.Errorf("Downstream response should contain %v header and not remove it", headerKey) } else if len(value) > 0 && headerValue != value[0] { t.Errorf("Downstream response should have header %v with value %v. Instead value was %v", headerKey, headerValue, value) } }
// ServeHTTP satisfies the middleware.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, upstream := range p.Upstreams { if !middleware.Path(r.URL.Path).Matches(upstream.From()) || !upstream.AllowedPath(r.URL.Path) { continue } var replacer middleware.Replacer start := time.Now() outreq := createUpstreamRequest(r) // Since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return http.StatusBadGateway, errUnreachable } if rr, ok := w.(*middleware.ResponseRecorder); ok && rr.Replacer != nil { rr.Replacer.Set("upstream", host.Name) } outreq.Host = host.Name if host.UpstreamHeaders != nil { if replacer == nil { rHost := r.Host replacer = middleware.NewReplacer(r, nil, "") outreq.Host = rHost } if v, ok := host.UpstreamHeaders["Host"]; ok { outreq.Host = replacer.Replace(v[len(v)-1]) } // Modify headers for request that will be sent to the upstream host upHeaders := createHeadersByRules(host.UpstreamHeaders, r.Header, replacer) for k, v := range upHeaders { outreq.Header[k] = v } } var downHeaderUpdateFn respUpdateFn if host.DownstreamHeaders != nil { if replacer == nil { rHost := r.Host replacer = middleware.NewReplacer(r, nil, "") outreq.Host = rHost } //Creates a function that is used to update headers the response received by the reverse proxy downHeaderUpdateFn = createRespHeaderUpdateFn(host.DownstreamHeaders, replacer) } proxy := host.ReverseProxy if baseURL, err := url.Parse(host.Name); err == nil { r.Host = baseURL.Host if proxy == nil { proxy = NewSingleHostReverseProxy(baseURL, host.WithoutPathPrefix) } } else if proxy == nil { return http.StatusInternalServerError, err } atomic.AddInt64(&host.Conns, 1) backendErr := proxy.ServeHTTP(w, outreq, downHeaderUpdateFn) atomic.AddInt64(&host.Conns, -1) if backendErr == nil { return 0, nil } timeout := host.FailTimeout if timeout == 0 { timeout = 10 * time.Second } atomic.AddInt32(&host.Fails, 1) go func(host *UpstreamHost, timeout time.Duration) { time.Sleep(timeout) atomic.AddInt32(&host.Fails, -1) }(host, timeout) } return http.StatusBadGateway, errUnreachable } return p.Next.ServeHTTP(w, r) }