Table of Contents

Cloudflare Workers provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.

developers.cloudflare.com/workers

Warning ⚠️ Cloudflare acts as a (trusted?) man-in-the-middle that can decrypt and analyze all the traffics. Be aware.

Basic setup

Pass the request to the upstream servers

let forwardHeaders = new Headers(request.headers)
let url = new URL(request.url)
url.host = upstreamHost
forwardHeaders.set('Host', url.hostname)
forwardHeaders.set('Referer', url.hostname)
let upstreamResponse = await fetch(url.href, {
  method: request.method,
  headers: forwardHeaders
})

Modify the response headers

let responseHeaders = new Headers(upstreamResponse.headers)
responseHeaders.set('Cache-Control', 'no-store')
responseHeaders.set('access-control-allow-origin', '*')
responseHeaders.set('access-control-allow-credentials', true)
responseHeaders.delete('content-security-policy')
responseHeaders.delete('content-security-policy-report-only')
responseHeaders.delete('clear-site-data')

Filter the response content

let contentType = responseHeaders.get('content-type')
let responseText = contentType.includes('text/html') && contentType.includes('UTF-8') ?
  await filterResponse(upstreamResponse, upstreamHost, url.hostname) :
  upstreamResponse.body
async function filterResponse(response, oldPattern, newPattern) {
  let filterRules = {oldPattern: newPattern} // ...
  let text = await response.text()
  for (let rule in filterRules) {
    let regex = new RegExp(rule, 'g')
    text = text.replace(regex, filterRules[rule])
  }
  return text
}

Block specific IPs for better security

let blockedIPs = ['XXX.XXX.XXX.XXX']
let requestIP = request.headers.get('cf-connecting-ip')
if (blockedIPs.includes(requestIP)) {
  return new Response('Access denied', {
    status: 403
  })
}

Add support for mobile devices

const upstreamMobileHost = '1.example.com'
const upstreamDesktopHost = '2.example.com'
let userAgent = request.headers.get('user-agent')
let upstreamHost = await isMobileUserAgent(userAgent) ?
  upstreamMobileHost : upstreamDesktopHost
async function isMobileUserAgent(userAgent) {
  const keywords = ["Android", "iPhone", "iPad", "Windows Phone"]
  return keywords.filter(keyword => userAgent.match(keyword)).length != 0
}

Complete example

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

async function handleRequest(request) {
  let blockedIPs = ['0.0.0.0']
  let requestIP = request.headers.get('cf-connecting-ip')
  if (blockedIPs.includes(requestIP)) {
    return new Response('Access denied', {
      status: 403
    })
  }

  const upstreamMobileHost = 'www.example.com'
  const upstreamDesktopHost = 'www.example.com'
  let userAgent = request.headers.get('user-agent')
  let upstreamHost = await isMobileUserAgent(userAgent) ?
    upstreamMobileHost : upstreamDesktopHost

  let forwardHeaders = new Headers(request.headers)
  let url = new URL(request.url)
  url.host = upstreamHost
  forwardHeaders.set('Host', url.hostname)
  forwardHeaders.set('Referer', url.hostname)
  let upstreamResponse = await fetch(url.href, {
    method: request.method,
    headers: forwardHeaders
  })

  let responseHeaders = new Headers(upstreamResponse.headers)
  responseHeaders.set('Cache-Control', 'no-store')
  responseHeaders.set('access-control-allow-origin', '*')
  responseHeaders.set('access-control-allow-credentials', true)
  responseHeaders.delete('content-security-policy')
  responseHeaders.delete('content-security-policy-report-only')
  responseHeaders.delete('clear-site-data')

  let contentType = responseHeaders.get('content-type')
  let responseText = contentType.includes('text/html') && contentType.includes('UTF-8') ?
  await filterResponse(upstreamResponse, upstreamHost, url.hostname) :
  upstreamResponse.body

  return new Response(responseText, {
    status: upstreamResponse.status,
    headers: responseHeaders
  })
}

async function filterResponse(response, oldPattern, newPattern) {
  let filterRules = {oldPattern: newPattern, '//example.com': ''}
  let text = await response.text()
  for (let rule in filterRules) {
    let regex = new RegExp(rule, 'g')
    text = text.replace(regex, filterRules[rule])
  }
  return text
}

async function isMobileUserAgent(userAgent) {
  const keywords = ["Android", "iPhone", "iPad", "Windows Phone"]
  return keywords.filter(keyword => userAgent.match(keyword)).length != 0
}