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 }