Skip to main content

Developer Guide

Overview

IO River Unified Edge Compute allows developers to write edge logic once using IO River APIs and deploy it automatically across multiple edge platforms such as:

  • Cloudflare Workers
  • CloudFront Lambda@Edge
  • Fastly Compute

The IO River runtime abstracts differences between these providers, so your code is platform-agnostic and portable.

Developers implement two optional lifecycle handlers:

  • onRequest(request) – Executed when a request arrives at the edge.
  • onResponse(request, response) – Executed before the response is sent to the client.

Execution Model

onRequest(request)

Triggered when the incoming HTTP request reaches the edge.

Use it to:

  • Modify headers
  • Rewrite URLs
  • Route to different backends
  • Call external services
  • Generate custom responses

Return value:

  • Can return either IORRequest or IORResponse
  • Can return the result of ior_fetch()

onResponse(request, response)

Triggered after a response is generated but before it is returned to the end user.

Use it to:

  • Modify response headers
  • Adjust status codes
  • Rewrite body
  • Add security headers

Return value:

  • Must return an IORResponse

Core APIs

IORRequest

Represents the incoming HTTP request.

Properties

PropertyTypeDescription
request.urlstringFull request URL
request.methodstringHTTP method (GET, POST, etc.)
request.headersHeadersRequest headers object

Accessing Body

Use either request.json() or request.text() to retrieve the request body. Note that these methods consume the body. If you only need to read the body and preserve it, you should clone the request and consume it from the cloned instance.

Accessing Headers

request.headers behaves like the standard Web Fetch API Headers.

Get a Header
let userAgent = request.headers.get("User-Agent");
Set a Header
request.headers.set("x-custom-header", "value");
Delete a Header
request.headers.delete("Cookie");
Check if Header Exists
request.headers.has("Authorization");

IORResponse

Represents an HTTP response.

Returned either from:

  • ior_fetch()
  • Manually constructed response in onRequest or onResponse
  • Modified inside onResponse

Properties

PropertyTypeDescription
response.statusnumberHTTP status code
response.headersHeadersResponse headers

Accessing Body

Use either response.json() or response.text() to retrieve the response body. Note that these methods consume the body. If you only need to read the body and preserve it, you should clone the request and consume it from the cloned instance. This is not supported for CloudFront.

Modifying Response Headers

Set a Header
response.headers.set("x-powered-by", "IO River");
Get a Header
let contentType = response.headers.get("Content-Type");
Delete a Header
response.headers.delete("Server");

Making Outbound Requests – ior_fetch()

ior_fetch() is the platform-agnostic fetch function. It works similarly to the standard Fetch API but is adapted internally per edge provider.

Syntax
let response = await ior_fetch(url, {
method: "POST",
headers: headers,
body: body,
});
Parameters
ParameterDescription
urlString or URL object
methodHTTP method
headersHeaders object
bodyOptional request body

Returns:

  • Promise<IORResponse>

Util Library

The ior_utils library provides a unified set of cryptographic primitives designed to run consistently across all supported CDN providers on the IO River edge compute platform.

FunctionDefinitionComments
sha256HashHexsha256HashHex(messageString)returns a SHA-256 digest as a hex string
hmacSHA256hmacSHA256(key, message)returns a raw SHA-256 HMAC buffer for key+message
hmacSHA256Base64UrlhmacSHA256Base64Url(key, message)computes that HMAC and encodes it as Base64URL
base64UrlDecodebase64UrlDecode(str)converts a Base64URL string into a Uint8Array of bytes
bufferToStringbufferToString(buf)bufferToString decodes a byte buffer into a UTF-8 text string

Note: you do not need to import these functions; they are automatically available within your edge functions via the ior_utils namespace

Examples

Example: Proxy Request to Another Host

async function onRequest(request) {
var url = new URL(request.url);
var headers = new Headers(request.headers);

url.hostname = "test.example.com";

headers.set("Authorization", "Basic abcd1234");

let response = await ior_fetch(url, {
headers: headers,
method: request.method,
});

return response;
}

