// Round trips the request to one of the endpoints and returns the response. func (l *HttpLocation) RoundTrip(req request.Request) (*http.Response, error) { // Get options and transport as one single read transaction. // Options and transport may change if someone calls SetOptions o, tr := l.GetOptionsAndTransport() originalRequest := req.GetHttpRequest() // Check request size first, if that exceeds the limit, we don't bother reading the request. if l.isRequestOverLimit(req) { return nil, errors.FromStatus(http.StatusRequestEntityTooLarge) } // Read the body while keeping this location's limits in mind. This reader controls the maximum bytes // to read into memory and disk. This reader returns anerror if the total request size exceeds the // prefefined MaxSizeBytes. This can occur if we got chunked request, in this case ContentLength would be set to -1 // and the reader would be unbounded bufio in the http.Server body, err := netutils.NewBodyBufferWithOptions(originalRequest.Body, netutils.BodyBufferOptions{ MemBufferBytes: o.Limits.MaxMemBodyBytes, MaxSizeBytes: o.Limits.MaxBodyBytes, }) if err != nil { return nil, err } if body == nil { return nil, fmt.Errorf("Empty body") } // Set request body to buffered reader that can replay the read and execute Seek req.SetBody(body) // Note that we don't change the original request Body as it's handled by the http server defer body.Close() for { _, err := req.GetBody().Seek(0, 0) if err != nil { return nil, err } endpoint, err := l.loadBalancer.NextEndpoint(req) if err != nil { log.Errorf("Load Balancer failure: %s", err) return nil, err } // Adds headers, changes urls. Note that we rewrite request each time we proxy it to the // endpoint, so that each try gets a fresh start req.SetHttpRequest(l.copyRequest(originalRequest, req.GetBody(), endpoint)) // In case if error is not nil, we allow load balancer to choose the next endpoint // e.g. to do request failover. Nil error means that we got proxied the request successfully. response, err := l.proxyToEndpoint(tr, &o, endpoint, req) if o.ShouldFailover(req) { continue } else { return response, err } } log.Errorf("All endpoints failed!") return nil, fmt.Errorf("All endpoints failed") }
func (rw *Rewriter) ProcessRequest(r request.Request) (*http.Response, error) { req := r.GetHttpRequest() if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { if rw.TrustForwardHeader { if prior, ok := req.Header[headers.XForwardedFor]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } } req.Header.Set(headers.XForwardedFor, clientIP) } if xfp := req.Header.Get(headers.XForwardedProto); xfp != "" && rw.TrustForwardHeader { req.Header.Set(headers.XForwardedProto, xfp) } else if req.TLS != nil { req.Header.Set(headers.XForwardedProto, "https") } else { req.Header.Set(headers.XForwardedProto, "http") } if req.Host != "" { req.Header.Set(headers.XForwardedHost, req.Host) } req.Header.Set(headers.XForwardedServer, rw.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, req.Header) // We need to set ContentLength based on known request size. The incoming request may have been // set without content length or using chunked TransferEncoding totalSize, err := r.GetBody().TotalSize() if err != nil { return nil, err } req.ContentLength = totalSize // Remove TransferEncoding that could have been previously set req.TransferEncoding = []string{} return nil, nil }
// Maps request to it's size in bytes func RequestToBytes(req request.Request) (int64, error) { return req.GetBody().TotalSize() }