// This function alters the original request - adds/removes headers, removes hop headers, // changes the request path. func rewriteRequest(req *http.Request, cmd *command.Forward, upstream *command.Upstream) *http.Request { outReq := new(http.Request) *outReq = *req // includes shallow copies of maps, but we handle this below outReq.URL.Scheme = upstream.Scheme outReq.URL.Host = fmt.Sprintf("%s:%d", upstream.Host, upstream.Port) if len(cmd.RewritePath) != 0 { outReq.URL.Path = cmd.RewritePath } outReq.URL.RawQuery = req.URL.RawQuery outReq.Proto = "HTTP/1.1" outReq.ProtoMajor = 1 outReq.ProtoMinor = 1 outReq.Close = false glog.Infof("Proxying request to: %v", outReq) outReq.Header = make(http.Header) netutils.CopyHeaders(outReq.Header, req.Header) if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // TODO(pquerna): configure this? Not all backends properly parse the header.. if TRUST_FORWARD_HEADER { if prior, ok := outReq.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } } outReq.Header.Set("X-Forwarded-For", clientIP) } if req.TLS != nil { outReq.Header.Set("X-Forwarded-Proto", "https") } else { outReq.Header.Set("X-Forwarded-Proto", "http") } if req.Host != "" { outReq.Header.Set("X-Forwarded-Host", req.Host) } outReq.Header.Set("X-Forwarded-Server", vulcanHostname) if len(cmd.RemoveHeaders) != 0 { netutils.RemoveHeaders(cmd.RemoveHeaders, outReq.Header) } // Add generic instructions headers to the request if len(cmd.AddHeaders) != 0 { glog.Info("Proxying instructions headers:", cmd.AddHeaders) netutils.CopyHeaders(outReq.Header, cmd.AddHeaders) } // Remove hop-by-hop headers to the backend. Especially // important is "Connection" because we want a persistent // connection, regardless of what the client sent to us. netutils.RemoveHeaders(hopHeaders, outReq.Header) return outReq }
func MakeRequest(url string, opts Opts) (*http.Response, []byte, error) { method := "GET" if opts.Method != "" { opts.Method = opts.Method } request, _ := http.NewRequest(method, url, strings.NewReader(opts.Body)) if opts.Headers != nil { netutils.CopyHeaders(request.Header, opts.Headers) } if len(opts.Host) != 0 { request.Host = opts.Host } var tr *http.Transport if strings.HasPrefix(url, "https") { tr = &http.Transport{ DisableKeepAlives: true, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } } else { tr = &http.Transport{ DisableKeepAlives: true, } } client := &http.Client{Transport: tr} response, err := client.Do(request) if err == nil { bodyBytes, err := ioutil.ReadAll(response.Body) return response, bodyBytes, err } return response, nil, err }
// Round trips the request to the selected location and writes back the response func (p *Proxy) proxyRequest(w http.ResponseWriter, r *http.Request) error { // Create a unique request with sequential ids that will be passed to all interfaces. req := request.NewBaseRequest(r, atomic.AddInt64(&p.lastRequestId, 1), nil) location, err := p.router.Route(req) if err != nil { return err } // Router could not find a matching location, we can do nothing else. if location == nil { log.Errorf("%s failed to route", req) return errors.FromStatus(http.StatusBadGateway) } response, err := location.RoundTrip(req) if response != nil { netutils.CopyHeaders(w.Header(), response.Header) w.WriteHeader(response.StatusCode) io.Copy(w, response.Body) response.Body.Close() return nil } else { return err } }
func (l *HttpLocation) copyRequest(req *http.Request, body netutils.MultiReader, endpoint endpoint.Endpoint) *http.Request { outReq := new(http.Request) *outReq = *req // includes shallow copies of maps, but we handle this below // Set the body to the enhanced body that can be re-read multiple times and buffered to disk outReq.Body = body endpointURL := endpoint.GetUrl() outReq.URL.Scheme = endpointURL.Scheme outReq.URL.Host = endpointURL.Host outReq.URL.Opaque = req.RequestURI // raw query is already included in RequestURI, so ignore it to avoid dupes outReq.URL.RawQuery = "" outReq.Proto = "HTTP/1.1" outReq.ProtoMajor = 1 outReq.ProtoMinor = 1 // Overwrite close flag so we can keep persistent connection for the backend servers outReq.Close = false outReq.Header = make(http.Header) netutils.CopyHeaders(outReq.Header, req.Header) return outReq }
func (w *WebhookSideEffect) Exec() error { r, err := http.NewRequest(w.w.Method, w.w.URL, w.getBody()) if err != nil { return err } if len(w.w.Headers) != 0 { netutils.CopyHeaders(r.Header, w.w.Headers) } if len(w.w.Form) != 0 { r.Header.Set("Content-Type", "application/x-www-form-urlencoded") } re, err := http.DefaultClient.Do(r) if err != nil { return err } if re.Body != nil { defer re.Body.Close() } body, err := ioutil.ReadAll(re.Body) if err != nil { return err } log.Infof("%v got response: (%s): %s", w, re.Status, string(body)) return nil }
func (tb *Recorder) ObserveRequest(req Request) { tb.mutex.Lock() defer tb.mutex.Unlock() if len(tb.Header) != 0 { netutils.CopyHeaders(req.GetHttpRequest().Header, tb.Header) } tb.ProcessedRequests = append(tb.ProcessedRequests, req) }
func (tb *Recorder) ProcessRequest(req Request) (*http.Response, error) { tb.mutex.Lock() defer tb.mutex.Unlock() if len(tb.Header) != 0 { netutils.CopyHeaders(req.GetHttpRequest().Header, tb.Header) } tb.ProcessedRequests = append(tb.ProcessedRequests, req) return tb.Response, tb.Error }
// replyError is a helper function that takes error and replies with HTTP compatible error to the client. func (p *Proxy) replyError(err error, w http.ResponseWriter, req *http.Request) { proxyError := convertError(err) statusCode, body, contentType := p.options.ErrorFormatter.Format(proxyError) w.Header().Set("Content-Type", contentType) if proxyError.Headers() != nil { netutils.CopyHeaders(w.Header(), proxyError.Headers()) } w.WriteHeader(statusCode) w.Write(body) }
func (tb *Recorder) ObserveResponse(req Request, a Attempt) { tb.mutex.Lock() defer tb.mutex.Unlock() if len(tb.Header) != 0 { netutils.CopyHeaders(req.GetHttpRequest().Header, tb.Header) } tb.ProcessedResponses = append(tb.ProcessedResponses, struct { R Request A Attempt }{R: req, A: a}) }
func Post(c *gocheck.C, requestUrl string, header http.Header, body url.Values) (*http.Response, []byte) { request, _ := http.NewRequest("POST", requestUrl, strings.NewReader(body.Encode())) netutils.CopyHeaders(request.Header, header) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.Close = true response, err := http.DefaultClient.Do(request) if err != nil { c.Fatalf("Post: %v", err) } bodyBytes, err := ioutil.ReadAll(response.Body) if err != nil { c.Fatalf("Post body failed: %v", err) } return response, bodyBytes }
func (l *HttpLocation) copyRequest(req *http.Request, endpoint Endpoint) *http.Request { outReq := new(http.Request) *outReq = *req // includes shallow copies of maps, but we handle this below outReq.URL.Scheme = endpoint.GetUrl().Scheme outReq.URL.Host = endpoint.GetUrl().Host outReq.URL.RawQuery = req.URL.RawQuery outReq.Proto = "HTTP/1.1" outReq.ProtoMajor = 1 outReq.ProtoMinor = 1 // Overwrite close flag so we can keep persistent connection for the backend servers outReq.Close = false outReq.Header = make(http.Header) netutils.CopyHeaders(outReq.Header, req.Header) return outReq }
func Get(c *gocheck.C, requestUrl string, header http.Header, body string) (*http.Response, []byte) { request, _ := http.NewRequest("GET", requestUrl, strings.NewReader(body)) netutils.CopyHeaders(request.Header, header) request.Close = true // the HTTP lib treats Host as a special header. it only respects the value on req.Host, and ignores // values in req.Headers if header.Get("Host") != "" { request.Host = header.Get("Host") } response, err := http.DefaultClient.Do(request) if err != nil { c.Fatalf("Get: %v", err) } bodyBytes, err := ioutil.ReadAll(response.Body) if err != nil { c.Fatalf("Get body failed: %v", err) } return response, bodyBytes }
// Round trips the request to the selected location and writes back the response func (p *Proxy) proxyRequest(w http.ResponseWriter, req *request.BaseRequest) error { location, err := p.router.Route(req) if err != nil { return err } // Router could not find a matching location, we can do nothing more if location == nil { log.Errorf("%s failed to route", req) return errors.FromStatus(http.StatusBadGateway) } response, err := location.RoundTrip(req) if response != nil { netutils.CopyHeaders(w.Header(), response.Header) w.WriteHeader(response.StatusCode) io.Copy(w, response.Body) defer response.Body.Close() return nil } else { return err } }
// This function alters the original request - adds/removes headers, removes hop headers, changes the request path. func (l *HttpLocation) rewriteRequest(req *http.Request, endpoint Endpoint) *http.Request { outReq := new(http.Request) *outReq = *req // includes shallow copies of maps, but we handle this below outReq.URL.Scheme = endpoint.GetUrl().Scheme outReq.URL.Host = endpoint.GetUrl().Host outReq.URL.RawQuery = req.URL.RawQuery outReq.Proto = "HTTP/1.1" outReq.ProtoMajor = 1 outReq.ProtoMinor = 1 outReq.Close = false outReq.Header = make(http.Header) netutils.CopyHeaders(outReq.Header, req.Header) if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { if l.options.TrustForwardHeader { if prior, ok := outReq.Header[headers.XForwardedFor]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } } outReq.Header.Set(headers.XForwardedFor, clientIP) } if req.TLS != nil { outReq.Header.Set(headers.XForwardedProto, "https") } else { outReq.Header.Set(headers.XForwardedProto, "http") } if req.Host != "" { outReq.Header.Set(headers.XForwardedHost, req.Host) } outReq.Header.Set(headers.XForwardedServer, l.options.Hostname) // Remove hop-by-hop headers to the backend. Especially important is "Connection" because we want a persistent // connection, regardless of what the client sent to us. netutils.RemoveHeaders(headers.HopHeaders, outReq.Header) return outReq }
// Proxy the request to the given upstream, in case if upstream is down // or failover code sequence has been recorded as the reply, return the error. // Failover sequence - is a special response code from the upstream that indicates // that upstream is shutting down and is not willing to accept new requests. func (p *ReverseProxy) proxyToUpstream( w http.ResponseWriter, req *http.Request, cmd *command.Forward, upstream *command.Upstream) error { // Rewrites the request: adds headers, changes urls etc. outReq := rewriteRequest(req, cmd, upstream) // Forward the reuest and mirror the response upstream.Metrics.Requests.Mark(1) startts := time.Now() res, err := p.httpTransport.RoundTrip(outReq) if err != nil { return err } defer res.Body.Close() upstream.Metrics.Latency.Update(time.Since(startts)) // In some cases upstreams may return special error codes that indicate that instead // of proxying the response of the upstream to the client we should initiate a failover if cmd.Failover != nil && len(cmd.Failover.Codes) != 0 { for _, code := range cmd.Failover.Codes { if res.StatusCode == code { upstream.Metrics.Failovers.Mark(1) glog.Errorf("Upstream %s initiated failover with status code %d", upstream, code) return fmt.Errorf("Upstream %s initiated failover with status code %d", upstream, code) } } } netutils.CopyHeaders(w.Header(), res.Header) w.WriteHeader(res.StatusCode) upstream.Metrics.Http.MarkResponseCode(res.StatusCode) io.Copy(w, res.Body) return nil }