// 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)
}
Exemple #2
0
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)
}