Skip to content

Commit

Permalink
Merge pull request #755 from strukturag/websocket-features
Browse files Browse the repository at this point in the history
Include list of supported features in websocket response.
  • Loading branch information
fancycode authored Jun 3, 2024
2 parents c46f7b7 + 6e1a2f6 commit ef01f2c
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 25 deletions.
10 changes: 8 additions & 2 deletions hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func init() {
}

type Hub struct {
version string
events AsyncEvents
upgrader websocket.Upgrader
cookie *securecookie.SecureCookie
Expand Down Expand Up @@ -300,7 +301,8 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer
}

hub := &Hub{
events: events,
version: version,
events: events,
upgrader: websocket.Upgrader{
ReadBufferSize: websocketReadBufferSize,
WriteBufferSize: websocketWriteBufferSize,
Expand Down Expand Up @@ -2626,7 +2628,11 @@ func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) {
addr := h.getRealUserIP(r)
agent := r.Header.Get("User-Agent")

conn, err := h.upgrader.Upgrade(w, r, nil)
header := http.Header{}
header.Set("Server", "nextcloud-spreed-signaling/"+h.version)
header.Set("X-Spreed-Signaling-Features", strings.Join(h.info.Features, ", "))

conn, err := h.upgrader.Upgrade(w, r, header)
if err != nil {
log.Printf("Could not upgrade request from %s: %s", addr, err)
return
Expand Down
40 changes: 40 additions & 0 deletions hub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,46 @@ func performHousekeeping(hub *Hub, now time.Time) *sync.WaitGroup {
return &wg
}

func TestWebsocketFeatures(t *testing.T) {
t.Parallel()
CatchLogForTest(t)
_, _, _, server := CreateHubForTest(t)

ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()

conn, response, err := websocket.DefaultDialer.DialContext(ctx, getWebsocketUrl(server.URL), nil)
if err != nil {
t.Fatal(err)
}
defer conn.Close() // nolint

if server := response.Header.Get("Server"); !strings.HasPrefix(server, "nextcloud-spreed-signaling/") {
t.Errorf("expected valid server header, got \"%s\"", server)
}
features := response.Header.Get("X-Spreed-Signaling-Features")
featuresList := make(map[string]bool)
for _, f := range strings.Split(features, ",") {
f = strings.TrimSpace(f)
if f != "" {
if _, found := featuresList[f]; found {
t.Errorf("duplicate feature id \"%s\" in \"%s\"", f, features)
}
featuresList[f] = true
}
}
if len(featuresList) <= 1 {
t.Errorf("expected valid features header, got \"%s\"", features)
}
if _, found := featuresList["hello-v2"]; !found {
t.Errorf("expected feature \"hello-v2\", got \"%s\"", features)
}

if err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}); err != nil {
t.Errorf("could not write close message: %s", err)
}
}

func TestInitialWelcome(t *testing.T) {
t.Parallel()
CatchLogForTest(t)
Expand Down
26 changes: 20 additions & 6 deletions proxy/proxy_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ const (
maxTokenAge = 5 * time.Minute

remotePublisherTimeout = 5 * time.Second

ProxyFeatureRemoteStreams = "remote-streams"
)

var (
defaultProxyFeatures = []string{
ProxyFeatureRemoteStreams,
}
)

type ContextKey string
Expand All @@ -93,6 +101,7 @@ type ProxyServer struct {
version string
country string
welcomeMessage string
welcomeMsg *signaling.WelcomeServerMessage
config *goconf.ConfigFile

url string
Expand Down Expand Up @@ -314,7 +323,12 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
version: version,
country: country,
welcomeMessage: string(welcomeMessage) + "\n",
config: config,
welcomeMsg: &signaling.WelcomeServerMessage{
Version: version,
Country: country,
Features: defaultProxyFeatures,
},
config: config,

shutdownChannel: make(chan struct{}),

Expand Down Expand Up @@ -611,7 +625,10 @@ func (s *ProxyServer) welcomeHandler(w http.ResponseWriter, r *http.Request) {

func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) {
addr := signaling.GetRealUserIP(r, s.trustedProxies.Load())
conn, err := s.upgrader.Upgrade(w, r, nil)
header := http.Header{}
header.Set("Server", "nextcloud-spreed-signaling-proxy/"+s.version)
header.Set("X-Spreed-Signaling-Features", strings.Join(s.welcomeMsg.Features, ", "))
conn, err := s.upgrader.Upgrade(w, r, header)
if err != nil {
log.Printf("Could not upgrade request from %s: %s", addr, err)
return
Expand Down Expand Up @@ -760,10 +777,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) {
Hello: &signaling.HelloProxyServerMessage{
Version: signaling.HelloVersionV1,
SessionId: session.PublicId(),
Server: &signaling.WelcomeServerMessage{
Version: s.version,
Country: s.country,
},
Server: s.welcomeMsg,
},
}
client.SendMessage(response)
Expand Down
90 changes: 73 additions & 17 deletions proxy/proxy_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@
package main

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net"
"net/http/httptest"
"os"
"strings"
"testing"
"time"

"github.com/dlintw/goconf"
"github.com/golang-jwt/jwt/v4"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
signaling "github.com/strukturag/nextcloud-spreed-signaling"
)

Expand All @@ -42,12 +46,22 @@ const (
TokenIdForTest = "foo"
)

func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey) {
func getWebsocketUrl(url string) string {
if strings.HasPrefix(url, "http://") {
return "ws://" + url[7:] + "/proxy"
} else if strings.HasPrefix(url, "https://") {
return "wss://" + url[8:] + "/proxy"
} else {
panic("Unsupported URL: " + url)
}
}

func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey, *httptest.Server) {
tempdir := t.TempDir()
var server *ProxyServer
var proxy *ProxyServer
t.Cleanup(func() {
if server != nil {
server.Stop()
if proxy != nil {
proxy.Stop()
}
})

Expand Down Expand Up @@ -87,15 +101,21 @@ func newProxyServerForTest(t *testing.T) (*ProxyServer, *rsa.PrivateKey) {
config := goconf.NewConfigFile()
config.AddOption("tokens", TokenIdForTest, pubkey.Name())

if server, err = NewProxyServer(r, "0.0", config); err != nil {
t.Fatalf("could not create server: %s", err)
if proxy, err = NewProxyServer(r, "0.0", config); err != nil {
t.Fatalf("could not create proxy server: %s", err)
}
return server, key

server := httptest.NewServer(r)
t.Cleanup(func() {
server.Close()
})

return proxy, key, server
}

func TestTokenValid(t *testing.T) {
signaling.CatchLogForTest(t)
server, key := newProxyServerForTest(t)
proxy, key, _ := newProxyServerForTest(t)

claims := &signaling.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Expand All @@ -113,7 +133,7 @@ func TestTokenValid(t *testing.T) {
Version: "1.0",
Token: tokenString,
}
session, err := server.NewSession(hello)
session, err := proxy.NewSession(hello)
if session != nil {
defer session.Close()
} else if err != nil {
Expand All @@ -123,7 +143,7 @@ func TestTokenValid(t *testing.T) {

func TestTokenNotSigned(t *testing.T) {
signaling.CatchLogForTest(t)
server, _ := newProxyServerForTest(t)
proxy, _, _ := newProxyServerForTest(t)

claims := &signaling.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Expand All @@ -141,7 +161,7 @@ func TestTokenNotSigned(t *testing.T) {
Version: "1.0",
Token: tokenString,
}
session, err := server.NewSession(hello)
session, err := proxy.NewSession(hello)
if session != nil {
defer session.Close()
t.Errorf("should not have created session")
Expand All @@ -152,7 +172,7 @@ func TestTokenNotSigned(t *testing.T) {

func TestTokenUnknown(t *testing.T) {
signaling.CatchLogForTest(t)
server, key := newProxyServerForTest(t)
proxy, key, _ := newProxyServerForTest(t)

claims := &signaling.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Expand All @@ -170,7 +190,7 @@ func TestTokenUnknown(t *testing.T) {
Version: "1.0",
Token: tokenString,
}
session, err := server.NewSession(hello)
session, err := proxy.NewSession(hello)
if session != nil {
defer session.Close()
t.Errorf("should not have created session")
Expand All @@ -181,7 +201,7 @@ func TestTokenUnknown(t *testing.T) {

func TestTokenInFuture(t *testing.T) {
signaling.CatchLogForTest(t)
server, key := newProxyServerForTest(t)
proxy, key, _ := newProxyServerForTest(t)

claims := &signaling.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Expand All @@ -199,7 +219,7 @@ func TestTokenInFuture(t *testing.T) {
Version: "1.0",
Token: tokenString,
}
session, err := server.NewSession(hello)
session, err := proxy.NewSession(hello)
if session != nil {
defer session.Close()
t.Errorf("should not have created session")
Expand All @@ -210,7 +230,7 @@ func TestTokenInFuture(t *testing.T) {

func TestTokenExpired(t *testing.T) {
signaling.CatchLogForTest(t)
server, key := newProxyServerForTest(t)
proxy, key, _ := newProxyServerForTest(t)

claims := &signaling.TokenClaims{
RegisteredClaims: jwt.RegisteredClaims{
Expand All @@ -228,7 +248,7 @@ func TestTokenExpired(t *testing.T) {
Version: "1.0",
Token: tokenString,
}
session, err := server.NewSession(hello)
session, err := proxy.NewSession(hello)
if session != nil {
defer session.Close()
t.Errorf("should not have created session")
Expand Down Expand Up @@ -271,3 +291,39 @@ func TestPublicIPs(t *testing.T) {
}
}
}

func TestWebsocketFeatures(t *testing.T) {
signaling.CatchLogForTest(t)
_, _, server := newProxyServerForTest(t)

conn, response, err := websocket.DefaultDialer.DialContext(context.Background(), getWebsocketUrl(server.URL), nil)
if err != nil {
t.Fatal(err)
}
defer conn.Close() // nolint

if server := response.Header.Get("Server"); !strings.HasPrefix(server, "nextcloud-spreed-signaling-proxy/") {
t.Errorf("expected valid server header, got \"%s\"", server)
}
features := response.Header.Get("X-Spreed-Signaling-Features")
featuresList := make(map[string]bool)
for _, f := range strings.Split(features, ",") {
f = strings.TrimSpace(f)
if f != "" {
if _, found := featuresList[f]; found {
t.Errorf("duplicate feature id \"%s\" in \"%s\"", f, features)
}
featuresList[f] = true
}
}
if len(featuresList) == 0 {
t.Errorf("expected valid features header, got \"%s\"", features)
}
if _, found := featuresList["remote-streams"]; !found {
t.Errorf("expected feature \"remote-streams\", got \"%s\"", features)
}

if err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Time{}); err != nil {
t.Errorf("could not write close message: %s", err)
}
}

0 comments on commit ef01f2c

Please sign in to comment.