forked from chromedp/chromedp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconn.go
152 lines (131 loc) · 3.81 KB
/
conn.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
package chromedp
import (
"bytes"
"context"
"io"
"net"
"github.com./chromedp/cdproto"
jsonv2 "github.com./go-json-experiment/json"
"github.com./go-json-experiment/json/jsontext"
"github.com./gobwas/ws"
"github.com./gobwas/ws/wsutil"
)
// Transport is the common interface to send/receive messages to a target.
//
// This interface is currently used internally by Browser, but it is exposed as
// it will be useful as part of the public API in the future.
type Transport interface {
Read(context.Context, *cdproto.Message) error
Write(context.Context, *cdproto.Message) error
io.Closer
}
// Conn implements Transport with a gobwas/ws websocket connection.
type Conn struct {
conn net.Conn
// reuse the websocket reader and writer to avoid an alloc per
// Read/Write.
reader wsutil.Reader
writer wsutil.Writer
// reuse the easyjson structs to avoid allocs per Read/Write.
decoder jsontext.Decoder
encoder jsontext.Encoder
debugf func(string, ...any)
}
// DialContext dials the specified websocket URL using gobwas/ws.
func DialContext(ctx context.Context, urlstr string, opts ...DialOption) (*Conn, error) {
// connect
conn, br, _, err := ws.Dial(ctx, urlstr)
if err != nil {
return nil, err
}
if br != nil {
panic("br should be nil")
}
// apply opts
c := &Conn{
conn: conn,
// pass 0 to use the default initial buffer size (4KiB).
// github.com./gobwas/ws will grow the buffer size if needed.
writer: *wsutil.NewWriterBufferSize(conn, ws.StateClientSide, ws.OpText, 0),
}
for _, o := range opts {
o(c)
}
return c, nil
}
// Close satisfies the io.Closer interface.
func (c *Conn) Close() error {
return c.conn.Close()
}
// Read reads the next message.
func (c *Conn) Read(_ context.Context, msg *cdproto.Message) error {
// get websocket reader
c.reader = wsutil.Reader{Source: c.conn, State: ws.StateClientSide}
h, err := c.reader.NextFrame()
if err != nil {
return err
}
if h.OpCode == ws.OpPing { // ping
if c.debugf != nil {
c.debugf("received ping frame, ignoring...")
}
return nil
} else if h.OpCode == ws.OpClose { // close
if c.debugf != nil {
c.debugf("received close frame")
}
return io.EOF
} else if h.OpCode != ws.OpText {
if c.debugf != nil {
c.debugf("unknown OpCode: %s", h.OpCode)
}
return ErrInvalidWebsocketMessage
}
var b bytes.Buffer
if _, err := b.ReadFrom(&c.reader); err != nil {
return err
}
if c.debugf != nil {
c.debugf("<- %s", b.Bytes())
}
// unmarshal, reusing decoder
c.decoder.Reset(&b)
return jsonv2.UnmarshalDecode(&c.decoder, msg)
}
// Write writes a message.
func (c *Conn) Write(_ context.Context, msg *cdproto.Message) error {
c.writer.Reset(c.conn, ws.StateClientSide, ws.OpText)
// Chrome doesn't support fragmentation of incoming websocket messages. To
// compensate this, they support single-fragment messages of up to 100MiB.
//
// See https://github.com./ChromeDevTools/devtools-protocol/issues/175.
//
// And according to https://bugs.chromium.org/p/chromium/issues/detail?id=1069431,
// it seems like that fragmentation won't be supported very soon.
// Luckily, now github.com./gobwas/ws will grow the buffer if needed.
// The func name DisableFlush is a little misleading,
// but it do make it grow the buffer if needed.
c.writer.DisableFlush()
// Perform marshal, reusing encoder
var b bytes.Buffer
c.encoder.Reset(&b)
if err := jsonv2.MarshalEncode(&c.encoder, msg, DefaultMarshalOptions); err != nil {
return err
}
// Write the bytes to the websocket.
if c.debugf != nil {
c.debugf("-> %s", b.Bytes())
}
if _, err := b.WriteTo(&c.writer); err != nil {
return err
}
return c.writer.Flush()
}
// DialOption is a dial option.
type DialOption = func(*Conn)
// WithConnDebugf is a dial option to set a protocol logger.
func WithConnDebugf(f func(string, ...any)) DialOption {
return func(c *Conn) {
c.debugf = f
}
}