diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4f35f426..4d6bbe02 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -18,7 +18,7 @@ jobs:
uses: golangci/golangci-lint-action@v3
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
- version: v1.51
+ version: v1.54
test:
name: Test and Coverage
@@ -27,12 +27,12 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
- go-version: 1.16
+ go-version: 1.18
- name: Check out code
uses: actions/checkout@v1
- - name: Run Unit tests.
+ - name: Run Unit tests
run: make test-coverage
- name: Upload Coverage report to CodeCov
@@ -79,7 +79,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
- go-version: 1.16
+ go-version: 1.18
- name: Check out code
uses: actions/checkout@v1
diff --git a/.run/go test signalr_test.run.xml b/.run/go test signalr_test.run.xml
index 5b51c9cb..d79f445d 100644
--- a/.run/go test signalr_test.run.xml
+++ b/.run/go test signalr_test.run.xml
@@ -9,12 +9,12 @@
-
+
-
-
+
+
diff --git a/Makefile b/Makefile
index 395bd77b..13269612 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ test-coverage: ## Run tests with coverage
@cat cover.out >> coverage.txt
build: dep ## Build the binary file
- @go build -i -o build/main $(PKG)
+ @go build -o build/main $(PKG)
clean: ## Remove previous build
@rm -f $(PROJECT_NAME)/build
diff --git a/clientoptions.go b/clientoptions.go
index f5465464..4aed5e53 100644
--- a/clientoptions.go
+++ b/clientoptions.go
@@ -1,8 +1,10 @@
package signalr
import (
+ "context"
"errors"
"fmt"
+
"github.com/cenkalti/backoff/v4"
)
@@ -36,6 +38,58 @@ func WithConnector(connectionFactory func() (Connection, error)) func(Party) err
}
}
+// HttpConnectionFactory is a connectionFactory for WithConnector which first tries to create a connection
+// with WebSockets (if it is allowed by the HttpConnection options) and if this fails, falls back to a SSE based connection.
+func HttpConnectionFactory(ctx context.Context, address string, options ...func(*httpConnection) error) (Connection, error) {
+ conn := &httpConnection{}
+ for i, option := range options {
+ if err := option(conn); err != nil {
+ return nil, err
+ }
+ if conn.transports != nil {
+ // Remove the WithTransports option
+ options = append(options[:i], options[i+1:]...)
+ break
+ }
+ }
+ // If no WithTransports was given, NewHTTPConnection fallbacks to both
+ if conn.transports == nil {
+ conn.transports = []TransportType{TransportWebSockets, TransportServerSentEvents}
+ }
+
+ for _, transport := range conn.transports {
+ // If Websockets are allowed, we try to connect with these
+ if transport == TransportWebSockets {
+ wsOptions := append(options, WithTransports(TransportWebSockets))
+ conn, err := NewHTTPConnection(ctx, address, wsOptions...)
+ // If this is ok, return the conn
+ if err == nil {
+ return conn, err
+ }
+ break
+ }
+ }
+ for _, transport := range conn.transports {
+ // If SSE is allowed, with fallback to try these
+ if transport == TransportServerSentEvents {
+ sseOptions := append(options, WithTransports(TransportServerSentEvents))
+ return NewHTTPConnection(ctx, address, sseOptions...)
+ }
+ }
+ // None of the transports worked
+ return nil, fmt.Errorf("can not connect with supported transports: %v", conn.transports)
+}
+
+// WithHttpConnection first tries to create a connection
+// with WebSockets (if it is allowed by the HttpConnection options) and if this fails, falls back to a SSE based connection.
+// This strategy is also used for auto reconnect if this option is used.
+// WithHttpConnection is a shortcut for WithConnector(HttpConnectionFactory(...))
+func WithHttpConnection(ctx context.Context, address string, options ...func(*httpConnection) error) func(Party) error {
+ return WithConnector(func() (Connection, error) {
+ return HttpConnectionFactory(ctx, address, options...)
+ })
+}
+
// WithReceiver sets the object which will receive server side calls to client methods (e.g. callbacks)
func WithReceiver(receiver interface{}) func(Party) error {
return func(party Party) error {
@@ -64,7 +118,7 @@ func WithBackoff(backoffFactory func() backoff.BackOff) func(party Party) error
}
// TransferFormat sets the transfer format used on the transport. Allowed values are "Text" and "Binary"
-func TransferFormat(format string) func(Party) error {
+func TransferFormat(format TransferFormatType) func(Party) error {
return func(p Party) error {
if c, ok := p.(*client); ok {
switch format {
diff --git a/doc.go b/doc.go
index 77c519ad..95c9862a 100644
--- a/doc.go
+++ b/doc.go
@@ -32,7 +32,7 @@ which kind of connection (Websockets, Server-Sent Events) will be used.
// Client with JSON encoding
client, err := NewClient(ctx,
WithConnection(conn),
- TransferFormat("Text"),
+ TransferFormat(TransferFormatText),
WithReceiver(receiver))
client.Start()
diff --git a/go.mod b/go.mod
index 305c7fcb..6420a099 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/philippseith/signalr
-go 1.16
+go 1.18
require (
github.com/cenkalti/backoff/v4 v4.2.1
@@ -16,7 +16,17 @@ require (
)
require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/klauspost/compress v1.16.6 // indirect
+ github.com/nxadm/tail v1.4.4 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
+ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
+ golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.10.0 // indirect
+ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index aa113a8a..00870af7 100644
--- a/go.sum
+++ b/go.sum
@@ -78,13 +78,9 @@ github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQ
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/teivah/onecontext v1.3.0 h1:tbikMhAlo6VhAuEGCvhc8HlTnpX4xTNPTOseWuhO1J0=
@@ -97,29 +93,20 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -127,31 +114,19 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/httpconnection.go b/httpconnection.go
index 5bb4d7b1..83448b85 100644
--- a/httpconnection.go
+++ b/httpconnection.go
@@ -18,8 +18,9 @@ type Doer interface {
}
type httpConnection struct {
- client Doer
- headers func() http.Header
+ client Doer
+ headers func() http.Header
+ transports []TransportType
}
// WithHTTPClient sets the http client used to connect to the signalR server.
@@ -39,6 +40,21 @@ func WithHTTPHeaders(headers func() http.Header) func(*httpConnection) error {
}
}
+func WithTransports(transports ...TransportType) func(*httpConnection) error {
+ return func(c *httpConnection) error {
+ for _, transport := range transports {
+ switch transport {
+ case TransportWebSockets, TransportServerSentEvents:
+ // Supported
+ default:
+ return fmt.Errorf("unsupported transport %s", transport)
+ }
+ }
+ c.transports = transports
+ return nil
+ }
+}
+
// NewHTTPConnection creates a signalR HTTP Connection for usage with a Client.
// ctx can be used to cancel the SignalR negotiation during the creation of the Connection
// but not the Connection itself.
@@ -56,6 +72,9 @@ func NewHTTPConnection(ctx context.Context, address string, options ...func(*htt
if httpConn.client == nil {
httpConn.client = http.DefaultClient
}
+ if len(httpConn.transports) == 0 {
+ httpConn.transports = []TransportType{TransportWebSockets, TransportServerSentEvents}
+ }
reqURL, err := url.Parse(address)
if err != nil {
@@ -88,22 +107,22 @@ func NewHTTPConnection(ctx context.Context, address string, options ...func(*htt
return nil, err
}
- nr := negotiateResponse{}
- if err := json.Unmarshal(body, &nr); err != nil {
+ negotiateResponse := negotiateResponse{}
+ if err := json.Unmarshal(body, &negotiateResponse); err != nil {
return nil, err
}
q := reqURL.Query()
- q.Set("id", nr.ConnectionID)
+ q.Set("id", negotiateResponse.ConnectionID)
reqURL.RawQuery = q.Encode()
// Select the best connection
var conn Connection
switch {
- case nr.getTransferFormats("WebTransports") != nil:
+ case negotiateResponse.hasTransport("WebTransports"):
// TODO
- case nr.getTransferFormats("WebSockets") != nil:
+ case httpConn.hasTransport(TransportWebSockets) && negotiateResponse.hasTransport(TransportWebSockets):
wsURL := reqURL
// switch to wss for secure connection
@@ -131,9 +150,9 @@ func NewHTTPConnection(ctx context.Context, address string, options ...func(*htt
}
// TODO think about if the API should give the possibility to cancel this connection
- conn = newWebSocketConnection(context.Background(), nr.ConnectionID, ws)
+ conn = newWebSocketConnection(context.Background(), negotiateResponse.ConnectionID, ws)
- case nr.getTransferFormats("ServerSentEvents") != nil:
+ case httpConn.hasTransport(TransportServerSentEvents) && negotiateResponse.hasTransport(TransportServerSentEvents):
req, err := http.NewRequest("GET", reqURL.String(), nil)
if err != nil {
return nil, err
@@ -149,7 +168,7 @@ func NewHTTPConnection(ctx context.Context, address string, options ...func(*htt
return nil, err
}
- conn, err = newClientSSEConnection(address, nr.ConnectionID, resp.Body)
+ conn, err = newClientSSEConnection(address, negotiateResponse.ConnectionID, resp.Body)
if err != nil {
return nil, err
}
@@ -165,3 +184,12 @@ func closeResponseBody(body io.ReadCloser) {
_, _ = io.Copy(io.Discard, body)
_ = body.Close()
}
+
+func (h *httpConnection) hasTransport(transport TransportType) bool {
+ for _, t := range h.transports {
+ if transport == t {
+ return true
+ }
+ }
+ return false
+}
diff --git a/httpmux.go b/httpmux.go
index 167be121..0291c780 100644
--- a/httpmux.go
+++ b/httpmux.go
@@ -211,17 +211,17 @@ func (h *httpMux) negotiate(w http.ResponseWriter, req *http.Request) {
var availableTransports []availableTransport
for _, transport := range h.server.availableTransports() {
switch transport {
- case "ServerSentEvents":
+ case TransportServerSentEvents:
availableTransports = append(availableTransports,
availableTransport{
- Transport: "ServerSentEvents",
- TransferFormats: []string{"Text"},
+ Transport: string(TransportServerSentEvents),
+ TransferFormats: []string{string(TransferFormatText)},
})
- case "WebSockets":
+ case TransportWebSockets:
availableTransports = append(availableTransports,
availableTransport{
- Transport: "WebSockets",
- TransferFormats: []string{"Text", "Binary"},
+ Transport: string(TransportWebSockets),
+ TransferFormats: []string{string(TransferFormatText), string(TransferFormatBinary)},
})
}
}
diff --git a/httpserver_test.go b/httpserver_test.go
index 8059ad4d..47466245 100644
--- a/httpserver_test.go
+++ b/httpserver_test.go
@@ -34,17 +34,25 @@ func (w *addHub) Echo(s string) string {
}
var _ = Describe("HTTP server", func() {
- for _, transport := range [][]string{
- {"WebSockets", "Text"},
- {"WebSockets", "Binary"},
- {"ServerSentEvents", "Text"},
- } {
- transport := transport
- Context(fmt.Sprintf("%v %v", transport[0], transport[1]), func() {
+ for i := 0; i < 3; i++ {
+ var transport TransportType
+ var transferFormat TransferFormatType
+ switch i {
+ case 0:
+ transport = TransportWebSockets
+ transferFormat = TransferFormatText
+ case 1:
+ transport = TransportWebSockets
+ transferFormat = TransferFormatBinary
+ case 2:
+ transport = TransportServerSentEvents
+ transferFormat = TransferFormatText
+ }
+ Context(fmt.Sprintf("%v %v", transport, transferFormat), func() {
Context("A correct negotiation request is sent", func() {
It(fmt.Sprintf("should send a correct negotiation response with support for %v with text protocol", transport), func(done Done) {
// Start server
- server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport[0]), testLoggerOption())
+ server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
router := http.NewServeMux()
server.MapHTTP(WithHTTPServeMux(router), "/hub")
@@ -59,11 +67,11 @@ var _ = Describe("HTTP server", func() {
Expect(len(avt)).To(BeNumerically(">", 0))
Expect(avt[0]).To(BeAssignableToTypeOf(map[string]interface{}{}))
avtVal := avt[0].(map[string]interface{})
- Expect(avtVal["transport"]).To(Equal(transport[0]))
+ Expect(avtVal["transport"]).To(Equal(string(transport)))
Expect(avtVal["transferFormats"]).To(BeAssignableToTypeOf([]interface{}{}))
tf := avtVal["transferFormats"].([]interface{})
Expect(tf).To(ContainElement("Text"))
- if transport[0] == "WebSockets" {
+ if transport == TransportWebSockets {
Expect(tf).To(ContainElement("Binary"))
}
testServer.Close()
@@ -74,7 +82,7 @@ var _ = Describe("HTTP server", func() {
Context("A invalid negotiation request is sent", func() {
It(fmt.Sprintf("should send a correct negotiation response with support for %v with text protocol", transport), func(done Done) {
// Start server
- server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport[0]), testLoggerOption())
+ server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
router := http.NewServeMux()
server.MapHTTP(WithHTTPServeMux(router), "/hub")
@@ -98,7 +106,7 @@ var _ = Describe("HTTP server", func() {
// Start server
ctx, cancel := context.WithCancel(context.Background())
server, err := NewServer(ctx,
- SimpleHubFactory(&addHub{}), HTTPTransports(transport[0]),
+ SimpleHubFactory(&addHub{}), HTTPTransports(transport),
MaximumReceiveMessageSize(50000),
Logger(logger, true))
Expect(err).NotTo(HaveOccurred())
@@ -115,7 +123,7 @@ var _ = Describe("HTTP server", func() {
WithConnection(conn),
MaximumReceiveMessageSize(60000),
Logger(logger, true),
- TransferFormat(transport[1]))
+ TransferFormat(transferFormat))
Expect(err).NotTo(HaveOccurred())
Expect(client).NotTo(BeNil())
client.Start()
@@ -130,7 +138,7 @@ var _ = Describe("HTTP server", func() {
client2, err := NewClient(ctx,
WithConnection(conn2),
Logger(logger, true),
- TransferFormat(transport[1]))
+ TransferFormat(transferFormat))
Expect(err).NotTo(HaveOccurred())
Expect(client2).NotTo(BeNil())
client2.Start()
@@ -154,7 +162,7 @@ var _ = Describe("HTTP server", func() {
Context("When no negotiation is send", func() {
It("should serve websocket requests", func(done Done) {
// Start server
- server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports("WebSockets"), testLoggerOption())
+ server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(TransportWebSockets), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
router := http.NewServeMux()
server.MapHTTP(WithHTTPServeMux(router), "/hub")
@@ -169,6 +177,34 @@ var _ = Describe("HTTP server", func() {
})
})
+var _ = Describe("HTTP client", func() {
+ Context("WithHttpConnection", func() {
+ It("should fallback to SSE (this can only be tested when httpConnection is tampered with)", func(done Done) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ server, err := NewServer(ctx, SimpleHubFactory(&addHub{}), HTTPTransports(TransportWebSockets, TransportServerSentEvents), testLoggerOption())
+ Expect(err).NotTo(HaveOccurred())
+ router := http.NewServeMux()
+ server.MapHTTP(WithHTTPServeMux(router), "/hub")
+ testServer := httptest.NewServer(router)
+ url, _ := url.Parse(testServer.URL)
+ port, _ := strconv.Atoi(url.Port())
+ waitForPort(port)
+
+ client, err := NewClient(ctx, WithHttpConnection(ctx, fmt.Sprintf("http://127.0.0.1:%v/hub", port)))
+ Expect(err).NotTo(HaveOccurred())
+
+ client.Start()
+ Expect(<-client.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred())
+ result := <-client.Invoke("Add2", 2)
+ Expect(result.Error).NotTo(HaveOccurred())
+
+ close(done)
+ }, 2.0)
+ })
+})
+
type nonProtocolLogger struct {
logger StructuredLogger
}
diff --git a/negotiateresponse.go b/negotiateresponse.go
index b06b04e5..6b2680b7 100644
--- a/negotiateresponse.go
+++ b/negotiateresponse.go
@@ -1,5 +1,15 @@
package signalr
+type TransportType string
+
+var TransportWebSockets TransportType = "WebSockets"
+var TransportServerSentEvents TransportType = "ServerSentEvents"
+
+type TransferFormatType string
+
+var TransferFormatText TransferFormatType = "Text"
+var TransferFormatBinary TransferFormatType = "Binary"
+
type availableTransport struct {
Transport string `json:"transport"`
TransferFormats []string `json:"transferFormats"`
@@ -12,11 +22,11 @@ type negotiateResponse struct {
AvailableTransports []availableTransport `json:"availableTransports"`
}
-func (nr *negotiateResponse) getTransferFormats(transportType string) []string {
+func (nr *negotiateResponse) hasTransport(transportType TransportType) bool {
for _, transport := range nr.AvailableTransports {
- if transport.Transport == transportType {
- return transport.TransferFormats
+ if transport.Transport == string(transportType) {
+ return true
}
}
- return nil
+ return false
}
diff --git a/router/router_test/router_test.go b/router/router_test/router_test.go
index 7eb01850..b3d45f91 100644
--- a/router/router_test/router_test.go
+++ b/router/router_test/router_test.go
@@ -21,6 +21,7 @@ import (
"github.com/gorilla/mux"
"github.com/philippseith/signalr"
+
"github.com/philippseith/signalr/router"
. "github.com/onsi/ginkgo"
@@ -72,7 +73,7 @@ var _ = Describe("Router", func() {
It("should send a correct negotiation response", func(done Done) {
// Start server
ctx, serverCancel := context.WithCancel(context.Background())
- server, err := signalr.NewServer(ctx, signalr.SimpleHubFactory(&addHub{}), signalr.HTTPTransports("WebSockets"))
+ server, err := signalr.NewServer(ctx, signalr.SimpleHubFactory(&addHub{}), signalr.HTTPTransports(signalr.TransportWebSockets))
Expect(err).NotTo(HaveOccurred())
port := freePort()
initFunc(server, port)
diff --git a/server.go b/server.go
index e600c231..6ef8dc2b 100644
--- a/server.go
+++ b/server.go
@@ -16,10 +16,12 @@ import (
// Server is a SignalR server for one type of hub.
//
-// MapHTTP(mux *http.ServeMux, path string)
+// MapHTTP(mux *http.ServeMux, path string)
+//
// maps the servers' hub to a path on a http.ServeMux.
//
-// Serve(conn Connection)
+// Serve(conn Connection)
+//
// serves the hub of the server on one connection.
// The same server might serve different connections in parallel. Serve does not return until the connection is closed
// or the servers' context is canceled.
@@ -32,7 +34,7 @@ type Server interface {
MapHTTP(routerFactory func() MappableRouter, path string)
Serve(conn Connection) error
HubClients() HubClients
- availableTransports() []string
+ availableTransports() []TransportType
}
type server struct {
@@ -42,7 +44,7 @@ type server struct {
defaultHubClients *defaultHubClients
groupManager GroupManager
reconnectAllowed bool
- transports []string
+ transports []TransportType
}
// NewServer creates a new server for one type of hub. The hub type is set by one of the
@@ -70,7 +72,7 @@ func NewServer(ctx context.Context, options ...func(Party) error) (Server, error
}
}
if server.transports == nil {
- server.transports = []string{"WebSockets", "ServerSentEvents"}
+ server.transports = []TransportType{TransportWebSockets, TransportServerSentEvents}
}
if server.newHub == nil {
return server, errors.New("cannot determine hub type. Neither UseHub, HubFactory or SimpleHubFactory given as option")
@@ -123,7 +125,7 @@ func (s *server) HubClients() HubClients {
return s.defaultHubClients
}
-func (s *server) availableTransports() []string {
+func (s *server) availableTransports() []TransportType {
return s.transports
}
diff --git a/serveroptions.go b/serveroptions.go
index 84931ac8..903381e9 100644
--- a/serveroptions.go
+++ b/serveroptions.go
@@ -42,12 +42,12 @@ func SimpleHubFactory(hubProto HubInterface) func(Party) error {
// HTTPTransports sets the list of available transports for http connections. Allowed transports are
// "WebSockets", "ServerSentEvents". Default is both transports are available.
-func HTTPTransports(transports ...string) func(Party) error {
+func HTTPTransports(transports ...TransportType) func(Party) error {
return func(p Party) error {
if s, ok := p.(*server); ok {
for _, transport := range transports {
switch transport {
- case "WebSockets", "ServerSentEvents":
+ case TransportWebSockets, TransportServerSentEvents:
s.transports = append(s.transports, transport)
default:
return fmt.Errorf("unsupported transport: %v", transport)
diff --git a/serveroptions_test.go b/serveroptions_test.go
index 8cfe0ae3..1c099d8f 100644
--- a/serveroptions_test.go
+++ b/serveroptions_test.go
@@ -347,22 +347,22 @@ var _ = Describe("Server options", func() {
Describe("HTTPTransports option", func() {
Context("When HTTPTransports is one of WebSockets, ServerSentEvents or both", func() {
It("should set these transports", func(done Done) {
- s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports("WebSockets"), testLoggerOption())
+ s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports(TransportWebSockets), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
- Expect(s.availableTransports()).To(ContainElement("WebSockets"))
+ Expect(s.availableTransports()).To(ContainElement(TransportWebSockets))
close(done)
})
It("should set these transports", func(done Done) {
- s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports("ServerSentEvents"), testLoggerOption())
+ s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports(TransportServerSentEvents), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
- Expect(s.availableTransports()).To(ContainElement("ServerSentEvents"))
+ Expect(s.availableTransports()).To(ContainElement(TransportServerSentEvents))
close(done)
})
It("should set these transports", func(done Done) {
- s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports("ServerSentEvents", "WebSockets"), testLoggerOption())
+ s, err := NewServer(context.TODO(), UseHub(&singleHub{}), HTTPTransports(TransportServerSentEvents, TransportWebSockets), testLoggerOption())
Expect(err).NotTo(HaveOccurred())
- Expect(s.availableTransports()).To(ContainElement("WebSockets"))
- Expect(s.availableTransports()).To(ContainElement("ServerSentEvents"))
+ Expect(s.availableTransports()).To(ContainElement(TransportWebSockets))
+ Expect(s.availableTransports()).To(ContainElement(TransportServerSentEvents))
close(done)
})
})
@@ -375,7 +375,7 @@ var _ = Describe("Server options", func() {
})
Context("When HTTPTransports is used on a client", func() {
It("should return an error", func(done Done) {
- _, err := NewClient(context.TODO(), WithConnection(newTestingConnection()), HTTPTransports("ServerSentEvents"), testLoggerOption())
+ _, err := NewClient(context.TODO(), WithConnection(newTestingConnection()), HTTPTransports(TransportServerSentEvents), testLoggerOption())
Expect(err).To(HaveOccurred())
close(done)
})
@@ -401,16 +401,16 @@ func newChannelWriter() *channelWriter {
return &channelWriter{make(chan []byte, 100)}
}
-//type mapLogger struct {
+// type mapLogger struct {
// c chan bool
// m map[string]string
-//}
+// }
//
-//func (m *mapLogger) Log(keyvals ...interface{}) error {
+// func (m *mapLogger) Log(keyvals ...interface{}) error {
// m.m = make(map[string]string)
// for i := 0; i < len(keyvals); i += 2 {
// m.m[keyvals[i].(string)] = keyvals[i+1].(string)
// }
// m.c <- true
// return nil
-//}
+// }
diff --git a/signalr_test/server_test.go b/signalr_test/server_test.go
index 5b87dbe4..dd62e6ea 100644
--- a/signalr_test/server_test.go
+++ b/signalr_test/server_test.go
@@ -49,15 +49,15 @@ func TestServerSmoke(t *testing.T) {
}
func TestServerJsonWebSockets(t *testing.T) {
- testServer(t, "^JSON", signalr.HTTPTransports("WebSockets"))
+ testServer(t, "^JSON", signalr.HTTPTransports(signalr.TransportWebSockets))
}
func TestServerJsonSSE(t *testing.T) {
- testServer(t, "^JSON", signalr.HTTPTransports("ServerSentEvents"))
+ testServer(t, "^JSON", signalr.HTTPTransports(signalr.TransportServerSentEvents))
}
func TestServerMessagePack(t *testing.T) {
- testServer(t, "^MessagePack", signalr.HTTPTransports("WebSockets"))
+ testServer(t, "^MessagePack", signalr.HTTPTransports(signalr.TransportWebSockets))
}
func testServer(t *testing.T, testNamePattern string, transports func(signalr.Party) error) {