// Round trips the request to one of the upstreams, returns the streamed // request body length in bytes and the upstream reply. func (p *ReverseProxy) proxyRequest( w http.ResponseWriter, req *http.Request, cmd *command.Forward, endpoints []loadbalance.Endpoint) (int64, error) { // We are allowed to fallback in case of upstream failure, // record the request body so we can replay it on errors. body, err := netutils.NewBodyBuffer(req.Body) if err != nil { glog.Errorf("Request read error %s", err) return 0, netutils.NewHttpError(http.StatusBadRequest) } requestLength, err := body.TotalSize() if err != nil { glog.Errorf("Failed to read stored body length: %s", err) return 0, netutils.NewHttpError(http.StatusInternalServerError) } p.metrics.RequestBodySize.Update(requestLength) req.Body = body defer body.Close() for i := 0; i < len(endpoints); i++ { _, err := body.Seek(0, 0) if err != nil { return 0, err } endpoint, err := p.nextEndpoint(endpoints) if err != nil { glog.Errorf("Load Balancer failure: %s", err) return 0, err } glog.Infof("With failover, proxy to upstream: %s", endpoint.Upstream) err = p.proxyToUpstream(w, req, cmd, endpoint.Upstream) if err != nil { if cmd.Failover == nil || !cmd.Failover.Active { return 0, err } glog.Errorf("Upstream: %s error: %s, falling back to another", endpoint.Upstream, err) // Mark the endpoint as inactive for the next round of the load balance iteration endpoint.Active = false } else { return 0, nil } } glog.Errorf("All upstreams failed!") return requestLength, netutils.NewHttpError(http.StatusBadGateway) }
func (ctrl *JsController) ConvertError(req *http.Request, inError error) (response *netutils.HttpError, err error) { response = netutils.NewHttpError(http.StatusInternalServerError) err = fmt.Errorf("Internal error") defer func() { if r := recover(); r != nil { glog.Errorf("Recovered: %v %s", r, debug.Stack()) } }() jsc, err := ctrl.getContextFromCache() if err != nil { return nil, err } defer jsc.Release() if jsc.handleError.IsUndefined() { glog.Infof("Missing error handler: %s", err) converted, err := errorFromJs(errorToJs(inError)) if err != nil { glog.Errorf("Failed to convert error: %s", err) return nil, err } return converted, nil } obj := errorToJs(inError) jsObj, err := jsc.otto.ToValue(obj) if err != nil { glog.Errorf("Error: %s", err) return nil, err } jsRequest, err := requestToJs(req) if err != nil { return nil, err } jsRequestValue, err := jsc.otto.ToValue(jsRequest) if err != nil { return nil, err } out, err := ctrl.callHandler(jsc.handleError, jsRequestValue, jsObj) if err != nil { glog.Errorf("Error: %s", err) return nil, err } converted, err := errorFromJs(out) if err != nil { glog.Errorf("Failed to convert error: %s", err) return nil, err } return converted, nil }
// Helper function to reply with http errors func (p *ReverseProxy) replyError(err error, w http.ResponseWriter, req *http.Request) { httpResponse, err := p.controller.ConvertError(req, err) if err != nil { glog.Errorf("Error converter failed: %s", err) httpResponse = netutils.NewHttpError(http.StatusInternalServerError) } // Discard the request body, so that clients can actually receive the response // Otherwise they can only see lost connection // TODO: actually check this io.Copy(ioutil.Discard, req.Body) w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpResponse.StatusCode) w.Write(httpResponse.Body) }