-
Notifications
You must be signed in to change notification settings - Fork 11
/
wslistener.go
105 lines (89 loc) · 2.67 KB
/
wslistener.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// +build !js,!wasm
package wasmws
import (
"context"
"fmt"
"log"
"net"
"net/http"
"nhooyr.io/websocket"
)
//WebSockListener implements net.Listener and provides connections that are
//incoming websocket connections
type WebSockListener struct {
ctx context.Context
ctxCancel context.CancelFunc
acceptCh chan net.Conn
}
var (
_ net.Listener = (*WebSockListener)(nil)
_ http.Handler = (*WebSockListener)(nil)
)
//NewWebSocketListener constructs a new WebSockListener, the provided context
//is for the lifetime of the listener.
func NewWebSocketListener(ctx context.Context) *WebSockListener {
ctx, cancel := context.WithCancel(ctx)
wsl := &WebSockListener{
ctx: ctx,
ctxCancel: cancel,
acceptCh: make(chan net.Conn, 8),
}
go func() { //Close queued connections
<-ctx.Done()
for {
select {
case conn := <-wsl.acceptCh:
conn.Close()
continue
default:
}
break
}
}()
return wsl
}
//ServeHTTP is a method that is mean to be used as http.HandlerFunc to accept inbound HTTP requests
// that are websocket connections
func (wsl *WebSockListener) ServeHTTP(wtr http.ResponseWriter, req *http.Request) {
select {
case <-wsl.ctx.Done():
http.Error(wtr, "503: Service is shutdown", http.StatusServiceUnavailable)
log.Printf("WebSockListener: WARN: A websocket listener's HTTP Accept was called when shutdown!")
return
default:
}
ws, err := websocket.Accept(wtr, req, nil)
if err != nil {
log.Printf("WebSockListener: ERROR: Could not accept websocket from %q; Details: %s", req.RemoteAddr, err)
}
conn := websocket.NetConn(wsl.ctx, ws, websocket.MessageBinary)
select {
case wsl.acceptCh <- conn:
case <-wsl.ctx.Done():
ws.Close(websocket.StatusBadGateway, fmt.Sprintf("Failed to accept connection before websocket listener shutdown; Details: %s", wsl.ctx.Err()))
case <-req.Context().Done():
ws.Close(websocket.StatusBadGateway, fmt.Sprintf("Failed to accept connection before websocket HTTP request cancelation; Details: %s", req.Context().Err()))
}
}
//Accept fulfills the net.Listener interface and returns net.Conn that are incoming
// websockets
func (wsl *WebSockListener) Accept() (net.Conn, error) {
select {
case conn := <-wsl.acceptCh:
return conn, nil
case <-wsl.ctx.Done():
return nil, fmt.Errorf("Listener closed; Details: %w", wsl.ctx.Err())
}
}
//Close closes the listener
func (wsl *WebSockListener) Close() error {
wsl.ctxCancel()
return nil
}
//RemoteAddr returns a dummy websocket address to satisfy net.Listener
func (wsl *WebSockListener) Addr() net.Addr {
return wsAddr{}
}
type wsAddr struct{}
func (wsAddr) Network() string { return "websocket" }
func (wsAddr) String() string { return "websocket" }