Example #1
0
// 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
}
Example #2
0
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]))
		}
	}
}
Example #3
0
// 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
}