📌

cloudflare/workerdのTCP接続機能を試す

2022/11/19に公開

最近workerd(Cloudflare Workersのランタイム)で実装され始めたTCP socket接続の機能がある。

https://github.com/cloudflare/workerd/pull/162

今まではHTTP越しにPostgreSQLサーバーなどに接続する必要があったが、これがあると接続先に直接繋ぐことができるというのがコンセプトのようだ(実装を読むと直接持続的な接続をするわけではなく間にアダプタを噛ませるアーキテクチャのようだけどまだ詳細は分からない)。

これをデバッグしようと思ったらそもそもbazel buildまでが結構難しかったので開発環境構築のメモもする。

システム環境

Name Version
macOS 12.6
Chip Apple M1 Pro
workerd 1f8a561

bazel 5.3

bazel 5.3を要求してくるので固定する。

❯ gh repo clone cloudflare/workerd && cd workerd
❯ curl -fL https://releases.bazel.build/5.3.0/release/bazel-5.3.0-darwin-arm64 -o bazel  && chmod +x bazel
❯ ./bazel --version
bazel 5.3.0

(追記)Bazeliskを利用するとさらに便利なようだ

python3パスの解決

❯ ./bazel build -c opt //src/workerd/server:workerd 

unknown command: python3. Perhaps you have to reshim?  
Target //src/workerd/server:workerd failed to build 

コード生成にホスト側のpython3コマンドが呼び出されるのだけど見つからないことがある。自分の場合はasdf-pythonのバーチャルな環境を参照できていなかったので、システム直下のpython3を使うようにPATHで調整した。

./bazel build -c opt //src/workerd/server:workerd --action_env=PATH=/usr/bin:$PATH

# /usr/bin/python3が先頭に来ていればok
❯ PATH=/usr/bin:$PATH which -a python3
/usr/bin/python3

MacOSX12.3.sdkへの切り替え

https://github.com/cloudflare/workerd/issues/45

external/v8/src/base/platform/platform-darwin.cc:56:22: error: 'getsectdatafromheader_64' is deprecated: first deprecated in macOS 13.0 [-Werror,-Wdeprecated-declarations]
    char* code_ptr = getsectdatafromheader_64(
                     ^~~~~~~~~~~~~~~~~~~~~~~~                                                 
                     use getsectiondata()                                                     
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk/usr/include/mach-o/getsect.h:130:14: note: 'getsectdatafromheader_64' has been explicitly 
marked deprecated here                    
extern char *getsectdatafromheader_64(
             ^                                                                                
1 error generated.                        
Error in child process '/usr/bin/xcrun'. 1

workerdのソースコードは最新のXcodeとMacOSX13.0.sdkでコンパイルできないので回避する必要がある。方法は2つあって

  1. Envoyコミッタのdioさんがシェアしてるパッチを当てる
  2. ひとつ前のMacOSX12.3.sdkを用意して使う

今回は「ひとつ前のMacOSX12.3.sdkを用意して使う」にした。

Xcode旧バージョンを取ってくるには

  • Apple Developer Account
  • ダウンロードURL

が必要で、セットアップを簡単にするためにxcodes(複数Xcode管理ツール)を使う。

https://github.com/RobotsAndPencils/xcodes/

12系SDKが14.0.1に含まれるのがXcode Releases | xcodereleases.comを見て分かったので以下を実行する。

❯ xcodes install 14.0.1

Xcode 14.0.1 has been installed to /Applications/Xcode-14.0.1.app

❯ xcodes select 14.0.1
❯ xcrun --show-sdk-version                                                               
12.3

最終的にビルドコマンドの引数はこうなった

❯ ./bazel build -c opt //src/workerd/server:workerd --action_env=PATH=/usr/bin:$PATH --action_env=XCODE_VERSION_OVERRIDE=14.0.1 

INFO: Elapsed time: 998.567s, Critical Path: 58.90s
INFO: 8345 processes: 3311 internal, 5033 darwin-sandbox, 1 local.
INFO: Build completed successfully, 8345 total actions

15分ぐらいかかってビルド完了。

connect() を試す

https://github.com/cloudflare/workerd/pull/180

❯ ./bazel run //src/workerd/server:workerd --action_env=PATH=/usr/bin:$PATH  -- serve $PWD/samples/tcp/config.capnp --watch --verbose 

以下のようにfetcherからconnect()関数を呼び出してソケットを読み書きする。

https://github.com/cloudflare/workerd/blob/1f8a561cd351214ec1fb3095f87df2b6e728c6b7/samples/tcp/gopher.js#L15-L55

GopherというのはGo言語のなにかというわけではなくて、HTTPより若いポート70のドキュメント転送プロトコルのこと。

https://en.wikipedia.org/wiki/Gopher_(protocol)

netcatなどでgopherサーバーと直接対話できる。

echo /fun | nc gopher.floodgap.com 70       
1Floodgap Systems gopher root   /       gopher.floodgap.com     70
i               error.host      1
i    .o88o.                         o8o             o8o                 error.host      1
i    888 `"                         `YP             `YP                 error.host      1
i   o888oo  oooo  oooo  ooo. .oo.    '  ooo. .oo.    '                  error.host      1
i    888    `888  `888  `888P"Y88b      `888P"Y88b                      error.host      1
i    888     888   888   888   888       888   888                      error.host      1
i    888     888   888   888   888       888   888                      error.host      1
i   o888o    `V88V"V8P' o888o o888o     o888o o888o                     error.host      1
i                                                                       error.host      1
i .oooooooo  .oooo.   ooo. .oo.  .oo.    .ooooo.   .oooo.o              error.host      1
i888' `88b  `P  )88b  `888P"Y88bP"Y88b  d88' `88b d88(  "8              error.host      1
i888   888   .oP"888   888   888   888  888ooo888 `"Y88b.               error.host      1
i`88bod8P'  d8(  888   888   888   888  888    .o o.  )88b              error.host      1
i`8oooooo.  `Y888""8o o888o o888o o888o `Y8bod8P' 8""888P'              

workerの動作については現状こんな感じ

❯ curl localhost:8080/
Socket connection failed: Error: internal error

workerd/jsg/util.c++:381: error: e = kj/compat/http.c++:5421: unimplemented: connect() is not implemented for NetworkHttpClient
  kj::Promise<void> connect(
      kj::StringPtr host, const kj::HttpHeaders& headers, ConnectResponse& tunnel) override {
    // This code is hit when the global `connect` function is called in a JS worker script.
    // It represents a proxy-less TCP connection, which means we can simply defer the handling of
    // the connection to the service adapter (likely NetworkHttpClient). Its behaviour will be to
    // connect directly to the host over TCP.
    return serviceAdapter->connect(host, headers, tunnel);
  }

https://github.com/cloudflare/workerd/blob/1f8a561cd351214ec1fb3095f87df2b6e728c6b7/src/workerd/server/server.c++#L658-L665

アダプタを間に噛ますのかもと言っていた部分がこれで、NetworkHttpClientはworkerdをホストする環境(Cloudflare Workers)で実装され(たぶん)、ユーザー側としてはJSからconnect()を呼ぶソケット読み書きできる、というイメージになるのかと思った(なのでNode.jsのライブラリそのまま動かすとかはできなさそう)。

Discussion