2021-04-01 02:37:28 -05:00
package proxy
import (
2022-01-05 23:57:05 -06:00
"bytes"
2021-04-01 02:37:28 -05:00
"context"
2024-01-07 22:19:50 +04:00
"crypto/tls"
2021-04-14 18:28:23 +02:00
"fmt"
2021-04-05 22:12:06 -05:00
"io"
2021-04-04 02:57:34 -05:00
"net"
2021-04-01 02:37:28 -05:00
"net/http"
"net/http/httputil"
"net/url"
2022-01-05 23:57:05 -06:00
"os"
"path/filepath"
2021-04-03 01:20:24 -05:00
"regexp"
"strconv"
2021-04-02 00:07:36 -05:00
"strings"
2021-04-01 02:37:28 -05:00
"time"
2021-04-02 03:13:49 -05:00
log "github.com/go-pkgz/lgr"
2021-04-03 00:22:54 -05:00
R "github.com/go-pkgz/rest"
2021-04-13 12:45:49 -05:00
"github.com/go-pkgz/rest/logger"
2021-04-05 22:12:06 -05:00
2021-04-05 03:37:28 -05:00
"github.com/umputun/reproxy/app/discovery"
2021-06-01 02:56:39 -05:00
"github.com/umputun/reproxy/app/plugin"
2021-04-01 02:37:28 -05:00
)
2021-04-03 01:20:24 -05:00
// Http is a proxy server for both http and https
2021-04-13 12:45:49 -05:00
type Http struct { // nolint golint
2021-04-01 02:37:28 -05:00
Matcher
2023-11-25 13:54:14 -06:00
Address string
AssetsLocation string
AssetsWebRoot string
Assets404 string
AssetsSPA bool
MaxBodySize int64
GzEnabled bool
ProxyHeaders [ ] string
DropHeader [ ] string
SSLConfig SSLConfig
2024-01-07 22:19:50 +04:00
Insecure bool
2023-11-25 13:54:14 -06:00
Version string
AccessLog io . Writer
StdOutEnabled bool
Signature bool
Timeouts Timeouts
CacheControl MiddlewareProvider
Metrics MiddlewareProvider
PluginConductor MiddlewareProvider
Reporter Reporter
2023-11-27 12:05:17 -06:00
LBSelector LBSelector
2023-11-25 13:54:14 -06:00
OnlyFrom * OnlyFrom
2021-11-07 15:31:33 -06:00
BasicAuthEnabled bool
BasicAuthAllowed [ ] string
2021-07-03 01:23:50 -05:00
ThrottleSystem int
2021-09-11 14:38:56 -05:00
ThrottleUser int
2024-01-25 12:28:54 +03:00
KeepHost bool
2024-09-17 11:37:24 +05:00
dnsResolvers [ ] string // used to mock DNS resolvers for testing
2021-04-01 02:37:28 -05:00
}
2021-04-03 01:20:24 -05:00
// Matcher source info (server and route) to the destination url
// If no match found return ok=false
2021-04-01 02:37:28 -05:00
type Matcher interface {
2021-05-16 18:34:51 -05:00
Match ( srv , src string ) ( res discovery . Matches )
2021-04-04 15:55:06 -05:00
Servers ( ) ( servers [ ] string )
2021-04-09 15:05:22 -05:00
Mappers ( ) ( mappers [ ] discovery . URLMapper )
2021-05-13 21:14:48 +02:00
CheckHealth ( ) ( pingResult map [ string ] error )
2021-04-01 02:37:28 -05:00
}
2021-04-26 18:51:48 -05:00
// MiddlewareProvider interface defines http middleware handler
type MiddlewareProvider interface {
2021-04-20 19:04:12 -05:00
Middleware ( next http . Handler ) http . Handler
}
2021-04-30 04:03:36 -05:00
// Reporter defines error reporting service
type Reporter interface {
Report ( w http . ResponseWriter , code int )
}
2023-11-27 12:05:17 -06:00
// LBSelector defines load balancer strategy
type LBSelector interface {
2024-11-27 03:36:27 -06:00
Select ( size int ) int // return index of picked server
2023-11-27 12:05:17 -06:00
}
2021-04-12 21:54:59 -05:00
// Timeouts consolidate timeouts for both server and transport
type Timeouts struct {
// server timeouts
ReadHeader time . Duration
Write time . Duration
Idle time . Duration
// transport timeouts
Dial time . Duration
KeepAlive time . Duration
IdleConn time . Duration
TLSHandshake time . Duration
ExpectContinue time . Duration
ResponseHeader time . Duration
}
2021-04-03 00:22:54 -05:00
// Run the lister and request's router, activate rest server
func ( h * Http ) Run ( ctx context . Context ) error {
2021-04-01 02:37:28 -05:00
2021-04-03 00:22:54 -05:00
if h . AssetsLocation != "" {
log . Printf ( "[DEBUG] assets file server enabled for %s, webroot %s" , h . AssetsLocation , h . AssetsWebRoot )
2022-01-05 23:57:05 -06:00
if h . Assets404 != "" {
log . Printf ( "[DEBUG] assets 404 file enabled for %s" , h . Assets404 )
}
2021-04-01 02:37:28 -05:00
}
2021-05-28 16:11:16 -05:00
if h . LBSelector == nil {
2023-11-27 12:05:17 -06:00
h . LBSelector = & RandomSelector { }
2021-05-28 16:11:16 -05:00
}
2021-04-02 03:13:49 -05:00
var httpServer , httpsServer * http . Server
go func ( ) {
<- ctx . Done ( )
if httpServer != nil {
if err := httpServer . Close ( ) ; err != nil {
log . Printf ( "[ERROR] failed to close proxy http server, %v" , err )
}
}
if httpsServer != nil {
if err := httpsServer . Close ( ) ; err != nil {
log . Printf ( "[ERROR] failed to close proxy https server, %v" , err )
}
}
} ( )
2021-04-03 00:22:54 -05:00
handler := R . Wrap ( h . proxyHandler ( ) ,
2023-11-26 17:10:16 -06:00
R . Recoverer ( log . Default ( ) ) , // recover on errors
signatureHandler ( h . Signature , h . Version ) , // send app signature
h . pingHandler , // respond to /ping
2021-11-07 15:31:33 -06:00
basicAuthHandler ( h . BasicAuthEnabled , h . BasicAuthAllowed ) , // basic auth
2023-11-26 17:10:16 -06:00
h . healthMiddleware , // respond to /health
h . matchHandler , // set matched routes to context
h . OnlyFrom . Handler , // limit source (remote) IPs if defined
limiterSystemHandler ( h . ThrottleSystem ) , // limit total requests/sec
limiterUserHandler ( h . ThrottleUser ) , // req/seq per user/route match
h . mgmtHandler ( ) , // handles /metrics and /routes for prometheus
h . pluginHandler ( ) , // prc to external plugins
headersHandler ( h . ProxyHeaders , h . DropHeader ) , // add response headers and delete some request headers
accessLogHandler ( h . AccessLog ) , // apache-format log file
2021-06-01 02:56:39 -05:00
stdoutLogHandler ( h . StdOutEnabled , logger . New ( logger . Log ( log . Default ( ) ) , logger . Prefix ( "[INFO]" ) ) . Handler ) ,
2021-07-03 01:23:50 -05:00
maxReqSizeHandler ( h . MaxBodySize ) , // limit request max size
gzipHandler ( h . GzEnabled ) , // gzip response
2021-04-02 03:13:49 -05:00
)
2022-02-23 17:56:59 -06:00
// no FQDNs defined, use the list of discovered servers
2021-05-05 01:41:23 -05:00
if len ( h . SSLConfig . FQDNs ) == 0 && h . SSLConfig . SSLMode == SSLAuto {
2022-02-23 17:56:59 -06:00
h . SSLConfig . FQDNs = h . discoveredServers ( ctx , 50 * time . Millisecond )
2021-04-09 02:48:18 -05:00
}
2021-04-02 03:13:49 -05:00
switch h . SSLConfig . SSLMode {
2021-04-03 00:22:54 -05:00
case SSLNone :
2021-04-02 03:13:49 -05:00
log . Printf ( "[INFO] activate http proxy server on %s" , h . Address )
httpServer = h . makeHTTPServer ( h . Address , handler )
httpServer . ErrorLog = log . ToStdLogger ( log . Default ( ) , "WARN" )
2021-04-03 00:22:54 -05:00
return httpServer . ListenAndServe ( )
case SSLStatic :
2021-04-02 03:13:49 -05:00
log . Printf ( "[INFO] activate https server in 'static' mode on %s" , h . Address )
httpsServer = h . makeHTTPSServer ( h . Address , handler )
httpsServer . ErrorLog = log . ToStdLogger ( log . Default ( ) , "WARN" )
2021-04-09 15:05:22 -05:00
httpServer = h . makeHTTPServer ( h . toHTTP ( h . Address , h . SSLConfig . RedirHTTPPort ) , h . httpToHTTPSRouter ( ) )
2021-04-02 03:13:49 -05:00
httpServer . ErrorLog = log . ToStdLogger ( log . Default ( ) , "WARN" )
go func ( ) {
2021-04-09 15:05:22 -05:00
log . Printf ( "[INFO] activate http redirect server on %s" , h . toHTTP ( h . Address , h . SSLConfig . RedirHTTPPort ) )
2021-04-02 03:13:49 -05:00
err := httpServer . ListenAndServe ( )
log . Printf ( "[WARN] http redirect server terminated, %s" , err )
} ( )
2021-04-14 18:58:05 +04:00
return httpsServer . ListenAndServeTLS ( h . SSLConfig . Cert , h . SSLConfig . Key )
2021-04-03 00:22:54 -05:00
case SSLAuto :
2021-04-02 03:13:49 -05:00
log . Printf ( "[INFO] activate https server in 'auto' mode on %s" , h . Address )
2021-04-09 02:48:18 -05:00
log . Printf ( "[DEBUG] FQDNs %v" , h . SSLConfig . FQDNs )
2021-04-02 03:13:49 -05:00
m := h . makeAutocertManager ( )
httpsServer = h . makeHTTPSAutocertServer ( h . Address , handler , m )
httpsServer . ErrorLog = log . ToStdLogger ( log . Default ( ) , "WARN" )
2021-04-09 15:05:22 -05:00
httpServer = h . makeHTTPServer ( h . toHTTP ( h . Address , h . SSLConfig . RedirHTTPPort ) , h . httpChallengeRouter ( m ) )
2021-04-02 03:13:49 -05:00
httpServer . ErrorLog = log . ToStdLogger ( log . Default ( ) , "WARN" )
go func ( ) {
2021-04-09 15:05:22 -05:00
log . Printf ( "[INFO] activate http challenge server on port %s" , h . toHTTP ( h . Address , h . SSLConfig . RedirHTTPPort ) )
2021-04-02 03:13:49 -05:00
err := httpServer . ListenAndServe ( )
log . Printf ( "[WARN] http challenge server terminated, %s" , err )
} ( )
2021-04-03 00:22:54 -05:00
return httpsServer . ListenAndServeTLS ( "" , "" )
2021-04-02 03:13:49 -05:00
}
2021-04-14 18:28:23 +02:00
return fmt . Errorf ( "unknown SSL type %v" , h . SSLConfig . SSLMode )
2021-04-02 03:13:49 -05:00
}
2021-06-01 02:56:39 -05:00
type contextKey string
const (
ctxURL = contextKey ( "url" )
ctxMatchType = contextKey ( "type" )
2021-06-01 03:59:23 -05:00
ctxMatch = contextKey ( "match" )
2024-01-25 12:28:54 +03:00
ctxKeepHost = contextKey ( "keepHost" )
2021-06-01 02:56:39 -05:00
)
2021-04-01 02:37:28 -05:00
func ( h * Http ) proxyHandler ( ) http . HandlerFunc {
reverseProxy := & httputil . ReverseProxy {
Director : func ( r * http . Request ) {
ctx := r . Context ( )
2021-06-01 04:00:38 -05:00
uu := ctx . Value ( ctxURL ) . ( * url . URL )
2024-01-25 12:28:54 +03:00
keepHost := ctx . Value ( ctxKeepHost ) . ( bool )
2021-05-10 03:50:00 -05:00
r . Header . Add ( "X-Forwarded-Host" , r . Host )
2024-03-15 17:49:11 -05:00
scheme := "http"
2024-02-01 20:38:06 +00:00
if h . SSLConfig . SSLMode == SSLAuto || h . SSLConfig . SSLMode == SSLStatic {
2024-02-05 23:23:04 -06:00
h . setHeaderIfNotExists ( r , "X-Forwarded-Proto" , "https" )
h . setHeaderIfNotExists ( r , "X-Forwarded-Port" , "443" )
2024-03-15 17:49:11 -05:00
scheme = "https"
2024-02-01 20:01:46 +00:00
}
2024-03-15 17:49:11 -05:00
r . Header . Set ( "X-Forwarded-URL" , fmt . Sprintf ( "%s://%s%s" , scheme , r . Host , r . URL . String ( ) ) )
2021-04-01 02:37:28 -05:00
r . URL . Path = uu . Path
r . URL . Host = uu . Host
r . URL . Scheme = uu . Scheme
2024-01-25 12:28:54 +03:00
if ! keepHost {
r . Host = uu . Host
2024-07-23 11:27:58 -05:00
} else {
log . Printf ( "[DEBUG] keep host %s" , r . Host )
2024-01-25 12:28:54 +03:00
}
2021-04-04 02:57:34 -05:00
h . setXRealIP ( r )
2021-04-01 02:37:28 -05:00
} ,
2021-04-09 20:55:21 -05:00
Transport : & http . Transport {
2021-04-12 21:54:59 -05:00
ResponseHeaderTimeout : h . Timeouts . ResponseHeader ,
2021-04-09 20:55:21 -05:00
DialContext : ( & net . Dialer {
2021-04-12 21:54:59 -05:00
Timeout : h . Timeouts . Dial ,
KeepAlive : h . Timeouts . KeepAlive ,
2021-04-09 20:55:21 -05:00
} ) . DialContext ,
ForceAttemptHTTP2 : true ,
MaxIdleConns : 100 ,
2021-04-12 21:54:59 -05:00
IdleConnTimeout : h . Timeouts . IdleConn ,
TLSHandshakeTimeout : h . Timeouts . TLSHandshake ,
ExpectContinueTimeout : h . Timeouts . ExpectContinue ,
2024-01-07 22:19:50 +04:00
TLSClientConfig : & tls . Config { InsecureSkipVerify : h . Insecure } , //nolint:gosec // G402: User defined option to disable verification for self-signed certificates
2021-04-09 20:55:21 -05:00
} ,
2021-04-13 14:03:25 -05:00
ErrorLog : log . ToStdLogger ( log . Default ( ) , "WARN" ) ,
2021-04-09 20:54:02 -05:00
}
2021-04-30 04:03:36 -05:00
assetsHandler := h . assetsHandler ( )
2021-04-01 02:37:28 -05:00
return func ( w http . ResponseWriter , r * http . Request ) {
2021-06-01 03:59:23 -05:00
if r . Context ( ) . Value ( ctxMatch ) == nil { // no route match detected by matchHandler
2021-04-30 04:03:36 -05:00
if h . isAssetRequest ( r ) {
assetsHandler . ServeHTTP ( w , r )
return
}
log . Printf ( "[WARN] no match for %s %s" , r . URL . Hostname ( ) , r . URL . Path )
h . Reporter . Report ( w , http . StatusBadGateway )
2021-04-01 02:37:28 -05:00
return
}
2021-06-01 03:59:23 -05:00
match := r . Context ( ) . Value ( ctxMatch ) . ( discovery . MatchedRoute )
2021-06-01 02:56:39 -05:00
matchType := r . Context ( ) . Value ( ctxMatchType ) . ( discovery . MatchType )
switch matchType {
2021-04-16 02:49:00 -05:00
case discovery . MTProxy :
2021-06-06 18:13:59 -05:00
switch match . Mapper . RedirectType {
case discovery . RTNone :
uu := r . Context ( ) . Value ( ctxURL ) . ( * url . URL )
log . Printf ( "[DEBUG] proxy to %s" , uu )
reverseProxy . ServeHTTP ( w , r )
case discovery . RTPerm :
log . Printf ( "[DEBUG] redirect (301) to %s" , match . Destination )
http . Redirect ( w , r , match . Destination , http . StatusMovedPermanently )
case discovery . RTTemp :
log . Printf ( "[DEBUG] redirect (302) to %s" , match . Destination )
http . Redirect ( w , r , match . Destination , http . StatusFound )
}
2021-04-16 02:49:00 -05:00
case discovery . MTStatic :
2021-06-07 18:57:42 -05:00
// static match result has webroot:location:[spa:normal], i.e. /www:/var/somedir/:normal
2021-06-01 02:56:39 -05:00
ae := strings . Split ( match . Destination , ":" )
2021-06-07 18:57:42 -05:00
if len ( ae ) != 3 { // shouldn't happen
2021-06-01 03:25:35 -05:00
log . Printf ( "[WARN] unexpected static assets destination: %s" , match . Destination )
2021-04-30 04:03:36 -05:00
h . Reporter . Report ( w , http . StatusInternalServerError )
2021-04-16 02:49:00 -05:00
return
}
2022-01-05 23:57:05 -06:00
fs , err := h . fileServer ( ae [ 0 ] , ae [ 1 ] , ae [ 2 ] == "spa" , nil )
2021-04-16 02:49:00 -05:00
if err != nil {
2021-06-01 03:25:35 -05:00
log . Printf ( "[WARN] file server error, %v" , err )
2021-04-30 04:03:36 -05:00
h . Reporter . Report ( w , http . StatusInternalServerError )
2021-04-16 02:49:00 -05:00
return
}
2021-04-26 18:51:48 -05:00
h . CacheControl . Middleware ( fs ) . ServeHTTP ( w , r )
2021-04-01 02:37:28 -05:00
}
}
}
2021-04-02 03:13:49 -05:00
2021-06-01 02:56:39 -05:00
// matchHandler is a part of middleware chain. Matches incoming request to one or more matched rules
// and if match found sets it to the request context. Context used by proxy handler as well as by plugin conductor
func ( h * Http ) matchHandler ( next http . Handler ) http . Handler {
2021-05-16 18:34:51 -05:00
2023-11-27 12:05:17 -06:00
getMatch := func ( mm discovery . Matches , picker LBSelector ) ( m discovery . MatchedRoute , ok bool ) {
2021-06-01 02:56:39 -05:00
if len ( mm . Routes ) == 0 {
return m , false
2021-05-16 18:34:51 -05:00
}
2021-05-28 16:11:16 -05:00
2021-06-01 02:56:39 -05:00
var matches [ ] discovery . MatchedRoute
for _ , m := range mm . Routes {
if m . Alive {
matches = append ( matches , m )
}
}
switch len ( matches ) {
case 0 :
return m , false
case 1 :
return matches [ 0 ] , true
default :
2023-11-27 12:05:17 -06:00
return matches [ picker . Select ( len ( matches ) ) ] , true
2021-06-01 02:56:39 -05:00
}
2021-05-16 18:34:51 -05:00
}
2021-06-01 02:56:39 -05:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
server := r . URL . Hostname ( )
if server == "" {
2021-06-07 18:57:42 -05:00
server = strings . Split ( r . Host , ":" ) [ 0 ] // drop port
2021-06-01 02:56:39 -05:00
}
2021-12-06 14:50:34 +00:00
matches := h . Match ( server , r . URL . EscapedPath ( ) ) // get all matches for the server:path pair
2021-06-01 02:56:39 -05:00
match , ok := getMatch ( matches , h . LBSelector )
if ok {
2021-06-01 03:59:23 -05:00
ctx := context . WithValue ( r . Context ( ) , ctxMatch , match ) // set match info
ctx = context . WithValue ( ctx , ctxMatchType , matches . MatchType ) // set match type
ctx = context . WithValue ( ctx , plugin . CtxMatch , match ) // set match info for plugin conductor
2021-06-01 03:50:20 -05:00
if matches . MatchType == discovery . MTProxy {
uu , err := url . Parse ( match . Destination )
if err != nil {
log . Printf ( "[WARN] can't parse destination %s, %v" , match . Destination , err )
h . Reporter . Report ( w , http . StatusBadGateway )
return
}
ctx = context . WithValue ( ctx , ctxURL , uu ) // set destination url in request's context
2024-01-25 12:28:54 +03:00
var keepHost bool
if match . Mapper . KeepHost == nil {
keepHost = h . KeepHost
} else {
keepHost = * match . Mapper . KeepHost
}
ctx = context . WithValue ( ctx , ctxKeepHost , keepHost ) // set keep host in request's context
2021-06-01 02:56:39 -05:00
}
r = r . WithContext ( ctx )
}
next . ServeHTTP ( w , r )
} )
2021-05-16 18:34:51 -05:00
}
2021-04-30 04:03:36 -05:00
func ( h * Http ) assetsHandler ( ) http . HandlerFunc {
if h . AssetsLocation == "" || h . AssetsWebRoot == "" {
2024-03-15 16:53:10 -05:00
return func ( _ http . ResponseWriter , _ * http . Request ) { }
2021-04-30 04:03:36 -05:00
}
2022-01-05 23:57:05 -06:00
var notFound [ ] byte
var err error
if h . Assets404 != "" {
if notFound , err = os . ReadFile ( filepath . Join ( h . AssetsLocation , h . Assets404 ) ) ; err != nil {
log . Printf ( "[WARN] can't read 404 file %s, %v" , h . Assets404 , err )
notFound = nil
}
}
log . Printf ( "[DEBUG] shared assets server enabled for %s %s, spa=%v, not-found=%q" ,
h . AssetsLocation , h . AssetsWebRoot , h . AssetsSPA , h . Assets404 )
fs , err := h . fileServer ( h . AssetsWebRoot , h . AssetsLocation , h . AssetsSPA , notFound )
2021-04-30 04:03:36 -05:00
if err != nil {
log . Printf ( "[WARN] can't initialize assets server, %v" , err )
2024-03-15 16:53:10 -05:00
return func ( _ http . ResponseWriter , _ * http . Request ) { }
2021-04-30 04:03:36 -05:00
}
return h . CacheControl . Middleware ( fs ) . ServeHTTP
}
2022-01-05 23:57:05 -06:00
func ( h * Http ) fileServer ( assetsWebRoot , assetsLocation string , spa bool , notFound [ ] byte ) ( http . Handler , error ) {
var notFoundReader io . Reader
if notFound != nil {
notFoundReader = bytes . NewReader ( notFound )
}
2021-06-07 18:57:42 -05:00
if spa {
2022-01-06 00:12:29 -06:00
return R . NewFileServer ( assetsWebRoot , assetsLocation , R . FsOptCustom404 ( notFoundReader ) , R . FsOptSPA )
2021-06-07 18:57:42 -05:00
}
2022-01-06 00:12:29 -06:00
return R . NewFileServer ( assetsWebRoot , assetsLocation , R . FsOptCustom404 ( notFoundReader ) )
2021-06-07 18:57:42 -05:00
}
2021-04-30 04:03:36 -05:00
func ( h * Http ) isAssetRequest ( r * http . Request ) bool {
if h . AssetsLocation == "" || h . AssetsWebRoot == "" {
return false
}
root := strings . TrimSuffix ( h . AssetsWebRoot , "/" )
return r . URL . Path == root || strings . HasPrefix ( r . URL . Path , root + "/" )
}
2021-04-09 15:05:22 -05:00
func ( h * Http ) toHTTP ( address string , httpPort int ) string {
2021-04-05 22:12:06 -05:00
rx := regexp . MustCompile ( ` (.*):(\d*) ` )
return rx . ReplaceAllString ( address , "$1:" ) + strconv . Itoa ( httpPort )
}
2021-06-01 02:56:39 -05:00
func ( h * Http ) pluginHandler ( ) func ( next http . Handler ) http . Handler {
2021-07-03 01:23:50 -05:00
if h . PluginConductor == nil {
return passThroughHandler
2021-04-13 12:45:49 -05:00
}
2021-07-03 01:23:50 -05:00
log . Printf ( "[INFO] plugin support enabled" )
return h . PluginConductor . Middleware
2021-04-13 12:45:49 -05:00
}
2021-05-22 10:44:21 -05:00
func ( h * Http ) mgmtHandler ( ) func ( next http . Handler ) http . Handler {
2021-07-03 01:23:50 -05:00
if h . Metrics == nil {
return passThroughHandler
2021-05-22 10:44:21 -05:00
}
2021-07-03 01:23:50 -05:00
log . Printf ( "[DEBUG] metrics enabled" )
return h . Metrics . Middleware
2021-05-12 21:54:41 -05:00
}
2021-04-02 03:13:49 -05:00
func ( h * Http ) makeHTTPServer ( addr string , router http . Handler ) * http . Server {
return & http . Server {
Addr : addr ,
Handler : router ,
2021-04-12 21:54:59 -05:00
ReadHeaderTimeout : h . Timeouts . ReadHeader ,
WriteTimeout : h . Timeouts . Write ,
IdleTimeout : h . Timeouts . Idle ,
2021-04-02 03:13:49 -05:00
}
}
2021-04-04 02:57:34 -05:00
func ( h * Http ) setXRealIP ( r * http . Request ) {
2023-11-25 13:54:14 -06:00
if forwarded := r . Header . Get ( "X-Forwarded-For" ) ; forwarded != "" {
// use the left-most non-private client IP address
// if there is no any non-private IP address, use the left-most address
r . Header . Set ( "X-Real-IP" , preferPublicIP ( strings . Split ( forwarded , "," ) ) )
return
2021-04-06 23:17:50 -05:00
}
2023-11-25 13:54:14 -06:00
ip , _ , err := net . SplitHostPort ( r . RemoteAddr )
2021-04-04 02:57:34 -05:00
if err != nil {
return
}
userIP := net . ParseIP ( ip )
if userIP == nil {
return
}
2023-11-25 13:54:14 -06:00
r . Header . Set ( "X-Real-IP" , ip )
2021-04-04 02:57:34 -05:00
}
2022-02-23 17:56:59 -06:00
// discoveredServers gets the list of servers discovered by providers.
// The underlying discovery is async and may happen not right away.
// We should try to get servers for some time and make sure we have the complete list of servers
// by checking if the number of servers has not changed between two calls.
func ( h * Http ) discoveredServers ( ctx context . Context , interval time . Duration ) ( servers [ ] string ) {
discoveredServers := 0
for i := 0 ; i < 100 ; i ++ {
select {
case <- ctx . Done ( ) :
return nil
default :
}
servers = h . Servers ( ) // fill all discovered if nothing defined
if len ( servers ) > 0 && len ( servers ) == discoveredServers {
break
}
discoveredServers = len ( servers )
time . Sleep ( interval )
}
return servers
}
2024-02-05 23:23:04 -06:00
func ( h * Http ) setHeaderIfNotExists ( r * http . Request , key , value string ) {
if _ , ok := r . Header [ key ] ; ! ok {
r . Header . Set ( key , value )
}
}