Possible to A/B test between two targets under one CNAME?

Viewed 177

Is it possible to dynamically route between two targets under one CNAME? I couldn't find any specific documentation where Cloudflare workers or page rules could accomplish this.

cloudflare ab test

For context my team is attempting an A/B test between two deployed Heroku apps as shown by "a-test.herokuapp.com", which is the control and "b-test.herokuapp.com", which is our B test.

I was hoping Cloudflare workers would be able to dynamically route between these two targets underneath the covers while keeping our URL the same e.g. app.domain.com.

Does anyone know if this is possible, and if so, is there any documentation around this? Thank you!

2 Answers

You can do this with Workers. Your code would look something like this:

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

async function handleRequest(request) {
  let url = new URL(request.url);
  if (isInGroupA(request)) {
    url.hostname = "a-test.herokuapp.com";
  } else {
    url.hostname = "b-test.herokuapp.com";
  }
  return fetch(url, request);
}

function isInGroupA(request) {
  // write your logic that chooses between A and B here...
}

Method 1:

So, Cloudflare has an A/B Testing example which uses Workers. This method uses cookies to achieve what you need. This would be the preferred method for most cases.

function handleRequest(request) {
  const NAME = "experiment-0"

  // The Responses below are placeholders. You can set up a custom path for each test (e.g. /control/somepath ).
  const TEST_RESPONSE = new Response("Test group") // e.g. await fetch("/test/sompath", request)
  const CONTROL_RESPONSE = new Response("Control group") // e.g. await fetch("/control/sompath", request)

  // Determine which group this requester is in.
  const cookie = request.headers.get("cookie")
  if (cookie && cookie.includes(`${NAME}=control`)) {
    return CONTROL_RESPONSE
  }
  else if (cookie && cookie.includes(`${NAME}=test`)) {
    return TEST_RESPONSE
  }
  else {
    // If there is no cookie, this is a new client. Choose a group and set the cookie.
    const group = Math.random() < 0.5 ? "test" : "control" // 50/50 split
    const response = group === "control" ? CONTROL_RESPONSE : TEST_RESPONSE
    response.headers.append("Set-Cookie", `${NAME}=${group}; path=/`)

    return response
  }
}

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

Method 2:

Another method would be to use a Cloudflare Loadbalancer. Assuming you have two origin servers (A and B) with one single domain configured on both, you can split requests to either servers using a decimal percentage equivalent metric (0.1 to 1). E.g. You could have 40% of requests going to your A environment and 60% of traffic going to your B environment.

Method 3:

You can also consider Cloudflare Tunnels, which are now available for free. Again this depends on the configuration/setup you have but Tunnels allow for a zero-ip setup. No need for load balancing between two origin server IPs, you only load balance between one or two instances of a Tunnel service running using the Tunnel ID. You basically Tunnels with a load balancer setup.

Related