Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/runn-cli-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,54 @@ jobs:
UZUMIBI_PROJECT_NAME: test-cloudflare-project-${{ github.run_id }}
run: runn run new_cloudflare.yml --verbose

cloudflare-queue-test:
name: Cloudflare Queue template test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install wasm32 target
run: rustup target add wasm32-unknown-unknown

- name: Cache cargo registry and build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-cloudflare-queue-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-cloudflare-queue-

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Install wasm-opt
run: |
curl -L https://github.com/WebAssembly/binaryen/releases/download/version_120/binaryen-version_120-x86_64-linux.tar.gz | tar xz
sudo cp binaryen-version_120/bin/wasm-opt /usr/local/bin/

- name: Install runn
uses: k1LoW/gh-setup@v1
with:
repo: k1LoW/runn
bin-match: runn

- name: Run Cloudflare Queue smoke test
working-directory: uzumibi-cli/tests/runn
env:
RUNN_SCOPES: "read:parent,run:exec"
UZUMIBI_TEST_BINARY: ${{ github.workspace }}/target/release/uzumibi
UZUMIBI_TEST_TMPDIR: ${{ runner.temp }}
UZUMIBI_PROJECT_NAME: test-cloudflare-queue-project-${{ github.run_id }}
run: runn run new_cloudflare_queue.yml --verbose

cloudrun-test:
name: Cloud Run template test
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion uzumibi-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uzumibi-cli"
version = "0.8.0"
version = "0.8.1"
edition = "2024"
authors = ["Uchio Kondo <udzura@udzura.jp>"]
description = "Uzumibi CLI tool to generate serverless mruby/edge apps"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
"name": "$$PROJECT_NAME$$",
"version": "0.0.0",
"private": true,
"uzumibi": {
"httpMaxBytes": 65536
},
"scripts": {
"deploy": "npm run build:wasm:asyncify && wrangler deploy",
"dev": "npm run build:wasm:asyncify && wrangler dev",
"build:wasm:asyncify": "cargo build --package $$PROJECT_NAME$$ --target wasm32-unknown-unknown --release --features enable-external && wasm-opt --enable-bulk-memory --enable-nontrapping-float-to-int --asyncify -O2 target/wasm32-unknown-unknown/release/$$PROJECT_NAME_UNDERSCORE$$.wasm -o ./src/$$PROJECT_NAME_UNDERSCORE$$.wasm",
"deploy": "pnpm run build:wasm:asyncify && wrangler deploy",
"dev": "pnpm run build:wasm:asyncify && wrangler dev",
"build:wasm:asyncify": "node scripts/build-wasm.mjs asyncify",
"start": "wrangler dev",
"test": "vitest"
},
Expand All @@ -15,4 +18,4 @@
"vitest": "~3.2.0",
"wrangler": "^4.54.0"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DurableObject } from "cloudflare:workers";
import { instantiate } from "asyncify-wasm";
import mod from "./$$PROJECT_NAME_UNDERSCORE$$.wasm";
import { RequestTooLargeError, writeRequestToWasm } from "./request-buffer.js";

const wasmModule = mod;

Expand All @@ -25,8 +26,6 @@ export default {
return new Response(null, { status: 404 });
}

const query = new URL(request.url).searchParams;

const kv = env.UZUMIBI_KV ?? null;

