1137 文字
6 分
Cloudflare WorkersでGleamを動かす

Gleamと​いう​静的型付け・関数型言語が​ある.

gleam-lang
/
gleam
Waiting for api.github.com...
00K
0K
0K
Waiting...

Erlang VMや​JavaScriptランタイムで​動く​よう​コンパイルされるのが​特徴.

今回は,​「JavaScriptに​トランスパイルできる」と​いう​ところに​注目し,​Cloudflare Workersで​Gleamを​動かしてみた.

環境構築

依存関係に​gleam_javascriptを​追加する.

また,​ローカルでの​動作確認・デプロイ用にwranglerを​インストールする.

gleam new workers_gleam
cd workers_gleam
gleam add gleam_javascript
pnpm init
pnpm add -D wrangler

gleam.tomlを​編集.​targetに​JavaScriptを​指定した.

name = "workers_gleam"
version = "1.0.0"
target = "javascript"

[dependencies]
gleam_stdlib = ">=0.34.0 and < 2.0.0"
gleam_javascript = ">=0.12.0 and < 1.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"

また,wrangler.tomlを​以下のように​記述する.

name = "workers-gleam"
main = "build/dev/javascript/workers_gleam/index.js"
compatibility_date = "2023-09-22"

[build]
command = "gleam build"

Gleamと​JavaScriptを​FFIで​繋ぐ#

Gleamは​JavaScriptや​Erlangと​相互に​運用できる​ため,​FFIの​仕組みが​備わっている.

ここでは,​JavaScriptで​定義されているResponseRequestと​いった​オブジェクトを​Gleamでも​使えるように​していく​.

まずはsrc/ffi.jsに​response, method, url, bodyを​追加する.

export function response(status, headersList, body) {
  let headers = new Headers()
  for (let [k, v] of headersList) headers.append(k, v)
  return new Response(body, { status, headers })
}

export function method(request) {
  return request.method
}

export function url(request) {
  return request.url
}

export function body(request) {
  return request.body
}

src/cloudflare/workers.gleamで​ffi.jsで​書いた​関数と​Gleamの​関数を​結びつける.

pub type Request

pub type Response

pub type Environment

pub type ExecutionContext

@external(javascript, "../ffi.js", "response")
pub fn response(
  status: Int,
  headers: List(#(String, String)),
  body: String,
) -> Response

@external(javascript, "../ffi.js", "method")
pub fn method(req: Request) -> String

@external(javascript, "../ffi.js", "url")
pub fn url(req: Request) -> String

@external(javascript, "../ffi.js", "body")
pub fn body(req: Request) -> String

これで​Gleam側でresponsemethodを​実行した​ときには,​JavaScript側の​関数を​呼び出すようになった.

ハンドラーを​作る#

これらの​型や​関数を​使って​ハンドラーを​書く.

handle_requestは​JavaScriptのPromise<Response>を​返さなければならないため,​返り値をPromise(Response)と​している.

また,promise.resolveに​渡す​ことでresponsePromiseで​ラップしている.

import cloudflare/workers.{type Request, type Response}
import gleam/javascript/promise.{type Promise}

pub fn handle_request(
  _request: Request,
) -> Promise(Response) {
  workers.response(200, [], "Cloudflare Workers上でGleamが動いてるよ")
  |> promise.resolve
}

エントリポイントを​作る#

最後に​Cf Workers用の​エントリポイントを​作る.src/index.jsの​中に​書いていく​.

import { handle_request } from "./workers_gleam.mjs"

export default {
	async fetch(request) {
		return handle_request(request)
	}
}

一行目で​importしているworkers_gleam.mjsは​ビルド時にsrc/workers_gleam.gleamから​生成される​JavaScriptモジュールで,​Gleamで​定義したhandle_requestが​そのまま​JavaScriptの​関数と​して​exportされている.

import * as $promise from "../gleam_javascript/gleam/javascript/promise.mjs";
import * as $workers from "./cloudflare/workers.mjs";
import { toList } from "./gleam.mjs";

export function handle_request(_) {
  let _pipe = $workers.response(200, toList([]), "Cloudflare Workers上でGleamが動いてるよ");
  return $promise.resolve(_pipe);
}

Envを​使う#

Cf Workersでは​fetch関数の​第二引数にenvを​受け取る.​Cf Workersに​設定した​環境変数が​Key-Value形式で​格納された​オブジェクトに​なっている.

.dev.varsに​環境変数を​追加する.

SECRET_KEY=5U93R_53CR37_K3Y

この​値を​Gleamから​取り出して​使う.

ffi.jsに​新たにread_environment(env, key)関数を​用意するが,​この​関数は​Gleamの​Result型を​返すように​する.

値が​存在すれば​Ok,​無ければ​Errorを​返すように​しておけば,​Gleamからは​安全に​Envの​値に​アクセスする​ことが​出来る​ためである.

JavaScriptから​Gleamの​機能に​アクセスするには,./gleam.mjsを​インポートする.gleam.mjsも​ビルド時に​生成され,​Gleamの​一部​機能を​JavaScriptから​使えるようになっている.

import { Ok, Error as Err } from "./gleam.mjs"

export function read_environment(env, key) {
	const value = env[key]
	return value ? new Ok(value) : new Err(undefined)
}

src/cloudflare/workers.gleamread_environmentを​追加する.

@external(javascript, "../ffi.js", "read_environment")
pub fn read_environment(env: Environment, key: String) -> Result(String, Nil)

ハンドラーで​呼び出す.

import cloudflare/workers.{type Environment, type Request, type Response}
import gleam/io
import gleam/javascript/promise.{type Promise}

pub fn handle_request(_request: Request, env: Environment) -> Promise(Response) {
  let _ =
    workers.read_environment(env, "SECRET_KEY")
    |> io.debug

  workers.response(200, [], "Cloudflare Workers上でGleamが動いてるよ")
  |> promise.resolve
}

エントリポイントも​Envを​受け取るように​変更する.

import { handle_request } from "./workers_gleam.mjs"

export default {
  async fetch(request, env) {
    return handle_request(request, env)
  }
}

実行すると,​ログに​設定した​環境変数が​表示される.

image.png


FFIコードを​書かなければならない​ものの,​Gleamを​Cf Workersで​動かせる​ことが​分かった.

KVや​R2などの​Bindingsが​使えないか​どうか,​今後も​調査を​進めて​いきたい.

この​記事を​書いている​最中に,​Deno Deployや​Cloudflare Workersでも​動かせるGlenと​いう​Gleam製フレームワークを​発見した.

実際に​Cf Workersに​載せるなら,​自力で​FFIを​頑張るのではなく,​このような​フレームワークを​使うのが​良いだろう.

MystPi
/
glen
Waiting for api.github.com...
00K
0K
0K
Waiting...

参考#

lpil
/
gleam-cloudflare-worker
Waiting for api.github.com...
00K
0K
0K
Waiting...
GitHubで編集を提案