// 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 convertError(err error) errors.ProxyError { switch e := err.(type) { case errors.ProxyError: return e case net.Error: if e.Timeout() { return errors.FromStatus(http.StatusRequestTimeout) } case *netutils.MaxSizeReachedError: return errors.FromStatus(http.StatusRequestEntityTooLarge) } return errors.FromStatus(http.StatusBadGateway) }
// 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 } }
// Accepts requests, round trips it to the endpoint, and writes back the response. func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Record the request body so we can replay it on errors. body, err := netutils.NewBodyBuffer(r.Body) if err != nil || body == nil { log.Errorf("Request read error %s", err) if netErr, ok := err.(net.Error); ok && netErr.Timeout() { p.replyError(errors.FromStatus(http.StatusRequestTimeout), w, r) } else { p.replyError(errors.FromStatus(http.StatusBadRequest), w, r) } return } defer body.Close() r.Body = body req := request.NewBaseRequest(r, atomic.AddInt64(&p.lastRequestId, 1), body) err = p.proxyRequest(w, req) if err != nil { log.Errorf("%s failed: %s", req, err) p.replyError(err, w, r) } }
// Helper function to reply with http errors func (p *Proxy) replyError(err error, w http.ResponseWriter, req *http.Request) { // Discard the request body, so that clients can actually receive the response // otherwise they can only see lost connection // TODO: actually check this proxyError, ok := err.(errors.ProxyError) if !ok { proxyError = errors.FromStatus(http.StatusBadGateway) } io.Copy(ioutil.Discard, req.Body) statusCode, body, contentType := p.options.ErrorFormatter.Format(proxyError) w.Header().Set("Content-Type", contentType) w.WriteHeader(statusCode) w.Write(body) }
// 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 } }
// Accepts requests, round trips it to the endpoint and writes backe the response. func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Record the request body so we can replay it on errors. body, err := netutils.NewBodyBuffer(r.Body) if err != nil || body == nil { log.Errorf("Request read error %s", err) p.replyError(errors.FromStatus(http.StatusBadRequest), w, r) return } defer body.Close() r.Body = body req := &request.BaseRequest{ HttpRequest: r, Id: atomic.AddInt64(&p.lastRequestId, 1), Body: body, } err = p.proxyRequest(w, req) if err != nil { log.Errorf("%s failed: %s", req, err) p.replyError(err, w, r) } }