// Durable Object stub retained for Uzumibi::LegacyKV.
Expand Down Expand Up @@ -229,92 +228,13 @@ export default {
const instance = await instantiate(wasmModule, importObject);
const exports = instance.exports;

const reqResult = await exports.uzumibi_initialize_request(65536);
const reqOffset = Number(reqResult & 0xFFFFFFFFn);
if (reqOffset === 0) {
const errOffset = Number((reqResult >> 32n) & 0xFFFFFFFFn);
const buffer = new Uint8Array(exports.memory.buffer, errOffset);
let errStr = "";
for (let i = 0; buffer[i] !== 0; i++) {
errStr += String.fromCharCode(buffer[i]);
}
throw new Error(`Failed to initialize request: ${errStr}`);
}
const requestBuffer = new Uint8Array(exports.memory.buffer, reqOffset, 65536);

let pos = 0;
const dataView = new DataView(exports.memory.buffer, reqOffset);

const method = encoder.encode(request.method);
requestBuffer.fill(0, pos, pos + 6);
requestBuffer.set(method.slice(0, 6), pos);
pos += 6;

// Path size (u16 little-endian)
const pathBytes = encoder.encode(path);
dataView.setUint16(pos, pathBytes.length, true);
pos += 2;

// Path
requestBuffer.set(pathBytes, pos);
pos += pathBytes.length;

// Query string size (u16 little-endian)
const queryString = query.toString();
const queryBytes = encoder.encode(queryString);
dataView.setUint16(pos, queryBytes.length, true);
pos += 2;

// Query string
requestBuffer.set(queryBytes, pos);
pos += queryBytes.length;

// Headers
const headers = [];
request.headers.forEach((value, key) => {
if (key.toLowerCase() !== 'cf-connecting-ip' &&
key.toLowerCase() !== 'cf-ray' &&
!key.toLowerCase().startsWith('x-')) {
headers.push({ key, value });
try {
await writeRequestToWasm(exports, request);
} catch (error) {
if (error instanceof RequestTooLargeError) {
return new Response(error.message, { status: 413 });
}
});

// Headers count (u16 little-endian)
dataView.setUint16(pos, headers.length, true);
pos += 2;

// Each header
for (const header of headers) {
// Header key size (u16 little-endian)
const keyBytes = encoder.encode(header.key);
dataView.setUint16(pos, keyBytes.length, true);
pos += 2;

// Header key
requestBuffer.set(keyBytes, pos);
pos += keyBytes.length;

// Header value size (u16 little-endian)
const valueBytes = encoder.encode(header.value);
dataView.setUint16(pos, valueBytes.length, true);
pos += 2;

// Header value
requestBuffer.set(valueBytes, pos);
pos += valueBytes.length;
}

// Request body size (u32 little-endian)
const bodyBytes = request.body ? new Uint8Array(await request.arrayBuffer()) : new Uint8Array(0);
dataView.setUint32(pos, bodyBytes.length, true);
pos += 4;

// Request body
requestBuffer.set(bodyBytes, pos);
pos += bodyBytes.length;

if (pos > 65536) {
throw new Error("Request data exceeds allocated buffer size");
throw error;
}

const resResult = await exports.uzumibi_start_request();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use mrubyedge::{
},
};

include!(concat!(env!("OUT_DIR"), "/uzumibi_config.rs"));

static MRB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/app.mrb"));

static mut MRUBY_VM: MaybeUninit<VM> = MaybeUninit::uninit();
Expand Down Expand Up @@ -58,6 +60,11 @@ fn assume_init_vm() -> Result<&'static mut VM, mrubyedge::Error> {
}

fn do_uzumibi_initialize_request(size: i32) -> Result<*mut u8, mrubyedge::Error> {
if size <= 0 || size as u32 > HTTP_MAX_BYTES {
return Err(mrubyedge::Error::RuntimeError(format!(
"Request size {size} exceeds the configured maximum of {HTTP_MAX_BYTES} bytes"
)));
}
let vm = assume_init_vm()?;
let size = RObject::integer(size as i64).to_refcount_assigned();
let app = vm
Expand All @@ -68,6 +75,11 @@ fn do_uzumibi_initialize_request(size: i32) -> Result<*mut u8, mrubyedge::Error>
ret.as_ref().try_into()
}

#[unsafe(export_name = "uzumibi_http_max_bytes")]
extern "C" fn uzumibi_http_max_bytes() -> u32 {
HTTP_MAX_BYTES
}

fn do_uzumibi_start_request() -> Result<*mut u8, mrubyedge::Error> {
uzumibi_cloudflare_ext::debug_console_log_internal("uzumibi_start_request called");
let vm = assume_init_vm()?;
Expand Down
11 changes: 7 additions & 4 deletions uzumibi-cli/templates/cloudflare/__features__/queue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
"name": "$$PROJECT_NAME$$",
"version": "0.0.0",
"private": true,
"uzumibi": {
"httpMaxBytes": 65536
},
"scripts": {
"deploy": "npm run build:wasm:asyncify && wrangler deploy",
"dev": "npm run build:wasm:asyncify && wrangler dev",
"build:wasm:asyncify": "cargo build --package $$PROJECT_NAME$$ --target wasm32-unknown-unknown --release --features queue && wasm-opt --enable-bulk-memory --enable-nontrapping-float-to-int --asyncify -O2 target/wasm32-unknown-unknown/release/$$PROJECT_NAME_UNDERSCORE$$.wasm -o ./src/$$PROJECT_NAME_UNDERSCORE$$_queue.wasm",
"deploy": "pnpm run build:wasm:queue && wrangler deploy",
"dev": "pnpm run build:wasm:queue && wrangler dev",
"build:wasm:queue": "node scripts/build-wasm.mjs queue",
"start": "wrangler dev",
"test": "vitest"
},
Expand All @@ -15,4 +18,4 @@
"vitest": "~3.2.0",
"wrangler": "^4.54.0"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
use std::path::Path;
use std::{fs, path::Path};

extern crate mruby_compiler2_sys;

fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let http_max_bytes = std::env::var("UZUMIBI_HTTP_MAX_BYTES")
.unwrap_or_else(|_| "65536".to_string())
.parse::<u32>()
.expect("UZUMIBI_HTTP_MAX_BYTES must be a positive 32-bit integer");
assert!(
http_max_bytes > 0 && http_max_bytes <= i32::MAX as u32,
"UZUMIBI_HTTP_MAX_BYTES must be between 1 and {}",
i32::MAX
);
fs::write(
Path::new(&out_dir).join("uzumibi_config.rs"),
format!("pub const HTTP_MAX_BYTES: u32 = {http_max_bytes};\n"),
)
.expect("failed to write Uzumibi build configuration");
println!("cargo:rerun-if-env-changed=UZUMIBI_HTTP_MAX_BYTES");

let mrb_path = Path::new(&out_dir).join("consumer.mrb");
let code = include_str!("../lib/consumer.rb");
println!("cargo:rerun-if-changed=../lib/consumer.rb");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use mrubyedge::{
},
};

include!(concat!(env!("OUT_DIR"), "/uzumibi_config.rs"));

#[cfg(not(feature = "queue"))]
static MRB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/app.mrb"));
#[cfg(feature = "queue")]
Expand Down Expand Up @@ -61,6 +63,11 @@ fn assume_init_vm() -> Result<&'static mut VM, mrubyedge::Error> {
}

