Skip to content

Conversation

@ebonura-fastly
Copy link
Contributor

Summary

Migrates all downstream client/server functions from the deprecated fastly_http_req WASM module to the new fastly_http_downstream module, and adds the tlsClientServername property which exposes the Server hostname from the TLS handshake, aligning the JS SDK with Fastly's recommended approach and enabling access to additional APIs that were previously unavailable.

New Property

  • tlsClientServername - Returns the SNI hostname from the TLS handshake (e.g., "example.com")

Migrated Functions (10 total)

All downstream functions now use fastly_http_downstream with request handle parameter:

  • downstream_client_ip_addr
  • downstream_server_ip_addr
  • downstream_tls_cipher_openssl_name
  • downstream_tls_protocol
  • downstream_tls_client_hello
  • downstream_tls_raw_client_certificate
  • downstream_tls_ja3_md5
  • downstream_tls_ja4
  • downstream_client_h2_fingerprint
  • downstream_client_oh_fingerprint

Investigation

The JS SDK was using the fastly_http_req module for downstream functions, but these functions existed in two places in the Fastly host API:

  1. fastly_http_req (legacy) - Functions don't require a request handle parameter
  2. fastly_http_downstream (modern) - Functions require a request handle as the first parameter

To understand the correct function signatures for fastly_http_downstream, I had a look at the Rust SDK's implementation in fastly-sys:

// From fastly-sys/src/lib.rs - fastly_http_downstream module
pub mod fastly_http_downstream {
    #[link(wasm_import_module = "fastly_http_downstream")]
    extern "C" {
        #[link_name = "downstream_tls_ja4"]
        pub fn downstream_tls_ja4(
            req_handle: RequestHandle,
            ja4_out: *mut u8,
            ja4_max_len: usize,
            nwritten: *mut usize,
        ) -> FastlyStatus;

        #[link_name = "downstream_tls_client_servername"]
        pub fn downstream_tls_client_servername(
            req_handle: RequestHandle,
            sni_out: *mut u8,
            sni_max_len: usize,
            nwritten: *mut usize,
        ) -> FastlyStatus;
        // ... other functions
    }
}

This confirmed that:

  1. All fastly_http_downstream functions require req_handle: RequestHandle as the first parameter
  2. The downstream_tls_client_servername function is available in this module (but not in fastly_http_req)

The JS SDK's existing code was using fastly_http_req without the request handle, which worked but didn't provide access to newer APIs like downstream_tls_client_servername.

Implementation

The key architectural change is that fastly_http_downstream functions require a request handle as the first parameter, whereas the old fastly_http_req functions were static:

Before (fastly_http_req):
  downstream_tls_ja4(buffer, len, nwritten) -> status

After (fastly_http_downstream):
  downstream_tls_ja4(req_handle, buffer, len, nwritten) -> status

1. WASM Host Import (runtime/fastly/host-api/fastly.h)

Changed module from fastly_http_req to fastly_http_downstream and added req_handle parameter to all functions:

WASM_IMPORT("fastly_http_downstream", "downstream_tls_ja4")
int downstream_tls_ja4(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten);

// New function for SNI
WASM_IMPORT("fastly_http_downstream", "downstream_tls_client_servername")
int downstream_tls_client_servername(uint32_t req_handle, uint8_t *ret, size_t ret_len, size_t *nwritten);

2. C++ Host API Wrapper (runtime/fastly/host-api/host_api_fastly.h + host_api.cpp)

Converted static methods to instance methods that use this->handle:

// Before (static)
static Result<std::optional<HostString>> http_req_downstream_tls_ja4();

// After (instance method)
Result<std::optional<HostString>> downstream_tls_ja4();

// New method
Result<std::optional<HostString>> downstream_tls_client_servername();

3. JavaScript Bindings (runtime/fastly/builtins/fetch-event.h + fetch-event.cpp)

  • Added RequestHandle slot to ClientInfo and ServerInfo to store the request handle
  • Added Servername slot for caching the SNI value
  • Added helper function get_request_handle() to retrieve handle from slot
  • Updated all getters to use instance methods: req.downstream_tls_ja4() instead of HttpReq::http_req_downstream_tls_ja4()
  • Added tls_client_servername_get getter and registered tlsClientServername property

