Add trusted-server-adapter-cloudflare crate (PR 17)#644
Add trusted-server-adapter-cloudflare crate (PR 17)#644prk-Jr wants to merge 10 commits intofeature/edgezero-pr16-axum-dev-serverfrom
Conversation
…ator wasm32-unknown-unknown (Cloudflare Workers) does not support std::time::Instant — it panics at runtime. web_time::Instant is a zero-cost drop-in on native and JS-backed on WASM.
Implements the Cloudflare Workers adapter following the same pattern as trusted-server-adapter-axum: TrustedServerApp implements the Hooks trait, platform services use noop stubs on native (CI-compilable), and the #[event(fetch)] entry point delegates to edgezero_adapter_cloudflare::run_app. Also adds UnavailableHttpClient to trusted-server-core platform module, parallel to the existing UnavailableKvStore.
CI: test-cloudflare job checks native host compile, wasm32-unknown-unknown compile (with cloudflare feature), and runs host-target unit tests. CLAUDE.md: add cloudflare crate to workspace layout and build commands.
…un_app The rev (38198f9) of edgezero used in this workspace requires worker 0.7 (not 0.6) and run_app() takes a manifest_src: &str as first argument. Updated Cargo.toml and lib.rs accordingly.
- Implement CloudflareHttpClient (wasm32 only) using worker::Fetch for real
outbound proxy requests; strip content-encoding/transfer-encoding headers
since the Workers runtime auto-decompresses responses
- Add build.sh with cd-to-SCRIPT_DIR guard so worker-build always runs from
the correct crate root regardless of invocation directory
- Switch wrangler dev task to use --cwd from workspace root (same DX as Fastly)
- Add js-sys to workspace dependencies; reference it via { workspace = true }
- Fix #[ignore] messages on Cloudflare integration tests
- Replace std::time::{SystemTime,UNIX_EPOCH} with web_time in test code for
signing.rs and proxy.rs (consistency with production paths)
- Add NoopConfigStore/NoopSecretStore TODO comment tracking the gap
- Add extract_client_ip unit tests (parses cf-connecting-ip, absent, invalid)
- Remove empty crate_compiles_on_host_target test
- Add CloudflareHttpClient timeout doc noting Workers CPU-budget tradeoff
Replace direct worker::Env store construction with edgezero handles already injected by run_app, reducing #[cfg(target_arch = "wasm32")] blocks from 5 to 2. - ConfigStoreHandleAdapter: bridges ctx.config_store() to PlatformConfigStore — reuses the already-parsed TRUSTED_SERVER_CONFIG JSON handle rather than re-parsing it on every request - KvHandleAdapter: bridges ctx.kv_handle() to PlatformKvStore — reuses the env.kv() handle opened by run_app rather than opening a new one - CloudflareGeo: moved outside #[cfg]; reads cf-ipcountry and related headers via ctx.request().headers() which needs no platform import - CloudflareSecretStoreAdapter: kept under #[cfg] — env.secret() is synchronous at the JS level but PlatformSecretStore::get_bytes is sync while SecretHandle::get_bytes is async; direct env access is required - Add .dev.vars to .gitignore (wrangler convention for local secrets) - Add bytes workspace dep for KvStore impl
…ezero-pr17-cloudflare-adapter
- Implement CloudflareWorkers::spawn() to start wrangler dev; in CI uses wrangler.ci.toml (no build step, uses pre-built bundle); locally uses wrangler.toml (triggers build.sh rebuild) - Add wrangler.ci.toml: no [build] section so wrangler dev skips the worker-build step when the bundle is pre-built as a CI artifact - Add build-cloudflare input to setup-integration-test-env: adds wasm32-unknown-unknown target and runs build.sh with integration test env vars - In prepare-artifacts: enable build-cloudflare and upload build/ dir as artifact - In integration-tests: restore CF build dir, install wrangler, set CLOUDFLARE_WRANGLER_DIR, and remove --skip flags for cloudflare tests
- Add GeoInfo happy-path test: build_geo_lookup_returns_some_with_populated_country verifies country, city, continent, latitude, and longitude are correctly populated when Cloudflare headers are present - Simplify CloudflareSecretStoreAdapter::get_bytes: collapse brittle JsError string-matching guards into a single error arm with contextual message - Document sequential fan-out latency in CloudflareHttpClient: explain sum(DSP_i) vs max(DSP_i) implication and why true parallelism is blocked by the ?Send bound on PlatformHttpClient - Fix stale wrangler.toml comment: update to reflect ConfigStoreHandleAdapter wiring rather than the now-fallback NoopConfigStore - Extend CI triggers to feature branches: format.yml and test.yml now run fmt/clippy/tests on PRs targeting feature/** so stacking PRs are gated - Fix fmt violations caught during pre-review verification: platform.rs two-line Err wrapping and plan doc Prettier formatting
The adapter's tokio dev-dependency uses rt-multi-thread which is not supported on wasm32. It is already tested in the dedicated test-cloudflare job; exclude it from the workspace wasm test to avoid the compile error.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Adds the Cloudflare adapter, runtime wiring, and CI coverage. CI is green, but I found two correctness issues that should be addressed before merge, plus two smaller follow-ups.
| } | ||
| }; | ||
|
|
||
| RouterService::builder() |
There was a problem hiding this comment.
🔧 wrench — Cloudflare routes skip the auth/finalize middleware chain.
Both existing adapters wrap their route tables with FinalizeResponseMiddleware and AuthMiddleware, but this router is built without either layer. That means Cloudflare no longer enforces per-path basic auth and it also skips the standard response finalization headers on every response.
Suggestion:
Port the Cloudflare adapter to use the same middleware chain as Axum/Fastly and add at least one middleware/auth test so this cannot regress silently.
| request: PlatformHttpRequest, | ||
| ) -> Result<PlatformPendingRequest, Report<PlatformError>> { | ||
| let backend_name = request.backend_name.clone(); | ||
| let response = self.execute(request).await?; |
There was a problem hiding this comment.
🔧 wrench — send_async() serializes auctions and does not enforce the request deadline.
send_async() eagerly awaits self.execute(request).await, so the auction orchestrator launches Cloudflare providers one by one instead of handing back true in-flight requests. execute() also waits on worker::Fetch::send().await without any timeout/abort path, so a slow upstream can block past the remaining auction budget.
Suggestion:
Make PlatformPendingRequest hold an actual in-flight fetch plus timeout/abort state and have select() resolve the ready request. If that is out of scope here, it would be safer to reject unsupported multi-provider fan-out on Cloudflare than to silently degrade auction behavior.
|
|
||
| impl PlatformBackend for NoopBackend { | ||
| fn predict_name(&self, spec: &PlatformBackendSpec) -> Result<String, Report<PlatformError>> { | ||
| Ok(format!("{}_{}", spec.scheme, spec.host)) |
There was a problem hiding this comment.
🤔 thinking — backend names are too coarse for reliable response correlation.
This only includes {scheme}_{host}, but the rest of the system treats port / timeout / certificate mode as part of backend identity. If two concurrent requests share a host but differ on those fields, the orchestrator's backend-to-provider map can collide and misattribute a response.
Suggestion:
Mirror the Axum/Fastly naming scheme and include at least port and timeout in the generated name.
| } | ||
|
|
||
| #[test] | ||
| #[ignore = "requires Docker and a running `wrangler dev` instance; see environments/cloudflare.rs"] |
There was a problem hiding this comment.
📝 note — this ignore message is stale now that CloudflareWorkers::spawn() starts wrangler dev itself.
The test no longer requires a separately running wrangler dev instance, so this text will mislead anyone trying to run the ignored test locally.
Suggestion:
Update the ignore reason to describe the real prerequisites instead (Docker, wrangler installed, and a prebuilt Cloudflare bundle).
Summary
Changes
Closes
Closes #498
Test plan
Checklist
Known deferred items