// To attempts rewrite. It attempts to rewrite to first valid path // or the last path if none of the paths are valid. func To(fs http.FileSystem, r *http.Request, to string, replacer httpserver.Replacer) Result { tos := strings.Fields(to) // try each rewrite paths t := "" query := "" for _, v := range tos { t = replacer.Replace(v) tparts := strings.SplitN(t, "?", 2) t = path.Clean(tparts[0]) if len(tparts) > 1 { query = tparts[1] } // add trailing slash for directories, if present if strings.HasSuffix(tparts[0], "/") && !strings.HasSuffix(t, "/") { t += "/" } // validate file if validFile(fs, t) { break } } // validate resulting path u, err := url.Parse(t) if err != nil { // Let the user know we got here. Rewrite is expected but // the resulting url is invalid. log.Printf("[ERROR] rewrite: resulting path '%v' is invalid. error: %v", t, err) return RewriteIgnored } // 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()) // perform rewrite r.URL.Path = u.Path if query != "" { // overwrite query string if present r.URL.RawQuery = query } if u.Fragment != "" { // overwrite fragment if present r.URL.Fragment = u.Fragment } return RewriteDone }
func mutateHeadersByRules(headers, rules http.Header, repl httpserver.Replacer) { for ruleField, ruleValues := range rules { if strings.HasPrefix(ruleField, "+") { for _, ruleValue := range ruleValues { headers.Add(strings.TrimPrefix(ruleField, "+"), repl.Replace(ruleValue)) } } else if strings.HasPrefix(ruleField, "-") { headers.Del(strings.TrimPrefix(ruleField, "-")) } else if len(ruleValues) > 0 { headers.Set(ruleField, repl.Replace(ruleValues[len(ruleValues)-1])) } } }
// ServeHTTP satisfies the httpserver.Handler interface. func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // start by selecting most specific matching upstream config upstream := p.match(r) if upstream == nil { return p.Next.ServeHTTP(w, r) } // this replacer is used to fill in header field values var replacer httpserver.Replacer // outreq is the request that makes a roundtrip to the backend outreq := createUpstreamRequest(r) // since Select() should give us "up" hosts, keep retrying // hosts until timeout (or until we get a nil host). start := time.Now() for time.Now().Sub(start) < tryDuration { host := upstream.Select() if host == nil { return http.StatusBadGateway, errUnreachable } if rr, ok := w.(*httpserver.ResponseRecorder); ok && rr.Replacer != nil { rr.Replacer.Set("upstream", host.Name) } proxy := host.ReverseProxy // a backend's name may contain more than just the host, // so we parse it as a URL to try to isolate the host. if nameURL, err := url.Parse(host.Name); err == nil { outreq.Host = nameURL.Host if proxy == nil { proxy = NewSingleHostReverseProxy(nameURL, host.WithoutPathPrefix) } } else { outreq.Host = host.Name } if proxy == nil { return http.StatusInternalServerError, errors.New("proxy for host '" + host.Name + "' is nil") } // set headers for request going upstream if host.UpstreamHeaders != nil { if replacer == nil { replacer = httpserver.NewReplacer(r, nil, "") } 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 } } // prepare a function that will update response // headers coming back downstream var downHeaderUpdateFn respUpdateFn if host.DownstreamHeaders != nil { if replacer == nil { replacer = httpserver.NewReplacer(r, nil, "") } downHeaderUpdateFn = createRespHeaderUpdateFn(host.DownstreamHeaders, replacer) } // tell the proxy to serve the request atomic.AddInt64(&host.Conns, 1) backendErr := proxy.ServeHTTP(w, outreq, downHeaderUpdateFn) atomic.AddInt64(&host.Conns, -1) // if no errors, we're done here; otherwise failover 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 }