-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.bs
304 lines (238 loc) · 9.96 KB
/
index.bs
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
<pre class='metadata'>
Title: Sockets API
Shortname: common-web-platform-api
Group: wintercg
Status: w3c/CG-DRAFT
Level: none
URL: https://sockets-api.proposal.wintercg.org/
Repository: https://github.com/wintercg/proposal-sockets-api
Editor: Dominik Picheta, Cloudflare https://cloudflare.com/, [email protected]
Abstract: Sockets API for Non-Browser EcmaScript-based runtimes.
Markup Shorthands: markdown yes
</pre>
<pre class=link-defaults>
spec:url; type:interface; text:URL
spec:html; type:attribute; for:Window; text:navigator
spec:html; type:method; for:Connect; text:connect
</pre>
Introduction {#intro}
=====================
*This section is non-normative.*
This document defines an API for establishing TCP connections in Non-Browser JavaScript runtime
environments. Existing standard APIs are reused as much as possible, for example ReadableStream
and WritableStream are used for reading and writing from a socket. Some options are inspired
by the existing Node.js `net.Socket` API.
The API described here is already implemented by Cloudflare Workers.
Workers-specific documentation is available in
https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/. This spec shares many
similarities with that document.
`Socket` interface {#socket-interface}
======================================
A `Socket` can be constructed using the global {{connect}} method, or a `connect` method
defined on a <a>binding object</a>.
<pre class="idl">
[Exposed=*]
interface Socket {
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
readonly attribute Promise<undefined> closed;
Promise<undefined> close();
Socket startTls();
};
</pre>
The terms {{ReadableStream}} and {{WritableStream}} are defined in [[!WHATWG-STREAMS]].
Instances of {{Socket}} are created with fields described in the following table:
<pre class="simpledef">
{{readable}}: a {{ReadableStream}} which receives data from the server the socket is connected to
{{writable}}: a {{WritableStream}} which sends data to the server the socket is connected to
{{closed}}: a promise that will be rejected (if socket connection fails or there is another error) or resolved (if the connection is gracefully closed)
</pre>
Methods available on a {{Socket}} instance are described by the following table:
<pre class="simpledef">
{{close()}}: closes the socket and its underlying connection
{{startTls()}}: begins TLS session on the socket connection, see <a>`startTls` method</a>
</pre>
`readable` attribute
--------------------
<aside class="example">
The below example shows typical {{ReadableStream}} usage to read data from a socket:
```javascript
const socket = connect("google.com:80");
const reader = socket.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
// the ReadableStream has been closed or cancelled
break;
}
// In many protocols the \`value\` needs to be decoded to be used:
const decoder = new TextDecoder();
console.log(decoder.decode(value));
}
reader.releaseLock();
```
</aside>
The ReadableStream operates in non-byte mode, that is the `type` parameter to the
ReadableStream constructor is not set.
This means the stream's controller is {{ReadableStreamDefaultController}}.
`writable` attribute
--------------------
<aside class="example">
The below example shows typical {{WritableStream}} usage to write data to a socket:
```javascript
const socket = connect("google.com:80");
const writer = socket.writable.getWriter();
const encoder = new TextEncoder();
writer.write(encoder.encode("GET / HTTP/1.0\r\n\r\n"));
```
</aside>
`closed` attribute
--------------------
The `closed` promise can be used to keep track of the socket state. It gets resolved under the
following circumstances:
* the `close` method is called on the socket
* the socket was constructed with the `allowHalfOpen` parameter set to `false`, the ReadableStream
is being read from, and the remote connection sends a FIN packet (graceful closure) or a RST
packet
<div class="note">
The current Cloudflare Workers implementation behaves as described above, specifically the
ReadableStream needs to be read until completion for the `closed` promise to resolve, if the
ReadableStream is not read then even if the server closes the connection the `closed` promise
will not resolve.
Whether the promise should resolve without the ReadableStream being read is up for discussion.
</div>
It can also be rejected with an exception under the following circumstances:
* a socket connection could not be established, either because the address/port combo requested is
blocked or due to a transient issue with the runtime
Cancelling the socket's ReadableStream and closing the socket's WritableStream does not resolve the
`closed` promise.
`startTls` method {#starttls-method}
------------------------------------
The <dfn>`startTls` method</dfn> enables opportunistic TLS (otherwise known as
[StartTLS](https://en.wikipedia.org/wiki/Opportunistic_TLS)) which is a requirement for some
protocols (primarily postgres/mysql and other DB protocols).
<div class="note">
The `startTls` method must fail with an exception if the `secureTransport` option set on
socket instance it was called on is not equal to "starttls".
</div>
In this `secureTransport` mode of operation the socket begins the
connection in plain-text, with messages read and written without any encryption. Then once the
`startTls` method is called on the socket, the following shall take place:
* the original socket is closed, though the original connection is kept alive
* a secure TLS connection is established over that connection
* a new socket is created and returned from the `startTls` call
<aside class="example">
Here is a simple code example showing usage:
```js
let sock = connect("google.com:443", { secureTransport: "starttls" });
// ... some code here ...
// We want to StartTLS at this point.
let tlsSock = sock.startTls();
```
</aside>
The original readers and writers based off the original socket will no longer work. You must create
new readers and writers from the new socket returned by `startTls`.
<div class="note">
The `startTls` method must fail with an exception if called on a TLS socket (i.e. one returned
by the `startTls` call)
</div>
`connect` method {#connect}
===========================
<pre class="idl">
[Exposed=*]
dictionary SocketAddress {
DOMString hostname;
unsigned short port;
};
typedef (DOMString or SocketAddress) AnySocketAddress;
enum SecureTransportKind { "off", "on", "starttls" };
[Exposed=*]
dictionary SocketOptions {
SecureTransportKind secureTransport = "off";
boolean allowHalfOpen = false;
};
[Exposed=*]
interface Connect {
Socket connect(AnySocketAddress address, optional SocketOptions opts);
};
</pre>
The `connect` method performs the following steps:
<ol>
<li>A connection is established to the specified `SocketAddress` asynchronously.</li>
<li>New {{Socket}} instance is created with each of its attributes initialised immediately.</li>
<li>The created {{Socket}} instance is returned immediately.</li>
<li>If the connection fails for any reason, the socket's `closed` promise is rejected with an exception describing why the connection failed.</li>
<li>The instance's {{ReadableStream}} and {{WritableStream}} streams can be used immediately.</li>
</ol>
At any point during the creation of the {{Socket}} instance, `connect` may throw an exception. One
case where this can happen is if the input address is incorrectly formatted.
Errors which occur asynchronously- after the socket instance has been returned- must reject the
socket's `closed` promise.
<div class="note">
The implementation may consider blocking connections to certain hostname/port combinations which can
pose a threat of abuse or security vulnerability.
For example, port 25 may be blocked to prevent abuse of SMTP servers and private IPs can be blocked
to avoid connecting to private services hosted locally (or on the server's LAN).
</div>
`SocketOptions` dictionary
---------------------------
<dl>
<dt>
<dfn>secureTransport</dfn> member
</dt>
<dd>
The secure transport mode to use.
<dl>
<dt>off</dt>
<dd>A connection is established in plain text.</dd>
<dt>on</dt>
<dd>A TLS connection is established using default CAs</dd>
<dt>starttls</dt>
<dd>Initially the same as the `off` option, the connection continues in plain text
until the <a>`startTls` method</a> is called</dd>
</dl>
</dd>
<dt>
<dfn>allowHalfOpen</dfn> member
</dt>
<dd>
This option is similar to that offered by the Node.js `net` module and allows interoperability
with code which utilizes it.
<dl>
<dt>false</dt>
<dd>The WritableStream- and the socket instance- will be automatically closed when a
FIN packet is received from the remote connection.</dd>
<dt>true</dt>
<dd>When a FIN packet is received, the socket will enter a "half-open" state where the
ReadableStream is closed but the WritableStream can still be written to.</dd>
</dl>
</dd>
</dl>
`AnySocketAddress` type
---------------------------
<dl>
<dt>
<dfn>SocketAddress</dfn> dictionary
</dt>
<dd>
The address to connect to. For example `{ hostname: "google.com", port: 443 }`.
<dl>
<dt>hostname</dt>
<dd>A connection is established in plain text.</dd>
<dt>port</dt>
<dd>A TLS connection is established using default CAs</dd>
</dl>
</dd>
<dt>
DOMString
</dt>
<dd>
A hostname/port combo separated by a colon. For example `"google.com:443"`.
</dd>
</dl>
Binding object {#binding-object}
================================
The <dfn>binding object</dfn> defines socket `connect` options. The options it contains can modify the
behaviour of the `connect` invoked on it. Some of the options it can define:
* TLS settings
* The HTTP proxy to use for the socket connection