105 lines
3 KiB
Go
105 lines
3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
|
"github.com/containous/traefik/v2/pkg/log"
|
|
"github.com/containous/traefik/v2/pkg/types"
|
|
)
|
|
|
|
// StatusClientClosedRequest non-standard HTTP status code for client disconnection
|
|
const StatusClientClosedRequest = 499
|
|
|
|
// StatusClientClosedRequestText non-standard HTTP status for client disconnection
|
|
const StatusClientClosedRequestText = "Client Closed Request"
|
|
|
|
func buildProxy(passHostHeader bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool, responseModifier func(*http.Response) error) (http.Handler, error) {
|
|
var flushInterval types.Duration
|
|
if responseForwarding != nil {
|
|
err := flushInterval.Set(responseForwarding.FlushInterval)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating flush interval: %v", err)
|
|
}
|
|
}
|
|
if flushInterval == 0 {
|
|
flushInterval = types.Duration(100 * time.Millisecond)
|
|
}
|
|
|
|
proxy := &httputil.ReverseProxy{
|
|
Director: func(outReq *http.Request) {
|
|
u := outReq.URL
|
|
if outReq.RequestURI != "" {
|
|
parsedURL, err := url.ParseRequestURI(outReq.RequestURI)
|
|
if err == nil {
|
|
u = parsedURL
|
|
}
|
|
}
|
|
|
|
outReq.URL.Path = u.Path
|
|
outReq.URL.RawPath = u.RawPath
|
|
outReq.URL.RawQuery = u.RawQuery
|
|
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
|
|
|
outReq.Proto = "HTTP/1.1"
|
|
outReq.ProtoMajor = 1
|
|
outReq.ProtoMinor = 1
|
|
|
|
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
|
if !passHostHeader {
|
|
outReq.Host = outReq.URL.Host
|
|
}
|
|
|
|
// Even if the websocket RFC says that headers should be case-insensitive,
|
|
// some servers need Sec-WebSocket-Key to be case-sensitive.
|
|
// https://tools.ietf.org/html/rfc6455#page-20
|
|
outReq.Header["Sec-WebSocket-Key"] = outReq.Header["Sec-Websocket-Key"]
|
|
delete(outReq.Header, "Sec-Websocket-Key")
|
|
},
|
|
Transport: defaultRoundTripper,
|
|
FlushInterval: time.Duration(flushInterval),
|
|
ModifyResponse: responseModifier,
|
|
BufferPool: bufferPool,
|
|
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
|
|
statusCode := http.StatusInternalServerError
|
|
|
|
switch {
|
|
case err == io.EOF:
|
|
statusCode = http.StatusBadGateway
|
|
case err == context.Canceled:
|
|
statusCode = StatusClientClosedRequest
|
|
default:
|
|
if e, ok := err.(net.Error); ok {
|
|
if e.Timeout() {
|
|
statusCode = http.StatusGatewayTimeout
|
|
} else {
|
|
statusCode = http.StatusBadGateway
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
|
w.WriteHeader(statusCode)
|
|
_, werr := w.Write([]byte(statusText(statusCode)))
|
|
if werr != nil {
|
|
log.Debugf("Error while writing status code", werr)
|
|
}
|
|
},
|
|
}
|
|
|
|
return proxy, nil
|
|
}
|
|
|
|
func statusText(statusCode int) string {
|
|
if statusCode == StatusClientClosedRequest {
|
|
return StatusClientClosedRequestText
|
|
}
|
|
return http.StatusText(statusCode)
|
|
}
|