Blog What we do Support Community
Login Sign up

Integrating redirection.io with Cloudflare Workers

by Guest Author.

The following is a guest post by Xavier Lacot, a developer at redirection.io and founder at JoliCode. He works primarily on Web and mobile projects as a consultant, trainer and technical expert.

What is redirection.io

Redirection.io is a Web traffic redirection manager. It provides a collection of tools for website administrators, SEO agencies, and developers, which help analyze HTTP errors, setup HTTP redirections, customize HTTP responses, and monitor the traffic efficiently.

The main part of a traditional redirection.io setup is the proxy, a software component which parses every request to check if a redirection or another response override is required. This "proxy" can be of several types - we provide libraries in several languages - but this setup can be simplified for Cloudflare clients by taking advantage of Cloudflare Workers.

Here come Cloudflare Workers

Earlier this year, Cloudflare unveiled its Workers product, a smart way of running code on the edge of Cloudflare locations. This computing feature is particularly interesting, as it allows performing several traffic operations without requiring any change on your own platform, code, or infrastructure: just enable Workers, write some code, and let Cloudflare handle the magic ✨

In practical terms, Workers act as application middleware. They proxy incoming HTTP requests to your stack, and can modify both requests and responses. Cloudflare has examples and code ideas to help you build your own Worker.

Here is a very basic Cloudflare Worker:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  console.log('Got request', request)
  const response = await fetch(request)
  console.log('Got response', response)
  return response
}

Luckily, redirection.io already has a public HTTP endpoint for exposing hosted agents (for shared hosting, for instance, where it is impossible to install your own binaries). Offering redirection.io to all Cloudflare Workers users is as simple as querying our hosted agent at each incoming request:

Integration of redirection.io with Clourdflare Workers

In terms of code, the pattern we use is very similar to the Workers example: we listen on the incoming request:

addEventListener('fetch', (event) => {
    event.respondWith(redirectAndLog(event.request));
});

async function redirectAndLog(request) {
    const response = await redirectOrPass(request);
    log(request, response);

    return response;
}

Of course, we must implement the redirectOrPass() and log() functions, which will do all the magic:

  • redirectOrPass() queries redirection.io's API to know if some action has to be run for the current request, and lets the request pass if necessary. This call is blocking; it will stop the request until a response is received from redirection.io's backend.
  • log() sends out log data to our backend for analysis and statistics purpose. This call is non-blocking, as it can be executed even after the response has been sent to the user.

Hence, both of these functions are defined as asynchronous non-blocking functions:

async function redirectOrPass(request) {
    ...
}

async function log(request, response) {
    ...
}

Let's go with the redirectOrPass() implementation! Using the fetch API, we can call redirection.io's agent API:

const urlObject = new URL(request.url);
const context = {
    'host': urlObject.host,
    'request_uri': urlObject.pathname,
    'user_agent': request.headers.get('user-agent'),
    'scheme': urlObject.protocol.includes('https') ? 'https' : 'http'
};
let response = null;

try {
    response = await fetch('https://proxy.redirection.io/' + options.token + '/get', {
        method: 'POST',
        body: JSON.stringify(context),
    });
} catch (error) {
    // if no action found, play the regular request
    return await fetch(request);
}

Should our API respond with a 404 status (which means that no redirection or action rule is defined for a request of that type), we simply return the standard response from your website's backend - this is achieved with return await fetch(request)

If a redirection has to be run, redirection.io's API will send a status 200 response with all the information on how to transform the response. The payload could, for instance, look like:

{
  "status_code": 302,
  "location": "/blog-yo"
}

... and we can simply use it to change the response:

const data = await response.text();

try {
    response = JSON.parse(data);
} catch (error) {
    // If some errors, play the regular request
    return await fetch(request);
}

// Send gone response
if (response.status_code === 410) {
    return new Response('', { status: 410 });
}

// Send redirection response
return new Response('', {
    status: Number(response.status_code),
    headers: {
        'Location': response.location
    }
});

The log() function implementation is pretty similar:

async function log(request, response) {
    const urlObject = new URL(request.url);
    const context = {
        'status_code': response.status,
        'host': urlObject.host,
        'request_uri': urlObject.pathname,
        'user_agent': request.headers.get('user-agent'),
        'scheme': urlObject.protocol.includes('https') ? 'https' : 'http',
    };

    try {
        return await fetch('https://proxy.redirection.io/' + options.token + '/log', {
            method: 'POST',
            body: JSON.stringify(context),
        });
    } catch (error) {
        // do nothing, does not matter if some logs are lost
    }
}

Impacts and performance concerns

Cloudflare workers run very fast. And by "very fast", we mean in microseconds, with the ability for Cloudflare to execute "many thousands of Worker scripts per second" as stated in their documentation.

The stack behind redirection.io is also very efficient, able to manage a similar level of traffic without any latency nor slowdown. However, there can be an occasional network slowdown, which could hurt the perceived performance of your web application. There are several ways to mitigate this possible impact.

First, Cloudflare Workers support caching subrequests made with the Fetch API. This means that, if a url of your application is often hit (for instance the homepage), you may want to cache the result from redirection.io's API. With this approach, the impact on the vast majority of your incoming requests will be close to zero.

Another approach is possible, to define the maximum timing overhead using redirection.io will add: we can simply define a timeout to the fetch() calls made by our Service Worker! Just nest the fetch() call inside a Promise.race():

response = await fetch('https://proxy.redirection.io/' + options.token + '/get', {
    method: 'POST',
    body: JSON.stringify(context),
});

becomes:

response = await Promise.race([
  fetch('https://proxy.redirection.io/' + options.token + '/get', {
    method: 'POST',
    body: JSON.stringify(context),
  }),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), options.timeout)
  ),
])

When talking about performance, it is a good idea to provide some numbers for a valid comparison:

Cloudflare enabled Workers enabled Request timing
~32ms
✅ (empty worker) ~36ms
✅ (redirection.io Worker with no cache) ~44ms

This means that you can benefit from redirection.io on your website through Cloudflare workers at the price of ~8ms. And, once responses from redirection.io's api are cached, we do not notice any difference compared to a standard Cloudflare-enabled website. We strongly believe Cloudflare Workers is an amazing way to integrate redirection.io into your website. Try redirection.io and Cloudflare Workers today, and report back!

comments powered by Disqus