What This Does

  • Rewrites hostname
  • Adds Authorization header
  • Forwards original HTTP method
  • Returns origin response

Example: Modify Response Before Sending to the End-User

async function onResponse(request, response) {
response.headers.set("foo", "bar");
return response;
}

Example: Working With URL

You can use standard Web API URL:

var url = new URL(request.url);
url.pathname = "/new-path";
url.searchParams.set("debug", "true");

Example: Creating a Custom Response

You may return a new response directly:

return new IORResponse("Blocked", {
status: 403,
headers: {
"content-type": "text/plain",
},
});

Example: Validating a JWT in a Request

async function jwt_decode(token, key, noVerify, algorithm) {
// check token
if (!token) {
throw new Error('No token supplied');
}
// check segments
const segments = token.split('.');
if (segments.length !== 3) {

throw new Error('Not enough or too many segments');
}

// All segment should be base64
const headerSeg = segments[0];
const payloadSeg = segments[1];
const signatureSeg = segments[2];

// base64 decode and parse JSON
let payloadStr = await ior_utils.bufferToString(await ior_utils.base64UrlDecode(payloadSeg));
const payload = JSON.parse(payloadStr);

if (!noVerify) {
// Verify signature. `sign` will return base64 string.
const signingInput = [headerSeg, payloadSeg].join('.');

if (!await _verify(signingInput, key, signatureSeg)) {
throw new Error('Signature verification failed');
}

// Support for nbf and exp claims.
// According to the RFC, they should be in seconds.
if (payload.nbf && Date.now() < payload.nbf*1000) {
throw new Error('Token not yet active');
}

if (payload.exp && Date.now() > payload.exp*1000) {
throw new Error('Token expired');
}
}

return payload;
}

function _constantTimeEquals(a, b) {
if (a.length != b.length) {
return false;
}

let xor = 0;
for (let i = 0; i < a.length; i++) {
xor |= (a.charCodeAt(i) ^ b.charCodeAt(i));
}

return 0 === xor;
}

async function _verify(input, key, signature) {
let expectedSignature = await _sign(input, key);
return _constantTimeEquals(signature, expectedSignature);
}

async function _sign(input, key) {
return await ior_utils.hmacSHA256Base64Url(key, input);
}


async function onRequest(request)
{

//Update with your own key.
const secret_key = await getSecret()

if(!secret_key) {
return new IORResponse(null, {status:401, statusText:"Unauthirized"});
}

const jwtToken = request.urlObj.searchParams.get('jwt');
// If no JWT token, then generate HTTP redirect 401 response.

if(!jwtToken) {
return new IORResponse(null, {status:401, statusText:"Unauthirized"});
}

try{
await jwt_decode(jwtToken, secret_key);
}
catch(e) {
console.log(e);
return new IORResponse(null, {status:401, statusText:"Unauthirized"});
}

//Remove the JWT from the query string if valid and return.
let currentURL = request.urlObj;
currentURL.searchParams.delete('jwt');
return new IORRequest(new Request(currentURL, requestCtx.request));
}

async function getSecret() {
return "a-string-secret-at-least-256-bits-long";

Best Practices

1. Clone Headers Before Modifying

Always create a new Headers object if forwarding:

var headers = new Headers(request.headers);

Avoid mutating the original unless necessary.

2. Preserve Method and Body for Proxying

let response = await ior_fetch(url, {
method: request.method,
headers: headers,
body: request.text(),
});

3. Avoid Blocking Operations

All handlers must be async and non-blocking.

Error Handling

Use try/catch inside handlers:

async function onRequest(request) {
try {
return await ior_fetch(request.url);
} catch (e) {
return new IORResponse("Upstream Error", { status: 502 });
}
}

Request Lifecycle

Incoming Request

onRequest()

(optional ior_fetch)

Origin Response

onResponse()

End User

Conclusion

IO River Unified Edge Compute enables:

  • Write once, deploy everywhere
  • Standard Web API-like interface
  • Full request and response control
  • Platform abstraction across major edge providers