traefik/pkg/server/service/proxy.go

130 lines
3.8 KiB
Go
Raw Normal View History

package service
import (
"context"
2020-11-06 08:26:03 +00:00
"errors"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
2022-06-27 13:16:08 +00:00
"strings"
"time"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
"golang.org/x/net/http/httpguts"
)
2020-05-11 10:06:07 +00:00
// StatusClientClosedRequest non-standard HTTP status code for client disconnection.
const StatusClientClosedRequest = 499
2020-05-11 10:06:07 +00:00
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
const StatusClientClosedRequestText = "Client Closed Request"
func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
return &httputil.ReverseProxy{
Director: directorBuilder(target, passHostHeader),
Transport: roundTripper,
FlushInterval: flushInterval,
BufferPool: bufferPool,
ErrorHandler: errorHandler,
}
}
func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Request) {
return func(outReq *http.Request) {
outReq.URL.Scheme = target.Scheme
outReq.URL.Host = target.Host
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
2023-06-20 17:06:51 +00:00
// If a plugin/middleware adds semicolons in query params, they should be urlEncoded.
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
outReq.RequestURI = "" // Outgoing request should not have RequestURI
outReq.Proto = "HTTP/1.1"
outReq.ProtoMajor = 1
outReq.ProtoMinor = 1
2023-03-21 11:00:46 +00:00
// Do not pass client Host header unless optsetter PassHostHeader is set.
if !passHostHeader {
outReq.Host = outReq.URL.Host
}
cleanWebSocketHeaders(outReq)
}
}
// cleanWebSocketHeaders Even if the websocket RFC says that headers should be case-insensitive,
// some servers need Sec-WebSocket-Key, Sec-WebSocket-Extensions, Sec-WebSocket-Accept,
// Sec-WebSocket-Protocol and Sec-WebSocket-Version to be case-sensitive.
// https://tools.ietf.org/html/rfc6455#page-20
func cleanWebSocketHeaders(req *http.Request) {
if !isWebSocketUpgrade(req) {
return
}
req.Header["Sec-WebSocket-Key"] = req.Header["Sec-Websocket-Key"]
delete(req.Header, "Sec-Websocket-Key")
req.Header["Sec-WebSocket-Extensions"] = req.Header["Sec-Websocket-Extensions"]
delete(req.Header, "Sec-Websocket-Extensions")
req.Header["Sec-WebSocket-Accept"] = req.Header["Sec-Websocket-Accept"]
delete(req.Header, "Sec-Websocket-Accept")
req.Header["Sec-WebSocket-Protocol"] = req.Header["Sec-Websocket-Protocol"]
delete(req.Header, "Sec-Websocket-Protocol")
req.Header["Sec-WebSocket-Version"] = req.Header["Sec-Websocket-Version"]
delete(req.Header, "Sec-Websocket-Version")
}
func isWebSocketUpgrade(req *http.Request) bool {
return httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") &&
strings.EqualFold(req.Header.Get("Upgrade"), "websocket")
}
func errorHandler(w http.ResponseWriter, req *http.Request, err error) {
statusCode := http.StatusInternalServerError
switch {
case errors.Is(err, io.EOF):
statusCode = http.StatusBadGateway
case errors.Is(err, context.Canceled):
statusCode = StatusClientClosedRequest
default:
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Timeout() {
statusCode = http.StatusGatewayTimeout
} else {
statusCode = http.StatusBadGateway
}
}
}
2022-11-21 17:36:05 +00:00
logger := log.Ctx(req.Context())
logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode))
w.WriteHeader(statusCode)
if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil {
2022-11-21 17:36:05 +00:00
logger.Debug().Err(werr).Msg("Error while writing status code")
}
}
func statusText(statusCode int) string {
if statusCode == StatusClientClosedRequest {
return StatusClientClosedRequestText
}
return http.StatusText(statusCode)
}