Files Changed

File Changes
runtime/fastly/host-api/fastly.h Changed module to fastly_http_downstream, added req_handle param, added downstream_tls_client_servername
runtime/fastly/host-api/host_api_fastly.h Converted static methods to instance methods, added downstream_tls_client_servername()
runtime/fastly/host-api/host_api.cpp Updated implementations to pass this->handle, added downstream_tls_client_servername()
runtime/fastly/builtins/fetch-event.h Added RequestHandle slot to ClientInfo/ServerInfo, added Servername slot, added getter declaration
runtime/fastly/builtins/fetch-event.cpp Added get_request_handle() helper, updated all getters, added tlsClientServername property
types/globals.d.ts Added TypeScript type for tlsClientServername
documentation/docs/globals/FetchEvent/FetchEvent.mdx Added documentation for tlsClientServername
integration-tests/js-compute/fixtures/app/src/client.js Added integration test for tlsClientServername
integration-tests/js-compute/fixtures/app/tests.json Added test entry for tlsClientServername

Testing

Local Build

Built the js-compute-runtime from source:

cd js-compute-runtime
npm run build:release

Local Testing (Viceroy)

Local testing with Viceroy 0.16.1 fails because Viceroy doesn't yet support the fastly_http_downstream module:

thread 'main' panicked at 'Unknown import: `fastly_http_downstream::downstream_client_ip_addr`'

This is expected - Viceroy needs an update to support the new module.

Edge Deployment

Created a test Compute service and deployed to Fastly edge:

cd js-test-mcsoc-3081
npm install
npm run build
fastly compute publish

Test endpoint: https://specially-usable-eft.edgecompute.app/

Sample response:

{
  "clientIP": "161.51.218.71",
  "tlsJA3MD5": "375C6162A492DFBF2795909110CE8424",
  "tlsJA4": "t13d4907h2_0d8feac7bc37_7395dae3b2f3",
  "h2Fingerprint": "2:0;3:100;4:10485760|1048510465|1:0:0:16|m,s,a,p",
  "ohFingerprint": "aB:a6:aT",
  "tlsProtocol": "TLSv1.3",
  "tlsCipher": "TLS_CHACHA20_POLY1305_SHA256",
  "tlsClientServername": "specially-usable-eft.edgecompute.app"
}

All existing properties continue to work correctly, and the new tlsClientServername returns the expected SNI hostname.

Breaking Changes

None - this is a transparent migration. The JavaScript API remains unchanged for existing properties.

TODO

  • Viceroy update needed to support fastly_http_downstream module for local testing

Checklist

  • Follows existing code patterns
  • All migrated functions tested on edge
  • New tlsClientServername property working
  • Built successfully
  • Verified on Fastly edge deployment
  • TypeScript types updated
  • Documentation updated
  • Integration tests added

…gerprint)

Add three new properties to ClientInfo for client fingerprinting:
- tlsJA4: JA4 TLS fingerprint (human-readable string format)
- h2Fingerprint: HTTP/2 fingerprint
- ohFingerprint: Original Header fingerprint

These properties provide parity with the Rust and Go Compute SDKs.
Add tests for tlsJA4, h2Fingerprint, and ohFingerprint properties
to ensure they return null locally and string on edge.
Document tlsJA4, h2Fingerprint, and ohFingerprint properties
in FetchEvent.mdx.
Maintainers will handle versioning on merge.
- Convert all downstream functions to use the new fastly_http_downstream module
- Functions now require request handle parameter instead of being static
- Add tlsClientServername property to ClientInfo (SNI hostname)
- Store request handle in ClientInfo/ServerInfo slots for lazy access

Migrated functions:
- downstream_client_ip_addr
- downstream_server_ip_addr
- downstream_tls_cipher_openssl_name
- downstream_tls_protocol
- downstream_tls_client_hello
- downstream_tls_raw_client_certificate
- downstream_tls_ja3_md5
- downstream_tls_ja4
- downstream_client_h2_fingerprint
- downstream_client_oh_fingerprint

New property:
- client.tlsClientServername - Returns TLS SNI hostname

Also includes:
- TypeScript types for tlsClientServername
- Integration tests for tlsClientServername
- Documentation updates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants