forked from chromedp/chromedp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conn.go
148 lines (127 loc) · 3.69 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
package chromedp
import (
"bytes"
"context"
"io"
"net"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/mailru/easyjson/jlexer"
"github.com/mailru/easyjson/jwriter"
"github.com/chromedp/cdproto"
)
// 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 jlexer.Lexer
encoder jwriter.Writer
dbgf func(string, ...interface{})
}
// 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.OpText {
return ErrInvalidWebsocketMessage
}
var b bytes.Buffer
if _, err := b.ReadFrom(&c.reader); err != nil {
return err
}
buf := b.Bytes()
if c.dbgf != nil {
c.dbgf("<- %s", buf)
}
// unmarshal, reusing lexer
c.decoder = jlexer.Lexer{Data: buf}
msg.UnmarshalEasyJSON(&c.decoder)
return c.decoder.Error()
}
// 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()
// Reuse the easyjson writer.
c.encoder = jwriter.Writer{}
// Perform the marshal.
msg.MarshalEasyJSON(&c.encoder)
if err := c.encoder.Error; err != nil {
return err
}
// Write the bytes to the websocket.
// BuildBytes consumes the buffer, so we can't use it as well as DumpTo.
if c.dbgf != nil {
buf, _ := c.encoder.BuildBytes()
c.dbgf("-> %s", buf)
if _, err := c.writer.Write(buf); err != nil {
return err
}
} else {
if _, err := c.encoder.DumpTo(&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, ...interface{})) DialOption {
return func(c *Conn) {
c.dbgf = f
}
}