Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

TCP ソケット

最終更新 Markdown で表示Agent セットアップ

Workers ランタイムは、Workers からアウトバウンドの TCP 接続 を作成するための connect() API を提供します。

多くのアプリケーション層プロトコルは、Transmission Control Protocol(TCP)の上に構築されています。SSH、MQTT、SMTP、FTP、IRC、および MySQL、PostgreSQL、MongoDB を含むほとんどのデータベース用ワイヤプロトコルは、動作するために基盤となる TCP ソケット API が必要です。

connect()

connect() 関数は、データの 読み取り可能 ストリームと 書き込み可能 ストリームの両方を持つ TCP ソケットを返します。接続が開いている限り、継続的にデータの読み書きができます。

connect()Runtime API として提供され、cloudflare:sockets から connect 関数をインポートして使います。手順は、Node.js で組み込みモジュールをインポートする場合と似ています。次のコードブロックは、TCP ソケットを作成し、書き込み、ソケットの読み取り側をレスポンスとして返す例です。

import { connect } from 'cloudflare:sockets';

export default {
  async fetch(req): Promise<Response> {
    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);
      await writer.close();

      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response("Socket connection failed: " + error, { status: 500 });
    }
  }
} satisfies ExportedHandler;
  • connect(address: SocketAddress | string, options?: optional SocketOptions) : Socket
    • connect() は、接続先のホスト名とポート番号を指定する URL 文字列または SocketAddress と、省略可能な設定オブジェクト SocketOptions を受け取ります。戻り値は Socket のインスタンスです。

SocketAddress

  • hostname string

    • 接続先のホスト名です。例: cloudflare.com
  • port number

    • 接続先のポート番号です。例: 5432

SocketOptions

  • secureTransport "off" | "on" | "starttls" — 既定値は off

    • TCP ソケット作成時に TLS を使うかどうかを指定します。
    • off — TLS を使いません。
    • on — TLS を使います。
    • starttls — 最初は TLS を使わず、startTls() を呼んで TLS へアップグレードできます。
  • allowHalfOpen boolean — 既定値は false

    • EOF(end-of-file)時に、TCP ソケットの書き込み側を自動で閉じるかどうかを決めます。false の場合、EOF 時に書き込み側は自動で閉じます。true の場合、EOF 時も書き込み側は開いたままです。
    • Node.js の net モジュール が提供するオプションと似ており、それを使うコードとの相互運用に役立ちます。

SocketInfo

  • remoteAddress string | null

    • ソケットが接続しているリモートピアのアドレスです。常に設定されるとは限りません。
  • localAddress string | null

    • このソケットのローカルネットワークエンドポイントのアドレスです。常に設定されるとは限りません。

Socket

  • readable : ReadableStream

    • TCP ソケットの読み取り側を返します。
  • writable : WritableStream

    • TCP ソケットの書き込み側を返します。
    • 返される WritableStream が受け付けるチャンクは、Uint8Array またはそのビューだけです。
  • opened Promise<SocketInfo>

    • ソケット接続が確立すると解決し、ソケットでエラーが起きると拒否される Promise です。
  • closed Promise<void>

    • ソケットが閉じると解決し、ソケットでエラーが起きると拒否される Promise です。
  • close() Promise<void>

    • TCP ソケットを閉じます。読み取りストリームと書き込みストリームの両方を強制的に閉じます。
  • startTls() : Socket

    • 非セキュアなソケットを、TLS を使うセキュアなソケットへアップグレードし、新しい Socket を返します。startTls() を呼ぶには、最初に connect() でソケットを作成するときに secureTransportstarttls に設定する必要があります。

便宜的 TLS(StartTLS)

データベースやメールサーバーを含む多くの TCP ベースのシステムでは、接続時に便宜的 TLS(StartTLS とも呼ばれます)を使う必要があります。このパターンでは、クライアントは最初に TLS なしの非セキュアな TCP ソケットを作り、その後 TLS を使うセキュアな TCP ソケットへアップグレードします。connect() API では、TLS を使う新しい Socket インスタンスを返す startTls() メソッドで、この手順を簡単にできます。

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() は、最初の TCP ソケット作成時に secureTransportstarttls に設定した場合にだけ呼べます。
  • startTls() を呼ぶと、最初のソケットは閉じられ、以降は読み書きできません。上の例では、startTls() の呼び出し後は、新しく作った secureSocket を使います。元のソケットから作った既存のリーダーとライターは使えなくなります。新しく作った secureSocket からリーダーとライターを作り直す必要があります。
  • 既存のソケットに対する startTls() の呼び出しは、1 回だけにしてください。

エラーを処理する

新しい TCP ソケットの作成、ソケットからの読み取り、ソケットへの書き込みでエラーを処理するには、これらの呼び出しを try...catch 文で囲みます。次の例は、Google.com へ接続を開き、HTTP リクエストを開始してレスポンスを返します。失敗して例外が投げられた場合は、500 レスポンスを返します。

import { connect } from 'cloudflare:sockets';
const connectionUrl = { hostname: "google.com", port: 80 };
export interface Env { }
export default {
  async fetch(req, env, ctx): Promise<Response> {
    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);
      await writer.close();

      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response(`Socket connection failed: ${error}`, { status: 500 });
    }
  }
} satisfies ExportedHandler<Env>;

TCP 接続を閉じる

ソケットの close() を呼ぶと、TCP 接続を閉じられます。読み取り側と書き込み側の両方が閉じます。

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

注意点

  • Cloudflare の IP レンジ へのアウトバウンド TCP ソケットはブロックされます。
  • TCP ソケットをグローバルスコープで作成してリクエスト間で共有することはできません。常にハンドラー(例: fetch()scheduled()queue())または alarm() の中で作成してください。
  • 開いている TCP ソケットは、同時に開ける 接続数の上限 にカウントされます。
  • Durable Object 内で作成した開いている TCP ソケットは、Durable Object をメモリ上に保ち、接続あたり最大 15 分間の duration 課金が発生します。15 分後、ソケットは Durable Object を生存させなくなり(ソケット自体は動作を続けます)、標準の退避ルール が再開されます。
  • 既定では、Workers はポート 25 へのアウトバウンド TCP 接続を作成して SMTP メールサーバーへメールを送れません。Cloudflare Email Workers が、メールの処理と転送の API を提供します。
  • インバウンド TCP 接続の処理は 近日対応予定 です。現時点では、CONNECT HTTP メソッドなどを使って Worker へインバウンド TCP 接続を行うことはできません。

トラブルシューティング

TCP ソケットを使うときに表示されることがある一般的なエラーメッセージ、その意味、対処方法を確認します。

proxy request failed, cannot connect to the specified address

ソケットが、許可されていないアドレスへ接続しようとしています。許可されないアドレスの例は、Cloudflare IP、localhost、プライベートネットワーク IP です。

ポート 80 または 443 のアドレスへ接続して HTTP リクエストを送る必要がある場合は、fetch を使ってください。

TCP Loop detected

ソケットが、アウトバウンド接続を開始した Worker 自身へ戻っています。つまり、Worker が自分自身へ接続しています。現時点ではサポートされていません。

Connections to port 25 are prohibited

ソケットがポート 25 のアドレスへ接続しようとしています。このポートは通常、SMTP メールサーバーで使われます。Workers はポート 25 へのアウトバウンド接続を作成できません。代わりに Cloudflare Email Workers の利用を検討してください。

役に立ちましたか?