func (rp *FastReverseProxy) serveWebsocket(dstHost string, reqData *RequestData, ctx *fasthttp.RequestCtx) { req := &ctx.Request uri := req.URI() uri.SetHost(dstHost) dstConn, err := rp.dialFunc(dstHost) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) return } if clientIP, _, err := net.SplitHostPort(ctx.RemoteAddr().String()); err == nil { if prior := string(req.Header.Peek("X-Forwarded-For")); len(prior) > 0 { clientIP = prior + ", " + clientIP } req.Header.Set("X-Forwarded-For", clientIP) } _, err = req.WriteTo(dstConn) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) return } ctx.Hijack(func(conn net.Conn) { defer dstConn.Close() defer conn.Close() errc := make(chan error, 2) cp := func(dst io.Writer, src io.Reader) { _, err := io.Copy(dst, src) errc <- err } go cp(dstConn, conn) go cp(conn, dstConn) <-errc }) }
func (rp *NativeReverseProxy) RoundTrip(req *http.Request) (*http.Response, error) { reqData, err := rp.Router.ChooseBackend(req.Host) if err != nil { log.LogError(reqData.String(), req.URL.Path, err) } req.URL.Scheme = "" req.URL.Host = "" u, err := url.Parse(reqData.Backend) if err == nil { req.URL.Host = u.Host req.URL.Scheme = u.Scheme } else { log.LogError(reqData.String(), req.URL.Path, err) } if req.URL.Host == "" { req.URL.Scheme = "http" req.URL.Host = reqData.Backend } if rp.RequestIDHeader != "" && req.Header.Get(rp.RequestIDHeader) == "" { unparsedID, err := uuid.NewV4() if err == nil { req.Header.Set(rp.RequestIDHeader, unparsedID.String()) } else { log.LogError(reqData.String(), req.URL.Path, fmt.Errorf("unable to generate request id: %s", err)) } } rsp := rp.roundTripWithData(req, reqData) return rsp, nil }
func (rp *NativeReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if req.Host == "__ping__" && req.URL.Path == "/" { rw.WriteHeader(http.StatusOK) rw.Write(okResponse) return } upgrade := req.Header.Get("Upgrade") if upgrade != "" && strings.ToLower(upgrade) == "websocket" { reqData, err := rp.serveWebsocket(rw, req) if err != nil { log.LogError(reqData.String(), req.URL.Path, err) http.Error(rw, "", http.StatusBadGateway) } return } rp.rp.ServeHTTP(rw, req) }
func (rp *FastReverseProxy) handler(ctx *fasthttp.RequestCtx) { req := &ctx.Request resp := &ctx.Response host := string(req.Header.Host()) uri := req.URI() if host == "__ping__" && len(uri.Path()) == 1 && uri.Path()[0] == byte('/') { resp.SetBody(okResponse) return } reqData, err := rp.Router.ChooseBackend(host) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) } dstScheme := "" dstHost := "" u, err := url.Parse(reqData.Backend) if err == nil { dstScheme = u.Scheme dstHost = u.Host } else { log.LogError(reqData.String(), string(uri.Path()), err) } if dstHost == "" { dstHost = reqData.Backend } upgrade := req.Header.Peek("Upgrade") if len(upgrade) > 0 && bytes.Compare(bytes.ToLower(upgrade), websocketUpgrade) == 0 { resp.SkipResponse = true rp.serveWebsocket(dstHost, reqData, ctx) return } var backendDuration time.Duration logEntry := func() *log.LogEntry { proto := "HTTP/1.0" if req.Header.IsHTTP11() { proto = "HTTP/1.1" } return &log.LogEntry{ Now: time.Now(), BackendDuration: backendDuration, TotalDuration: time.Since(reqData.StartTime), BackendKey: reqData.BackendKey, RemoteAddr: ctx.RemoteAddr().String(), Method: string(ctx.Method()), Path: string(uri.Path()), Proto: proto, Referer: string(ctx.Referer()), UserAgent: string(ctx.UserAgent()), RequestIDHeader: rp.RequestIDHeader, RequestID: string(req.Header.Peek(rp.RequestIDHeader)), StatusCode: resp.StatusCode(), ContentLength: int64(resp.Header.ContentLength()), } } isDebug := len(req.Header.Peek("X-Debug-Router")) > 0 req.Header.Del("X-Debug-Router") if dstHost == "" { resp.SetStatusCode(http.StatusBadRequest) resp.SetBody(noRouteResponseContent) rp.debugHeaders(resp, reqData, isDebug) endErr := rp.Router.EndRequest(reqData, false, logEntry) if endErr != nil { log.LogError(reqData.String(), string(uri.Path()), endErr) } return } if rp.RequestIDHeader != "" && len(req.Header.Peek(rp.RequestIDHeader)) == 0 { unparsedID, err := uuid.NewV4() if err == nil { req.Header.Set(rp.RequestIDHeader, unparsedID.String()) } else { log.LogError(reqData.String(), string(uri.Path()), fmt.Errorf("unable to generate request id: %s", err)) } } hostOnly, _, _ := net.SplitHostPort(dstHost) if hostOnly == "" { hostOnly = dstHost } isIP := net.ParseIP(hostOnly) != nil if !isIP { req.Header.SetBytesV("X-Host", uri.Host()) req.Header.SetBytesV("X-Forwarded-Host", uri.Host()) uri.SetHost(hostOnly) } client := rp.getClient(dstHost, dstScheme == "https") t0 := time.Now().UTC() err = client.Do(req, resp) backendDuration = time.Since(t0) markAsDead := false if err != nil { var isTimeout bool if netErr, ok := err.(net.Error); ok { markAsDead = !netErr.Temporary() isTimeout = netErr.Timeout() } if isTimeout { markAsDead = false err = fmt.Errorf("request timed out after %v: %s", time.Since(reqData.StartTime), err) } else { err = fmt.Errorf("error in backend request: %s", err) } if markAsDead { err = fmt.Errorf("%s *DEAD*", err) } resp.SetStatusCode(http.StatusServiceUnavailable) log.LogError(reqData.String(), string(uri.Path()), err) } rp.debugHeaders(resp, reqData, isDebug) endErr := rp.Router.EndRequest(reqData, markAsDead, logEntry) if endErr != nil { log.LogError(reqData.String(), string(uri.Path()), endErr) } }
func (rp *NativeReverseProxy) roundTripWithData(req *http.Request, reqData *RequestData) *http.Response { var rsp *http.Response var backendDuration time.Duration logEntry := func() *log.LogEntry { return &log.LogEntry{ Now: time.Now(), BackendDuration: backendDuration, TotalDuration: time.Since(reqData.StartTime), BackendKey: reqData.BackendKey, RemoteAddr: req.RemoteAddr, Method: req.Method, Path: req.URL.Path, Proto: req.Proto, Referer: req.Referer(), UserAgent: req.UserAgent(), RequestIDHeader: rp.RequestIDHeader, RequestID: req.Header.Get(rp.RequestIDHeader), StatusCode: rsp.StatusCode, ContentLength: rsp.ContentLength, } } var err error isDebug := req.Header.Get("X-Debug-Router") != "" req.Header.Del("X-Debug-Router") if req.URL.Scheme == "" || req.URL.Host == "" { rsp = &http.Response{ Request: req, StatusCode: http.StatusBadRequest, ProtoMajor: req.ProtoMajor, ProtoMinor: req.ProtoMinor, ContentLength: int64(len(noRouteResponseBody.value)), Header: http.Header{}, Body: noRouteResponseBody, } rp.debugHeaders(rsp, reqData, isDebug) err = rp.Router.EndRequest(reqData, false, logEntry) if err != nil { log.LogError(reqData.String(), req.URL.Path, err) } return rsp } var timedout int32 if rp.RequestTimeout > 0 { timer := time.AfterFunc(rp.RequestTimeout, func() { atomic.AddInt32(&timedout, 1) rp.Transport.CancelRequest(req) }) defer timer.Stop() } host, _, _ := net.SplitHostPort(req.URL.Host) if host == "" { host = req.URL.Host } isIP := net.ParseIP(host) != nil if !isIP { req.Header.Set("X-Host", req.Host) req.Header.Set("X-Forwarded-Host", req.Host) req.Host = host } t0 := time.Now().UTC() rsp, err = rp.Transport.RoundTrip(req) backendDuration = time.Since(t0) markAsDead := false if err != nil { if netErr, ok := err.(net.Error); ok { markAsDead = !netErr.Temporary() } isTimeout := atomic.LoadInt32(&timedout) == int32(1) if isTimeout { markAsDead = false err = fmt.Errorf("request timed out after %v: %s", time.Since(reqData.StartTime), err) } else { err = fmt.Errorf("error in backend request: %s", err) } if markAsDead { err = fmt.Errorf("%s *DEAD*", err) } log.LogError(reqData.String(), req.URL.Path, err) rsp = &http.Response{ Request: req, StatusCode: http.StatusServiceUnavailable, ProtoMajor: req.ProtoMajor, ProtoMinor: req.ProtoMinor, Header: http.Header{}, Body: emptyResponseBody, } } rp.debugHeaders(rsp, reqData, isDebug) endErr := rp.Router.EndRequest(reqData, markAsDead, logEntry) if endErr != nil { log.LogError(reqData.String(), req.URL.Path, endErr) } return rsp }