Make use of Cloudflare workers - Part I: Reverse proxy
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.
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
}
Read other posts