forked from coder/websocket
-
Notifications
You must be signed in to change notification settings - Fork 3
/
netconn.go
166 lines (142 loc) · 3.58 KB
/
netconn.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package websocket
import (
"context"
"fmt"
"io"
"math"
"net"
"sync"
"time"
)
// NetConn converts a *websocket.Conn into a net.Conn.
//
// It's for tunneling arbitrary protocols over WebSockets.
// Few users of the library will need this but it's tricky to implement
// correctly and so provided in the library.
// See https://github.com/nhooyr/websocket/issues/100.
//
// Every Write to the net.Conn will correspond to a message write of
// the given type on *websocket.Conn.
//
// The passed ctx bounds the lifetime of the net.Conn. If cancelled,
// all reads and writes on the net.Conn will be cancelled.
//
// If a message is read that is not of the correct type, the connection
// will be closed with StatusUnsupportedData and an error will be returned.
//
// Close will close the *websocket.Conn with StatusNormalClosure.
//
// When a deadline is hit, the connection will be closed. This is
// different from most net.Conn implementations where only the
// reading/writing goroutines are interrupted but the connection is kept alive.
//
// The Addr methods will return a mock net.Addr that returns "websocket" for Network
// and "websocket/unknown-addr" for String.
//
// A received StatusNormalClosure or StatusGoingAway close frame will be translated to
// io.EOF when reading.
func NetConn(ctx context.Context, c *Conn, msgType MessageType) net.Conn {
nc := &netConn{
c: c,
msgType: msgType,
}
var cancel context.CancelFunc
nc.writeContext, cancel = context.WithCancel(ctx)
nc.writeTimer = time.AfterFunc(math.MaxInt64, cancel)
if !nc.writeTimer.Stop() {
<-nc.writeTimer.C
}
nc.readContext, cancel = context.WithCancel(ctx)
nc.readTimer = time.AfterFunc(math.MaxInt64, cancel)
if !nc.readTimer.Stop() {
<-nc.readTimer.C
}
return nc
}
type netConn struct {
c *Conn
msgType MessageType
writeTimer *time.Timer
writeContext context.Context
readTimer *time.Timer
readContext context.Context
readMu sync.Mutex
eofed bool
reader io.Reader
}
var _ net.Conn = &netConn{}
func (c *netConn) Close() error {
return c.c.Close(StatusNormalClosure, "")
}
func (c *netConn) Write(p []byte) (int, error) {
err := c.c.Write(c.writeContext, c.msgType, p)
if err != nil {
return 0, err
}
return len(p), nil
}
func (c *netConn) Read(p []byte) (int, error) {
c.readMu.Lock()
defer c.readMu.Unlock()
if c.eofed {
return 0, io.EOF
}
if c.reader == nil {
typ, r, err := c.c.Reader(c.readContext)
if err != nil {
switch CloseStatus(err) {
case StatusNormalClosure, StatusGoingAway:
c.eofed = true
return 0, io.EOF
}
return 0, err
}
if typ != c.msgType {
err := fmt.Errorf("unexpected frame type read (expected %v): %v", c.msgType, typ)
c.c.Close(StatusUnsupportedData, err.Error())
return 0, err
}
c.reader = r
}
n, err := c.reader.Read(p)
if err == io.EOF {
c.reader = nil
err = nil
}
return n, err
}
type websocketAddr struct {
}
func (a websocketAddr) Network() string {
return "websocket"
}
func (a websocketAddr) String() string {
return "websocket/unknown-addr"
}
func (c *netConn) RemoteAddr() net.Addr {
return websocketAddr{}
}
func (c *netConn) LocalAddr() net.Addr {
return websocketAddr{}
}
func (c *netConn) SetDeadline(t time.Time) error {
c.SetWriteDeadline(t)
c.SetReadDeadline(t)
return nil
}
func (c *netConn) SetWriteDeadline(t time.Time) error {
if t.IsZero() {
c.writeTimer.Stop()
} else {
c.writeTimer.Reset(t.Sub(time.Now()))
}
return nil
}
func (c *netConn) SetReadDeadline(t time.Time) error {
if t.IsZero() {
c.readTimer.Stop()
} else {
c.readTimer.Reset(t.Sub(time.Now()))
}
return nil
}