之前的文章:
书接上回,在上一篇文章里,咱们聊了怎么在请求到达服务器之前,像个“中间人”一样对它“动手动脚”。这次,咱们把视角翻转一下,看看当服务器把响应(Response)辛辛苦苦准备好,准备发回给浏览器时,我们又能怎么在半路上把它截胡,然后来一波骚操作。有了它,你就能对响应内容进行实时、动态的修改。想想看,这意味着什么?
你可能会问,后端返回啥,我给用户看不就完了,折腾个啥?此言差矣!修改后端响应的能力,简直是给老系统“微创手术”的神技,有时候甚至是“无创”!你根本不用去动那些陈年老代码,就能玩出新花样。比如:
request.cf.country里),动态修改页面内容,给美国用户看英文,给中国用户看中文,是不是很贴心?不过修改响应显然不是一个好主意,更好的主意应该是在请求后端前就重定向到不同的路径,比如/cn或者/us等是不是很吊?
要想操作响应,你得先拿到它。在Workers里,这事儿通常靠fetch(request)搞定。这个函数会把原始请求发出去,然后带回来一个标准的Response对象。
// worker.js
export default {
async fetch(request, env, ctx) {
// 1. 将原始请求发往源站
const originalResponse = await fetch(request);
// 2. 读取响应状态码和响应头
const statusCode = originalResponse.status; // 200, 404, 500 etc.
const contentType = originalResponse.headers.get('Content-Type') || '';
console.log(`后端响应状态码: ${statusCode}`);
console.log(`后端响应类型: ${contentType}`);
// 3. 为了演示,我们先直接返回原始响应
return originalResponse;
},
};
这里有个关键点,和Request对象一样,Response对象的body也是一个可读流(ReadableStream)。这玩意儿就像一次性的磁带,只能被读取一次!读完了就没了。所以,如果你想对响应体做点啥,就得先把它读出来,然后重新组装一个新的响应。
改造响应的核心思想就一句话:“复制、修改、替换”。拿到原始响应,别直接在上面改,而是以它为蓝本,创建一个全新的、修改过的Response对象,最后把这个新的响应返回给浏览器。
这个最简单也最常用。比如,咱们想给所有从服务器返回的HTML页面,都强制加上一个严格的内容安全策略(CSP)头,增强网站的安全性。
// worker.js
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const contentType = response.headers.get('Content-Type') || '';
// 只对HTML响应进行操作
if (contentType.includes('text/html')) {
// 1. 克隆原始响应,这样我们才能修改它的Headers
const newResponse = new Response(response.body, response);
// 2. 设置新的安全头
newResponse.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';"
);
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
return newResponse;
}
return response;
},
};
HTMLRewriter如果只是改改响应头,那还不够刺激。真正的大杀器是修改响应体!特别是修改HTML。但HTML通常很大,如果整个读到内存里再修改,那Worker非得累死不可(虽然说跟我们免费用户没啥关系,但是太慢了也不行是不)。所以,Cloudflare给我们提供了一个神器:HTMLRewriter。
这玩意儿牛逼之处在于,它是个流式解析器。你可以把它想象成一个在HTML代码流过的管道上的智能机器人,你给它设定好规则(比如“找到所有的<img>标签”),它就在代码流过的时候,实时地对匹配到的部分进行操作,整个过程根本不需要把完整的HTML加载到内存里。性能自然比等响应全部达到之后在处理快很多。
实战一下:咱们就用它来实现刚才说的,在所有HTML页面的</body>标签前,自动注入Google Analytics的跟踪代码。
// worker.js
// 定义一个处理类,用于注入脚本
class AnalyticsInjector {
constructor(analyticsScript) {
this.analyticsScript = analyticsScript;
}
// 当匹配到 </body> 元素时,在其前面追加内容
element(element) {
element.before(this.analyticsScript, { html: true });
}
}
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
// Google Analytics 脚本
const gaScript = `
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>`;
// 使用HTMLRewriter进行转换
return new HTMLRewriter()
.on('body', new AnalyticsInjector(gaScript))
.transform(response);
},
};
现在前后端分离,API返回JSON是家常便饭。同样的,我们也可以在边缘对JSON进行修改。
实战一下:假设有个API返回了用户的所有信息,但我们不希望把用户的电话phone和地址address这种敏感信息暴露给前端
别问后端为啥改,问就是需求太满,没时间做!!!
// worker.js
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const contentType = response.headers.get('Content-Type') || '';
if (contentType.includes('application/json')) {
// 1. 读取原始JSON响应体并解析
// 因为响应体只能读一次,所以我们克隆一下,以防后续需要原始响应
const originalData = await response.clone().json();
// 2. 过滤敏感字段
const filteredData = {
id: originalData.id,
name: originalData.name,
email: originalData.email,
};
// 3. 创建一个新的JSON响应
return new Response(JSON.stringify(filteredData, null, 2), {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
}
return response;
},
};
有时候我们根本不需要理会后端服务器。Worker自己就能凭空捏造一个响应出来。这在很多场景下特别有用,比如做一个健康检查的接口,或者直接从缓存里返回内容,或者处理一些非常简单的API请求,根本没必要去打扰后端老大哥。
实战一下:咱们来实现一个GET /health的接口,它会返回一个JSON,告诉别人“我还活着”。
用来监控网站健康状态不错,但是因为不直接请求后端,其实可能造成"假健康"。
// worker.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// 实现一个简单的健康检查接口
if (url.pathname === '/health') {
const responseData = { status: 'ok', timestamp: new Date().toISOString() };
return new Response(JSON.stringify(responseData), {
headers: { 'Content-Type': 'application/json' },
});
}
// 其他请求正常转发
return fetch(request);
},
};
今天我们了解了Cloudflare Workers的响应操作能力,这让我们拥有了在边缘动态“捏造”响应的超能力。总结一下核心知识点:
response.body是可读流,操作前记得clone()是个好习惯。new Response(body, init)来创建修改后的响应。HTMLRewriter是处理HTML响应的不二之选,性能高,使用方便。.json()解析成对象,改完再JSON.stringify()回去。new Response()创建全新的响应,无需请求源站。掌握了请求和响应的“增删改查”,你就已经掌握了Cloudflare Workers最核心、最强大的能力。毫不夸张地说,你能用它实现半个API网关的功能了。
当然,Workers的能耐远不止于此。
之前的文章:
书接上回,在上一篇文章里,咱们聊了怎么在请求到达服务器之前,像个“中间人”一样对它“动手动脚”。这次,咱们把视角翻转一下,看看当服务器把响应(Response)辛辛苦苦准备好,准备发回给浏览器时,我们又能怎么在半路上把它截胡,然后来一波骚操作。有了它,你就能对响应内容进行实时、动态的修改。想想看,这意味着什么?
你可能会问,后端返回啥,我给用户看不就完了,折腾个啥?此言差矣!修改后端响应的能力,简直是给老系统“微创手术”的神技,有时候甚至是“无创”!你根本不用去动那些陈年老代码,就能玩出新花样。比如:
request.cf.country里),动态修改页面内容,给美国用户看英文,给中国用户看中文,是不是很贴心?不过修改响应显然不是一个好主意,更好的主意应该是在请求后端前就重定向到不同的路径,比如/cn或者/us等是不是很吊?
要想操作响应,你得先拿到它。在Workers里,这事儿通常靠fetch(request)搞定。这个函数会把原始请求发出去,然后带回来一个标准的Response对象。
// worker.js
export default {
async fetch(request, env, ctx) {
// 1. 将原始请求发往源站
const originalResponse = await fetch(request);
// 2. 读取响应状态码和响应头
const statusCode = originalResponse.status; // 200, 404, 500 etc.
const contentType = originalResponse.headers.get('Content-Type') || '';
console.log(`后端响应状态码: ${statusCode}`);
console.log(`后端响应类型: ${contentType}`);
// 3. 为了演示,我们先直接返回原始响应
return originalResponse;
},
};
这里有个关键点,和Request对象一样,Response对象的body也是一个可读流(ReadableStream)。这玩意儿就像一次性的磁带,只能被读取一次!读完了就没了。所以,如果你想对响应体做点啥,就得先把它读出来,然后重新组装一个新的响应。
改造响应的核心思想就一句话:“复制、修改、替换”。拿到原始响应,别直接在上面改,而是以它为蓝本,创建一个全新的、修改过的Response对象,最后把这个新的响应返回给浏览器。
这个最简单也最常用。比如,咱们想给所有从服务器返回的HTML页面,都强制加上一个严格的内容安全策略(CSP)头,增强网站的安全性。
// worker.js
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const contentType = response.headers.get('Content-Type') || '';
// 只对HTML响应进行操作
if (contentType.includes('text/html')) {
// 1. 克隆原始响应,这样我们才能修改它的Headers
const newResponse = new Response(response.body, response);
// 2. 设置新的安全头
newResponse.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';"
);
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
return newResponse;
}
return response;
},
};
HTMLRewriter如果只是改改响应头,那还不够刺激。真正的大杀器是修改响应体!特别是修改HTML。但HTML通常很大,如果整个读到内存里再修改,那Worker非得累死不可(虽然说跟我们免费用户没啥关系,但是太慢了也不行是不)。所以,Cloudflare给我们提供了一个神器:HTMLRewriter。
这玩意儿牛逼之处在于,它是个流式解析器。你可以把它想象成一个在HTML代码流过的管道上的智能机器人,你给它设定好规则(比如“找到所有的<img>标签”),它就在代码流过的时候,实时地对匹配到的部分进行操作,整个过程根本不需要把完整的HTML加载到内存里。性能自然比等响应全部达到之后在处理快很多。
实战一下:咱们就用它来实现刚才说的,在所有HTML页面的</body>标签前,自动注入Google Analytics的跟踪代码。
// worker.js
// 定义一个处理类,用于注入脚本
class AnalyticsInjector {
constructor(analyticsScript) {
this.analyticsScript = analyticsScript;
}
// 当匹配到 </body> 元素时,在其前面追加内容
element(element) {
element.before(this.analyticsScript, { html: true });
}
}
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
// Google Analytics 脚本
const gaScript = `
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>`;
// 使用HTMLRewriter进行转换
return new HTMLRewriter()
.on('body', new AnalyticsInjector(gaScript))
.transform(response);
},
};
现在前后端分离,API返回JSON是家常便饭。同样的,我们也可以在边缘对JSON进行修改。
实战一下:假设有个API返回了用户的所有信息,但我们不希望把用户的电话phone和地址address这种敏感信息暴露给前端
别问后端为啥改,问就是需求太满,没时间做!!!
// worker.js
export default {
async fetch(request, env, ctx) {
const response = await fetch(request);
const contentType = response.headers.get('Content-Type') || '';
if (contentType.includes('application/json')) {
// 1. 读取原始JSON响应体并解析
// 因为响应体只能读一次,所以我们克隆一下,以防后续需要原始响应
const originalData = await response.clone().json();
// 2. 过滤敏感字段
const filteredData = {
id: originalData.id,
name: originalData.name,
email: originalData.email,
};
// 3. 创建一个新的JSON响应
return new Response(JSON.stringify(filteredData, null, 2), {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
}
return response;
},
};
有时候我们根本不需要理会后端服务器。Worker自己就能凭空捏造一个响应出来。这在很多场景下特别有用,比如做一个健康检查的接口,或者直接从缓存里返回内容,或者处理一些非常简单的API请求,根本没必要去打扰后端老大哥。
实战一下:咱们来实现一个GET /health的接口,它会返回一个JSON,告诉别人“我还活着”。
用来监控网站健康状态不错,但是因为不直接请求后端,其实可能造成"假健康"。
// worker.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// 实现一个简单的健康检查接口
if (url.pathname === '/health') {
const responseData = { status: 'ok', timestamp: new Date().toISOString() };
return new Response(JSON.stringify(responseData), {
headers: { 'Content-Type': 'application/json' },
});
}
// 其他请求正常转发
return fetch(request);
},
};
今天我们了解了Cloudflare Workers的响应操作能力,这让我们拥有了在边缘动态“捏造”响应的超能力。总结一下核心知识点:
response.body是可读流,操作前记得clone()是个好习惯。new Response(body, init)来创建修改后的响应。HTMLRewriter是处理HTML响应的不二之选,性能高,使用方便。.json()解析成对象,改完再JSON.stringify()回去。new Response()创建全新的响应,无需请求源站。掌握了请求和响应的“增删改查”,你就已经掌握了Cloudflare Workers最核心、最强大的能力。毫不夸张地说,你能用它实现半个API网关的功能了。
当然,Workers的能耐远不止于此。