Resync oxy with original repository
This commit is contained in:
parent
da5e4a13bf
commit
bee8ebb00b
4
glide.lock
generated
4
glide.lock
generated
|
@ -1,4 +1,4 @@
|
||||||
hash: 2a604d8b74e8659df7db72d063432fa0822fdee6c81bc6657efa3c1bf0d9cd8a
|
hash: fec4fec4363272870c49e10cea64cc51095ecd0987b9c020c9714d950cf38784
|
||||||
updated: 2017-11-17T14:21:55.148450413+01:00
|
updated: 2017-11-17T14:21:55.148450413+01:00
|
||||||
imports:
|
imports:
|
||||||
- name: cloud.google.com/go
|
- name: cloud.google.com/go
|
||||||
|
@ -516,7 +516,7 @@ imports:
|
||||||
- name: github.com/VividCortex/gohistogram
|
- name: github.com/VividCortex/gohistogram
|
||||||
version: 51564d9861991fb0ad0f531c99ef602d0f9866e6
|
version: 51564d9861991fb0ad0f531c99ef602d0f9866e6
|
||||||
- name: github.com/vulcand/oxy
|
- name: github.com/vulcand/oxy
|
||||||
version: 7e9763c4dc71b9758379da3581e6495c145caaab
|
version: 7b6e758ab449705195df638765c4ca472248908a
|
||||||
repo: https://github.com/containous/oxy.git
|
repo: https://github.com/containous/oxy.git
|
||||||
vcs: git
|
vcs: git
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -12,7 +12,7 @@ import:
|
||||||
- package: github.com/cenk/backoff
|
- package: github.com/cenk/backoff
|
||||||
- package: github.com/containous/flaeg
|
- package: github.com/containous/flaeg
|
||||||
- package: github.com/vulcand/oxy
|
- package: github.com/vulcand/oxy
|
||||||
version: 7e9763c4dc71b9758379da3581e6495c145caaab
|
version: 7b6e758ab449705195df638765c4ca472248908a
|
||||||
repo: https://github.com/containous/oxy.git
|
repo: https://github.com/containous/oxy.git
|
||||||
vcs: git
|
vcs: git
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -224,10 +224,12 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
|
||||||
var client helloworld.Greeter_StreamExampleClient
|
var client helloworld.Greeter_StreamExampleClient
|
||||||
client, closer, err := callStreamExampleClientGRPC()
|
client, closer, err := callStreamExampleClientGRPC()
|
||||||
defer closer()
|
defer closer()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
received := make(chan bool)
|
received := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
tr, _ := client.Recv()
|
tr, err := client.Recv()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(tr.Data), check.Equals, 512)
|
c.Assert(len(tr.Data), check.Equals, 512)
|
||||||
received <- true
|
received <- true
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -395,7 +395,7 @@ func (s *WebsocketSuite) TestURLWithURLEncodedChar(c *check.C) {
|
||||||
var upgrader = gorillawebsocket.Upgrader{} // use default options
|
var upgrader = gorillawebsocket.Upgrader{} // use default options
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
c.Assert(r.URL.Path, check.Equals, "/ws/http%3A%2F%2Ftest")
|
c.Assert(r.URL.EscapedPath(), check.Equals, "/ws/http%3A%2F%2Ftest")
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,29 +2,8 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containous/traefik/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OxyLogger implements oxy Logger interface with logrus.
|
|
||||||
type OxyLogger struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs specified string as Debug level in logrus.
|
|
||||||
func (oxylogger *OxyLogger) Infof(format string, args ...interface{}) {
|
|
||||||
log.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs specified string as Warning level in logrus.
|
|
||||||
func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
log.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs specified string as Warningf level in logrus.
|
|
||||||
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
log.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import (
|
||||||
"github.com/eapache/channels"
|
"github.com/eapache/channels"
|
||||||
thoas_stats "github.com/thoas/stats"
|
thoas_stats "github.com/thoas/stats"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
"github.com/vulcand/oxy/cbreaker"
|
|
||||||
"github.com/vulcand/oxy/connlimit"
|
"github.com/vulcand/oxy/connlimit"
|
||||||
"github.com/vulcand/oxy/forward"
|
"github.com/vulcand/oxy/forward"
|
||||||
"github.com/vulcand/oxy/ratelimit"
|
"github.com/vulcand/oxy/ratelimit"
|
||||||
|
@ -48,10 +47,6 @@ import (
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
oxyLogger = &OxyLogger{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server is the reverse-proxy/load-balancer engine
|
// Server is the reverse-proxy/load-balancer engine
|
||||||
type Server struct {
|
type Server struct {
|
||||||
serverEntryPoints serverEntryPoints
|
serverEntryPoints serverEntryPoints
|
||||||
|
@ -959,7 +954,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
|
||||||
}
|
}
|
||||||
|
|
||||||
fwd, err := forward.New(
|
fwd, err := forward.New(
|
||||||
forward.Logger(oxyLogger),
|
forward.Stream(true),
|
||||||
forward.PassHostHeader(frontend.PassHostHeader),
|
forward.PassHostHeader(frontend.PassHostHeader),
|
||||||
forward.RoundTripper(roundTripper),
|
forward.RoundTripper(roundTripper),
|
||||||
forward.ErrorHandler(errorHandler),
|
forward.ErrorHandler(errorHandler),
|
||||||
|
@ -1006,10 +1001,10 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
|
||||||
switch lbMethod {
|
switch lbMethod {
|
||||||
case types.Drr:
|
case types.Drr:
|
||||||
log.Debugf("Creating load-balancer drr")
|
log.Debugf("Creating load-balancer drr")
|
||||||
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
|
rebalancer, _ := roundrobin.NewRebalancer(rr)
|
||||||
if sticky != nil {
|
if sticky != nil {
|
||||||
log.Debugf("Sticky session with cookie %v", cookieName)
|
log.Debugf("Sticky session with cookie %v", cookieName)
|
||||||
rebalancer, _ = roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger), roundrobin.RebalancerStickySession(sticky))
|
rebalancer, _ = roundrobin.NewRebalancer(rr, roundrobin.RebalancerStickySession(sticky))
|
||||||
}
|
}
|
||||||
lb = rebalancer
|
lb = rebalancer
|
||||||
if err := configureLBServers(rebalancer, config, frontend); err != nil {
|
if err := configureLBServers(rebalancer, config, frontend); err != nil {
|
||||||
|
@ -1080,7 +1075,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
|
||||||
continue frontend
|
continue frontend
|
||||||
}
|
}
|
||||||
log.Debugf("Creating load-balancer connlimit")
|
log.Debugf("Creating load-balancer connlimit")
|
||||||
lb, err = connlimit.New(lb, extractFunc, maxConns.Amount, connlimit.Logger(oxyLogger))
|
lb, err = connlimit.New(lb, extractFunc, maxConns.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating connlimit: %v", err)
|
log.Errorf("Error creating connlimit: %v", err)
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
@ -1151,7 +1146,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
|
||||||
|
|
||||||
if config.Backends[frontend.Backend].CircuitBreaker != nil {
|
if config.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||||
log.Debugf("Creating circuit breaker %s", config.Backends[frontend.Backend].CircuitBreaker.Expression)
|
log.Debugf("Creating circuit breaker %s", config.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||||
circuitBreaker, err := middlewares.NewCircuitBreaker(lb, config.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))
|
circuitBreaker, err := middlewares.NewCircuitBreaker(lb, config.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating circuit breaker: %v", err)
|
log.Errorf("Error creating circuit breaker: %v", err)
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
@ -1445,7 +1440,7 @@ func (server *Server) buildRateLimiter(handler http.Handler, rlConfig *types.Rat
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ratelimit.New(handler, extractFunc, rateSet, ratelimit.Logger(oxyLogger))
|
return ratelimit.New(handler, extractFunc, rateSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) buildRetryMiddleware(handler http.Handler, globalConfig configuration.GlobalConfiguration, countServers int, backendName string) http.Handler {
|
func (server *Server) buildRetryMiddleware(handler http.Handler, globalConfig configuration.GlobalConfiguration, countServers int, backendName string) http.Handler {
|
||||||
|
|
27
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
27
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
|
@ -1,4 +1,4 @@
|
||||||
// package cbreaker implements circuit breaker similar to https://github.com/Netflix/Hystrix/wiki/How-it-Works
|
// Package cbreaker implements circuit breaker similar to https://github.com/Netflix/Hystrix/wiki/How-it-Works
|
||||||
//
|
//
|
||||||
// Vulcan circuit breaker watches the error condtion to match
|
// Vulcan circuit breaker watches the error condtion to match
|
||||||
// after which it activates the fallback scenario, e.g. returns the response code
|
// after which it activates the fallback scenario, e.g. returns the response code
|
||||||
|
@ -31,6 +31,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/mailgun/timetools"
|
"github.com/mailgun/timetools"
|
||||||
"github.com/vulcand/oxy/memmetrics"
|
"github.com/vulcand/oxy/memmetrics"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
|
@ -60,7 +62,6 @@ type CircuitBreaker struct {
|
||||||
fallback http.Handler
|
fallback http.Handler
|
||||||
next http.Handler
|
next http.Handler
|
||||||
|
|
||||||
log utils.Logger
|
|
||||||
clock timetools.TimeProvider
|
clock timetools.TimeProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,6 @@ func New(next http.Handler, expression string, options ...CircuitBreakerOption)
|
||||||
fallbackDuration: defaultFallbackDuration,
|
fallbackDuration: defaultFallbackDuration,
|
||||||
recoveryDuration: defaultRecoveryDuration,
|
recoveryDuration: defaultRecoveryDuration,
|
||||||
fallback: defaultFallback,
|
fallback: defaultFallback,
|
||||||
log: utils.NullLogger,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range options {
|
for _, s := range options {
|
||||||
|
@ -100,6 +100,11 @@ func New(next http.Handler, expression string, options ...CircuitBreakerOption)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/circuitbreaker: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/circuitbreaker: competed ServeHttp on request")
|
||||||
|
}
|
||||||
if c.activateFallback(w, req) {
|
if c.activateFallback(w, req) {
|
||||||
c.fallback.ServeHTTP(w, req)
|
c.fallback.ServeHTTP(w, req)
|
||||||
return
|
return
|
||||||
|
@ -121,7 +126,7 @@ func (c *CircuitBreaker) activateFallback(w http.ResponseWriter, req *http.Reque
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
|
||||||
c.log.Infof("%v is in error state", c)
|
log.Infof("%v is in error state", c)
|
||||||
|
|
||||||
switch c.state {
|
switch c.state {
|
||||||
case stateStandby:
|
case stateStandby:
|
||||||
|
@ -186,13 +191,13 @@ func (c *CircuitBreaker) exec(s SideEffect) {
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.Exec(); err != nil {
|
if err := s.Exec(); err != nil {
|
||||||
c.log.Errorf("%v side effect failure: %v", c, err)
|
log.Errorf("%v side effect failure: %v", c, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CircuitBreaker) setState(new cbState, until time.Time) {
|
func (c *CircuitBreaker) setState(new cbState, until time.Time) {
|
||||||
c.log.Infof("%v setting state to %v, until %v", c, new, until)
|
log.Infof("%v setting state to %v, until %v", c, new, until)
|
||||||
c.state = new
|
c.state = new
|
||||||
c.until = until
|
c.until = until
|
||||||
switch new {
|
switch new {
|
||||||
|
@ -225,7 +230,7 @@ func (c *CircuitBreaker) checkAndSet() {
|
||||||
c.lastCheck = c.clock.UtcNow().Add(c.checkPeriod)
|
c.lastCheck = c.clock.UtcNow().Add(c.checkPeriod)
|
||||||
|
|
||||||
if c.state == stateTripped {
|
if c.state == stateTripped {
|
||||||
c.log.Infof("%v skip set tripped", c)
|
log.Infof("%v skip set tripped", c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,14 +314,6 @@ func Fallback(h http.Handler) CircuitBreakerOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger adds logging for the CircuitBreaker.
|
|
||||||
func Logger(l utils.Logger) CircuitBreakerOption {
|
|
||||||
return func(c *CircuitBreaker) error {
|
|
||||||
c.log = l
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cbState is the state of the circuit breaker
|
// cbState is the state of the circuit breaker
|
||||||
type cbState int
|
type cbState int
|
||||||
|
|
||||||
|
|
4
vendor/github.com/vulcand/oxy/cbreaker/effect.go
generated
vendored
4
vendor/github.com/vulcand/oxy/cbreaker/effect.go
generated
vendored
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,9 +69,10 @@ func (w *WebhookSideEffect) Exec() error {
|
||||||
if re.Body != nil {
|
if re.Body != nil {
|
||||||
defer re.Body.Close()
|
defer re.Body.Close()
|
||||||
}
|
}
|
||||||
_, err = ioutil.ReadAll(re.Body)
|
body, err := ioutil.ReadAll(re.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Infof("%v got response: (%s): %s", w, re.Status, string(body))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
38
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
38
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
|
@ -5,6 +5,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@ -25,20 +28,31 @@ func NewResponseFallback(r Response) (*ResponseFallback, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/fallback/response: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/fallback/response: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
if f.r.ContentType != "" {
|
if f.r.ContentType != "" {
|
||||||
w.Header().Set("Content-Type", f.r.ContentType)
|
w.Header().Set("Content-Type", f.r.ContentType)
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(f.r.Body)))
|
w.Header().Set("Content-Length", strconv.Itoa(len(f.r.Body)))
|
||||||
w.WriteHeader(f.r.StatusCode)
|
w.WriteHeader(f.r.StatusCode)
|
||||||
w.Write(f.r.Body)
|
_, err := w.Write(f.r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("vulcand/oxy/fallback/response: failed to write response, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
URL string
|
URL string
|
||||||
|
PreservePath bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedirectFallback struct {
|
type RedirectFallback struct {
|
||||||
u *url.URL
|
u *url.URL
|
||||||
|
r Redirect
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedirectFallback(r Redirect) (*RedirectFallback, error) {
|
func NewRedirectFallback(r Redirect) (*RedirectFallback, error) {
|
||||||
|
@ -46,11 +60,25 @@ func NewRedirectFallback(r Redirect) (*RedirectFallback, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &RedirectFallback{u: u}, nil
|
return &RedirectFallback{u: u, r: r}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *RedirectFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (f *RedirectFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Header().Set("Location", f.u.String())
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/fallback/redirect: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/fallback/redirect: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
|
location := f.u.String()
|
||||||
|
if f.r.PreservePath {
|
||||||
|
location += req.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Location", location)
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
w.Write([]byte(http.StatusText(http.StatusFound)))
|
_, err := w.Write([]byte(http.StatusText(http.StatusFound)))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("vulcand/oxy/fallback/redirect: failed to write response, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
vendor/github.com/vulcand/oxy/cbreaker/predicates.go
generated
vendored
3
vendor/github.com/vulcand/oxy/cbreaker/predicates.go
generated
vendored
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/vulcand/predicate"
|
"github.com/vulcand/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ func latencyAtQuantile(quantile float64) toInt {
|
||||||
return func(c *CircuitBreaker) int {
|
return func(c *CircuitBreaker) int {
|
||||||
h, err := c.metrics.LatencyHistogram()
|
h, err := c.metrics.LatencyHistogram()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Errorf("Failed to get latency histogram, for %v error: %v", c, err)
|
log.Errorf("Failed to get latency histogram, for %v error: %v", c, err)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return int(h.LatencyAtQuantile(quantile) / time.Millisecond)
|
return int(h.LatencyAtQuantile(quantile) / time.Millisecond)
|
||||||
|
|
4
vendor/github.com/vulcand/oxy/cbreaker/ratio.go
generated
vendored
4
vendor/github.com/vulcand/oxy/cbreaker/ratio.go
generated
vendored
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/mailgun/timetools"
|
"github.com/mailgun/timetools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,14 +34,17 @@ func (r *ratioController) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ratioController) allowRequest() bool {
|
func (r *ratioController) allowRequest() bool {
|
||||||
|
log.Infof("%v", r)
|
||||||
t := r.targetRatio()
|
t := r.targetRatio()
|
||||||
// This condition answers the question - would we satisfy the target ratio if we allow this request?
|
// This condition answers the question - would we satisfy the target ratio if we allow this request?
|
||||||
e := r.computeRatio(r.allowed+1, r.denied)
|
e := r.computeRatio(r.allowed+1, r.denied)
|
||||||
if e < t {
|
if e < t {
|
||||||
r.allowed++
|
r.allowed++
|
||||||
|
log.Infof("%v allowed", r)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
r.denied++
|
r.denied++
|
||||||
|
log.Infof("%v denied", r)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
vendor/github.com/vulcand/oxy/connlimit/connlimit.go
generated
vendored
27
vendor/github.com/vulcand/oxy/connlimit/connlimit.go
generated
vendored
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +21,6 @@ type ConnLimiter struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
|
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
log utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(next http.Handler, extract utils.SourceExtractor, maxConnections int64, options ...ConnLimitOption) (*ConnLimiter, error) {
|
func New(next http.Handler, extract utils.SourceExtractor, maxConnections int64, options ...ConnLimitOption) (*ConnLimiter, error) {
|
||||||
|
@ -40,9 +40,6 @@ func New(next http.Handler, extract utils.SourceExtractor, maxConnections int64,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cl.log == nil {
|
|
||||||
cl.log = utils.NullLogger
|
|
||||||
}
|
|
||||||
if cl.errHandler == nil {
|
if cl.errHandler == nil {
|
||||||
cl.errHandler = defaultErrHandler
|
cl.errHandler = defaultErrHandler
|
||||||
}
|
}
|
||||||
|
@ -56,12 +53,12 @@ func (cl *ConnLimiter) Wrap(h http.Handler) {
|
||||||
func (cl *ConnLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (cl *ConnLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
token, amount, err := cl.extract.Extract(r)
|
token, amount, err := cl.extract.Extract(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cl.log.Errorf("failed to extract source of the connection: %v", err)
|
log.Errorf("failed to extract source of the connection: %v", err)
|
||||||
cl.errHandler.ServeHTTP(w, r, err)
|
cl.errHandler.ServeHTTP(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := cl.acquire(token, amount); err != nil {
|
if err := cl.acquire(token, amount); err != nil {
|
||||||
cl.log.Infof("limiting request source %s: %v", token, err)
|
log.Infof("limiting request source %s: %v", token, err)
|
||||||
cl.errHandler.ServeHTTP(w, r, err)
|
cl.errHandler.ServeHTTP(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -81,7 +78,7 @@ func (cl *ConnLimiter) acquire(token string, amount int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.connections[token] += amount
|
cl.connections[token] += amount
|
||||||
cl.totalConnections += int64(amount)
|
cl.totalConnections += amount
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +87,7 @@ func (cl *ConnLimiter) release(token string, amount int64) {
|
||||||
defer cl.mutex.Unlock()
|
defer cl.mutex.Unlock()
|
||||||
|
|
||||||
cl.connections[token] -= amount
|
cl.connections[token] -= amount
|
||||||
cl.totalConnections -= int64(amount)
|
cl.totalConnections -= amount
|
||||||
|
|
||||||
// Otherwise it would grow forever
|
// Otherwise it would grow forever
|
||||||
if cl.connections[token] == 0 {
|
if cl.connections[token] == 0 {
|
||||||
|
@ -110,6 +107,12 @@ type ConnErrHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ConnErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
func (e *ConnErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/connlimit: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/connlimit: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := err.(*MaxConnError); ok {
|
if _, ok := err.(*MaxConnError); ok {
|
||||||
w.WriteHeader(429)
|
w.WriteHeader(429)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
|
@ -120,14 +123,6 @@ func (e *ConnErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err
|
||||||
|
|
||||||
type ConnLimitOption func(l *ConnLimiter) error
|
type ConnLimitOption func(l *ConnLimiter) error
|
||||||
|
|
||||||
// Logger sets the logger that will be used by this middleware.
|
|
||||||
func Logger(l utils.Logger) ConnLimitOption {
|
|
||||||
return func(cl *ConnLimiter) error {
|
|
||||||
cl.log = l
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorHandler sets error handler of the server
|
// ErrorHandler sets error handler of the server
|
||||||
func ErrorHandler(h utils.ErrorHandler) ConnLimitOption {
|
func ErrorHandler(h utils.ErrorHandler) ConnLimitOption {
|
||||||
return func(cl *ConnLimiter) error {
|
return func(cl *ConnLimiter) error {
|
||||||
|
|
394
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
394
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -7,13 +7,15 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
@ -29,15 +31,7 @@ type optSetter func(f *Forwarder) error
|
||||||
// be delegated
|
// be delegated
|
||||||
func PassHostHeader(b bool) optSetter {
|
func PassHostHeader(b bool) optSetter {
|
||||||
return func(f *Forwarder) error {
|
return func(f *Forwarder) error {
|
||||||
f.passHost = b
|
f.httpForwarder.passHost = b
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamResponse forces streaming body (flushes response directly to client)
|
|
||||||
func StreamResponse(b bool) optSetter {
|
|
||||||
return func(f *Forwarder) error {
|
|
||||||
f.httpForwarder.streamResponse = b
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +40,7 @@ func StreamResponse(b bool) optSetter {
|
||||||
// Forwarder will use http.DefaultTransport as a default round tripper
|
// Forwarder will use http.DefaultTransport as a default round tripper
|
||||||
func RoundTripper(r http.RoundTripper) optSetter {
|
func RoundTripper(r http.RoundTripper) optSetter {
|
||||||
return func(f *Forwarder) error {
|
return func(f *Forwarder) error {
|
||||||
f.roundTripper = r
|
f.httpForwarder.roundTripper = r
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,10 +53,11 @@ func Rewriter(r ReqRewriter) optSetter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebsocketRewriter defines a request rewriter for the websocket forwarder
|
// PassHostHeader specifies if a client's Host header field should
|
||||||
func WebsocketRewriter(r ReqRewriter) optSetter {
|
// be delegated
|
||||||
|
func WebsocketTLSClientConfig(tcc *tls.Config) optSetter {
|
||||||
return func(f *Forwarder) error {
|
return func(f *Forwarder) error {
|
||||||
f.websocketForwarder.rewriter = r
|
f.httpForwarder.tlsClientConfig = tcc
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,27 +70,74 @@ func ErrorHandler(h utils.ErrorHandler) optSetter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger specifies the logger to use.
|
// Stream specifies if HTTP responses should be streamed.
|
||||||
// Forwarder will default to oxyutils.NullLogger if no logger has been specified
|
func Stream(stream bool) optSetter {
|
||||||
func Logger(l utils.Logger) optSetter {
|
return func(f *Forwarder) error {
|
||||||
|
f.stream = stream
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger defines the logger the forwarder will use.
|
||||||
|
//
|
||||||
|
// It defaults to logrus.StandardLogger(), the global logger used by logrus.
|
||||||
|
func Logger(l *log.Logger) optSetter {
|
||||||
return func(f *Forwarder) error {
|
return func(f *Forwarder) error {
|
||||||
f.log = l
|
f.log = l
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StateListener(stateListener UrlForwardingStateListener) optSetter {
|
||||||
|
return func(f *Forwarder) error {
|
||||||
|
f.stateListener = stateListener
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResponseModifier(responseModifier func(*http.Response) error) optSetter {
|
||||||
|
return func(f *Forwarder) error {
|
||||||
|
f.httpForwarder.modifyResponse = responseModifier
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamingFlushInterval(flushInterval time.Duration) optSetter {
|
||||||
|
return func(f *Forwarder) error {
|
||||||
|
f.httpForwarder.flushInterval = flushInterval
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorHandlingRoundTripper struct {
|
||||||
|
http.RoundTripper
|
||||||
|
errorHandler utils.ErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt ErrorHandlingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
res, err := rt.RoundTripper.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
// We use the recorder from httptest because there isn't another `public` implementation of a recorder.
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
rt.errorHandler.ServeHTTP(recorder, req, err)
|
||||||
|
res = recorder.Result()
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
// Forwarder wraps two traffic forwarding implementations: HTTP and websockets.
|
// Forwarder wraps two traffic forwarding implementations: HTTP and websockets.
|
||||||
// It decides based on the specified request which implementation to use
|
// It decides based on the specified request which implementation to use
|
||||||
type Forwarder struct {
|
type Forwarder struct {
|
||||||
*httpForwarder
|
*httpForwarder
|
||||||
*websocketForwarder
|
|
||||||
*handlerContext
|
*handlerContext
|
||||||
|
stateListener UrlForwardingStateListener
|
||||||
|
stream bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerContext defines a handler context for error reporting and logging
|
// handlerContext defines a handler context for error reporting and logging
|
||||||
type handlerContext struct {
|
type handlerContext struct {
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
log utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpForwarder is a handler that can reverse proxy
|
// httpForwarder is a handler that can reverse proxy
|
||||||
|
@ -104,32 +146,40 @@ type httpForwarder struct {
|
||||||
roundTripper http.RoundTripper
|
roundTripper http.RoundTripper
|
||||||
rewriter ReqRewriter
|
rewriter ReqRewriter
|
||||||
passHost bool
|
passHost bool
|
||||||
streamResponse bool
|
flushInterval time.Duration
|
||||||
|
modifyResponse func(*http.Response) error
|
||||||
|
|
||||||
|
tlsClientConfig *tls.Config
|
||||||
|
|
||||||
|
log *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// websocketForwarder is a handler that can reverse proxy
|
const (
|
||||||
// websocket traffic
|
defaultFlushInterval = time.Duration(100) * time.Millisecond
|
||||||
type websocketForwarder struct {
|
StateConnected = iota
|
||||||
rewriter ReqRewriter
|
StateDisconnected
|
||||||
TLSClientConfig *tls.Config
|
)
|
||||||
}
|
|
||||||
|
type UrlForwardingStateListener func(*url.URL, int)
|
||||||
|
|
||||||
// New creates an instance of Forwarder based on the provided list of configuration options
|
// New creates an instance of Forwarder based on the provided list of configuration options
|
||||||
func New(setters ...optSetter) (*Forwarder, error) {
|
func New(setters ...optSetter) (*Forwarder, error) {
|
||||||
f := &Forwarder{
|
f := &Forwarder{
|
||||||
httpForwarder: &httpForwarder{},
|
httpForwarder: &httpForwarder{log: log.StandardLogger()},
|
||||||
websocketForwarder: &websocketForwarder{},
|
handlerContext: &handlerContext{},
|
||||||
handlerContext: &handlerContext{},
|
|
||||||
}
|
}
|
||||||
for _, s := range setters {
|
for _, s := range setters {
|
||||||
if err := s(f); err != nil {
|
if err := s(f); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.httpForwarder.roundTripper == nil {
|
|
||||||
f.httpForwarder.roundTripper = http.DefaultTransport
|
if !f.stream {
|
||||||
|
f.flushInterval = 0
|
||||||
|
} else if f.flushInterval == 0 {
|
||||||
|
f.flushInterval = defaultFlushInterval
|
||||||
}
|
}
|
||||||
f.websocketForwarder.TLSClientConfig = f.httpForwarder.roundTripper.(*http.Transport).TLSClientConfig
|
|
||||||
if f.httpForwarder.rewriter == nil {
|
if f.httpForwarder.rewriter == nil {
|
||||||
h, err := os.Hostname()
|
h, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -137,136 +187,104 @@ func New(setters ...optSetter) (*Forwarder, error) {
|
||||||
}
|
}
|
||||||
f.httpForwarder.rewriter = &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
|
f.httpForwarder.rewriter = &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
|
||||||
}
|
}
|
||||||
if f.log == nil {
|
|
||||||
f.log = utils.NullLogger
|
if f.httpForwarder.roundTripper == nil {
|
||||||
|
f.httpForwarder.roundTripper = http.DefaultTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.errHandler == nil {
|
if f.errHandler == nil {
|
||||||
f.errHandler = utils.DefaultHandler
|
f.errHandler = utils.DefaultHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.tlsClientConfig == nil {
|
||||||
|
f.tlsClientConfig = f.httpForwarder.roundTripper.(*http.Transport).TLSClientConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
f.httpForwarder.roundTripper = ErrorHandlingRoundTripper{
|
||||||
|
RoundTripper: f.httpForwarder.roundTripper,
|
||||||
|
errorHandler: f.errHandler,
|
||||||
|
}
|
||||||
|
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP decides which forwarder to use based on the specified
|
// ServeHTTP decides which forwarder to use based on the specified
|
||||||
// request and delegates to the proper implementation
|
// request and delegates to the proper implementation
|
||||||
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if isWebsocketRequest(req) {
|
if f.log.Level >= log.DebugLevel {
|
||||||
f.websocketForwarder.serveHTTP(w, req, f.handlerContext)
|
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/forward: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.stateListener != nil {
|
||||||
|
f.stateListener(req.URL, StateConnected)
|
||||||
|
defer f.stateListener(req.URL, StateDisconnected)
|
||||||
|
}
|
||||||
|
if IsWebsocketRequest(req) {
|
||||||
|
f.httpForwarder.serveWebSocket(w, req, f.handlerContext)
|
||||||
} else {
|
} else {
|
||||||
f.httpForwarder.serveHTTP(w, req, f.handlerContext)
|
f.httpForwarder.serveHTTP(w, req, f.handlerContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveHTTP forwards HTTP traffic using the configured transport
|
func (f *httpForwarder) getUrlFromRequest(req *http.Request) *url.URL {
|
||||||
func (f *httpForwarder) serveHTTP(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
|
// If the Request was created by Go via a real HTTP request, RequestURI will
|
||||||
start := time.Now().UTC()
|
// contain the original query string. If the Request was created in code, RequestURI
|
||||||
|
// will be empty, and we will use the URL object instead
|
||||||
response, err := f.roundTripper.RoundTrip(f.copyRequest(req, req.URL))
|
u := req.URL
|
||||||
|
if req.RequestURI != "" {
|
||||||
if err != nil {
|
parsedURL, err := url.ParseRequestURI(req.RequestURI)
|
||||||
ctx.log.Errorf("Error forwarding to %v, err: %v", req.URL, err)
|
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.CopyHeaders(w.Header(), response.Header)
|
|
||||||
// Remove hop-by-hop headers.
|
|
||||||
utils.RemoveHeaders(w.Header(), HopHeaders...)
|
|
||||||
|
|
||||||
announcedTrailerKeyCount := len(response.Trailer)
|
|
||||||
if announcedTrailerKeyCount > 0 {
|
|
||||||
trailerKeys := make([]string, 0, announcedTrailerKeyCount)
|
|
||||||
for k := range response.Trailer {
|
|
||||||
trailerKeys = append(trailerKeys, k)
|
|
||||||
}
|
|
||||||
w.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(response.StatusCode)
|
|
||||||
|
|
||||||
stream := f.streamResponse
|
|
||||||
if !stream {
|
|
||||||
contentType, err := utils.GetHeaderMediaType(response.Header, ContentType)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
stream = contentType == "text/event-stream"
|
u = parsedURL
|
||||||
|
} else {
|
||||||
|
f.log.Warnf("vulcand/oxy/forward: error when parsing RequestURI: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return u
|
||||||
flush := stream || req.ProtoMajor == 2
|
|
||||||
written, err := io.Copy(newResponseFlusher(w, flush), response.Body)
|
|
||||||
if err != nil {
|
|
||||||
ctx.log.Errorf("Error copying upstream response body: %v", err)
|
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
forceSetTrailers := len(response.Trailer) != announcedTrailerKeyCount
|
|
||||||
shallowCopyTrailers(w.Header(), response.Trailer, forceSetTrailers)
|
|
||||||
|
|
||||||
if written != 0 {
|
|
||||||
w.Header().Set(ContentLength, strconv.FormatInt(written, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.TLS != nil {
|
|
||||||
ctx.log.Infof("Round trip: %v, code: %v, duration: %v tls:version: %x, tls:resume:%t, tls:csuite:%x, tls:server:%v",
|
|
||||||
req.URL, response.StatusCode, time.Now().UTC().Sub(start),
|
|
||||||
req.TLS.Version,
|
|
||||||
req.TLS.DidResume,
|
|
||||||
req.TLS.CipherSuite,
|
|
||||||
req.TLS.ServerName)
|
|
||||||
} else {
|
|
||||||
ctx.log.Infof("Round trip: %v, code: %v, duration: %v",
|
|
||||||
req.URL, response.StatusCode, time.Now().UTC().Sub(start))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyRequest makes a copy of the specified request to be sent using the configured
|
// Modify the request to handle the target URL
|
||||||
// transport
|
func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||||
func (f *httpForwarder) copyRequest(req *http.Request, u *url.URL) *http.Request {
|
outReq.URL = utils.CopyURL(outReq.URL)
|
||||||
outReq := new(http.Request)
|
outReq.URL.Scheme = target.Scheme
|
||||||
*outReq = *req // includes shallow copies of maps, but we handle this below
|
outReq.URL.Host = target.Host
|
||||||
|
|
||||||
|
u := f.getUrlFromRequest(outReq)
|
||||||
|
|
||||||
|
outReq.URL.Path = u.Path
|
||||||
|
outReq.URL.RawPath = u.RawPath
|
||||||
|
outReq.URL.RawQuery = u.RawQuery
|
||||||
|
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||||
|
|
||||||
outReq.URL = utils.CopyURL(req.URL)
|
|
||||||
outReq.URL.Scheme = u.Scheme
|
|
||||||
outReq.URL.Host = u.Host
|
|
||||||
outReq.URL.Opaque = req.RequestURI
|
|
||||||
// raw query is already included in RequestURI, so ignore it to avoid dupes
|
|
||||||
outReq.URL.RawQuery = ""
|
|
||||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||||
if !f.passHost {
|
if !f.passHost {
|
||||||
outReq.Host = u.Host
|
outReq.Host = target.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
outReq.Proto = "HTTP/1.1"
|
outReq.Proto = "HTTP/1.1"
|
||||||
outReq.ProtoMajor = 1
|
outReq.ProtoMajor = 1
|
||||||
outReq.ProtoMinor = 1
|
outReq.ProtoMinor = 1
|
||||||
|
|
||||||
// Overwrite close flag so we can keep persistent connection for the backend servers
|
|
||||||
outReq.Close = false
|
|
||||||
|
|
||||||
outReq.Header = make(http.Header)
|
|
||||||
utils.CopyHeaders(outReq.Header, req.Header)
|
|
||||||
|
|
||||||
if f.rewriter != nil {
|
if f.rewriter != nil {
|
||||||
f.rewriter.Rewrite(outReq)
|
f.rewriter.Rewrite(outReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ContentLength == 0 {
|
|
||||||
// https://github.com/golang/go/issues/16036: nil Body for http.Transport retries
|
|
||||||
outReq.Body = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return outReq
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveHTTP forwards websocket traffic
|
// serveHTTP forwards websocket traffic
|
||||||
func (f *websocketForwarder) serveHTTP(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
|
func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
|
||||||
outReq := f.copyRequest(req, req.URL)
|
if f.log.Level >= log.DebugLevel {
|
||||||
|
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/forward/websocket: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/forward/websocket: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
|
outReq := f.copyWebSocketRequest(req)
|
||||||
|
|
||||||
dialer := websocket.DefaultDialer
|
dialer := websocket.DefaultDialer
|
||||||
if outReq.URL.Scheme == "wss" && f.TLSClientConfig != nil {
|
if outReq.URL.Scheme == "wss" && f.tlsClientConfig != nil {
|
||||||
dialer.TLSClientConfig = f.TLSClientConfig.Clone()
|
dialer.TLSClientConfig = f.tlsClientConfig.Clone()
|
||||||
|
// WebSocket is only in http/1.1
|
||||||
dialer.TLSClientConfig.NextProtos = []string{"http/1.1"}
|
dialer.TLSClientConfig.NextProtos = []string{"http/1.1"}
|
||||||
}
|
}
|
||||||
targetConn, resp, err := dialer.Dial(outReq.URL.String(), outReq.Header)
|
targetConn, resp, err := dialer.Dial(outReq.URL.String(), outReq.Header)
|
||||||
|
@ -274,33 +292,33 @@ func (f *websocketForwarder) serveHTTP(w http.ResponseWriter, req *http.Request,
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
ctx.errHandler.ServeHTTP(w, req, err)
|
||||||
} else {
|
} else {
|
||||||
ctx.log.Errorf("Error dialing %q: %v with resp: %d %s", outReq.Host, err, resp.StatusCode, resp.Status)
|
log.Errorf("vulcand/oxy/forward/websocket: Error dialing %q: %v with resp: %d %s", outReq.Host, err, resp.StatusCode, resp.Status)
|
||||||
hijacker, ok := w.(http.Hijacker)
|
hijacker, ok := w.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.log.Errorf("%s can not be hijack", reflect.TypeOf(w))
|
log.Errorf("vulcand/oxy/forward/websocket: %s can not be hijack", reflect.TypeOf(w))
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
ctx.errHandler.ServeHTTP(w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, _, err := hijacker.Hijack()
|
conn, _, errHijack := hijacker.Hijack()
|
||||||
if err != nil {
|
if errHijack != nil {
|
||||||
ctx.log.Errorf("Failed to hijack responseWriter")
|
log.Errorf("vulcand/oxy/forward/websocket: Failed to hijack responseWriter")
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
ctx.errHandler.ServeHTTP(w, req, errHijack)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = resp.Write(conn)
|
errWrite := resp.Write(conn)
|
||||||
if err != nil {
|
if errWrite != nil {
|
||||||
ctx.log.Errorf("Failed to forward response")
|
log.Errorf("vulcand/oxy/forward/websocket: Failed to forward response")
|
||||||
ctx.errHandler.ServeHTTP(w, req, err)
|
ctx.errHandler.ServeHTTP(w, req, errWrite)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Only the targetConn choose to CheckOrigin or not
|
// Only the targetConn choose to CheckOrigin or not
|
||||||
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
}}
|
}}
|
||||||
|
@ -308,62 +326,64 @@ func (f *websocketForwarder) serveHTTP(w http.ResponseWriter, req *http.Request,
|
||||||
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
||||||
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.log.Errorf("Error while upgrading connection : %v", err)
|
log.Errorf("vulcand/oxy/forward/websocket: Error while upgrading connection : %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer underlyingConn.Close()
|
defer underlyingConn.Close()
|
||||||
defer targetConn.Close()
|
defer targetConn.Close()
|
||||||
|
|
||||||
errc := make(chan error, 2)
|
errc := make(chan error, 2)
|
||||||
replicate := func(dst io.Writer, src io.Reader) {
|
replicate := func(dst io.Writer, src io.Reader, dstName string, srcName string) {
|
||||||
_, err := io.Copy(dst, src)
|
_, errCopy := io.Copy(dst, src)
|
||||||
errc <- err
|
if errCopy != nil {
|
||||||
|
f.log.Errorf("vulcand/oxy/forward/websocket: Error when copying from %s to %s using io.Copy: %v", srcName, dstName, errCopy)
|
||||||
|
} else {
|
||||||
|
f.log.Infof("vulcand/oxy/forward/websocket: Copying from %s to %s using io.Copy completed without error.", srcName, dstName)
|
||||||
|
}
|
||||||
|
errc <- errCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn())
|
go replicate(targetConn.UnderlyingConn(), underlyingConn.UnderlyingConn(), "backend", "client")
|
||||||
|
|
||||||
// Try to read the first message
|
// Try to read the first message
|
||||||
t, msg, err := targetConn.ReadMessage()
|
msgType, msg, err := targetConn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.log.Errorf("Couldn't read first message : %v", err)
|
log.Errorf("vulcand/oxy/forward/websocket: Couldn't read first message : %v", err)
|
||||||
} else {
|
} else {
|
||||||
underlyingConn.WriteMessage(t, msg)
|
underlyingConn.WriteMessage(msgType, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn())
|
go replicate(underlyingConn.UnderlyingConn(), targetConn.UnderlyingConn(), "client", "backend")
|
||||||
<-errc
|
<-errc
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyRequest makes a copy of the specified request.
|
// copyWebsocketRequest makes a copy of the specified request.
|
||||||
func (f *websocketForwarder) copyRequest(req *http.Request, u *url.URL) (outReq *http.Request) {
|
func (f *httpForwarder) copyWebSocketRequest(req *http.Request) (outReq *http.Request) {
|
||||||
outReq = new(http.Request)
|
outReq = new(http.Request)
|
||||||
*outReq = *req // includes shallow copies of maps, but we handle this below
|
*outReq = *req // includes shallow copies of maps, but we handle this below
|
||||||
|
|
||||||
outReq.URL = utils.CopyURL(req.URL)
|
outReq.URL = utils.CopyURL(req.URL)
|
||||||
outReq.URL.Scheme = u.Scheme
|
outReq.URL.Scheme = req.URL.Scheme
|
||||||
|
|
||||||
//sometimes backends might be registered as HTTP/HTTPS servers so translate URLs to websocket URLs.
|
// sometimes backends might be registered as HTTP/HTTPS servers so translate URLs to websocket URLs.
|
||||||
switch u.Scheme {
|
switch req.URL.Scheme {
|
||||||
case "https":
|
case "https":
|
||||||
outReq.URL.Scheme = "wss"
|
outReq.URL.Scheme = "wss"
|
||||||
case "http":
|
case "http":
|
||||||
outReq.URL.Scheme = "ws"
|
outReq.URL.Scheme = "ws"
|
||||||
}
|
}
|
||||||
|
|
||||||
if requestURI, err := url.ParseRequestURI(outReq.RequestURI); err == nil {
|
u := f.getUrlFromRequest(outReq)
|
||||||
if requestURI.RawPath != "" {
|
|
||||||
outReq.URL.Path = requestURI.RawPath
|
|
||||||
} else {
|
|
||||||
outReq.URL.Path = requestURI.Path
|
|
||||||
}
|
|
||||||
outReq.URL.RawQuery = requestURI.RawQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
outReq.URL.Host = u.Host
|
outReq.URL.Path = u.Path
|
||||||
|
outReq.URL.RawPath = u.RawPath
|
||||||
|
outReq.URL.RawQuery = u.RawQuery
|
||||||
|
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||||
|
|
||||||
|
outReq.URL.Host = req.URL.Host
|
||||||
|
|
||||||
outReq.Header = make(http.Header)
|
outReq.Header = make(http.Header)
|
||||||
//gorilla websocket use this header to set the request.Host tested in checkSameOrigin
|
// gorilla websocket use this header to set the request.Host tested in checkSameOrigin
|
||||||
outReq.Header.Set("Host", outReq.Host)
|
outReq.Header.Set("Host", outReq.Host)
|
||||||
utils.CopyHeaders(outReq.Header, req.Header)
|
utils.CopyHeaders(outReq.Header, req.Header)
|
||||||
utils.RemoveHeaders(outReq.Header, WebsocketDialHeaders...)
|
utils.RemoveHeaders(outReq.Header, WebsocketDialHeaders...)
|
||||||
|
@ -374,9 +394,48 @@ func (f *websocketForwarder) copyRequest(req *http.Request, u *url.URL) (outReq
|
||||||
return outReq
|
return outReq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serveHTTP forwards HTTP traffic using the configured transport
|
||||||
|
func (f *httpForwarder) serveHTTP(w http.ResponseWriter, inReq *http.Request, ctx *handlerContext) {
|
||||||
|
if f.log.Level >= log.DebugLevel {
|
||||||
|
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(inReq))
|
||||||
|
logEntry.Debug("vulcand/oxy/forward/http: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/forward/http: completed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw := &utils.ProxyWriter{
|
||||||
|
W: w,
|
||||||
|
}
|
||||||
|
start := time.Now().UTC()
|
||||||
|
|
||||||
|
outReq := new(http.Request)
|
||||||
|
*outReq = *inReq // includes shallow copies of maps, but we handle this in Director
|
||||||
|
|
||||||
|
revproxy := httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
f.modifyRequest(req, inReq.URL)
|
||||||
|
},
|
||||||
|
Transport: f.roundTripper,
|
||||||
|
FlushInterval: f.flushInterval,
|
||||||
|
ModifyResponse: f.modifyResponse,
|
||||||
|
}
|
||||||
|
revproxy.ServeHTTP(pw, outReq)
|
||||||
|
|
||||||
|
if inReq.TLS != nil {
|
||||||
|
f.log.Infof("vulcand/oxy/forward/http: Round trip: %v, code: %v, Length: %v, duration: %v tls:version: %x, tls:resume:%t, tls:csuite:%x, tls:server:%v",
|
||||||
|
inReq.URL, pw.Code, pw.Length, time.Now().UTC().Sub(start),
|
||||||
|
inReq.TLS.Version,
|
||||||
|
inReq.TLS.DidResume,
|
||||||
|
inReq.TLS.CipherSuite,
|
||||||
|
inReq.TLS.ServerName)
|
||||||
|
} else {
|
||||||
|
f.log.Infof("vulcand/oxy/forward/http: Round trip: %v, code: %v, Length: %v, duration: %v",
|
||||||
|
inReq.URL, pw.Code, pw.Length, time.Now().UTC().Sub(start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isWebsocketRequest determines if the specified HTTP request is a
|
// isWebsocketRequest determines if the specified HTTP request is a
|
||||||
// websocket handshake request
|
// websocket handshake request
|
||||||
func isWebsocketRequest(req *http.Request) bool {
|
func IsWebsocketRequest(req *http.Request) bool {
|
||||||
containsHeader := func(name, value string) bool {
|
containsHeader := func(name, value string) bool {
|
||||||
items := strings.Split(req.Header.Get(name), ",")
|
items := strings.Split(req.Header.Get(name), ",")
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
@ -388,12 +447,3 @@ func isWebsocketRequest(req *http.Request) bool {
|
||||||
}
|
}
|
||||||
return containsHeader(Connection, "upgrade") && containsHeader(Upgrade, "websocket")
|
return containsHeader(Connection, "upgrade") && containsHeader(Upgrade, "websocket")
|
||||||
}
|
}
|
||||||
|
|
||||||
func shallowCopyTrailers(dstHeader, srcTrailer http.Header, forceSetTrailers bool) {
|
|
||||||
for k, vv := range srcTrailer {
|
|
||||||
if forceSetTrailers {
|
|
||||||
k = http.TrailerPrefix + k
|
|
||||||
}
|
|
||||||
dstHeader[k] = vv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
1
vendor/github.com/vulcand/oxy/forward/headers.go
generated
vendored
1
vendor/github.com/vulcand/oxy/forward/headers.go
generated
vendored
|
@ -16,7 +16,6 @@ const (
|
||||||
TransferEncoding = "Transfer-Encoding"
|
TransferEncoding = "Transfer-Encoding"
|
||||||
Upgrade = "Upgrade"
|
Upgrade = "Upgrade"
|
||||||
ContentLength = "Content-Length"
|
ContentLength = "Content-Length"
|
||||||
ContentType = "Content-Type"
|
|
||||||
SecWebsocketKey = "Sec-Websocket-Key"
|
SecWebsocketKey = "Sec-Websocket-Key"
|
||||||
SecWebsocketVersion = "Sec-Websocket-Version"
|
SecWebsocketVersion = "Sec-Websocket-Version"
|
||||||
SecWebsocketExtensions = "Sec-Websocket-Extensions"
|
SecWebsocketExtensions = "Sec-Websocket-Extensions"
|
||||||
|
|
53
vendor/github.com/vulcand/oxy/forward/responseflusher.go
generated
vendored
53
vendor/github.com/vulcand/oxy/forward/responseflusher.go
generated
vendored
|
@ -1,53 +0,0 @@
|
||||||
package forward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ http.Hijacker = &responseFlusher{}
|
|
||||||
_ http.Flusher = &responseFlusher{}
|
|
||||||
_ http.CloseNotifier = &responseFlusher{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type responseFlusher struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
flush bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResponseFlusher(rw http.ResponseWriter, flush bool) *responseFlusher {
|
|
||||||
return &responseFlusher{
|
|
||||||
ResponseWriter: rw,
|
|
||||||
flush: flush,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wf *responseFlusher) Write(p []byte) (int, error) {
|
|
||||||
written, err := wf.ResponseWriter.Write(p)
|
|
||||||
if wf.flush {
|
|
||||||
wf.Flush()
|
|
||||||
}
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wf *responseFlusher) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
hijacker, ok := wf.ResponseWriter.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
|
|
||||||
}
|
|
||||||
return hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wf *responseFlusher) CloseNotify() <-chan bool {
|
|
||||||
return wf.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wf *responseFlusher) Flush() {
|
|
||||||
flusher, ok := wf.ResponseWriter.(http.Flusher)
|
|
||||||
if ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
35
vendor/github.com/vulcand/oxy/forward/rewrite.go
generated
vendored
35
vendor/github.com/vulcand/oxy/forward/rewrite.go
generated
vendored
|
@ -14,16 +14,25 @@ type HeaderRewriter struct {
|
||||||
Hostname string
|
Hostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean up IP in case if it is ipv6 address and it has {zone} information in it, like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692"
|
||||||
|
func ipv6fix(clientIP string) string {
|
||||||
|
return strings.Split(clientIP, "%")[0]
|
||||||
|
}
|
||||||
|
|
||||||
func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||||
if !rw.TrustForwardHeader {
|
if !rw.TrustForwardHeader {
|
||||||
utils.RemoveHeaders(req.Header, XHeaders...)
|
utils.RemoveHeaders(req.Header, XHeaders...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
if prior, ok := req.Header[XForwardedFor]; ok {
|
clientIP = ipv6fix(clientIP)
|
||||||
req.Header.Set(XForwardedFor, strings.Join(prior, ", ")+", "+clientIP)
|
// If not websocket, done in http.ReverseProxy
|
||||||
} else {
|
if IsWebsocketRequest(req) {
|
||||||
req.Header.Set(XForwardedFor, clientIP)
|
if prior, ok := req.Header[XForwardedFor]; ok {
|
||||||
|
req.Header.Set(XForwardedFor, strings.Join(prior, ", ")+", "+clientIP)
|
||||||
|
} else {
|
||||||
|
req.Header.Set(XForwardedFor, clientIP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Header.Get(XRealIp) == "" {
|
if req.Header.Get(XRealIp) == "" {
|
||||||
|
@ -40,7 +49,15 @@ func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if xfp := req.Header.Get(XForwardedPort); xfp == "" {
|
if IsWebsocketRequest(req) {
|
||||||
|
if req.Header.Get(XForwardedProto) == "https" {
|
||||||
|
req.Header.Set(XForwardedProto, "wss")
|
||||||
|
} else {
|
||||||
|
req.Header.Set(XForwardedProto, "ws")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xfPort := req.Header.Get(XForwardedPort); xfPort == "" {
|
||||||
req.Header.Set(XForwardedPort, forwardedPort(req))
|
req.Header.Set(XForwardedPort, forwardedPort(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,9 +69,11 @@ func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||||
req.Header.Set(XForwardedServer, rw.Hostname)
|
req.Header.Set(XForwardedServer, rw.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hop-by-hop headers to the backend. Especially important is "Connection" because we want a persistent
|
if !IsWebsocketRequest(req) {
|
||||||
// connection, regardless of what the client sent to us.
|
// Remove hop-by-hop headers to the backend. Especially important is "Connection" because we want a persistent
|
||||||
utils.RemoveHeaders(req.Header, HopHeaders...)
|
// connection, regardless of what the client sent to us.
|
||||||
|
utils.RemoveHeaders(req.Header, HopHeaders...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func forwardedPort(req *http.Request) string {
|
func forwardedPort(req *http.Request) string {
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/memmetrics/anomaly.go
generated
vendored
2
vendor/github.com/vulcand/oxy/memmetrics/anomaly.go
generated
vendored
|
@ -40,7 +40,7 @@ func SplitRatios(values []float64) (good map[float64]bool, bad map[float64]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitFloat64 provides simple anomaly detection for skewed data sets with no particular distribution.
|
// SplitFloat64 provides simple anomaly detection for skewed data sets with no particular distribution.
|
||||||
// In essense it applies the formula if(v > median(values) + threshold * medianAbsoluteDeviation) -> anomaly
|
// In essence it applies the formula if(v > median(values) + threshold * medianAbsoluteDeviation) -> anomaly
|
||||||
// There's a corner case where there are just 2 values, so by definition there's no value that exceeds the threshold.
|
// There's a corner case where there are just 2 values, so by definition there's no value that exceeds the threshold.
|
||||||
// This case is solved by introducing additional value that we know is good, e.g. 0. That helps to improve the detection results
|
// This case is solved by introducing additional value that we know is good, e.g. 0. That helps to improve the detection results
|
||||||
// on such data sets.
|
// on such data sets.
|
||||||
|
|
4
vendor/github.com/vulcand/oxy/memmetrics/counter.go
generated
vendored
4
vendor/github.com/vulcand/oxy/memmetrics/counter.go
generated
vendored
|
@ -71,9 +71,7 @@ func (c *RollingCounter) Clone() *RollingCounter {
|
||||||
lastBucket: c.lastBucket,
|
lastBucket: c.lastBucket,
|
||||||
lastUpdated: c.lastUpdated,
|
lastUpdated: c.lastUpdated,
|
||||||
}
|
}
|
||||||
for i, v := range c.values {
|
copy(other.values, c.values)
|
||||||
other.values[i] = v
|
|
||||||
}
|
|
||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
vendor/github.com/vulcand/oxy/memmetrics/histogram.go
generated
vendored
33
vendor/github.com/vulcand/oxy/memmetrics/histogram.go
generated
vendored
|
@ -34,6 +34,15 @@ func NewHDRHistogram(low, high int64, sigfigs int) (h *HDRHistogram, err error)
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *HDRHistogram) Export() *HDRHistogram {
|
||||||
|
var hist *hdrhistogram.Histogram = nil
|
||||||
|
if r.h != nil {
|
||||||
|
snapshot := r.h.Export()
|
||||||
|
hist = hdrhistogram.Import(snapshot)
|
||||||
|
}
|
||||||
|
return &HDRHistogram{low: r.low, high: r.high, sigfigs: r.sigfigs, h: hist}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns latency at quantile with microsecond precision
|
// Returns latency at quantile with microsecond precision
|
||||||
func (h *HDRHistogram) LatencyAtQuantile(q float64) time.Duration {
|
func (h *HDRHistogram) LatencyAtQuantile(q float64) time.Duration {
|
||||||
return time.Duration(h.ValueAtQuantile(q)) * time.Microsecond
|
return time.Duration(h.ValueAtQuantile(q)) * time.Microsecond
|
||||||
|
@ -118,6 +127,26 @@ func NewRollingHDRHistogram(low, high int64, sigfigs int, period time.Duration,
|
||||||
return rh, nil
|
return rh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RollingHDRHistogram) Export() *RollingHDRHistogram {
|
||||||
|
export := &RollingHDRHistogram{}
|
||||||
|
export.idx = r.idx
|
||||||
|
export.lastRoll = r.lastRoll
|
||||||
|
export.period = r.period
|
||||||
|
export.bucketCount = r.bucketCount
|
||||||
|
export.low = r.low
|
||||||
|
export.high = r.high
|
||||||
|
export.sigfigs = r.sigfigs
|
||||||
|
export.clock = r.clock
|
||||||
|
|
||||||
|
exportBuckets := make([]*HDRHistogram, len(r.buckets))
|
||||||
|
for i, hist := range r.buckets {
|
||||||
|
exportBuckets[i] = hist.Export()
|
||||||
|
}
|
||||||
|
export.buckets = exportBuckets
|
||||||
|
|
||||||
|
return export
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RollingHDRHistogram) Append(o *RollingHDRHistogram) error {
|
func (r *RollingHDRHistogram) Append(o *RollingHDRHistogram) error {
|
||||||
if r.bucketCount != o.bucketCount || r.period != o.period || r.low != o.low || r.high != o.high || r.sigfigs != o.sigfigs {
|
if r.bucketCount != o.bucketCount || r.period != o.period || r.low != o.low || r.high != o.high || r.sigfigs != o.sigfigs {
|
||||||
return fmt.Errorf("can't merge")
|
return fmt.Errorf("can't merge")
|
||||||
|
@ -150,8 +179,8 @@ func (r *RollingHDRHistogram) Merged() (*HDRHistogram, error) {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
for _, h := range r.buckets {
|
for _, h := range r.buckets {
|
||||||
if m.Merge(h); err != nil {
|
if errMerge := m.Merge(h); errMerge != nil {
|
||||||
return nil, err
|
return nil, errMerge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
61
vendor/github.com/vulcand/oxy/memmetrics/roundtrip.go
generated
vendored
61
vendor/github.com/vulcand/oxy/memmetrics/roundtrip.go
generated
vendored
|
@ -20,6 +20,7 @@ type RTMetrics struct {
|
||||||
statusCodes map[int]*RollingCounter
|
statusCodes map[int]*RollingCounter
|
||||||
statusCodesLock sync.RWMutex
|
statusCodesLock sync.RWMutex
|
||||||
histogram *RollingHDRHistogram
|
histogram *RollingHDRHistogram
|
||||||
|
histogramLock sync.RWMutex
|
||||||
|
|
||||||
newCounter NewCounterFn
|
newCounter NewCounterFn
|
||||||
newHist NewRollingHistogramFn
|
newHist NewRollingHistogramFn
|
||||||
|
@ -102,12 +103,39 @@ func NewRTMetrics(settings ...rrOptSetter) (*RTMetrics, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a new RTMetrics which is a copy of the current one
|
||||||
|
func (m *RTMetrics) Export() *RTMetrics {
|
||||||
|
m.statusCodesLock.RLock()
|
||||||
|
defer m.statusCodesLock.RUnlock()
|
||||||
|
m.histogramLock.RLock()
|
||||||
|
defer m.histogramLock.RUnlock()
|
||||||
|
|
||||||
|
export := &RTMetrics{}
|
||||||
|
export.statusCodesLock = sync.RWMutex{}
|
||||||
|
export.histogramLock = sync.RWMutex{}
|
||||||
|
export.total = m.total.Clone()
|
||||||
|
export.netErrors = m.netErrors.Clone()
|
||||||
|
exportStatusCodes := map[int]*RollingCounter{}
|
||||||
|
for code, rollingCounter := range m.statusCodes {
|
||||||
|
exportStatusCodes[code] = rollingCounter.Clone()
|
||||||
|
}
|
||||||
|
export.statusCodes = exportStatusCodes
|
||||||
|
if m.histogram != nil {
|
||||||
|
export.histogram = m.histogram.Export()
|
||||||
|
}
|
||||||
|
export.newCounter = m.newCounter
|
||||||
|
export.newHist = m.newHist
|
||||||
|
export.clock = m.clock
|
||||||
|
|
||||||
|
return export
|
||||||
|
}
|
||||||
|
|
||||||
func (m *RTMetrics) CounterWindowSize() time.Duration {
|
func (m *RTMetrics) CounterWindowSize() time.Duration {
|
||||||
return m.total.WindowSize()
|
return m.total.WindowSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNetworkErrorRatio calculates the amont of network errors such as time outs and dropped connection
|
// GetNetworkErrorRatio calculates the amont of network errors such as time outs and dropped connection
|
||||||
// that occured in the given time window compared to the total requests count.
|
// that occurred in the given time window compared to the total requests count.
|
||||||
func (m *RTMetrics) NetworkErrorRatio() float64 {
|
func (m *RTMetrics) NetworkErrorRatio() float64 {
|
||||||
if m.total.Count() == 0 {
|
if m.total.Count() == 0 {
|
||||||
return 0
|
return 0
|
||||||
|
@ -148,11 +176,13 @@ func (m *RTMetrics) Append(other *RTMetrics) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copied := other.Export()
|
||||||
|
|
||||||
m.statusCodesLock.Lock()
|
m.statusCodesLock.Lock()
|
||||||
defer m.statusCodesLock.Unlock()
|
defer m.statusCodesLock.Unlock()
|
||||||
other.statusCodesLock.RLock()
|
m.histogramLock.Lock()
|
||||||
defer other.statusCodesLock.RUnlock()
|
defer m.histogramLock.Unlock()
|
||||||
for code, c := range other.statusCodes {
|
for code, c := range copied.statusCodes {
|
||||||
o, ok := m.statusCodes[code]
|
o, ok := m.statusCodes[code]
|
||||||
if ok {
|
if ok {
|
||||||
if err := o.Append(c); err != nil {
|
if err := o.Append(c); err != nil {
|
||||||
|
@ -163,7 +193,7 @@ func (m *RTMetrics) Append(other *RTMetrics) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.histogram.Append(other.histogram)
|
return m.histogram.Append(copied.histogram)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RTMetrics) Record(code int, duration time.Duration) {
|
func (m *RTMetrics) Record(code int, duration time.Duration) {
|
||||||
|
@ -200,35 +230,36 @@ func (m *RTMetrics) StatusCodesCounts() map[int]int64 {
|
||||||
|
|
||||||
// GetLatencyHistogram computes and returns resulting histogram with latencies observed.
|
// GetLatencyHistogram computes and returns resulting histogram with latencies observed.
|
||||||
func (m *RTMetrics) LatencyHistogram() (*HDRHistogram, error) {
|
func (m *RTMetrics) LatencyHistogram() (*HDRHistogram, error) {
|
||||||
|
m.histogramLock.Lock()
|
||||||
|
defer m.histogramLock.Unlock()
|
||||||
return m.histogram.Merged()
|
return m.histogram.Merged()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RTMetrics) Reset() {
|
func (m *RTMetrics) Reset() {
|
||||||
|
m.statusCodesLock.Lock()
|
||||||
|
defer m.statusCodesLock.Unlock()
|
||||||
|
m.histogramLock.Lock()
|
||||||
|
defer m.histogramLock.Unlock()
|
||||||
m.histogram.Reset()
|
m.histogram.Reset()
|
||||||
m.total.Reset()
|
m.total.Reset()
|
||||||
m.netErrors.Reset()
|
m.netErrors.Reset()
|
||||||
m.statusCodesLock.Lock()
|
|
||||||
defer m.statusCodesLock.Unlock()
|
|
||||||
m.statusCodes = make(map[int]*RollingCounter)
|
m.statusCodes = make(map[int]*RollingCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RTMetrics) recordNetError() error {
|
|
||||||
m.netErrors.Inc(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *RTMetrics) recordLatency(d time.Duration) error {
|
func (m *RTMetrics) recordLatency(d time.Duration) error {
|
||||||
|
m.histogramLock.Lock()
|
||||||
|
defer m.histogramLock.Unlock()
|
||||||
return m.histogram.RecordLatencies(d, 1)
|
return m.histogram.RecordLatencies(d, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RTMetrics) recordStatusCode(statusCode int) error {
|
func (m *RTMetrics) recordStatusCode(statusCode int) error {
|
||||||
m.statusCodesLock.RLock()
|
m.statusCodesLock.Lock()
|
||||||
if c, ok := m.statusCodes[statusCode]; ok {
|
if c, ok := m.statusCodes[statusCode]; ok {
|
||||||
c.Inc(1)
|
c.Inc(1)
|
||||||
m.statusCodesLock.RUnlock()
|
m.statusCodesLock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.statusCodesLock.RUnlock()
|
m.statusCodesLock.Unlock()
|
||||||
|
|
||||||
m.statusCodesLock.Lock()
|
m.statusCodesLock.Lock()
|
||||||
defer m.statusCodesLock.Unlock()
|
defer m.statusCodesLock.Unlock()
|
||||||
|
|
17
vendor/github.com/vulcand/oxy/ratelimit/tokenlimiter.go
generated
vendored
17
vendor/github.com/vulcand/oxy/ratelimit/tokenlimiter.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/mailgun/timetools"
|
"github.com/mailgun/timetools"
|
||||||
"github.com/mailgun/ttlmap"
|
"github.com/mailgun/ttlmap"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
|
@ -65,7 +66,6 @@ type TokenLimiter struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
bucketSets *ttlmap.TtlMap
|
bucketSets *ttlmap.TtlMap
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
log utils.Logger
|
|
||||||
capacity int
|
capacity int
|
||||||
next http.Handler
|
next http.Handler
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ func (tl *TokenLimiter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tl.consumeRates(req, source, amount); err != nil {
|
if err := tl.consumeRates(req, source, amount); err != nil {
|
||||||
tl.log.Infof("limiting request %v %v, limit: %v", req.Method, req.URL, err)
|
log.Infof("limiting request %v %v, limit: %v", req.Method, req.URL, err)
|
||||||
tl.errHandler.ServeHTTP(w, req, err)
|
tl.errHandler.ServeHTTP(w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ func (tl *TokenLimiter) resolveRates(req *http.Request) *RateSet {
|
||||||
|
|
||||||
rates, err := tl.extractRates.Extract(req)
|
rates, err := tl.extractRates.Extract(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tl.log.Errorf("Failed to retrieve rates: %v", err)
|
log.Errorf("Failed to retrieve rates: %v", err)
|
||||||
return tl.defaultRates
|
return tl.defaultRates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,14 +190,6 @@ func (e *RateErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err
|
||||||
|
|
||||||
type TokenLimiterOption func(l *TokenLimiter) error
|
type TokenLimiterOption func(l *TokenLimiter) error
|
||||||
|
|
||||||
// Logger sets the logger that will be used by this middleware.
|
|
||||||
func Logger(l utils.Logger) TokenLimiterOption {
|
|
||||||
return func(cl *TokenLimiter) error {
|
|
||||||
cl.log = l
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorHandler sets error handler of the server
|
// ErrorHandler sets error handler of the server
|
||||||
func ErrorHandler(h utils.ErrorHandler) TokenLimiterOption {
|
func ErrorHandler(h utils.ErrorHandler) TokenLimiterOption {
|
||||||
return func(cl *TokenLimiter) error {
|
return func(cl *TokenLimiter) error {
|
||||||
|
@ -233,9 +225,6 @@ func Capacity(cap int) TokenLimiterOption {
|
||||||
var defaultErrHandler = &RateErrHandler{}
|
var defaultErrHandler = &RateErrHandler{}
|
||||||
|
|
||||||
func setDefaults(tl *TokenLimiter) {
|
func setDefaults(tl *TokenLimiter) {
|
||||||
if tl.log == nil {
|
|
||||||
tl.log = utils.NullLogger
|
|
||||||
}
|
|
||||||
if tl.capacity <= 0 {
|
if tl.capacity <= 0 {
|
||||||
tl.capacity = DefaultCapacity
|
tl.capacity = DefaultCapacity
|
||||||
}
|
}
|
||||||
|
|
5
vendor/github.com/vulcand/oxy/roundrobin/RequestRewriteListener.go
generated
vendored
Normal file
5
vendor/github.com/vulcand/oxy/roundrobin/RequestRewriteListener.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package roundrobin
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type RequestRewriteListener func(oldReq *http.Request, newReq *http.Request)
|
79
vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go
generated
vendored
79
vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/mailgun/timetools"
|
"github.com/mailgun/timetools"
|
||||||
"github.com/vulcand/oxy/memmetrics"
|
"github.com/vulcand/oxy/memmetrics"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
|
@ -42,22 +43,15 @@ type Rebalancer struct {
|
||||||
// errHandler is HTTP handler called in case of errors
|
// errHandler is HTTP handler called in case of errors
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
|
|
||||||
log utils.Logger
|
|
||||||
|
|
||||||
ratings []float64
|
ratings []float64
|
||||||
|
|
||||||
// creates new meters
|
// creates new meters
|
||||||
newMeter NewMeterFn
|
newMeter NewMeterFn
|
||||||
|
|
||||||
// sticky session object
|
// sticky session object
|
||||||
ss *StickySession
|
stickySession *StickySession
|
||||||
}
|
|
||||||
|
|
||||||
func RebalancerLogger(log utils.Logger) RebalancerOption {
|
requestRewriteListener RequestRewriteListener
|
||||||
return func(r *Rebalancer) error {
|
|
||||||
r.log = log
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RebalancerClock(clock timetools.TimeProvider) RebalancerOption {
|
func RebalancerClock(clock timetools.TimeProvider) RebalancerOption {
|
||||||
|
@ -89,18 +83,26 @@ func RebalancerErrorHandler(h utils.ErrorHandler) RebalancerOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RebalancerStickySession(ss *StickySession) RebalancerOption {
|
func RebalancerStickySession(stickySession *StickySession) RebalancerOption {
|
||||||
return func(r *Rebalancer) error {
|
return func(r *Rebalancer) error {
|
||||||
r.ss = ss
|
r.stickySession = stickySession
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebalancerErrorHandler is a functional argument that sets error handler of the server
|
||||||
|
func RebalancerRequestRewriteListener(rrl RequestRewriteListener) RebalancerOption {
|
||||||
|
return func(r *Rebalancer) error {
|
||||||
|
r.requestRewriteListener = rrl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRebalancer(handler balancerHandler, opts ...RebalancerOption) (*Rebalancer, error) {
|
func NewRebalancer(handler balancerHandler, opts ...RebalancerOption) (*Rebalancer, error) {
|
||||||
rb := &Rebalancer{
|
rb := &Rebalancer{
|
||||||
mtx: &sync.Mutex{},
|
mtx: &sync.Mutex{},
|
||||||
next: handler,
|
next: handler,
|
||||||
ss: nil,
|
stickySession: nil,
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(rb); err != nil {
|
if err := o(rb); err != nil {
|
||||||
|
@ -113,9 +115,6 @@ func NewRebalancer(handler balancerHandler, opts ...RebalancerOption) (*Rebalanc
|
||||||
if rb.backoffDuration == 0 {
|
if rb.backoffDuration == 0 {
|
||||||
rb.backoffDuration = 10 * time.Second
|
rb.backoffDuration = 10 * time.Second
|
||||||
}
|
}
|
||||||
if rb.log == nil {
|
|
||||||
rb.log = &utils.NOPLogger{}
|
|
||||||
}
|
|
||||||
if rb.newMeter == nil {
|
if rb.newMeter == nil {
|
||||||
rb.newMeter = func() (Meter, error) {
|
rb.newMeter = func() (Meter, error) {
|
||||||
rc, err := memmetrics.NewRatioCounter(10, time.Second, memmetrics.RatioClock(rb.clock))
|
rc, err := memmetrics.NewRatioCounter(10, time.Second, memmetrics.RatioClock(rb.clock))
|
||||||
|
@ -143,6 +142,12 @@ func (rb *Rebalancer) Servers() []*url.URL {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
pw := &utils.ProxyWriter{W: w}
|
pw := &utils.ProxyWriter{W: w}
|
||||||
start := rb.clock.UtcNow()
|
start := rb.clock.UtcNow()
|
||||||
|
|
||||||
|
@ -150,16 +155,15 @@ func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
newReq := *req
|
newReq := *req
|
||||||
stuck := false
|
stuck := false
|
||||||
|
|
||||||
if rb.ss != nil {
|
if rb.stickySession != nil {
|
||||||
cookie_url, present, err := rb.ss.GetBackend(&newReq, rb.Servers())
|
cookieUrl, present, err := rb.stickySession.GetBackend(&newReq, rb.Servers())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.errHandler.ServeHTTP(w, req, err)
|
log.Infof("vulcand/oxy/roundrobin/rebalancer: error using server from cookie: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if present {
|
if present {
|
||||||
newReq.URL = cookie_url
|
newReq.URL = cookieUrl
|
||||||
stuck = true
|
stuck = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,12 +175,23 @@ func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if rb.ss != nil {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
rb.ss.StickBackend(url, &w)
|
//log which backend URL we're sending this request to
|
||||||
|
log.WithFields(log.Fields{"Request": utils.DumpHttpRequest(req), "ForwardURL": url}).Debugf("vulcand/oxy/roundrobin/rebalancer: Forwarding this request to URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rb.stickySession != nil {
|
||||||
|
rb.stickySession.StickBackend(url, &w)
|
||||||
}
|
}
|
||||||
|
|
||||||
newReq.URL = url
|
newReq.URL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Emit event to a listener if one exists
|
||||||
|
if rb.requestRewriteListener != nil {
|
||||||
|
rb.requestRewriteListener(req, &newReq)
|
||||||
|
}
|
||||||
|
|
||||||
rb.next.Next().ServeHTTP(pw, &newReq)
|
rb.next.Next().ServeHTTP(pw, &newReq)
|
||||||
|
|
||||||
rb.recordMetrics(newReq.URL, pw.Code, rb.clock.UtcNow().Sub(start))
|
rb.recordMetrics(newReq.URL, pw.Code, rb.clock.UtcNow().Sub(start))
|
||||||
|
@ -262,11 +277,11 @@ func (rb *Rebalancer) upsertServer(u *url.URL, weight int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rebalancer) findServer(u *url.URL) (*rbServer, int) {
|
func (rb *Rebalancer) findServer(u *url.URL) (*rbServer, int) {
|
||||||
if len(r.servers) == 0 {
|
if len(rb.servers) == 0 {
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
for i, s := range r.servers {
|
for i, s := range rb.servers {
|
||||||
if sameURL(u, s.url) {
|
if sameURL(u, s.url) {
|
||||||
return s, i
|
return s, i
|
||||||
}
|
}
|
||||||
|
@ -304,7 +319,7 @@ func (rb *Rebalancer) adjustWeights() {
|
||||||
|
|
||||||
func (rb *Rebalancer) applyWeights() {
|
func (rb *Rebalancer) applyWeights() {
|
||||||
for _, srv := range rb.servers {
|
for _, srv := range rb.servers {
|
||||||
rb.log.Infof("upsert server %v, weight %v", srv.url, srv.curWeight)
|
log.Infof("upsert server %v, weight %v", srv.url, srv.curWeight)
|
||||||
rb.next.UpsertServer(srv.url, Weight(srv.curWeight))
|
rb.next.UpsertServer(srv.url, Weight(srv.curWeight))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +331,7 @@ func (rb *Rebalancer) setMarkedWeights() bool {
|
||||||
if srv.good {
|
if srv.good {
|
||||||
weight := increase(srv.curWeight)
|
weight := increase(srv.curWeight)
|
||||||
if weight <= FSMMaxWeight {
|
if weight <= FSMMaxWeight {
|
||||||
rb.log.Infof("increasing weight of %v from %v to %v", srv.url, srv.curWeight, weight)
|
log.Infof("increasing weight of %v from %v to %v", srv.url, srv.curWeight, weight)
|
||||||
srv.curWeight = weight
|
srv.curWeight = weight
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
@ -363,13 +378,13 @@ func (rb *Rebalancer) markServers() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(g) != 0 && len(b) != 0 {
|
if len(g) != 0 && len(b) != 0 {
|
||||||
rb.log.Infof("bad: %v good: %v, ratings: %v", b, g, rb.ratings)
|
log.Infof("bad: %v good: %v, ratings: %v", b, g, rb.ratings)
|
||||||
}
|
}
|
||||||
return len(g) != 0 && len(b) != 0
|
return len(g) != 0 && len(b) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rb *Rebalancer) convergeWeights() bool {
|
func (rb *Rebalancer) convergeWeights() bool {
|
||||||
// If we have previoulsy changed servers try to restore weights to the original state
|
// If we have previously changed servers try to restore weights to the original state
|
||||||
changed := false
|
changed := false
|
||||||
for _, s := range rb.servers {
|
for _, s := range rb.servers {
|
||||||
if s.origWeight == s.curWeight {
|
if s.origWeight == s.curWeight {
|
||||||
|
@ -377,7 +392,7 @@ func (rb *Rebalancer) convergeWeights() bool {
|
||||||
}
|
}
|
||||||
changed = true
|
changed = true
|
||||||
newWeight := decrease(s.origWeight, s.curWeight)
|
newWeight := decrease(s.origWeight, s.curWeight)
|
||||||
rb.log.Infof("decreasing weight of %v from %v to %v", s.url, s.curWeight, newWeight)
|
log.Infof("decreasing weight of %v from %v to %v", s.url, s.curWeight, newWeight)
|
||||||
s.curWeight = newWeight
|
s.curWeight = newWeight
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
|
|
64
vendor/github.com/vulcand/oxy/roundrobin/rr.go
generated
vendored
64
vendor/github.com/vulcand/oxy/roundrobin/rr.go
generated
vendored
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,9 +30,17 @@ func ErrorHandler(h utils.ErrorHandler) LBOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnableStickySession(ss *StickySession) LBOption {
|
func EnableStickySession(stickySession *StickySession) LBOption {
|
||||||
return func(s *RoundRobin) error {
|
return func(s *RoundRobin) error {
|
||||||
s.ss = ss
|
s.stickySession = stickySession
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandler is a functional argument that sets error handler of the server
|
||||||
|
func RoundRobinRequestRewriteListener(rrl RequestRewriteListener) LBOption {
|
||||||
|
return func(s *RoundRobin) error {
|
||||||
|
s.requestRewriteListener = rrl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,19 +50,20 @@ type RoundRobin struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
// Current index (starts from -1)
|
// Current index (starts from -1)
|
||||||
index int
|
index int
|
||||||
servers []*server
|
servers []*server
|
||||||
currentWeight int
|
currentWeight int
|
||||||
ss *StickySession
|
stickySession *StickySession
|
||||||
|
requestRewriteListener RequestRewriteListener
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(next http.Handler, opts ...LBOption) (*RoundRobin, error) {
|
func New(next http.Handler, opts ...LBOption) (*RoundRobin, error) {
|
||||||
rr := &RoundRobin{
|
rr := &RoundRobin{
|
||||||
next: next,
|
next: next,
|
||||||
index: -1,
|
index: -1,
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
servers: []*server{},
|
servers: []*server{},
|
||||||
ss: nil,
|
stickySession: nil,
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(rr); err != nil {
|
if err := o(rr); err != nil {
|
||||||
|
@ -71,19 +81,24 @@ func (r *RoundRobin) Next() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
|
logEntry.Debug("vulcand/oxy/roundrobin/rr: begin ServeHttp on request")
|
||||||
|
defer logEntry.Debug("vulcand/oxy/roundrobin/rr: competed ServeHttp on request")
|
||||||
|
}
|
||||||
|
|
||||||
// make shallow copy of request before chaning anything to avoid side effects
|
// make shallow copy of request before chaning anything to avoid side effects
|
||||||
newReq := *req
|
newReq := *req
|
||||||
stuck := false
|
stuck := false
|
||||||
if r.ss != nil {
|
if r.stickySession != nil {
|
||||||
cookie_url, present, err := r.ss.GetBackend(&newReq, r.Servers())
|
cookieURL, present, err := r.stickySession.GetBackend(&newReq, r.Servers())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.errHandler.ServeHTTP(w, req, err)
|
log.Infof("vulcand/oxy/roundrobin/rr: error using server from cookie: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if present {
|
if present {
|
||||||
newReq.URL = cookie_url
|
newReq.URL = cookieURL
|
||||||
stuck = true
|
stuck = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,11 +110,22 @@ func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ss != nil {
|
if r.stickySession != nil {
|
||||||
r.ss.StickBackend(url, &w)
|
r.stickySession.StickBackend(url, &w)
|
||||||
}
|
}
|
||||||
newReq.URL = url
|
newReq.URL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
|
//log which backend URL we're sending this request to
|
||||||
|
log.WithFields(log.Fields{"Request": utils.DumpHttpRequest(req), "ForwardURL": newReq.URL}).Debugf("vulcand/oxy/roundrobin/rr: Forwarding this request to URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Emit event to a listener if one exists
|
||||||
|
if r.requestRewriteListener != nil {
|
||||||
|
r.requestRewriteListener(req, &newReq)
|
||||||
|
}
|
||||||
|
|
||||||
r.next.ServeHTTP(w, &newReq)
|
r.next.ServeHTTP(w, &newReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +170,6 @@ func (r *RoundRobin) nextServer() (*server, error) {
|
||||||
return srv, nil
|
return srv, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We did full circle and found no available servers
|
|
||||||
return nil, fmt.Errorf("no available servers")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoundRobin) RemoveServer(u *url.URL) error {
|
func (r *RoundRobin) RemoveServer(u *url.URL) error {
|
||||||
|
|
23
vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go
generated
vendored
23
vendor/github.com/vulcand/oxy/roundrobin/stickysessions.go
generated
vendored
|
@ -7,16 +7,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StickySession struct {
|
type StickySession struct {
|
||||||
cookiename string
|
cookieName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStickySession(c string) *StickySession {
|
func NewStickySession(cookieName string) *StickySession {
|
||||||
return &StickySession{c}
|
return &StickySession{cookieName}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBackend returns the backend URL stored in the sticky cookie, iff the backend is still in the valid list of servers.
|
// GetBackend returns the backend URL stored in the sticky cookie, iff the backend is still in the valid list of servers.
|
||||||
func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) {
|
func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) {
|
||||||
cookie, err := req.Cookie(s.cookiename)
|
cookie, err := req.Cookie(s.cookieName)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
case http.ErrNoCookie:
|
case http.ErrNoCookie:
|
||||||
|
@ -25,22 +25,21 @@ func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s_url, err := url.Parse(cookie.Value)
|
serverURL, err := url.Parse(cookie.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.isBackendAlive(s_url, servers) {
|
if s.isBackendAlive(serverURL, servers) {
|
||||||
return s_url, true, nil
|
return serverURL, true, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
|
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
|
||||||
c := &http.Cookie{Name: s.cookiename, Value: backend.String(), Path: "/"}
|
cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/"}
|
||||||
http.SetCookie(*w, c)
|
http.SetCookie(*w, cookie)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StickySession) isBackendAlive(needle *url.URL, haystack []*url.URL) bool {
|
func (s *StickySession) isBackendAlive(needle *url.URL, haystack []*url.URL) bool {
|
||||||
|
@ -48,8 +47,8 @@ func (s *StickySession) isBackendAlive(needle *url.URL, haystack []*url.URL) boo
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range haystack {
|
for _, serverURL := range haystack {
|
||||||
if sameURL(needle, s) {
|
if sameURL(needle, serverURL) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
323
vendor/github.com/vulcand/oxy/stream/stream.go
generated
vendored
323
vendor/github.com/vulcand/oxy/stream/stream.go
generated
vendored
|
@ -1,9 +1,21 @@
|
||||||
/*
|
/*
|
||||||
package stream provides http.Handler middleware that solves several problems when dealing with http requests:
|
package stream provides http.Handler middleware that passes-through the entire request
|
||||||
|
|
||||||
Reads the entire request and response into buffer, optionally buffering it to disk for large requests.
|
Stream works around several limitations caused by buffering implementations, but
|
||||||
Checks the limits for the requests and responses, rejecting in case if the limit was exceeded.
|
also introduces certain risks.
|
||||||
Changes request content-transfer-encoding from chunked and provides total size to the handlers.
|
|
||||||
|
Workarounds for buffering limitations:
|
||||||
|
1. Streaming really large chunks of data (large file transfers, or streaming videos,
|
||||||
|
etc.)
|
||||||
|
|
||||||
|
2. Streaming (chunking) sparse data. For example, an implementation might
|
||||||
|
send a health check or a heart beat over a long-lived connection. This
|
||||||
|
does not play well with buffering.
|
||||||
|
|
||||||
|
Risks:
|
||||||
|
1. Connections could survive for very long periods of time.
|
||||||
|
|
||||||
|
2. There is no easy way to enforce limits on size/time of a connection.
|
||||||
|
|
||||||
Examples of a streaming middleware:
|
Examples of a streaming middleware:
|
||||||
|
|
||||||
|
@ -12,340 +24,69 @@ Examples of a streaming middleware:
|
||||||
w.Write([]byte("hello"))
|
w.Write([]byte("hello"))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Stream will read the body in buffer before passing the request to the handler
|
// Stream will literally pass through to the next handler without ANY buffering
|
||||||
// calculate total size of the request and transform it from chunked encoding
|
// or validation of the data.
|
||||||
// before passing to the server
|
|
||||||
stream.New(handler)
|
stream.New(handler)
|
||||||
|
|
||||||
// This version will buffer up to 2MB in memory and will serialize any extra
|
|
||||||
// to a temporary file, if the request size exceeds 10MB it will reject the request
|
|
||||||
stream.New(handler,
|
|
||||||
stream.MemRequestBodyBytes(2 * 1024 * 1024),
|
|
||||||
stream.MaxRequestBodyBytes(10 * 1024 * 1024))
|
|
||||||
|
|
||||||
// Will do the same as above, but with responses
|
|
||||||
stream.New(handler,
|
|
||||||
stream.MemResponseBodyBytes(2 * 1024 * 1024),
|
|
||||||
stream.MaxResponseBodyBytes(10 * 1024 * 1024))
|
|
||||||
|
|
||||||
// Stream will replay the request if the handler returns error at least 3 times
|
|
||||||
// before returning the response
|
|
||||||
stream.New(handler, stream.Retry(`IsNetworkError() && Attempts() <= 2`))
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/mailgun/multibuf"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Store up to 1MB in RAM
|
|
||||||
DefaultMemBodyBytes = 1048576
|
|
||||||
// No limit by default
|
// No limit by default
|
||||||
DefaultMaxBodyBytes = -1
|
DefaultMaxBodyBytes = -1
|
||||||
// Maximum retry attempts
|
|
||||||
DefaultMaxRetryAttempts = 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errHandler utils.ErrorHandler = &SizeErrHandler{}
|
// Stream is responsible for buffering requests and responses
|
||||||
|
// It buffers large requests and responses to disk,
|
||||||
// Streamer is responsible for streaming requests and responses
|
type Stream struct {
|
||||||
// It buffers large reqeuests and responses to disk,
|
|
||||||
type Streamer struct {
|
|
||||||
maxRequestBodyBytes int64
|
maxRequestBodyBytes int64
|
||||||
memRequestBodyBytes int64
|
|
||||||
|
|
||||||
maxResponseBodyBytes int64
|
maxResponseBodyBytes int64
|
||||||
memResponseBodyBytes int64
|
|
||||||
|
|
||||||
retryPredicate hpredicate
|
retryPredicate hpredicate
|
||||||
|
|
||||||
next http.Handler
|
next http.Handler
|
||||||
errHandler utils.ErrorHandler
|
errHandler utils.ErrorHandler
|
||||||
log utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new streamer middleware. New() function supports optional functional arguments
|
// New returns a new streamer middleware. New() function supports optional functional arguments
|
||||||
func New(next http.Handler, setters ...optSetter) (*Streamer, error) {
|
func New(next http.Handler, setters ...optSetter) (*Stream, error) {
|
||||||
strm := &Streamer{
|
strm := &Stream{
|
||||||
next: next,
|
next: next,
|
||||||
|
|
||||||
maxRequestBodyBytes: DefaultMaxBodyBytes,
|
maxRequestBodyBytes: DefaultMaxBodyBytes,
|
||||||
memRequestBodyBytes: DefaultMemBodyBytes,
|
|
||||||
|
|
||||||
maxResponseBodyBytes: DefaultMaxBodyBytes,
|
maxResponseBodyBytes: DefaultMaxBodyBytes,
|
||||||
memResponseBodyBytes: DefaultMemBodyBytes,
|
|
||||||
}
|
}
|
||||||
for _, s := range setters {
|
for _, s := range setters {
|
||||||
if err := s(strm); err != nil {
|
if err := s(strm); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strm.errHandler == nil {
|
|
||||||
strm.errHandler = errHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
if strm.log == nil {
|
|
||||||
strm.log = utils.NullLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
return strm, nil
|
return strm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type optSetter func(s *Streamer) error
|
type optSetter func(s *Stream) error
|
||||||
|
|
||||||
// Retry provides a predicate that allows stream middleware to replay the request
|
|
||||||
// if it matches certain condition, e.g. returns special error code. Available functions are:
|
|
||||||
//
|
|
||||||
// Attempts() - limits the amount of retry attempts
|
|
||||||
// ResponseCode() - returns http response code
|
|
||||||
// IsNetworkError() - tests if response code is related to networking error
|
|
||||||
//
|
|
||||||
// Example of the predicate:
|
|
||||||
//
|
|
||||||
// `Attempts() <= 2 && ResponseCode() == 502`
|
|
||||||
//
|
|
||||||
func Retry(predicate string) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
p, err := parseExpression(predicate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.retryPredicate = p
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger sets the logger that will be used by this middleware.
|
|
||||||
func Logger(l utils.Logger) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
s.log = l
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorHandler sets error handler of the server
|
|
||||||
func ErrorHandler(h utils.ErrorHandler) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
s.errHandler = h
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxRequestBodyBytes sets the maximum request body size in bytes
|
|
||||||
func MaxRequestBodyBytes(m int64) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
if m < 0 {
|
|
||||||
return fmt.Errorf("max bytes should be >= 0 got %d", m)
|
|
||||||
}
|
|
||||||
s.maxRequestBodyBytes = m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxRequestBody bytes sets the maximum request body to be stored in memory
|
|
||||||
// stream middleware will serialize the excess to disk.
|
|
||||||
func MemRequestBodyBytes(m int64) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
if m < 0 {
|
|
||||||
return fmt.Errorf("mem bytes should be >= 0 got %d", m)
|
|
||||||
}
|
|
||||||
s.memRequestBodyBytes = m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxResponseBodyBytes sets the maximum request body size in bytes
|
|
||||||
func MaxResponseBodyBytes(m int64) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
if m < 0 {
|
|
||||||
return fmt.Errorf("max bytes should be >= 0 got %d", m)
|
|
||||||
}
|
|
||||||
s.maxResponseBodyBytes = m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemResponseBodyBytes sets the maximum request body to be stored in memory
|
|
||||||
// stream middleware will serialize the excess to disk.
|
|
||||||
func MemResponseBodyBytes(m int64) optSetter {
|
|
||||||
return func(s *Streamer) error {
|
|
||||||
if m < 0 {
|
|
||||||
return fmt.Errorf("mem bytes should be >= 0 got %d", m)
|
|
||||||
}
|
|
||||||
s.memResponseBodyBytes = m
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap sets the next handler to be called by stream handler.
|
// Wrap sets the next handler to be called by stream handler.
|
||||||
func (s *Streamer) Wrap(next http.Handler) error {
|
func (s *Stream) Wrap(next http.Handler) error {
|
||||||
s.next = next
|
s.next = next
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Streamer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (s *Stream) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if err := s.checkLimit(req); err != nil {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
s.log.Infof("request body over limit: %v", err)
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
logEntry.Debug("vulcand/oxy/stream: begin ServeHttp on request")
|
||||||
return
|
defer logEntry.Debug("vulcand/oxy/stream: competed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the body while keeping limits in mind. This reader controls the maximum bytes
|
s.next.ServeHTTP(w, req)
|
||||||
// to read into memory and disk. This reader returns an error if the total request size exceeds the
|
|
||||||
// prefefined MaxSizeBytes. This can occur if we got chunked request, in this case ContentLength would be set to -1
|
|
||||||
// and the reader would be unbounded bufio in the http.Server
|
|
||||||
body, err := multibuf.New(req.Body, multibuf.MaxBytes(s.maxRequestBodyBytes), multibuf.MemBytes(s.memRequestBodyBytes))
|
|
||||||
if err != nil || body == nil {
|
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set request body to buffered reader that can replay the read and execute Seek
|
|
||||||
// Note that we don't change the original request body as it's handled by the http server
|
|
||||||
// and we don'w want to mess with standard library
|
|
||||||
defer body.Close()
|
|
||||||
|
|
||||||
// We need to set ContentLength based on known request size. The incoming request may have been
|
|
||||||
// set without content length or using chunked TransferEncoding
|
|
||||||
totalSize, err := body.Size()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Errorf("failed to get size, err %v", err)
|
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
outreq := s.copyRequest(req, body, totalSize)
|
|
||||||
|
|
||||||
attempt := 1
|
|
||||||
for {
|
|
||||||
// We create a special writer that will limit the response size, buffer it to disk if necessary
|
|
||||||
writer, err := multibuf.NewWriterOnce(multibuf.MaxBytes(s.maxResponseBodyBytes), multibuf.MemBytes(s.memResponseBodyBytes))
|
|
||||||
if err != nil {
|
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are mimicking http.ResponseWriter to replace writer with our special writer
|
|
||||||
b := &bufferWriter{
|
|
||||||
header: make(http.Header),
|
|
||||||
buffer: writer,
|
|
||||||
}
|
|
||||||
defer b.Close()
|
|
||||||
|
|
||||||
s.next.ServeHTTP(b, outreq)
|
|
||||||
|
|
||||||
var reader multibuf.MultiReader
|
|
||||||
if b.expectBody(outreq) {
|
|
||||||
rdr, err := writer.Reader()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Errorf("failed to read response, err %v", err)
|
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rdr.Close()
|
|
||||||
reader = rdr
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.retryPredicate == nil || attempt > DefaultMaxRetryAttempts) ||
|
|
||||||
!s.retryPredicate(&context{r: req, attempt: attempt, responseCode: b.code, log: s.log}) {
|
|
||||||
utils.CopyHeaders(w.Header(), b.Header())
|
|
||||||
w.WriteHeader(b.code)
|
|
||||||
if reader != nil {
|
|
||||||
io.Copy(w, reader)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
attempt += 1
|
|
||||||
if _, err := body.Seek(0, 0); err != nil {
|
|
||||||
s.log.Errorf("Failed to rewind: error: %v", err)
|
|
||||||
s.errHandler.ServeHTTP(w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
outreq = s.copyRequest(req, body, totalSize)
|
|
||||||
s.log.Infof("retry Request(%v %v) attempt %v", req.Method, req.URL, attempt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Streamer) copyRequest(req *http.Request, body io.ReadCloser, bodySize int64) *http.Request {
|
|
||||||
o := *req
|
|
||||||
o.URL = utils.CopyURL(req.URL)
|
|
||||||
o.Header = make(http.Header)
|
|
||||||
utils.CopyHeaders(o.Header, req.Header)
|
|
||||||
o.ContentLength = bodySize
|
|
||||||
// remove TransferEncoding that could have been previously set because we have transformed the request from chunked encoding
|
|
||||||
o.TransferEncoding = []string{}
|
|
||||||
// http.Transport will close the request body on any error, we are controlling the close process ourselves, so we override the closer here
|
|
||||||
o.Body = ioutil.NopCloser(body)
|
|
||||||
return &o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Streamer) checkLimit(req *http.Request) error {
|
|
||||||
if s.maxRequestBodyBytes <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if req.ContentLength > s.maxRequestBodyBytes {
|
|
||||||
return &multibuf.MaxSizeReachedError{MaxSize: s.maxRequestBodyBytes}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type bufferWriter struct {
|
|
||||||
header http.Header
|
|
||||||
code int
|
|
||||||
buffer multibuf.WriterOnce
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC2616 #4.4
|
|
||||||
func (b *bufferWriter) expectBody(r *http.Request) bool {
|
|
||||||
if r.Method == "HEAD" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (b.code >= 100 && b.code < 200) || b.code == 204 || b.code == 304 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if b.header.Get("Content-Length") == "" && b.header.Get("Transfer-Encoding") == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if b.header.Get("Content-Length") == "0" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Close() error {
|
|
||||||
return b.buffer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Header() http.Header {
|
|
||||||
return b.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufferWriter) Write(buf []byte) (int, error) {
|
|
||||||
return b.buffer.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader sets rw.Code.
|
|
||||||
func (b *bufferWriter) WriteHeader(code int) {
|
|
||||||
b.code = code
|
|
||||||
}
|
|
||||||
|
|
||||||
type SizeErrHandler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SizeErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
|
||||||
if _, ok := err.(*multibuf.MaxSizeReachedError); ok {
|
|
||||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
|
||||||
w.Write([]byte(http.StatusText(http.StatusRequestEntityTooLarge)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
utils.DefaultHandler.ServeHTTP(w, req, err)
|
|
||||||
}
|
}
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/stream/threshold.go
generated
vendored
2
vendor/github.com/vulcand/oxy/stream/threshold.go
generated
vendored
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/vulcand/oxy/utils"
|
|
||||||
"github.com/vulcand/predicate"
|
"github.com/vulcand/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +16,6 @@ type context struct {
|
||||||
r *http.Request
|
r *http.Request
|
||||||
attempt int
|
attempt int
|
||||||
responseCode int
|
responseCode int
|
||||||
log utils.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type hpredicate func(*context) bool
|
type hpredicate func(*context) bool
|
||||||
|
|
12
vendor/github.com/vulcand/oxy/utils/auth.go
generated
vendored
12
vendor/github.com/vulcand/oxy/utils/auth.go
generated
vendored
|
@ -22,18 +22,18 @@ func ParseAuthHeader(header string) (*BasicAuth, error) {
|
||||||
return nil, fmt.Errorf(fmt.Sprintf("Failed to parse header '%s'", header))
|
return nil, fmt.Errorf(fmt.Sprintf("Failed to parse header '%s'", header))
|
||||||
}
|
}
|
||||||
|
|
||||||
auth_type := strings.ToLower(values[0])
|
authType := strings.ToLower(values[0])
|
||||||
if auth_type != "basic" {
|
if authType != "basic" {
|
||||||
return nil, fmt.Errorf("Expected basic auth type, got '%s'", auth_type)
|
return nil, fmt.Errorf("Expected basic auth type, got '%s'", authType)
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded_string := values[1]
|
encodedString := values[1]
|
||||||
decoded_string, err := base64.StdEncoding.DecodeString(encoded_string)
|
decodedString, err := base64.StdEncoding.DecodeString(encodedString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to parse header '%s', base64 failed: %s", header, err)
|
return nil, fmt.Errorf("Failed to parse header '%s', base64 failed: %s", header, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
values = strings.SplitN(string(decoded_string), ":", 2)
|
values = strings.SplitN(string(decodedString), ":", 2)
|
||||||
if len(values) != 2 {
|
if len(values) != 2 {
|
||||||
return nil, fmt.Errorf("Failed to parse header '%s', expected separator ':'", header)
|
return nil, fmt.Errorf("Failed to parse header '%s', expected separator ':'", header)
|
||||||
}
|
}
|
||||||
|
|
60
vendor/github.com/vulcand/oxy/utils/dumpreq.go
generated
vendored
Normal file
60
vendor/github.com/vulcand/oxy/utils/dumpreq.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SerializableHttpRequest struct {
|
||||||
|
Method string
|
||||||
|
URL *url.URL
|
||||||
|
Proto string // "HTTP/1.0"
|
||||||
|
ProtoMajor int // 1
|
||||||
|
ProtoMinor int // 0
|
||||||
|
Header http.Header
|
||||||
|
ContentLength int64
|
||||||
|
TransferEncoding []string
|
||||||
|
Host string
|
||||||
|
Form url.Values
|
||||||
|
PostForm url.Values
|
||||||
|
MultipartForm *multipart.Form
|
||||||
|
Trailer http.Header
|
||||||
|
RemoteAddr string
|
||||||
|
RequestURI string
|
||||||
|
TLS *tls.ConnectionState
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clone(r *http.Request) *SerializableHttpRequest {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := new(SerializableHttpRequest)
|
||||||
|
rc.Method = r.Method
|
||||||
|
rc.URL = r.URL
|
||||||
|
rc.Proto = r.Proto
|
||||||
|
rc.ProtoMajor = r.ProtoMajor
|
||||||
|
rc.ProtoMinor = r.ProtoMinor
|
||||||
|
rc.Header = r.Header
|
||||||
|
rc.ContentLength = r.ContentLength
|
||||||
|
rc.Host = r.Host
|
||||||
|
rc.RemoteAddr = r.RemoteAddr
|
||||||
|
rc.RequestURI = r.RequestURI
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SerializableHttpRequest) ToJson() string {
|
||||||
|
if jsonVal, err := json.Marshal(s); err != nil || jsonVal == nil {
|
||||||
|
return fmt.Sprintf("Error marshalling SerializableHttpRequest to json: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
return string(jsonVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DumpHttpRequest(req *http.Request) string {
|
||||||
|
return fmt.Sprintf("%v", Clone(req).ToJson())
|
||||||
|
}
|
86
vendor/github.com/vulcand/oxy/utils/logging.go
generated
vendored
86
vendor/github.com/vulcand/oxy/utils/logging.go
generated
vendored
|
@ -1,86 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var NullLogger Logger = &NOPLogger{}
|
|
||||||
|
|
||||||
// Logger defines a simple logging interface
|
|
||||||
type Logger interface {
|
|
||||||
Infof(format string, args ...interface{})
|
|
||||||
Warningf(format string, args ...interface{})
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileLogger struct {
|
|
||||||
info *log.Logger
|
|
||||||
warn *log.Logger
|
|
||||||
error *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFileLogger(w io.Writer, lvl LogLevel) *FileLogger {
|
|
||||||
l := &FileLogger{}
|
|
||||||
flag := log.Ldate | log.Ltime | log.Lmicroseconds
|
|
||||||
if lvl <= INFO {
|
|
||||||
l.info = log.New(w, "INFO: ", flag)
|
|
||||||
}
|
|
||||||
if lvl <= WARN {
|
|
||||||
l.warn = log.New(w, "WARN: ", flag)
|
|
||||||
}
|
|
||||||
if lvl <= ERROR {
|
|
||||||
l.error = log.New(w, "ERR: ", flag)
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileLogger) Infof(format string, args ...interface{}) {
|
|
||||||
if f.info == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.info.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
if f.warn == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.warn.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
if f.error == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.error.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NOPLogger struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NOPLogger) Infof(format string, args ...interface{}) {
|
|
||||||
|
|
||||||
}
|
|
||||||
func (*NOPLogger) Warningf(format string, args ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NOPLogger) Errorf(format string, args ...interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NOPLogger) Info(string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
func (*NOPLogger) Warning(string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*NOPLogger) Error(string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
INFO = iota
|
|
||||||
WARN
|
|
||||||
ERROR
|
|
||||||
)
|
|
51
vendor/github.com/vulcand/oxy/utils/netutils.go
generated
vendored
51
vendor/github.com/vulcand/oxy/utils/netutils.go
generated
vendored
|
@ -2,19 +2,23 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProxyWriter helps to capture response headers and status code
|
// ProxyWriter helps to capture response headers and status code
|
||||||
// from the ServeHTTP. It can be safely passed to ServeHTTP handler,
|
// from the ServeHTTP. It can be safely passed to ServeHTTP handler,
|
||||||
// wrapping the real response writer.
|
// wrapping the real response writer.
|
||||||
type ProxyWriter struct {
|
type ProxyWriter struct {
|
||||||
W http.ResponseWriter
|
W http.ResponseWriter
|
||||||
Code int
|
Code int
|
||||||
|
Length int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyWriter) StatusCode() int {
|
func (p *ProxyWriter) StatusCode() int {
|
||||||
|
@ -31,6 +35,7 @@ func (p *ProxyWriter) Header() http.Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProxyWriter) Write(buf []byte) (int, error) {
|
func (p *ProxyWriter) Write(buf []byte) (int, error) {
|
||||||
|
p.Length = p.Length + int64(len(buf))
|
||||||
return p.W.Write(buf)
|
return p.W.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +50,20 @@ func (p *ProxyWriter) Flush() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ProxyWriter) CloseNotify() <-chan bool {
|
||||||
|
if cn, ok := p.W.(http.CloseNotifier); ok {
|
||||||
|
return cn.CloseNotify()
|
||||||
|
}
|
||||||
|
log.Warningf("Upstream ResponseWriter of type %v does not implement http.CloseNotifier. Returning dummy channel.", reflect.TypeOf(p.W))
|
||||||
|
return make(<-chan bool)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ProxyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (p *ProxyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
return p.W.(http.Hijacker).Hijack()
|
if hi, ok := p.W.(http.Hijacker); ok {
|
||||||
|
return hi.Hijack()
|
||||||
|
}
|
||||||
|
log.Warningf("Upstream ResponseWriter of type %v does not implement http.Hijacker. Returning dummy channel.", reflect.TypeOf(p.W))
|
||||||
|
return nil, nil, fmt.Errorf("The response writer that was wrapped in this proxy, does not implement http.Hijacker. It is of type: %v", reflect.TypeOf(p.W))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufferWriter(w io.WriteCloser) *BufferWriter {
|
func NewBufferWriter(w io.WriteCloser) *BufferWriter {
|
||||||
|
@ -79,8 +96,20 @@ func (b *BufferWriter) WriteHeader(code int) {
|
||||||
b.Code = code
|
b.Code = code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BufferWriter) CloseNotify() <-chan bool {
|
||||||
|
if cn, ok := b.W.(http.CloseNotifier); ok {
|
||||||
|
return cn.CloseNotify()
|
||||||
|
}
|
||||||
|
log.Warningf("Upstream ResponseWriter of type %v does not implement http.CloseNotifier. Returning dummy channel.", reflect.TypeOf(b.W))
|
||||||
|
return make(<-chan bool)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BufferWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (b *BufferWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
return b.W.(http.Hijacker).Hijack()
|
if hi, ok := b.W.(http.Hijacker); ok {
|
||||||
|
return hi.Hijack()
|
||||||
|
}
|
||||||
|
log.Warningf("Upstream ResponseWriter of type %v does not implement http.Hijacker. Returning dummy channel.", reflect.TypeOf(b.W))
|
||||||
|
return nil, nil, fmt.Errorf("The response writer that was wrapped in this proxy, does not implement http.Hijacker. It is of type: %v", reflect.TypeOf(b.W))
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopWriteCloser struct {
|
type nopWriteCloser struct {
|
||||||
|
@ -106,11 +135,9 @@ func CopyURL(i *url.URL) *url.URL {
|
||||||
|
|
||||||
// CopyHeaders copies http headers from source to destination, it
|
// CopyHeaders copies http headers from source to destination, it
|
||||||
// does not overide, but adds multiple headers
|
// does not overide, but adds multiple headers
|
||||||
func CopyHeaders(dst, src http.Header) {
|
func CopyHeaders(dst http.Header, src http.Header) {
|
||||||
for k, vv := range src {
|
for k, vv := range src {
|
||||||
for _, v := range vv {
|
dst[k] = append(dst[k], vv...)
|
||||||
dst.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,9 +157,3 @@ func RemoveHeaders(headers http.Header, names ...string) {
|
||||||
headers.Del(h)
|
headers.Del(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the MIME media type value of a header.
|
|
||||||
func GetHeaderMediaType(headers http.Header, name string) (string, error) {
|
|
||||||
mediatype, _, err := mime.ParseMediaType(headers.Get(name))
|
|
||||||
return mediatype, err
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue