mirror of
https://github.com/hanzoai/oxy.git
synced 2026-09-30 23:42:31 +00:00
forward: rewrite to use httputil.ReverseProxy (#229)
Co-authored-by: Fernandez Ludovic <[email protected]>
This commit is contained in:
co-authored by
Fernandez Ludovic
parent
9260f86ffc
commit
d996bf5376
@@ -38,7 +38,7 @@ import (
|
||||
)
|
||||
|
||||
// Forwards incoming requests to whatever location URL points to, adds proper forwarding headers
|
||||
fwd, _ := forward.New()
|
||||
fwd := forward.New(false)
|
||||
|
||||
redirect := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// let us forward this request to another server
|
||||
@@ -66,7 +66,7 @@ import (
|
||||
)
|
||||
|
||||
// Forwards incoming requests to whatever location URL points to, adds proper forwarding headers
|
||||
fwd, _ := forward.New()
|
||||
fwd := forward.New(false)
|
||||
lb, _ := roundrobin.New(fwd)
|
||||
|
||||
lb.UpsertServer(url1)
|
||||
@@ -93,7 +93,7 @@ import (
|
||||
|
||||
// Forwards incoming requests to whatever location URL points to, adds proper forwarding headers
|
||||
|
||||
fwd, _ := forward.New()
|
||||
fwd := forward.New(false)
|
||||
lb, _ := roundrobin.New(fwd)
|
||||
|
||||
// buffer will read the request body and will replay the request again in case if forward returned status
|
||||
|
||||
+12
-24
@@ -25,8 +25,7 @@ func TestSimple(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -60,8 +59,7 @@ func TestChunkedEncodingSuccess(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -95,8 +93,7 @@ func TestChunkedEncodingLimitReached(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -129,8 +126,7 @@ func TestChunkedResponse(t *testing.T) {
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
@@ -156,8 +152,7 @@ func TestRequestLimitReached(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -184,8 +179,7 @@ func TestResponseLimitReached(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -212,8 +206,7 @@ func TestFileStreamingResponse(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -241,8 +234,7 @@ func TestCustomErrorHandler(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -273,8 +265,7 @@ func TestNotModified(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -301,8 +292,7 @@ func TestNoBody(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -331,8 +321,7 @@ func TestPreservesTLS(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
var cs *tls.ConnectionState
|
||||
// this is our redirect to server
|
||||
@@ -363,8 +352,7 @@ func TestNotNilBody(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
@@ -76,8 +76,7 @@ func newBufferMiddleware(t *testing.T, p string) (*roundrobin.RoundRobin, *Buffe
|
||||
t.Helper()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// load balancer will round robin request
|
||||
lb, err := roundrobin.New(fwd)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func ExampleNew_customErrHandler() {
|
||||
f := New(true)
|
||||
f.ErrorHandler = func(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
_, _ = w.Write([]byte(http.StatusText(http.StatusTeapot)))
|
||||
}
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL, _ = url.ParseRequestURI("http://localhost:63450")
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
resp, err := http.Get(proxy.URL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(resp.StatusCode)
|
||||
fmt.Println(string(body))
|
||||
|
||||
// output:
|
||||
// 418
|
||||
// I'm a teapot
|
||||
}
|
||||
|
||||
func ExampleNew_responseModifier() {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := New(true)
|
||||
f.ModifyResponse = func(resp *http.Response) error {
|
||||
resp.Header.Add("X-Test", "CUSTOM")
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL, _ = url.ParseRequestURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
resp, err := http.Get(proxy.URL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(resp.StatusCode)
|
||||
fmt.Println(resp.Header.Get("X-Test"))
|
||||
|
||||
// Output:
|
||||
// 200
|
||||
// CUSTOM
|
||||
}
|
||||
|
||||
func ExampleNew_customTransport() {
|
||||
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := New(true)
|
||||
|
||||
f.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL, _ = url.ParseRequestURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
resp, err := http.Get(proxy.URL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(resp.StatusCode)
|
||||
fmt.Println(string(body))
|
||||
|
||||
// Output:
|
||||
// 200
|
||||
// hello
|
||||
}
|
||||
|
||||
func ExampleNewStateListener() {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := New(true)
|
||||
f.ModifyResponse = func(resp *http.Response) error {
|
||||
resp.Header.Add("X-Test", "CUSTOM")
|
||||
return nil
|
||||
}
|
||||
|
||||
stateLn := NewStateListener(f, func(u *url.URL, i int) {
|
||||
fmt.Println(u.Hostname(), i)
|
||||
})
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL, _ = url.ParseRequestURI(srv.URL)
|
||||
stateLn.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
resp, err := http.Get(proxy.URL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(resp.StatusCode)
|
||||
|
||||
// Output:
|
||||
// 127.0.0.1 0
|
||||
// 127.0.0.1 1
|
||||
// 200
|
||||
}
|
||||
+26
-525
@@ -1,290 +1,35 @@
|
||||
// Package forward implements http handler that forwards requests to remote server
|
||||
// and serves back the response
|
||||
// websocket proxying support based on https://github.com/yhat/wsutil
|
||||
// Package forward creates a pre-configured httputil.ReverseProxy.
|
||||
package forward
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vulcand/oxy/internal/holsterv4/clock"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// OxyLogger interface of the internal.
|
||||
type OxyLogger interface {
|
||||
log.FieldLogger
|
||||
GetLevel() log.Level
|
||||
}
|
||||
// New creates a new ReverseProxy.
|
||||
func New(passHostHeader bool) *httputil.ReverseProxy {
|
||||
h := NewHeaderRewriter()
|
||||
|
||||
type internalLogger struct {
|
||||
*log.Logger
|
||||
}
|
||||
return &httputil.ReverseProxy{
|
||||
Director: func(request *http.Request) {
|
||||
modifyRequest(request)
|
||||
|
||||
func (i *internalLogger) GetLevel() log.Level {
|
||||
return i.Level
|
||||
}
|
||||
h.Rewrite(request)
|
||||
|
||||
// ReqRewriter can alter request headers and body.
|
||||
type ReqRewriter interface {
|
||||
Rewrite(r *http.Request)
|
||||
}
|
||||
|
||||
type optSetter func(f *Forwarder) error
|
||||
|
||||
// PassHostHeader specifies if a client's Host header field should be delegated.
|
||||
func PassHostHeader(b bool) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.passHost = b
|
||||
return nil
|
||||
if !passHostHeader {
|
||||
request.Host = request.URL.Host
|
||||
}
|
||||
},
|
||||
ErrorHandler: utils.DefaultHandler.ServeHTTP,
|
||||
}
|
||||
}
|
||||
|
||||
// RoundTripper sets a new http.RoundTripper
|
||||
// Forwarder will use http.DefaultTransport as a default round tripper.
|
||||
func RoundTripper(r http.RoundTripper) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.roundTripper = r
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Rewriter defines a request rewriter for the HTTP forwarder.
|
||||
func Rewriter(r ReqRewriter) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.rewriter = r
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketTLSClientConfig define the websocker client TLS configuration.
|
||||
func WebsocketTLSClientConfig(tcc *tls.Config) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.tlsClientConfig = tcc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorHandler is a functional argument that sets error handler of the server.
|
||||
func ErrorHandler(h utils.ErrorHandler) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.errHandler = h
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// BufferPool specifies a buffer pool for httputil.ReverseProxy.
|
||||
func BufferPool(pool httputil.BufferPool) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.bufferPool = pool
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Stream specifies if HTTP responses should be streamed.
|
||||
func Stream(stream bool) 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.FieldLogger) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
if logger, ok := l.(OxyLogger); ok {
|
||||
f.log = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
if logger, ok := l.(*log.Logger); ok {
|
||||
f.log = &internalLogger{Logger: logger}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("the type of the logger must be OxyLogger or logrus.Logger")
|
||||
}
|
||||
}
|
||||
|
||||
// StateListener defines a state listener for the HTTP forwarder.
|
||||
func StateListener(stateListener URLForwardingStateListener) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.stateListener = stateListener
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketConnectionClosedHook defines a hook called when websocket connection is closed.
|
||||
func WebsocketConnectionClosedHook(hook func(req *http.Request, conn net.Conn)) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.websocketConnectionClosedHook = hook
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseModifier defines a response modifier for the HTTP forwarder.
|
||||
func ResponseModifier(responseModifier func(*http.Response) error) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.modifyResponse = responseModifier
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// StreamingFlushInterval defines a streaming flush interval for the HTTP forwarder.
|
||||
func StreamingFlushInterval(flushInterval time.Duration) optSetter {
|
||||
return func(f *Forwarder) error {
|
||||
f.httpForwarder.flushInterval = flushInterval
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Forwarder wraps two traffic forwarding implementations: HTTP and websockets.
|
||||
// It decides based on the specified request which implementation to use.
|
||||
type Forwarder struct {
|
||||
*httpForwarder
|
||||
*handlerContext
|
||||
stateListener URLForwardingStateListener
|
||||
stream bool
|
||||
}
|
||||
|
||||
// handlerContext defines a handler context for error reporting and logging.
|
||||
type handlerContext struct {
|
||||
errHandler utils.ErrorHandler
|
||||
}
|
||||
|
||||
// httpForwarder is a handler that can reverse proxy HTTP traffic.
|
||||
type httpForwarder struct {
|
||||
roundTripper http.RoundTripper
|
||||
rewriter ReqRewriter
|
||||
passHost bool
|
||||
flushInterval time.Duration
|
||||
modifyResponse func(*http.Response) error
|
||||
|
||||
tlsClientConfig *tls.Config
|
||||
|
||||
log OxyLogger
|
||||
|
||||
bufferPool httputil.BufferPool
|
||||
websocketConnectionClosedHook func(req *http.Request, conn net.Conn)
|
||||
}
|
||||
|
||||
const defaultFlushInterval = 100 * clock.Millisecond
|
||||
|
||||
// Connection states.
|
||||
const (
|
||||
StateConnected = iota
|
||||
StateDisconnected
|
||||
)
|
||||
|
||||
// UrlForwardingStateListener alias on URLForwardingStateListener.
|
||||
// Deprecated: use URLForwardingStateListener instead.
|
||||
type UrlForwardingStateListener = URLForwardingStateListener
|
||||
|
||||
// URLForwardingStateListener URL forwarding state listener.
|
||||
type URLForwardingStateListener func(*url.URL, int)
|
||||
|
||||
// New creates an instance of Forwarder based on the provided list of configuration options.
|
||||
func New(setters ...optSetter) (*Forwarder, error) {
|
||||
f := &Forwarder{
|
||||
httpForwarder: &httpForwarder{log: &internalLogger{Logger: log.StandardLogger()}},
|
||||
handlerContext: &handlerContext{},
|
||||
}
|
||||
for _, s := range setters {
|
||||
if err := s(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.stream {
|
||||
f.flushInterval = 0
|
||||
} else if f.flushInterval == 0 {
|
||||
f.flushInterval = defaultFlushInterval
|
||||
}
|
||||
|
||||
if f.httpForwarder.rewriter == nil {
|
||||
h, err := os.Hostname()
|
||||
if err != nil {
|
||||
h = "localhost"
|
||||
}
|
||||
f.httpForwarder.rewriter = &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
|
||||
}
|
||||
|
||||
if f.httpForwarder.roundTripper == nil {
|
||||
f.httpForwarder.roundTripper = http.DefaultTransport
|
||||
}
|
||||
|
||||
if f.errHandler == nil {
|
||||
f.errHandler = utils.DefaultHandler
|
||||
}
|
||||
|
||||
if f.tlsClientConfig == nil {
|
||||
if ht, ok := f.httpForwarder.roundTripper.(*http.Transport); ok {
|
||||
f.tlsClientConfig = ht.TLSClientConfig
|
||||
}
|
||||
}
|
||||
|
||||
f.postConfig()
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ServeHTTP decides which forwarder to use based on the specified
|
||||
// request and delegates to the proper implementation.
|
||||
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if f.log.GetLevel() >= log.DebugLevel {
|
||||
logEntry := f.log.WithField("Request", utils.DumpHTTPRequest(req))
|
||||
logEntry.Debug("vulcand/oxy/forward: begin ServeHttp on request")
|
||||
defer logEntry.Debug("vulcand/oxy/forward: completed 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 {
|
||||
f.httpForwarder.serveHTTP(w, req, f.handlerContext)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *httpForwarder) getURLFromRequest(req *http.Request) *url.URL {
|
||||
// If the Request was created by Go via a real HTTP request, RequestURI will
|
||||
// contain the original query string. If the Request was created in code, RequestURI
|
||||
// will be empty, and we will use the URL object instead
|
||||
u := req.URL
|
||||
if req.RequestURI != "" {
|
||||
parsedURL, err := url.ParseRequestURI(req.RequestURI)
|
||||
if err == nil {
|
||||
u = parsedURL
|
||||
} else {
|
||||
f.log.Warnf("vulcand/oxy/forward: error when parsing RequestURI: %s", err)
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// Modify the request to handle the target URL.
|
||||
func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||
outReq.URL = utils.CopyURL(outReq.URL)
|
||||
outReq.URL.Scheme = target.Scheme
|
||||
outReq.URL.Host = target.Host
|
||||
|
||||
u := f.getURLFromRequest(outReq)
|
||||
func modifyRequest(outReq *http.Request) {
|
||||
u := getURLFromRequest(outReq)
|
||||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
@@ -294,263 +39,19 @@ func (f *httpForwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
if f.rewriter != nil {
|
||||
f.rewriter.Rewrite(outReq)
|
||||
}
|
||||
|
||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||
if !f.passHost {
|
||||
outReq.Host = target.Host
|
||||
}
|
||||
}
|
||||
|
||||
// serveWebSocket forwards websocket traffic.
|
||||
func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request, ctx *handlerContext) {
|
||||
if f.log.GetLevel() >= 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: completed ServeHttp on request")
|
||||
}
|
||||
|
||||
outReq := f.copyWebSocketRequest(req)
|
||||
|
||||
dialer := websocket.DefaultDialer
|
||||
if outReq.URL.Scheme == "wss" && f.tlsClientConfig != nil {
|
||||
dialer.TLSClientConfig = f.tlsClientConfig.Clone()
|
||||
// WebSocket is only in http/1.1
|
||||
dialer.TLSClientConfig.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
targetConn, resp, err := dialer.DialContext(outReq.Context(), outReq.URL.String(), outReq.Header)
|
||||
if err != nil {
|
||||
if resp == nil {
|
||||
ctx.errHandler.ServeHTTP(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
f.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)
|
||||
if !ok {
|
||||
f.log.Errorf("vulcand/oxy/forward/websocket: %s can not be hijack", reflect.TypeOf(w))
|
||||
ctx.errHandler.ServeHTTP(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
conn, _, errHijack := hijacker.Hijack()
|
||||
if errHijack != nil {
|
||||
f.log.Errorf("vulcand/oxy/forward/websocket: Failed to hijack responseWriter")
|
||||
ctx.errHandler.ServeHTTP(w, req, errHijack)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
if f.websocketConnectionClosedHook != nil {
|
||||
f.websocketConnectionClosedHook(req, conn)
|
||||
}
|
||||
}()
|
||||
|
||||
errWrite := resp.Write(conn)
|
||||
if errWrite != nil {
|
||||
f.log.Errorf("vulcand/oxy/forward/websocket: Failed to forward response")
|
||||
ctx.errHandler.ServeHTTP(w, req, errWrite)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Only the targetConn choose to CheckOrigin or not
|
||||
upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
}}
|
||||
|
||||
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
||||
utils.CopyHeaders(resp.Header, w.Header())
|
||||
|
||||
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
||||
if err != nil {
|
||||
f.log.Errorf("vulcand/oxy/forward/websocket: Error while upgrading connection : %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = underlyingConn.Close()
|
||||
_ = targetConn.Close()
|
||||
if f.websocketConnectionClosedHook != nil {
|
||||
f.websocketConnectionClosedHook(req, underlyingConn.UnderlyingConn())
|
||||
}
|
||||
}()
|
||||
|
||||
errClient := make(chan error, 1)
|
||||
errBackend := make(chan error, 1)
|
||||
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
||||
forward := func(messageType int, reader io.Reader) error {
|
||||
writer, err := dst.NextWriter(messageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writer.Close()
|
||||
}
|
||||
|
||||
src.SetPingHandler(func(data string) error {
|
||||
return forward(websocket.PingMessage, bytes.NewReader([]byte(data)))
|
||||
})
|
||||
|
||||
src.SetPongHandler(func(data string) error {
|
||||
return forward(websocket.PongMessage, bytes.NewReader([]byte(data)))
|
||||
})
|
||||
|
||||
for {
|
||||
msgType, reader, err := src.NextReader()
|
||||
if err != nil {
|
||||
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, err.Error())
|
||||
if e, ok := err.(*websocket.CloseError); ok {
|
||||
if e.Code != websocket.CloseNoStatusReceived {
|
||||
m = nil
|
||||
// Following codes are not valid on the wire so just close the
|
||||
// underlying TCP connection without sending a close frame.
|
||||
if e.Code != websocket.CloseAbnormalClosure && e.Code != websocket.CloseTLSHandshake {
|
||||
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
errc <- err
|
||||
if m != nil {
|
||||
_ = forward(websocket.CloseMessage, bytes.NewReader(m))
|
||||
}
|
||||
break
|
||||
}
|
||||
err = forward(msgType, reader)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
break
|
||||
}
|
||||
func getURLFromRequest(req *http.Request) *url.URL {
|
||||
// If the Request was created by Go via a real HTTP request,
|
||||
// RequestURI will contain the original query string.
|
||||
// If the Request was created in code,
|
||||
// RequestURI will be empty, and we will use the URL object instead
|
||||
u := req.URL
|
||||
if req.RequestURI != "" {
|
||||
parsedURL, err := url.ParseRequestURI(req.RequestURI)
|
||||
if err == nil {
|
||||
return parsedURL
|
||||
}
|
||||
}
|
||||
|
||||
go replicateWebsocketConn(underlyingConn, targetConn, errClient)
|
||||
go replicateWebsocketConn(targetConn, underlyingConn, errBackend)
|
||||
|
||||
var message string
|
||||
select {
|
||||
case err = <-errClient:
|
||||
message = "vulcand/oxy/forward/websocket: Error when copying from backend to client: %v"
|
||||
case err = <-errBackend:
|
||||
message = "vulcand/oxy/forward/websocket: Error when copying from client to backend: %v"
|
||||
}
|
||||
|
||||
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
|
||||
f.log.Errorf(message, err)
|
||||
}
|
||||
}
|
||||
|
||||
// copyWebsocketRequest makes a copy of the specified request.
|
||||
func (f *httpForwarder) copyWebSocketRequest(req *http.Request) (outReq *http.Request) {
|
||||
outReq = new(http.Request)
|
||||
*outReq = *req // includes shallow copies of maps, but we handle this below
|
||||
|
||||
outReq.URL = utils.CopyURL(req.URL)
|
||||
outReq.URL.Scheme = req.URL.Scheme
|
||||
|
||||
// sometimes backends might be registered as HTTP/HTTPS servers so translate URLs to websocket URLs.
|
||||
switch req.URL.Scheme {
|
||||
case "https":
|
||||
outReq.URL.Scheme = "wss"
|
||||
case "http":
|
||||
outReq.URL.Scheme = "ws"
|
||||
}
|
||||
|
||||
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.Host = req.URL.Host
|
||||
if !f.passHost {
|
||||
outReq.Host = req.URL.Host
|
||||
}
|
||||
|
||||
outReq.Header = make(http.Header)
|
||||
// gorilla websocket use this header to set the request.Host tested in checkSameOrigin
|
||||
outReq.Header.Set("Host", outReq.Host)
|
||||
utils.CopyHeaders(outReq.Header, req.Header)
|
||||
utils.RemoveHeaders(outReq.Header, WebsocketDialHeaders...)
|
||||
|
||||
if f.rewriter != nil {
|
||||
f.rewriter.Rewrite(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.GetLevel() >= 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")
|
||||
}
|
||||
|
||||
start := clock.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,
|
||||
BufferPool: f.bufferPool,
|
||||
ErrorHandler: ctx.errHandler.ServeHTTP,
|
||||
}
|
||||
|
||||
if f.log.GetLevel() >= log.DebugLevel {
|
||||
pw := utils.NewProxyWriter(w)
|
||||
revproxy.ServeHTTP(pw, outReq)
|
||||
|
||||
if inReq.TLS != nil {
|
||||
f.log.Debugf("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.StatusCode(), pw.GetLength(), clock.Now().UTC().Sub(start),
|
||||
inReq.TLS.Version,
|
||||
inReq.TLS.DidResume,
|
||||
inReq.TLS.CipherSuite,
|
||||
inReq.TLS.ServerName)
|
||||
} else {
|
||||
f.log.Debugf("vulcand/oxy/forward/http: Round trip: %v, code: %v, Length: %v, duration: %v",
|
||||
inReq.URL, pw.StatusCode(), pw.GetLength(), clock.Now().UTC().Sub(start))
|
||||
}
|
||||
} else {
|
||||
revproxy.ServeHTTP(w, outReq)
|
||||
}
|
||||
|
||||
for key := range w.Header() {
|
||||
if strings.HasPrefix(key, http.TrailerPrefix) {
|
||||
if fl, ok := w.(http.Flusher); ok {
|
||||
fl.Flush()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsWebsocketRequest determines if the specified HTTP request is a websocket handshake request.
|
||||
func IsWebsocketRequest(req *http.Request) bool {
|
||||
containsHeader := func(name, value string) bool {
|
||||
items := strings.Split(req.Header.Get(name), ",")
|
||||
for _, item := range items {
|
||||
if value == strings.ToLower(strings.TrimSpace(item)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return containsHeader(Connection, "upgrade") && containsHeader(Upgrade, "websocket")
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
//go:build !go1.16
|
||||
// +build !go1.16
|
||||
|
||||
package forward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vulcand/oxy/testutils"
|
||||
)
|
||||
|
||||
func TestChunkedResponseConversion(t *testing.T) {
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
h := w.(http.Hijacker)
|
||||
conn, _, _ := h.Hijack()
|
||||
fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n5\r\ntest1\r\n5\r\ntest2\r\n0\r\n\r\n")
|
||||
conn.Close()
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, body, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "testtest1test2", string(body))
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package forward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vulcand/oxy/testutils"
|
||||
)
|
||||
|
||||
func TestChunkedResponseConversion(t *testing.T) {
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
h := w.(http.Hijacker)
|
||||
conn, _, _ := h.Hijack()
|
||||
fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n5\r\ntest1\r\n5\r\ntest2\r\n0\r\n\r\n")
|
||||
conn.Close()
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, body, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "testtest1test2", string(body))
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
|
||||
// Also as per RFC2616 (https://tools.ietf.org/html/rfc2616#section-4.4)
|
||||
// "Messages MUST NOT include both a Content-Length header field and a non-identity transfer-coding.
|
||||
// If the message does include a non-identity transfer-coding, the Content-Length MUST be ignored."
|
||||
_, ok := re.Header["Content-Length"]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
+18
-337
@@ -1,8 +1,6 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -10,105 +8,21 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vulcand/oxy/internal/holsterv4/clock"
|
||||
"github.com/vulcand/oxy/testutils"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// Makes sure hop-by-hop headers are removed.
|
||||
func TestForwardHopHeaders(t *testing.T) {
|
||||
called := false
|
||||
var outHeaders http.Header
|
||||
var outHost, expectedHost string
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
called = true
|
||||
outHeaders = req.Header
|
||||
outHost = req.Host
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
expectedHost = req.URL.Host
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
headers := http.Header{
|
||||
Connection: []string{"close"},
|
||||
KeepAlive: []string{"timeout=600"},
|
||||
}
|
||||
|
||||
re, body, err := testutils.Get(proxy.URL, testutils.Headers(headers))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hello", string(body))
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, true, called)
|
||||
assert.Equal(t, "", outHeaders.Get(Connection))
|
||||
assert.Equal(t, "", outHeaders.Get(KeepAlive))
|
||||
assert.Equal(t, expectedHost, outHost)
|
||||
}
|
||||
|
||||
func TestDefaultErrHandler(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI("http://localhost:63450")
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusBadGateway, re.StatusCode)
|
||||
}
|
||||
|
||||
func TestCustomErrHandler(t *testing.T) {
|
||||
f, err := New(ErrorHandler(utils.ErrorHandlerFunc(func(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
_, _ = w.Write([]byte(http.StatusText(http.StatusTeapot)))
|
||||
})))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI("http://localhost:63450")
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, body, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusTeapot, re.StatusCode)
|
||||
assert.Equal(t, http.StatusText(http.StatusTeapot), string(body))
|
||||
}
|
||||
|
||||
func TestResponseModifier(t *testing.T) {
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New(ResponseModifier(func(resp *http.Response) error {
|
||||
resp.Header.Add("X-Test", "CUSTOM")
|
||||
return nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
resp, err := http.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, "CUSTOM", re.Header.Get("X-Test"))
|
||||
assert.Equal(t, http.StatusBadGateway, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestXForwardedHostHeader(t *testing.T) {
|
||||
@@ -140,274 +54,41 @@ func TestXForwardedHostHeader(t *testing.T) {
|
||||
t.Run(test.Description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f, err := New(PassHostHeader(test.PassHostHeader))
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, test.TargetURL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
backendURL, err := url.Parse(test.ProxyfiedURL)
|
||||
require.NoError(t, err)
|
||||
f.modifyRequest(r, backendURL)
|
||||
r.URL = backendURL
|
||||
|
||||
f.Director(r)
|
||||
require.Equal(t, test.ExpectedXForwardedHost, r.Header.Get(XForwardedHost))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure hop-by-hop headers are removed.
|
||||
func TestForwardedHeaders(t *testing.T) {
|
||||
var outHeaders http.Header
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
outHeaders = req.Header
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New(Rewriter(&HeaderRewriter{TrustForwardHeader: true, Hostname: "hello"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
headers := http.Header{
|
||||
XForwardedProto: []string{"httpx"},
|
||||
XForwardedFor: []string{"192.168.1.1"},
|
||||
XForwardedServer: []string{"foobar"},
|
||||
XForwardedHost: []string{"upstream-foobar"},
|
||||
}
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL, testutils.Headers(headers))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, "httpx", outHeaders.Get(XForwardedProto))
|
||||
assert.Contains(t, outHeaders.Get(XForwardedFor), "192.168.1.1")
|
||||
assert.Contains(t, "upstream-foobar", outHeaders.Get(XForwardedHost))
|
||||
assert.Equal(t, "hello", outHeaders.Get(XForwardedServer))
|
||||
}
|
||||
|
||||
func TestCustomRewriter(t *testing.T) {
|
||||
var outHeaders http.Header
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
outHeaders = req.Header
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New(Rewriter(&HeaderRewriter{TrustForwardHeader: false, Hostname: "hello"}))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
headers := http.Header{
|
||||
XForwardedProto: []string{"httpx"},
|
||||
XForwardedFor: []string{"192.168.1.1"},
|
||||
}
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL, testutils.Headers(headers))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, "http", outHeaders.Get(XForwardedProto))
|
||||
assert.NotContains(t, outHeaders.Get(XForwardedFor), "192.168.1.1")
|
||||
}
|
||||
|
||||
func TestCustomTransportTimeout(t *testing.T) {
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
clock.Sleep(20 * clock.Millisecond)
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New(RoundTripper(
|
||||
&http.Transport{
|
||||
ResponseHeaderTimeout: 5 * clock.Millisecond,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusGatewayTimeout, re.StatusCode)
|
||||
}
|
||||
|
||||
func TestCustomLogger(t *testing.T) {
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
}
|
||||
|
||||
func TestRouteForwarding(t *testing.T) {
|
||||
var outPath string
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
outPath = req.RequestURI
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
tests := []struct {
|
||||
Path string
|
||||
Query string
|
||||
|
||||
ExpectedPath string
|
||||
}{
|
||||
{"/hello", "", "/hello"},
|
||||
{"//hello", "", "//hello"},
|
||||
{"///hello", "", "///hello"},
|
||||
{"/hello", "abc=def&def=123", "/hello?abc=def&def=123"},
|
||||
{"/log/http%3A%2F%2Fwww.site.com%2Fsomething?a=b", "", "/log/http%3A%2F%2Fwww.site.com%2Fsomething?a=b"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
proxyURL := proxy.URL + test.Path
|
||||
if test.Query != "" {
|
||||
proxyURL = proxyURL + "?" + test.Query
|
||||
}
|
||||
request, err := http.NewRequest("GET", proxyURL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
re, err := http.DefaultClient.Do(request)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, test.ExpectedPath, outPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardedProto(t *testing.T) {
|
||||
var proto string
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
proto = req.Header.Get(XForwardedProto)
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
proxy := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
proxy := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
tproxy := httptest.NewUnstartedServer(proxy)
|
||||
tproxy.StartTLS()
|
||||
defer tproxy.Close()
|
||||
}))
|
||||
proxy.StartTLS()
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(tproxy.URL)
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, "https", proto)
|
||||
}
|
||||
|
||||
func TestContextWithValueInErrHandler(t *testing.T) {
|
||||
originalBool := false
|
||||
originalPBool := &originalBool
|
||||
|
||||
type MyKey string
|
||||
const key MyKey = "test"
|
||||
|
||||
f, err := New(ErrorHandler(utils.ErrorHandlerFunc(func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
test, isBool := req.Context().Value(key).(*bool)
|
||||
if isBool {
|
||||
*test = true
|
||||
}
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
})))
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
// We need a network error
|
||||
req.URL = testutils.ParseURI("http://localhost:63450")
|
||||
newReq := req.WithContext(context.WithValue(req.Context(), key, originalPBool))
|
||||
|
||||
f.ServeHTTP(w, newReq)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusBadGateway, re.StatusCode)
|
||||
assert.True(t, *originalPBool)
|
||||
}
|
||||
|
||||
func TestTeTrailer(t *testing.T) {
|
||||
var teHeader string
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
teHeader = req.Header.Get(Te)
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxy := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
tproxy := httptest.NewUnstartedServer(proxy)
|
||||
tproxy.StartTLS()
|
||||
defer tproxy.Close()
|
||||
|
||||
re, _, err := testutils.Get(tproxy.URL, testutils.Header("Te", "trailers"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, re.StatusCode)
|
||||
assert.Equal(t, "trailers", teHeader)
|
||||
}
|
||||
|
||||
func TestUnannouncedTrailer(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(200)
|
||||
rw.(http.Flusher).Flush()
|
||||
|
||||
rw.Header().Add(http.TrailerPrefix+"X-Trailer", "foo")
|
||||
}))
|
||||
|
||||
proxy, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxySrv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
proxy.ServeHTTP(rw, req)
|
||||
}))
|
||||
|
||||
resp, _ := http.Get(proxySrv.URL)
|
||||
_, err = io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, resp.Trailer.Get("X-Trailer"), "foo")
|
||||
}
|
||||
|
||||
+19
-156
@@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
gorillawebsocket "github.com/gorilla/websocket"
|
||||
@@ -19,8 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func TestWebSocketTCPClose(t *testing.T) {
|
||||
f, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
@@ -57,56 +55,8 @@ func TestWebSocketTCPClose(t *testing.T) {
|
||||
assert.Equal(t, 1006, wsErr.Code)
|
||||
}
|
||||
|
||||
func TestWebsocketConnectionClosedHook(t *testing.T) {
|
||||
closed := make(chan struct{})
|
||||
f, err := New(WebsocketConnectionClosedHook(func(req *http.Request, conn net.Conn) {
|
||||
close(closed)
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
msg := make([]byte, 4)
|
||||
_, _ = conn.Read(msg)
|
||||
_, _ = conn.Write(msg)
|
||||
_ = conn.Close()
|
||||
}))
|
||||
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
serverAddr := proxy.Listener.Addr().String()
|
||||
|
||||
headers := http.Header{}
|
||||
webSocketURL := "ws://" + serverAddr + "/ws"
|
||||
headers.Add("Origin", webSocketURL)
|
||||
|
||||
conn, resp, err := gorillawebsocket.DefaultDialer.Dial(webSocketURL, headers)
|
||||
require.NoError(t, err, "Error during Dial with response: %+v", resp)
|
||||
|
||||
_ = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
|
||||
t.Log(conn.ReadMessage())
|
||||
|
||||
_ = conn.Close()
|
||||
|
||||
select {
|
||||
case <-clock.After(clock.Second):
|
||||
t.Errorf("Websocket Hook not called")
|
||||
case <-closed:
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSocketPingPong(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{
|
||||
HandshakeTimeout: 10 * clock.Second,
|
||||
@@ -167,8 +117,7 @@ func TestWebSocketPingPong(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketEcho(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
@@ -224,10 +173,7 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
f, err := New()
|
||||
f.passHost = test.passHost
|
||||
|
||||
require.NoError(t, err)
|
||||
f := New(test.passHost)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
@@ -275,54 +221,8 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSocketNumGoRoutine(t *testing.T) {
|
||||
t.Skip("Flaky on goroutine")
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
msg := make([]byte, 4)
|
||||
_, _ = conn.Read(msg)
|
||||
t.Log(string(msg))
|
||||
_, _ = conn.Write(msg)
|
||||
_ = conn.Close()
|
||||
}))
|
||||
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
proxy := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
serverAddr := proxy.Listener.Addr().String()
|
||||
|
||||
num := runtime.NumGoroutine()
|
||||
|
||||
headers := http.Header{}
|
||||
webSocketURL := "ws://" + serverAddr + "/ws"
|
||||
headers.Add("Origin", webSocketURL)
|
||||
|
||||
conn, resp, err := gorillawebsocket.DefaultDialer.Dial(webSocketURL, headers)
|
||||
require.NoError(t, err, "Error during Dial with response: %+v", resp)
|
||||
|
||||
_ = conn.WriteMessage(gorillawebsocket.TextMessage, []byte("OK"))
|
||||
t.Log(conn.ReadMessage())
|
||||
|
||||
_ = conn.Close()
|
||||
|
||||
clock.Sleep(clock.Second)
|
||||
assert.Equal(t, num, runtime.NumGoroutine())
|
||||
}
|
||||
|
||||
func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
@@ -362,8 +262,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
f, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -389,7 +288,7 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
_, err = newWebsocketRequest(
|
||||
_, err := newWebsocketRequest(
|
||||
withServer(proxyAddr),
|
||||
withPath("/ws"),
|
||||
withData("echo"),
|
||||
@@ -408,8 +307,7 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||
f, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -448,8 +346,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
@@ -480,8 +377,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||
f, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -519,38 +415,12 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||
assert.Equal(t, "ok", resp)
|
||||
}
|
||||
|
||||
func TestDetectsWebSocketRequest(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
_, _ = conn.Write([]byte("ok"))
|
||||
_ = conn.Close()
|
||||
}))
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
websocketRequest := IsWebsocketRequest(req)
|
||||
assert.Equal(t, true, websocketRequest)
|
||||
mux.ServeHTTP(w, req)
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
serverAddr := srv.Listener.Addr().String()
|
||||
|
||||
resp, err := newWebsocketRequest(
|
||||
withServer(serverAddr),
|
||||
withPath("/ws"),
|
||||
withData("echo"),
|
||||
).send()
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "ok", resp)
|
||||
}
|
||||
|
||||
func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/ws", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(400)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
srv := testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
@@ -564,11 +434,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
// Set new backend URL
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
req.URL.Path = path
|
||||
websocketRequest := IsWebsocketRequest(req)
|
||||
assert.Equal(t, true, websocketRequest)
|
||||
f.ServeHTTP(w, req)
|
||||
} else {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
})
|
||||
defer proxy.Close()
|
||||
@@ -596,8 +462,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestForwardsWebsocketTraffic(t *testing.T) {
|
||||
f, err := New()
|
||||
require.NoError(t, err)
|
||||
f := New(true)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
@@ -645,7 +510,7 @@ func createTLSWebsocketServer() *httptest.Server {
|
||||
return srv
|
||||
}
|
||||
|
||||
func createProxyWithForwarder(forwarder *Forwarder, uri string) *httptest.Server {
|
||||
func createProxyWithForwarder(forwarder http.Handler, uri string) *httptest.Server {
|
||||
return testutils.NewHandler(func(w http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path // keep the original path
|
||||
// Set new backend URL
|
||||
@@ -660,15 +525,14 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
srv := createTLSWebsocketServer()
|
||||
defer srv.Close()
|
||||
|
||||
forwarderWithoutTLSConfig, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
forwarderWithoutTLSConfig := New(true)
|
||||
|
||||
proxyWithoutTLSConfig := createProxyWithForwarder(forwarderWithoutTLSConfig, srv.URL)
|
||||
defer proxyWithoutTLSConfig.Close()
|
||||
|
||||
proxyAddr := proxyWithoutTLSConfig.Listener.Addr().String()
|
||||
|
||||
_, err = newWebsocketRequest(
|
||||
_, err := newWebsocketRequest(
|
||||
withServer(proxyAddr),
|
||||
withPath("/ws"),
|
||||
withData("ok"),
|
||||
@@ -679,8 +543,8 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
forwarderWithTLSConfig, err := New(PassHostHeader(true), RoundTripper(transport))
|
||||
require.NoError(t, err)
|
||||
forwarderWithTLSConfig := New(true)
|
||||
forwarderWithTLSConfig.Transport = transport
|
||||
|
||||
proxyWithTLSConfig := createProxyWithForwarder(forwarderWithTLSConfig, srv.URL)
|
||||
defer proxyWithTLSConfig.Close()
|
||||
@@ -698,8 +562,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
forwarderWithTLSConfigFromDefaultTransport, err := New(PassHostHeader(true))
|
||||
require.NoError(t, err)
|
||||
forwarderWithTLSConfigFromDefaultTransport := New(true)
|
||||
|
||||
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(forwarderWithTLSConfigFromDefaultTransport, srv.URL)
|
||||
defer proxyWithTLSConfig.Close()
|
||||
|
||||
+6
-51
@@ -2,59 +2,14 @@ package forward
|
||||
|
||||
// Headers.
|
||||
const (
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedPort = "X-Forwarded-Port"
|
||||
XForwardedServer = "X-Forwarded-Server"
|
||||
XRealIp = "X-Real-Ip"
|
||||
Connection = "Connection"
|
||||
KeepAlive = "Keep-Alive"
|
||||
ProxyAuthenticate = "Proxy-Authenticate"
|
||||
ProxyAuthorization = "Proxy-Authorization"
|
||||
Te = "Te" // canonicalized version of "TE"
|
||||
Trailers = "Trailers"
|
||||
TransferEncoding = "Transfer-Encoding"
|
||||
Upgrade = "Upgrade"
|
||||
ContentLength = "Content-Length"
|
||||
SecWebsocketKey = "Sec-Websocket-Key"
|
||||
SecWebsocketVersion = "Sec-Websocket-Version"
|
||||
SecWebsocketExtensions = "Sec-Websocket-Extensions"
|
||||
SecWebsocketAccept = "Sec-Websocket-Accept"
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedPort = "X-Forwarded-Port"
|
||||
XForwardedServer = "X-Forwarded-Server"
|
||||
XRealIp = "X-Real-Ip"
|
||||
)
|
||||
|
||||
// HopHeaders Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||
// Copied from reverseproxy.go, too bad.
|
||||
var HopHeaders = []string{
|
||||
Connection,
|
||||
KeepAlive,
|
||||
ProxyAuthenticate,
|
||||
ProxyAuthorization,
|
||||
Te, // canonicalized version of "TE"
|
||||
Trailers,
|
||||
TransferEncoding,
|
||||
Upgrade,
|
||||
}
|
||||
|
||||
// WebsocketDialHeaders Websocket dial headers.
|
||||
var WebsocketDialHeaders = []string{
|
||||
Upgrade,
|
||||
Connection,
|
||||
SecWebsocketKey,
|
||||
SecWebsocketVersion,
|
||||
SecWebsocketExtensions,
|
||||
SecWebsocketAccept,
|
||||
}
|
||||
|
||||
// WebsocketUpgradeHeaders Websocket upgrade headers.
|
||||
var WebsocketUpgradeHeaders = []string{
|
||||
Upgrade,
|
||||
Connection,
|
||||
SecWebsocketAccept,
|
||||
SecWebsocketExtensions,
|
||||
}
|
||||
|
||||
// XHeaders X-* headers.
|
||||
var XHeaders = []string{
|
||||
XForwardedProto,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Connection states.
|
||||
const (
|
||||
StateConnected = iota
|
||||
StateDisconnected
|
||||
)
|
||||
|
||||
// URLForwardingStateListener URL forwarding state listener.
|
||||
type URLForwardingStateListener func(*url.URL, int)
|
||||
|
||||
// StateListener listens on state change for urls.
|
||||
type StateListener struct {
|
||||
next http.Handler
|
||||
stateListener URLForwardingStateListener
|
||||
}
|
||||
|
||||
// NewStateListener creates a new StateListener middleware.
|
||||
func NewStateListener(next http.Handler, stateListener URLForwardingStateListener) *StateListener {
|
||||
return &StateListener{next: next, stateListener: stateListener}
|
||||
}
|
||||
|
||||
func (s *StateListener) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
s.stateListener(req.URL, StateConnected)
|
||||
s.next.ServeHTTP(rw, req)
|
||||
s.stateListener(req.URL, StateDisconnected)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
//go:build go1.11
|
||||
// +build go1.11
|
||||
|
||||
package forward
|
||||
|
||||
func (f *Forwarder) postConfig() {}
|
||||
@@ -1,43 +0,0 @@
|
||||
//go:build !go1.11
|
||||
// +build !go1.11
|
||||
|
||||
package forward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type key string
|
||||
|
||||
const (
|
||||
teHeader key = "TeHeader"
|
||||
)
|
||||
|
||||
type TeTrailerRoundTripper struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *TeTrailerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
teHeader := req.Context().Value(teHeader)
|
||||
if teHeader != nil {
|
||||
req.Header.Set("Te", teHeader.(string))
|
||||
}
|
||||
return t.RoundTripper.RoundTrip(req)
|
||||
}
|
||||
|
||||
type TeTrailerRewriter struct {
|
||||
ReqRewriter
|
||||
}
|
||||
|
||||
func (t *TeTrailerRewriter) Rewrite(req *http.Request) {
|
||||
if req.Header.Get("Te") == "trailers" {
|
||||
*req = *req.WithContext(context.WithValue(req.Context(), teHeader, req.Header.Get("Te")))
|
||||
}
|
||||
t.ReqRewriter.Rewrite(req)
|
||||
}
|
||||
|
||||
func (f *Forwarder) postConfig() {
|
||||
f.roundTripper = &TeTrailerRoundTripper{RoundTripper: f.roundTripper}
|
||||
f.rewriter = &TeTrailerRewriter{ReqRewriter: f.rewriter}
|
||||
}
|
||||
+11
-17
@@ -3,11 +3,21 @@ package forward
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// NewHeaderRewriter creates a new HeaderRewriter middleware.
|
||||
func NewHeaderRewriter() *HeaderRewriter {
|
||||
h, err := os.Hostname()
|
||||
if err != nil {
|
||||
h = "localhost"
|
||||
}
|
||||
return &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
|
||||
}
|
||||
|
||||
// HeaderRewriter is responsible for removing hop-by-hop headers and setting forwarding headers.
|
||||
type HeaderRewriter struct {
|
||||
TrustForwardHeader bool
|
||||
@@ -19,7 +29,7 @@ func ipv6fix(clientIP string) string {
|
||||
return strings.Split(clientIP, "%")[0]
|
||||
}
|
||||
|
||||
// Rewrite rewrite request headers.
|
||||
// Rewrite request headers.
|
||||
func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||
if !rw.TrustForwardHeader {
|
||||
utils.RemoveHeaders(req.Header, XHeaders...)
|
||||
@@ -27,14 +37,6 @@ func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
clientIP = ipv6fix(clientIP)
|
||||
// If not websocket, done in http.ReverseProxy
|
||||
if IsWebsocketRequest(req) {
|
||||
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) == "" {
|
||||
req.Header.Set(XRealIp, clientIP)
|
||||
@@ -50,14 +52,6 @@ func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ func TestRebalancerNormalOperation(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -40,8 +39,7 @@ func TestRebalancerNormalOperation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRebalancerNoServers(t *testing.T) {
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -62,8 +60,7 @@ func TestRebalancerRemoveServer(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -90,8 +87,7 @@ func TestRebalancerRecovery(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -156,8 +152,7 @@ func TestRebalancerCascading(t *testing.T) {
|
||||
defer b.Close()
|
||||
defer d.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -223,8 +218,7 @@ func TestRebalancerAllBad(t *testing.T) {
|
||||
defer b.Close()
|
||||
defer d.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -274,8 +268,7 @@ func TestRebalancerReset(t *testing.T) {
|
||||
defer b.Close()
|
||||
defer d.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -330,8 +323,7 @@ func TestRebalancerRequestRewriteListenerLive(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -371,8 +363,7 @@ func TestRebalancerRequestRewriteListener(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -394,8 +385,7 @@ func TestRebalancerStickySession(t *testing.T) {
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
|
||||
+9
-18
@@ -13,8 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNoServers(t *testing.T) {
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -40,8 +39,7 @@ func TestCustomErrHandler(t *testing.T) {
|
||||
_, _ = w.Write([]byte(http.StatusText(http.StatusTeapot)))
|
||||
})
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd, ErrorHandler(errHandler))
|
||||
require.NoError(t, err)
|
||||
@@ -58,8 +56,7 @@ func TestOneServer(t *testing.T) {
|
||||
a := testutils.NewResponder("a")
|
||||
defer a.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -79,8 +76,7 @@ func TestSimple(t *testing.T) {
|
||||
b := testutils.NewResponder("b")
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -101,8 +97,7 @@ func TestRemoveServer(t *testing.T) {
|
||||
b := testutils.NewResponder("b")
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -125,8 +120,7 @@ func TestUpsertSame(t *testing.T) {
|
||||
a := testutils.NewResponder("a")
|
||||
defer a.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -147,8 +141,7 @@ func TestUpsertWeight(t *testing.T) {
|
||||
b := testutils.NewResponder("b")
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -179,8 +172,7 @@ func TestWeighted(t *testing.T) {
|
||||
z := testutils.NewResponder("z")
|
||||
defer z.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd)
|
||||
require.NoError(t, err)
|
||||
@@ -218,8 +210,7 @@ func TestRequestRewriteListener(t *testing.T) {
|
||||
b := testutils.NewResponder("b")
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
lb, err := New(fwd,
|
||||
RoundRobinRequestRewriteListener(func(oldReq *http.Request, newReq *http.Request) {}))
|
||||
|
||||
@@ -23,8 +23,7 @@ func TestBasic(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -65,8 +64,7 @@ func TestBasicWithHashValue(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -119,8 +117,7 @@ func TestBasicWithAESValue(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -175,8 +172,7 @@ func TestStickyCookie(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -327,8 +323,7 @@ func TestStickyCookieWithOptions(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySessionWithOptions(test.name, test.options)
|
||||
require.NotNil(t, sticky)
|
||||
@@ -360,8 +355,7 @@ func TestRemoveRespondingServer(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -430,8 +424,7 @@ func TestRemoveAllServers(t *testing.T) {
|
||||
defer a.Close()
|
||||
defer b.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
@@ -483,8 +476,7 @@ func TestBadCookieVal(t *testing.T) {
|
||||
|
||||
defer a.Close()
|
||||
|
||||
fwd, err := forward.New()
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
sticky := NewStickySession("test")
|
||||
require.NotNil(t, sticky)
|
||||
|
||||
+9
-18
@@ -35,8 +35,7 @@ func TestSimple(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -83,8 +82,7 @@ func TestChunkedEncodingSuccess(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -127,8 +125,7 @@ func TestRequestLimitReached(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -155,8 +152,7 @@ func TestResponseLimitReached(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -183,8 +179,7 @@ func TestFileStreamingResponse(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -212,8 +207,7 @@ func TestCustomErrorHandler(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -239,8 +233,7 @@ func TestNotModified(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -267,8 +260,7 @@ func TestNoBody(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
// this is our redirect to server
|
||||
rdr := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -297,8 +289,7 @@ func TestPreservesTLS(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
// forwarder will proxy the request to whatever destination
|
||||
fwd, err := forward.New(forward.Stream(true))
|
||||
require.NoError(t, err)
|
||||
fwd := forward.New(false)
|
||||
|
||||
var cs *tls.ConnectionState
|
||||
// this is our redirect to server
|
||||
|
||||
Reference in New Issue
Block a user