You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Deno Oak:如何在单个响应中向浏览器发送多Blob对象

解决方案

方案一:修复FormData响应的Content-Type与序列化问题

Oak直接返回FormData对象时,不会自动处理multipart/form-data的boundary和正确序列化逻辑,需要手动将FormData转为Blob并设置响应头:

后端(Oak)代码

import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const router = new Router();

router.post("/myroute/get", async (context) => {
  // 构建FormData,包含文本和Blob示例
  const formData = new FormData();
  formData.append("key1", "value1");
  formData.append("key2", "value2");
  
  // 模拟图片Blob(替换为你的实际文件路径)
  const imageBuffer = await Deno.readFile("./test-image.jpg");
  const imageBlob = new Blob([imageBuffer], { type: "image/jpeg" });
  formData.append("image1", imageBlob, "test.jpg");

  // 将FormData序列化为Blob,Deno会自动生成boundary
  const formBlob = new Blob([formData], { type: "multipart/form-data" });
  const contentType = formBlob.type; // 包含自动生成的boundary

  // 设置响应头与响应体
  context.response.headers.set("Content-Type", contentType);
  context.response.body = formBlob;
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

客户端代码

async function fetchFormData() {
  const myFormData = new FormData();
  myFormData.append("clientKey", "clientValue");

  try {
    const res = await fetch(`http://localhost:8000/myroute/get`, { 
      method: 'POST',
      body: myFormData,
    });
    const formData = await res.formData();
    
    // 读取响应内容
    console.log("key1:", formData.get("key1"));
    console.log("key2:", formData.get("key2"));
    const imageBlob = formData.get("image1");
    console.log("图片Blob:", imageBlob);
    // 可将Blob转为URL用于显示:document.getElementById('img').src = URL.createObjectURL(imageBlob);
  } catch (err) {
    console.error("请求失败:", err);
  }
}

fetchFormData();

方案二:用Zip打包多个Blob(替代FormData)

如果不需要键值对结构,将多个Blob打包为Zip返回也是高效的方案,客户端下载后解压读取:

后端(Oak + Deno Zip API)代码

import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const router = new Router();

router.post("/myroute/get-zip", async (context) => {
  // 创建Zip写入器
  const zipWriter = new Deno.ZipWriter(new Blob());
  
  // 添加文本内容
  await zipWriter.add("key1.txt", { data: "value1" });
  await zipWriter.add("key2.txt", { data: "value2" });
  
  // 添加图片文件
  const imageData = await Deno.readFile("./test-image.jpg");
  await zipWriter.add("test.jpg", { data: imageData });

  // 完成Zip打包并转为Blob
  const zipBlob = await zipWriter.close();

  // 设置响应头,标记为Zip文件
  context.response.headers.set("Content-Type", "application/zip");
  context.response.headers.set("Content-Disposition", "attachment; filename=\"assets.zip\"");
  context.response.body = zipBlob;
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

客户端代码(使用JSZip解压)

// 引入JSZip库
import JSZip from "https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js";

async function fetchAndExtractZip() {
  const myFormData = new FormData();
  myFormData.append("clientKey", "clientValue");

  try {
    const res = await fetch(`http://localhost:8000/myroute/get-zip`, { 
      method: 'POST',
      body: myFormData,
    });
    const zipBlob = await res.blob();
    const zip = await JSZip.loadAsync(zipBlob);

    // 读取解压后的内容
    const key1 = await zip.file("key1.txt")?.async("text");
    const key2 = await zip.file("key2.txt")?.async("text");
    const imageBlob = await zip.file("test.jpg")?.async("blob");

    console.log("key1:", key1);
    console.log("key2:", key2);
    console.log("图片Blob:", imageBlob);
  } catch (err) {
    console.error("Zip处理失败:", err);
  }
}

fetchAndExtractZip();

内容的提问来源于stack exchange,提问作者Imagitori

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:45:32