Rework servers load-balancer to use the WRR
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
67d9c8da0b
commit
fadee5e87b
70 changed files with 2085 additions and 2211 deletions
|
@ -3,7 +3,6 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -12,8 +11,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
@ -24,103 +21,107 @@ const StatusClientClosedRequest = 499
|
|||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
|
||||
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
|
||||
var flushInterval ptypes.Duration
|
||||
if responseForwarding != nil {
|
||||
err := flushInterval.Set(responseForwarding.FlushInterval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating flush interval: %w", err)
|
||||
}
|
||||
}
|
||||
if flushInterval == 0 {
|
||||
flushInterval = ptypes.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 = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
if _, ok := outReq.Header["User-Agent"]; !ok {
|
||||
outReq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||
if passHostHeader != nil && !*passHostHeader {
|
||||
outReq.Host = outReq.URL.Host
|
||||
}
|
||||
|
||||
// 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
|
||||
if isWebSocketUpgrade(outReq) {
|
||||
outReq.Header["Sec-WebSocket-Key"] = outReq.Header["Sec-Websocket-Key"]
|
||||
outReq.Header["Sec-WebSocket-Extensions"] = outReq.Header["Sec-Websocket-Extensions"]
|
||||
outReq.Header["Sec-WebSocket-Accept"] = outReq.Header["Sec-Websocket-Accept"]
|
||||
outReq.Header["Sec-WebSocket-Protocol"] = outReq.Header["Sec-Websocket-Protocol"]
|
||||
outReq.Header["Sec-WebSocket-Version"] = outReq.Header["Sec-Websocket-Version"]
|
||||
delete(outReq.Header, "Sec-Websocket-Key")
|
||||
delete(outReq.Header, "Sec-Websocket-Extensions")
|
||||
delete(outReq.Header, "Sec-Websocket-Accept")
|
||||
delete(outReq.Header, "Sec-Websocket-Protocol")
|
||||
delete(outReq.Header, "Sec-Websocket-Version")
|
||||
}
|
||||
},
|
||||
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: time.Duration(flushInterval),
|
||||
FlushInterval: flushInterval,
|
||||
BufferPool: bufferPool,
|
||||
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
ErrorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
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)
|
||||
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 = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
if _, ok := outReq.Header["User-Agent"]; !ok {
|
||||
outReq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
// Do not pass client Host header unless 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
|
||||
}
|
||||
|
||||
return proxy, nil
|
||||
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 {
|
||||
if !httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
|
||||
return false
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.EqualFold(req.Header.Get("Upgrade"), "websocket")
|
||||
logger := log.FromContext(req.Context())
|
||||
logger.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil {
|
||||
logger.Debugf("Error while writing status code", werr)
|
||||
}
|
||||
}
|
||||
|
||||
func statusText(statusCode int) string {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue