diff --git a/content/workers/platform/betas.md b/content/workers/platform/betas.md index bcd5f5f29d6..fba0561d1be 100644 --- a/content/workers/platform/betas.md +++ b/content/workers/platform/betas.md @@ -18,6 +18,6 @@ These are the current alphas and betas relevant to the Cloudflare Workers platfo | Green Compute | | | ✅ |[Blog](https://blog.cloudflare.com/earth-day-2022-green-compute-open-beta/) | | Pub/Sub | | ✅ | |[Docs](/pub-sub) | | Queues | | | ✅ |[Docs](/queues) | -| TCP Workers | | ✅ | |[Blog](https://blog.cloudflare.com/introducing-socket-workers/) | +| TCP Workers | | | ✅ |[Docs](/workers/runtime-apis/tcp-sockets) | | Workers Analytics Engine | | | ✅ |[Docs](/analytics/analytics-engine/) | | Workers Deployments | | | ✅ |[Docs](/workers/platform/deployments) | diff --git a/content/workers/platform/limits.md b/content/workers/platform/limits.md index 2a6db92fe3f..58ac3a26718 100644 --- a/content/workers/platform/limits.md +++ b/content/workers/platform/limits.md @@ -242,6 +242,7 @@ While handling a request, each Worker is allowed to have up to six connections o - `put()`, `match()`, and `delete()` methods of [Cache objects](/workers/runtime-apis/cache/). - `list()`, `get()`, `put()`, `delete()`, and `head()` methods of [R2](/r2/). - `send()` and `sendBatch()`, methods of [Queues](/queues/). +- Opening a TCP socket using the [`connect()`](/workers/runtime-apis/tcp-sockets/) API. Once a Worker has six connections open, it can still attempt to open additional connections. However, these attempts are put in a pending queue — the connections will not be initiated until one of the currently open connections has closed. Since earlier connections can delay later ones, if a Worker tries to make many simultaneous subrequests, its later subrequests may appear to take longer to start. diff --git a/content/workers/platform/protocols.md b/content/workers/platform/protocols.md new file mode 100644 index 00000000000..09ec0ed5313 --- /dev/null +++ b/content/workers/platform/protocols.md @@ -0,0 +1,17 @@ +--- +pcx_content_type: concept +title: Protocols +--- + +# Protocols + +Cloudflare Workers support the following protocols and interfaces: + +| Protocol | Inbound | Outbound | +|---|---|---| +| **HTTP / HTTPS** | Handle incoming HTTP requests using the [`fetch()` handler](/workers/runtime-apis/fetch-event/#syntax-module-worker/) | Make HTTP subrequests using the [`fetch()` API](/workers/runtime-apis/fetch/) | +| **Direct TCP sockets** | Support for handling inbound TCP connections is [coming soon](https://blog.cloudflare.com/introducing-socket-workers/) | Create outbound TCP connections using the [`connect()` API](/workers/runtime-apis/tcp-sockets/) | +| **WebSockets** | Accept incoming WebSocket connections using the [`WebSocket` API](/workers/runtime-apis/websockets/), or with [MQTT over WebSockets (Pub/Sub)](/pub-sub/learning/websockets-browsers/) | [MQTT over WebSockets (Pub/Sub)](/pub-sub/learning/websockets-browsers/) | +| **MQTT** | Handle incoming messages to an MQTT broker with [Pub Sub](/pub-sub/learning/integrate-workers/) | Support for publishing MQTT messages to an MQTT topic is [coming soon](/pub-sub/learning/integrate-workers/) | +| **HTTP/3 (QUIC)** | Accept inbound requests over [HTTP/3](https://www.cloudflare.com/learning/performance/what-is-http3/) by enabling it on your [zone](/fundamentals/get-started/concepts/accounts-and-zones/#zones) in the network tab of the [Cloudflare dashboard](https://dash.cloudflare.com/). | | +| **SMTP** | Use [Email Workers](/email-routing/email-workers/) to process and forward email, without having to manage TCP connections to SMTP email servers | [Email Workers](/email-routing/email-workers/) | \ No newline at end of file diff --git a/content/workers/runtime-apis/tcp-sockets.md b/content/workers/runtime-apis/tcp-sockets.md new file mode 100644 index 00000000000..00c02dc7664 --- /dev/null +++ b/content/workers/runtime-apis/tcp-sockets.md @@ -0,0 +1,161 @@ +--- +pcx_content_type: configuration +title: TCP sockets +--- + +# TCP sockets + +The Workers runtime provides the `connect()` API for creating outbound [TCP connections](https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/) from Workers. + +Many application-layer protocols are built on top of the Transmission Control Protocol (TCP). These application-layer protocols, including SSH, MQTT, SMTP, FTP, IRC, and most database wire protocols including MySQL, PostgreSQL, MongoDB, require an underlying TCP socket API in order to work. + +## `connect()` + +The `connect()` function returns a TCP socket, with both a [readable](/workers/runtime-apis/streams/readablestream/) and [writable](/workers/runtime-apis/streams/writablestream/) stream of data. This allows you to read and write data on an ongoing basis, as long as the connection remains open. + +`connect()` is provided as a [Runtime API](/workers/runtime-apis/), and is accessed by importing the `connect` function from `cloudflare:sockets`. This process is similar to how one imports built-in modules in Node.js. Refer to the following codeblock for an example of creating a TCP socket, writing to it, and returning the readable side of the socket as a response: + +```typescript +import { connect } from 'cloudflare:sockets'; + +export default { + async fetch(req: Request) { + const gopherAddr = { hostname: "gopher.floodgap.com", port: 70 }; + const url = new URL(req.url); + + try { + const socket = connect(gopherAddr); + + const writer = socket.writable.getWriter() + const encoder = new TextEncoder(); + const encoded = encoder.encode(url.pathname + "\r\n"); + await writer.write(encoded); + + return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } }); + } catch (error) { + return new Response("Socket connection failed: " + error, { status: 500 }); + } + } +}; +``` + +{{}} + +- {{}}connect(address: {{}}SocketAddress{{}} | string, options?: {{}}optional{{}} {{}}SocketOptions{{}}){{}} : {{}}`Socket`{{}} + - `connect()` accepts either a URL string or [`SocketAddress`](/workers/runtime-apis/tcp-sockets/#socketaddress) to define the hostname and port number to connect to, and an optional configuration object, [`SocketOptions`](/workers/runtime-apis/tcp-sockets/#socketoptions). It returns an instance of a [`Socket`](/workers/runtime-apis/tcp-sockets/#socket). +{{}} + +### `SocketAddress` + +{{}} + +- `hostname` {{}}string{{}} + - The hostname to connect to. Example: `cloudflare.com`. + +- `port` {{}}number{{}} + - The port number to connect to. Example: `5432`. + +{{}} + +### `SocketOptions` + +{{}} + +- `secureTransport` {{}}"off" | "on" | "starttls"{{}} — Defaults to `off` + - Specifies whether or not to use [TLS](https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/) when creating the TCP socket. + - `off` — Do not use TLS. + - `on` — Use TLS. + - `starttls` — Do not use TLS initially, but allow the socket to be upgraded to use TLS by calling [`startTls()`](/workers/runtime-apis/tcp-sockets/#how-to-implement-the-starttls-pattern). + +- `allowHalfOpen` {{}}boolean{{}} — Defaults to `false` + - Defines whether the writable side of the TCP socket will automatically close on end-of-file (EOF). When set to `false`, the writable side of the TCP socket will automatically close on EOF. When set to `true`, the writable side of the TCP socket will remain open on EOF. + - This option is similar to that offered by the Node.js [`net` module](https://nodejs.org/api/net.html) and allows interoperability with code which utilizes it. + +{{}} + +### `Socket` + +{{}} + +- {{}}readable(){{}} : {{}}ReadableStream{{}} + - Returns the readable side of the TCP socket. + +- {{}}writeable(){{}} : {{}}WriteableStream{{}} + - Returns the writable side of the TCP socket. + +- `closed()` {{}}`Promise`{{}} + - This promise is resolved when the socket is closed and is rejected if the socket encounters an error. + +- `close()` {{}}`Promise`{{}} + - Closes the TCP socket. Both the readable and writable streams are forcibly closed. + +- {{}}startTls(){{}} : {{}}Socket{{}} + - Upgrades an insecure socket to a secure one that uses TLS, returning a new [Socket](/workers/runtime-apis/tcp-sockets#socket). Note that in order to call `startTls()`, you must set [`secureTransport`](/workers/runtime-apis/tcp-sockets/#socketoptions) to `starttls` when initially calling `connect()` to create the socket. + +{{}} + +## Opportunistic TLS (StartTLS) + +Many TCP-based systems, including databases and email servers, require that clients use opportunistic TLS (otherwise known as [StartTLS](https://en.wikipedia.org/wiki/Opportunistic_TLS)) when connecting. In this pattern, the client first creates an insecure TCP socket, without TLS, and then upgrades it to a secure TCP socket, that uses TLS. The `connect()` API simplifies this by providing a method, `startTls()`, which returns a new `Socket` instance that uses TLS: + +```typescript +import { connect } from "cloudflare:sockets" + +const address = { + hostname: "example-postgres-db.com", + port: 5432 +}; +const socket = connect(address, { secureTransport: "starttls" }); +const secureSocket = socket.startTls(); +``` + +- `startTls()` can only be called if `secureTransport` is set to `starttls` when creating the initial TCP socket. +- Once `startTls()` is called, the initial socket is closed and can no longer be read from or written to. In the example above, anytime after `startTls()` is called, you would use the newly created `secureSocket`. Any existing readers and writers based off the original socket will no longer work. You must create new readers and writers from the newly created `secureSocket`. +- `startTls()` should only be called once on an existing socket. + +## Handle errors + +To handle errors when creating a new TCP socket, reading from a socket, or writing to a socket, wrap these calls inside `try..catch` blocks. The following example opens a connection to Google.com, initiates a HTTP request, and returns the response. If any of this fails and throws an exception, it returns a `500` response: + +```typescript +import { connect } from 'cloudflare:sockets'; +const connectionUrl = { hostname: "google.com", port: 80 }; +export interface Env { } +export default { + async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise { + try { + const socket = connect(connectionUrl); + const writer = socket.writable.getWriter(); + const encoder = new TextEncoder(); + const encoded = encoder.encode("GET / HTTP/1.0\r\n\r\n"); + await writer.write(encoded); + + return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } }); + } catch (error) { + return new Response(`Socket connection failed: ${error}`, { status: 500 }); + } + } +}; +``` + +## Close TCP connections + +You can close a TCP connection by calling `close()` on the socket. This will close both the readable and writeable sides of the socket. + +```typescript +import { connect } from "cloudflare:sockets" + +const socket = connect({ hostname: "my-url.com", port: 70 }); +const reader = socket.readable.getReader(); +socket.close(); + +// After close() is called, you can no longer read from the readable side of the socket +const reader = socket.readable.getReader(); // This fails +``` + +## Considerations + +- When developing locally with [Wrangler](/workers/wrangler/), you must pass the [`--experimental-local`](/workers/wrangler/commands/#dev) flag, instead of the `--local` flag, to use `connect()`. +- TCP sockets must be created within the [`fetch()` handler](/workers/get-started/guide/#3-write-code) of a Worker. TCP sockets cannot be created in global scope and shared across requests. +- Each open TCP socket counts towards the maximum number of [open connections](/workers/platform/limits/#simultaneous-open-connections) that can be simultaneously open. +- By default, Workers cannot create outbound TCP connections on port `25` to send email to SMTP mail servers. [Cloudflare Email Workers](/email-routing/email-workers/) provides APIs to process and forward email. \ No newline at end of file