github.com/philippseith/signalr@v0.6.3/httpserver_test.go (about)

     1  package signalr
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"strconv"
    14  
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/go-kit/log/level"
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/gomega"
    21  	"nhooyr.io/websocket"
    22  )
    23  
    24  type addHub struct {
    25  	Hub
    26  }
    27  
    28  func (w *addHub) Add2(i int) int {
    29  	return i + 2
    30  }
    31  
    32  func (w *addHub) Echo(s string) string {
    33  	return s
    34  }
    35  
    36  var _ = Describe("HTTP server", func() {
    37  	for i := 0; i < 3; i++ {
    38  		var transport TransportType
    39  		var transferFormat TransferFormatType
    40  		switch i {
    41  		case 0:
    42  			transport = TransportWebSockets
    43  			transferFormat = TransferFormatText
    44  		case 1:
    45  			transport = TransportWebSockets
    46  			transferFormat = TransferFormatBinary
    47  		case 2:
    48  			transport = TransportServerSentEvents
    49  			transferFormat = TransferFormatText
    50  		}
    51  		Context(fmt.Sprintf("%v %v", transport, transferFormat), func() {
    52  			Context("A correct negotiation request is sent", func() {
    53  				It(fmt.Sprintf("should send a correct negotiation response with support for %v with text protocol", transport), func(done Done) {
    54  					// Start server
    55  					server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport), testLoggerOption())
    56  					Expect(err).NotTo(HaveOccurred())
    57  					router := http.NewServeMux()
    58  					server.MapHTTP(WithHTTPServeMux(router), "/hub")
    59  					testServer := httptest.NewServer(router)
    60  					url, _ := url.Parse(testServer.URL)
    61  					port, _ := strconv.Atoi(url.Port())
    62  					// Negotiate
    63  					negResp := negotiateWebSocketTestServer(port)
    64  					Expect(negResp["connectionId"]).NotTo(BeNil())
    65  					Expect(negResp["availableTransports"]).To(BeAssignableToTypeOf([]interface{}{}))
    66  					avt := negResp["availableTransports"].([]interface{})
    67  					Expect(len(avt)).To(BeNumerically(">", 0))
    68  					Expect(avt[0]).To(BeAssignableToTypeOf(map[string]interface{}{}))
    69  					avtVal := avt[0].(map[string]interface{})
    70  					Expect(avtVal["transport"]).To(Equal(string(transport)))
    71  					Expect(avtVal["transferFormats"]).To(BeAssignableToTypeOf([]interface{}{}))
    72  					tf := avtVal["transferFormats"].([]interface{})
    73  					Expect(tf).To(ContainElement("Text"))
    74  					if transport == TransportWebSockets {
    75  						Expect(tf).To(ContainElement("Binary"))
    76  					}
    77  					testServer.Close()
    78  					close(done)
    79  				}, 2.0)
    80  			})
    81  
    82  			Context("A invalid negotiation request is sent", func() {
    83  				It(fmt.Sprintf("should send a correct negotiation response with support for %v with text protocol", transport), func(done Done) {
    84  					// Start server
    85  					server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(transport), testLoggerOption())
    86  					Expect(err).NotTo(HaveOccurred())
    87  					router := http.NewServeMux()
    88  					server.MapHTTP(WithHTTPServeMux(router), "/hub")
    89  					testServer := httptest.NewServer(router)
    90  					url, _ := url.Parse(testServer.URL)
    91  					port, _ := strconv.Atoi(url.Port())
    92  					waitForPort(port)
    93  					// Negotiate the wrong way
    94  					resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%v/hub/negotiate", port))
    95  					Expect(err).To(BeNil())
    96  					Expect(resp).NotTo(BeNil())
    97  					Expect(resp.StatusCode).ToNot(Equal(200))
    98  					testServer.Close()
    99  					close(done)
   100  				}, 2.0)
   101  			})
   102  
   103  			Context("Connection with client", func() {
   104  				It("should successfully handle an Invoke call", func(done Done) {
   105  					logger := &nonProtocolLogger{testLogger()}
   106  					// Start server
   107  					ctx, cancel := context.WithCancel(context.Background())
   108  					server, err := NewServer(ctx,
   109  						SimpleHubFactory(&addHub{}), HTTPTransports(transport),
   110  						MaximumReceiveMessageSize(50000),
   111  						Logger(logger, true))
   112  					Expect(err).NotTo(HaveOccurred())
   113  					router := http.NewServeMux()
   114  					server.MapHTTP(WithHTTPServeMux(router), "/hub")
   115  					testServer := httptest.NewServer(router)
   116  					url, _ := url.Parse(testServer.URL)
   117  					port, _ := strconv.Atoi(url.Port())
   118  					waitForPort(port)
   119  					// Try first connection
   120  					conn, err := NewHTTPConnection(context.Background(), fmt.Sprintf("http://127.0.0.1:%v/hub", port))
   121  					Expect(err).NotTo(HaveOccurred())
   122  					client, err := NewClient(ctx,
   123  						WithConnection(conn),
   124  						MaximumReceiveMessageSize(60000),
   125  						Logger(logger, true),
   126  						TransferFormat(transferFormat))
   127  					Expect(err).NotTo(HaveOccurred())
   128  					Expect(client).NotTo(BeNil())
   129  					client.Start()
   130  					Expect(<-client.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred())
   131  					result := <-client.Invoke("Add2", 1)
   132  					Expect(result.Error).NotTo(HaveOccurred())
   133  					Expect(result.Value).To(BeEquivalentTo(3))
   134  
   135  					// Try second connection
   136  					conn2, err := NewHTTPConnection(context.Background(), fmt.Sprintf("http://127.0.0.1:%v/hub", port))
   137  					Expect(err).NotTo(HaveOccurred())
   138  					client2, err := NewClient(ctx,
   139  						WithConnection(conn2),
   140  						Logger(logger, true),
   141  						TransferFormat(transferFormat))
   142  					Expect(err).NotTo(HaveOccurred())
   143  					Expect(client2).NotTo(BeNil())
   144  					client2.Start()
   145  					Expect(<-client2.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred())
   146  					result = <-client2.Invoke("Add2", 2)
   147  					Expect(result.Error).NotTo(HaveOccurred())
   148  					Expect(result.Value).To(BeEquivalentTo(4))
   149  					// Huge message
   150  					hugo := strings.Repeat("#", 2500)
   151  					result = <-client.Invoke("Echo", hugo)
   152  					Expect(result.Error).NotTo(HaveOccurred())
   153  					s := result.Value.(string)
   154  					Expect(s).To(Equal(hugo))
   155  					cancel()
   156  					go testServer.Close()
   157  					close(done)
   158  				}, 2.0)
   159  			})
   160  		})
   161  	}
   162  	Context("When no negotiation is send", func() {
   163  		It("should serve websocket requests", func(done Done) {
   164  			// Start server
   165  			server, err := NewServer(context.TODO(), SimpleHubFactory(&addHub{}), HTTPTransports(TransportWebSockets), testLoggerOption())
   166  			Expect(err).NotTo(HaveOccurred())
   167  			router := http.NewServeMux()
   168  			server.MapHTTP(WithHTTPServeMux(router), "/hub")
   169  			testServer := httptest.NewServer(router)
   170  			url, _ := url.Parse(testServer.URL)
   171  			port, _ := strconv.Atoi(url.Port())
   172  			waitForPort(port)
   173  			handShakeAndCallWebSocketTestServer(port, "")
   174  			testServer.Close()
   175  			close(done)
   176  		}, 5.0)
   177  	})
   178  })
   179  
   180  var _ = Describe("HTTP client", func() {
   181  	Context("WithHttpConnection", func() {
   182  		It("should fallback to SSE (this can only be tested when httpConnection is tampered with)", func(done Done) {
   183  			ctx, cancel := context.WithCancel(context.Background())
   184  			defer cancel()
   185  
   186  			server, err := NewServer(ctx, SimpleHubFactory(&addHub{}), HTTPTransports(TransportWebSockets, TransportServerSentEvents), testLoggerOption())
   187  			Expect(err).NotTo(HaveOccurred())
   188  			router := http.NewServeMux()
   189  			server.MapHTTP(WithHTTPServeMux(router), "/hub")
   190  			testServer := httptest.NewServer(router)
   191  			url, _ := url.Parse(testServer.URL)
   192  			port, _ := strconv.Atoi(url.Port())
   193  			waitForPort(port)
   194  
   195  			client, err := NewClient(ctx, WithHttpConnection(ctx, fmt.Sprintf("http://127.0.0.1:%v/hub", port)))
   196  			Expect(err).NotTo(HaveOccurred())
   197  
   198  			client.Start()
   199  			Expect(<-client.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred())
   200  			result := <-client.Invoke("Add2", 2)
   201  			Expect(result.Error).NotTo(HaveOccurred())
   202  
   203  			close(done)
   204  		}, 2.0)
   205  	})
   206  })
   207  
   208  type nonProtocolLogger struct {
   209  	logger StructuredLogger
   210  }
   211  
   212  func (n *nonProtocolLogger) Log(keyVals ...interface{}) error {
   213  	for _, kv := range keyVals {
   214  		if kv == "protocol" {
   215  			return nil
   216  		}
   217  	}
   218  	return n.logger.Log(keyVals...)
   219  }
   220  
   221  func negotiateWebSocketTestServer(port int) map[string]interface{} {
   222  	waitForPort(port)
   223  	buf := bytes.Buffer{}
   224  	resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%v/hub/negotiate", port), "text/plain;charset=UTF-8", &buf)
   225  	Expect(err).To(BeNil())
   226  	Expect(resp).ToNot(BeNil())
   227  	defer func() {
   228  		_ = resp.Body.Close()
   229  	}()
   230  	var body []byte
   231  	body, err = io.ReadAll(resp.Body)
   232  	Expect(err).To(BeNil())
   233  	response := make(map[string]interface{})
   234  	err = json.Unmarshal(body, &response)
   235  	Expect(err).To(BeNil())
   236  	return response
   237  }
   238  
   239  func handShakeAndCallWebSocketTestServer(port int, connectionID string) {
   240  	waitForPort(port)
   241  	logger := testLogger()
   242  	protocol := jsonHubProtocol{}
   243  	protocol.setDebugLogger(level.Debug(logger))
   244  	var urlParam string
   245  	if connectionID != "" {
   246  		urlParam = fmt.Sprintf("?id=%v", connectionID)
   247  	}
   248  	ws, _, err := websocket.Dial(context.Background(), fmt.Sprintf("ws://127.0.0.1:%v/hub%v", port, urlParam), nil)
   249  	Expect(err).To(BeNil())
   250  	defer func() {
   251  		_ = ws.Close(websocket.StatusNormalClosure, "")
   252  	}()
   253  	wsConn := newWebSocketConnection(context.TODO(), connectionID, ws)
   254  	cliConn := newHubConnection(wsConn, &protocol, 1<<15, testLogger())
   255  	_, _ = wsConn.Write(append([]byte(`{"protocol": "json","version": 1}`), 30))
   256  	_, _ = wsConn.Write(append([]byte(`{"type":1,"invocationId":"666","target":"add2","arguments":[1]}`), 30))
   257  	result := make(chan interface{})
   258  	go func() {
   259  		for recvResult := range cliConn.Receive() {
   260  			if completionMessage, ok := recvResult.message.(completionMessage); ok {
   261  				result <- completionMessage.Result
   262  				return
   263  			}
   264  		}
   265  	}()
   266  	select {
   267  	case r := <-result:
   268  		var f float64
   269  		Expect(protocol.UnmarshalArgument(r, &f)).NotTo(HaveOccurred())
   270  		Expect(f).To(Equal(3.0))
   271  	case <-time.After(1000 * time.Millisecond):
   272  		Fail("timed out")
   273  	}
   274  }
   275  
   276  func waitForPort(port int) {
   277  	for {
   278  		if _, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%v", port)); err == nil {
   279  			return
   280  		}
   281  		time.Sleep(100 * time.Millisecond)
   282  	}
   283  }