@@ -11,6 +11,7 @@ licenses/APL2.txt.
11
11
package blip
12
12
13
13
import (
14
+ "context"
14
15
"fmt"
15
16
"log"
16
17
"net"
@@ -577,6 +578,138 @@ func TestOrigin(t *testing.T) {
577
578
}
578
579
}
579
580
581
+ // TestServerContextClose tests closing server using cancellable context, ensure that clients are disconnected
582
+ //
583
+ // Test:
584
+ // - Start two blip contexts: an echo server and an echo client
585
+ // - The echo server is configured to respond to incoming echo requests and return responses
586
+ // - The echo client sends echo requests on a loop
587
+ // - Expected: the echo client should receive some sort of error when the server closes the connection, and should not block
588
+ func TestServerContextClose (t * testing.T ) {
589
+
590
+ serverCancelCtx , cancelFunc := context .WithCancel (context .Background ())
591
+ contextOptionsWithCancel := ContextOptions {
592
+ ProtocolIds : []string {BlipTestAppProtocolId },
593
+ CancelCtx : serverCancelCtx ,
594
+ }
595
+ blipContextEchoServer , err := NewContext (contextOptionsWithCancel )
596
+ if err != nil {
597
+ t .Fatal (err )
598
+ }
599
+
600
+ receivedRequests := sync.WaitGroup {}
601
+
602
+ // ----------------- Setup Echo Server that will be closed via cancellation context -------------------------
603
+
604
+ // Create a blip profile handler to respond to echo requests
605
+ dispatchEcho := func (request * Message ) {
606
+ defer receivedRequests .Done ()
607
+ body , err := request .Body ()
608
+ if err != nil {
609
+ log .Printf ("ERROR reading body of %s: %s" , request , err )
610
+ return
611
+ }
612
+ if request .Properties ["Content-Type" ] != "application/octet-stream" {
613
+ t .Fatalf ("Incorrect properties: %#x" , request .Properties )
614
+ }
615
+ if response := request .Response (); response != nil {
616
+ response .SetBody (body )
617
+ response .Properties ["Content-Type" ] = request .Properties ["Content-Type" ]
618
+ }
619
+ }
620
+
621
+ // Blip setup
622
+ blipContextEchoServer .HandlerForProfile ["BLIPTest/EchoData" ] = dispatchEcho
623
+ blipContextEchoServer .LogMessages = true
624
+ blipContextEchoServer .LogFrames = true
625
+
626
+ // Websocket Server
627
+ server := blipContextEchoServer .WebSocketServer ()
628
+
629
+ // HTTP Handler wrapping websocket server
630
+ http .Handle ("/TestServerContextClose" , server )
631
+ listener , err := net .Listen ("tcp" , ":0" )
632
+ if err != nil {
633
+ t .Fatal (err )
634
+ }
635
+ defer listener .Close ()
636
+ go func () {
637
+ err := http .Serve (listener , nil )
638
+ log .Printf ("server goroutine closed with error: %v" , err )
639
+ }()
640
+
641
+ // ----------------- Setup Echo Client ----------------------------------------
642
+ blipContextEchoClient , err := NewContext (defaultContextOptions )
643
+ if err != nil {
644
+ t .Fatal (err )
645
+ }
646
+ port := listener .Addr ().(* net.TCPAddr ).Port
647
+ destUrl := fmt .Sprintf ("ws://localhost:%d/TestServerContextClose" , port )
648
+ sender , err := blipContextEchoClient .Dial (destUrl )
649
+ if err != nil {
650
+ t .Fatalf ("Error opening WebSocket: %v" , err )
651
+ }
652
+
653
+ var closeWg , delayWg sync.WaitGroup
654
+
655
+ // Start a goroutine to send echo request every 100 ms, time out after 30s (if test fails)
656
+ delayWg .Add (1 ) // wait for connection and messages to be sent before cancelling server context
657
+ closeWg .Add (1 ) // wait for client to disconnect before exiting test
658
+ go func () {
659
+ defer closeWg .Done ()
660
+ timeout := time .After (time .Second * 30 )
661
+ ticker := time .NewTicker (time .Millisecond * 50 )
662
+ echoCount := 0
663
+ for {
664
+ select {
665
+ case <- timeout :
666
+ t .Error ("Echo client connection wasn't closed before timeout expired" )
667
+ return
668
+ case <- ticker .C :
669
+ {
670
+ echoCount ++
671
+ // After sending 10 echoes, close delayWg to trigger server-side cancellation
672
+ log .Printf ("Sending echo %v" , echoCount )
673
+ if echoCount == 10 {
674
+ delayWg .Done ()
675
+ }
676
+ // Create echo request
677
+ echoResponseBody := []byte ("hello" )
678
+ echoRequest := NewRequest ()
679
+ echoRequest .SetProfile ("BLIPTest/EchoData" )
680
+ echoRequest .Properties ["Content-Type" ] = "application/octet-stream"
681
+ echoRequest .SetBody (echoResponseBody )
682
+ receivedRequests .Add (1 )
683
+ sent := sender .Send (echoRequest )
684
+ assert .True (t , sent )
685
+
686
+ // Read the echo response. Closed connection will result in empty response, as EOF message
687
+ // isn't currently returned by blip client
688
+ response := echoRequest .Response ()
689
+ responseBody , err := response .Body ()
690
+ assert .True (t , err == nil )
691
+ if len (responseBody ) == 0 {
692
+ log .Printf ("empty response, connection closed" )
693
+ return
694
+ }
695
+
696
+ assert .Equal (t , echoResponseBody , responseBody )
697
+ }
698
+ }
699
+ }
700
+ }()
701
+
702
+ // Wait for client to start sending echo messages before stopping server
703
+ delayWg .Wait ()
704
+
705
+ // Cancel context on server
706
+ cancelFunc ()
707
+
708
+ // Wait for client echo loop to exit due to closed connection before exiting test
709
+ closeWg .Wait ()
710
+
711
+ }
712
+
580
713
// assert that the server handshake callback is called with an error.
581
714
func assertHandlerError (t * testing.T , server * BlipWebsocketServer , wg * sync.WaitGroup ) {
582
715
wg .Add (1 )
0 commit comments