forked from chromedp/chromedp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conn.go
149 lines (127 loc) · 3.61 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
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{})
}
// Chrome doesn't support fragmentation of incoming websocket messages. To
// compensate this, they support single-fragment messages of up to 100MiB.
//
// If our write buffer size is too small, large messages will get fragmented,
// and Chrome will silently crash. And if it's too large, chromedp will require
// more memory for all users.
//
// For now, make this a middle ground. 1MiB is large enough for practically any
// outgoing message, but small enough to not take too much meomry.
//
// See https://github.com/ChromeDevTools/devtools-protocol/issues/175.
const wsWriteBufferSize = 1 << 20
// 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,
writer: *wsutil.NewWriterBufferSize(conn,
ws.StateClientSide, ws.OpText, wsWriteBufferSize),
}
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)
// 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
}
}