本示例展示了如何一次请求多个文件并流式返回这些文件。
网站页面中有多个没有依赖关系的静态文件需要加载时,浏览器会分别去请求对应的文件。但浏览器允许的并发请求资源数有限,为了减少 HTTP 请求的发送次数,可以合并浏览器的静态资源请求。具体的实现方式是通过 URL 查询参数来合并多个文件的请求。
例如,网站页面中的 <script> 标签的 src 属性的值包含一个参数名为 urllist 的查询参数。该查询参数包括多个 .js 文件的 URL 路径。
<script src="http://www.example.com/?urllist=/react/a.min.js,/react/b.min.js"></script>
函数收到用户请求时,先读取该查询参数,获取每个 .js 文件的 URL 路径。然后将其拼接为完整的 URL,然后,函数依次请求各个文件,并在每个文件响应完成后继续写入下一个文件的响应内容。对于非最后一个文件,函数保持输出流开启;在最后一个文件写入完成后,函数关闭输出流并将完整内容返回给用户。在获取文件的同时,函数把获取的文件流式返回给用户。
async function handle(event) { const request = event.request; const url = new URL(request.url); const urllist = url.searchParams.get("urllist"); if (!urllist) { return new Response("Missing urllist parameter", { status: 400, }); } const fetchList = urllist.split(",").filter(Boolean); if (fetchList.length === 0) { return new Response("The urllist parameter is empty", { status: 400, }); } const { readable, writable } = new TransformStream(); void (async () => { try { for (let index = 0; index < fetchList.length; index++) { const path = fetchList[index]; // 仅允许请求当前域名下以 / 开头的资源路径。 if (!path.startsWith("/") || path.startsWith("//")) { throw new Error(`Invalid resource path: ${path}`); } const response = await fetch( new URL(path, url.origin).toString() ); if (!response.ok) { throw new Error( `Failed to fetch ${path}: ${response.status} ${response.statusText}` ); } if (!response.body) { throw new Error(`The response body of ${path} is empty`); } const isLastFile = index === fetchList.length - 1; await response.body.pipeTo(writable, { preventClose: !isLastFile, }); } } catch (error) { await writable.abort(error); } })(); return new Response(readable, { status: 200, headers: { "Content-Type": "application/javascript; charset=utf-8", }, }); } addEventListener("fetch", (event) => { event.respondWith(handle(event)); });