Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WS ProxyPath option #974

Merged
merged 5 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions nats.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ type Options struct {
// supports compression. If the server does too, then data will be compressed.
Compression bool

// For websocket connections, adds a path to connections url.
// This is useful when connecting to NATS behind a proxy.
ProxyPath string

// InboxPrefix allows the default _INBOX prefix to be customized
InboxPrefix string
}
Expand Down Expand Up @@ -1147,6 +1151,15 @@ func Compression(enabled bool) Option {
}
}

// ProxyPath is an option for websocket connections that adds a path to connections url.
// This is useful when connecting to NATS behind a proxy.
func ProxyPath(path string) Option {
return func(o *Options) error {
o.ProxyPath = path
return nil
}
}

// CustomInboxPrefix configures the request + reply inbox prefix
func CustomInboxPrefix(p string) Option {
return func(o *Options) error {
Expand Down
9 changes: 9 additions & 0 deletions ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,15 @@ func (nc *Conn) wsInitHandshake(u *url.URL) error {
scheme = "https"
}
ustr := fmt.Sprintf("%s://%s", scheme, u.Host)

if nc.Opts.ProxyPath != "" {
proxyPath := nc.Opts.ProxyPath
if !strings.HasPrefix(proxyPath, "/") {
proxyPath = "/" + proxyPath
}
ustr += proxyPath
}

u, err = url.Parse(ustr)
if err != nil {
return err
Expand Down
36 changes: 36 additions & 0 deletions ws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ package nats
import (
"bytes"
"compress/flate"
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"reflect"
"runtime"
"strings"
Expand Down Expand Up @@ -1108,3 +1111,36 @@ func TestWSNoDeadlockOnAuthFailure(t *testing.T) {

tm.Stop()
}

func TestWSProxyPath(t *testing.T) {
const proxyPath = "/proxy1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of commenting line by line, I feel it is easier to show you how I would like the test to look like. The main difference with yours is that we are going to check with a prefix "/" and without, and use a go channel to check that the proxy was invoked. (note that you don't need to run the NATS Server since the connection is actually not happening)

        const proxyPath = "proxy1"

	// Listen to a random port
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		t.Fatalf("Error in listen: %v", err)
	}
	defer l.Close()

	proxyPort := l.Addr().(*net.TCPAddr).Port

	ch := make(chan struct{}, 1)
	proxySrv := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path == "/"+proxyPath {
				ch <- struct{}{}
			}
		}),
	}
	defer proxySrv.Shutdown(context.Background())
	go proxySrv.Serve(l)

	for _, test := range []struct {
		name string
		path string
	}{
		{"without slash", proxyPath},
		{"with slash", "/" + proxyPath},
	} {
		t.Run(test.name, func(t *testing.T) {
			url := fmt.Sprintf("ws://127.0.0.1:%d", proxyPort)
			nc, err := Connect(url, ProxyPath(test.path))
			if err == nil {
				nc.Close()
				t.Fatal("Did not expect to connect")
			}
			select {
			case <-ch:
				// OK:
			case <-time.After(time.Second):
				t.Fatal("Proxy was not reached")
			}
		})
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your great feedback.
Done.

var proxyCalled bool

// Listen to a random port
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("Error in listen: %v", err)
}
defer l.Close()

proxyPort := l.Addr().(*net.TCPAddr).Port

proxySrv := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxyCalled = r.URL.Path == proxyPath
}),
}
defer proxySrv.Shutdown(context.Background())
go proxySrv.Serve(l)

opt := testWSGetDefaultOptions(t, false)
s := RunServerWithOptions(opt)
defer s.Shutdown()

url := fmt.Sprintf("ws://127.0.0.1:%d", proxyPort)
Connect(url, ProxyPath(proxyPath))

if !proxyCalled {
t.Fatal("Proxy haven't been called")
}
}