fn do_uzumibi_initialize_request(size: i32) -> Result<*mut u8, mrubyedge::Error> {
if size <= 0 || size as u32 > HTTP_MAX_BYTES {
return Err(mrubyedge::Error::RuntimeError(format!(
"Request size {size} exceeds the configured maximum of {HTTP_MAX_BYTES} bytes"
)));
}
let vm = assume_init_vm()?;
let size = RObject::integer(size as i64).to_refcount_assigned();
let app = vm
Expand All @@ -71,6 +78,11 @@ fn do_uzumibi_initialize_request(size: i32) -> Result<*mut u8, mrubyedge::Error>
ret.as_ref().try_into()
}

#[unsafe(export_name = "uzumibi_http_max_bytes")]
extern "C" fn uzumibi_http_max_bytes() -> u32 {
HTTP_MAX_BYTES
}

fn do_uzumibi_start_request() -> Result<*mut u8, mrubyedge::Error> {
uzumibi_cloudflare_ext::debug_console_log_internal("uzumibi_start_request called");
let vm = assume_init_vm()?;
Expand Down
10 changes: 7 additions & 3 deletions uzumibi-cli/templates/cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"name": "$$PROJECT_NAME$$",
"version": "0.0.0",
"private": true,
"uzumibi": {
"httpMaxBytes": 65536
},
"scripts": {
"deploy": "wrangler deploy",
"dev": "cargo build --package $$PROJECT_NAME$$ --target wasm32-unknown-unknown --release && cp -v -f target/wasm32-unknown-unknown/release/$$PROJECT_NAME_UNDERSCORE$$.wasm src/ && wrangler dev",
"deploy": "pnpm run build:wasm:vanilla && wrangler deploy",
"dev": "pnpm run build:wasm:vanilla && wrangler dev",
"build:wasm:vanilla": "node scripts/build-wasm.mjs vanilla",
"start": "wrangler dev",
"test": "vitest"
},
Expand All @@ -13,4 +17,4 @@
"vitest": "~3.2.0",
"wrangler": "^4.54.0"
}
}
}
Loading
Loading