http流式返回

  1. HTTP流式返回(Stream)是一种服务器向客户端传输数据的方式允许数据分块发送而不是一次性发送完毕。
    这样客户端可以在接收到第一部分数据时就开始处理,而不必等待整个响应完成。

  2. 应用场景:
    2.1 业务场景:图表的监听(股票行情);体育比分;ota升级进度条;chartgpt聊天消息返回
    2.2 开发场景:视频流,文件的下载

  3. 分块传输的具体过程
    3.1 客户端请求数据:客户端向服务器发送HTTP请求。
    3.2 服务器响应头:服务器发送响应头,其中包含Transfer-Encoding: chunked,表明将使用分块传输编码。
    3.3 发送数据块:数据分块并依次发送每个块
    3.4 结束块:所有数据块发送完后,服务器发送一个大小为0的结束块,表示数据传输完成。

  4. 客户端处理步骤
    4.1 读取响应头:客户端首先读取响应头(Transfer-Encoding: chunked),了解使用了分块传输编码。
    4.2 逐块处理数据:客户端依次读取每个块的大小和数据内容,并进行处理。处理完一个块后,继续读取下一个块,直到读取到大小为0的结束块。

  5. 流式返回工作原理:
    5.1 块大小:每个数据块的大小以十六进制数表示,后跟一个回车换行(CRLF,·\r\n’)。
    5.2 数据块:实际的数据块(以二进制形式传输)。
    5.3 终止块:最后一个数据块大小为0(‘01r\n\z\n’),表示数据传输结束。

ps:HTTP流式返回(也称为分块传输编码,chunked transfer encoding)实际上是通过十六进制表示块的大小,然后传输实际数据块的二进制内容。因此,流式返回的数据块以二进制形式传输,但每个数据块的大小通过十六进制来表示。

  1. 优点:
    6.1 实时性:流式返回允许客户端在接收到部分数据时就开始处理,减少了等待时间。
    6.2 减少内存消耗:服务器不需要一次性准备所有数据,适用于大数据量的传输。
    6.3 节省带宽:仅在有数据更新时传输,避免不必要的开销。

  2. 缺点:
    7.1 丢包和重传:在网络不稳定的情况下,数据块可能会丢失或损坏,导致数据不完整。虽然可以通过重传机制解决,但这增加了实现的复杂度和系统的负担。
    7.2 浏览器兼容性:并非所有浏览器都完美支持流式返回,尤其是一些老旧的浏览器,可能无法正确处理分块传输编码(Chunked Transfer Encoding)或Fetch API中的流处理。

  3. 常见问题:
    8.1 实时聊天应用:如果网络不稳定,可能导致消息丢常见问题,需要复杂的错误处理和重传机制
    8.2大文件下载:如果网络中断,文件下载可能不完整,需要支持断点续传功能。

  4. 常见优化方案:
    9.1 错误处理和重传:实现可靠的错误检测和重传机制,确保数据的完整性和正确性。
    9.2 优化连接管理:使用高效的连接管理和负载均衡技术,减少长时间连接对服务器资源的占用。
    9.3 客户端处理优化:在客户端实现高效的数据块拼接和解析逻辑,确保数据处理的顺序和正确性。

  5. 前端代码示例:

// app/api/streaming/route.js
const story = [
  "李焊玲是一个女汉子。",
  "她力大无穷,",
  "喜欢挑战各种极限运动。",
  "有一天,",
  "她决定去攀登一座险峻的高山。",
  "在山脚下,",
  "她遇到了一群正准备放弃的登山者。",
  "李焊玲鼓励他们,",
  "说,",
  "只要坚持,",
  "没有什么是不可能的。",
  "于是,",
  "她带领这群登山者一起向山顶进发。",
  "一路上,",
  "她用自己的力量帮助大家克服各种困难,",
  "大家都被她的勇气和毅力所感染。",
  "最终,",
  "在李焊玲的带领下,",
  "他们成功登上了山顶。",
  "大家欢呼雀跃,",
  "李焊玲却笑着说,",
  "这只是人生中的一个小挑战,",
  "未来还有更多的冒险等着我们。",
  "从那以后,",
  "李焊玲的故事在登山者中广为流传,",
  "她成为了大家心目中的英雄。"
];




export async function GET(request) {
  const readable = new ReadableStream({
    async start(controller) {
      // 第一块数据
      // controller.enqueue("\n");

      // 模拟延迟并逐步发送数据块
      const encoder = new TextEncoder();
      
      for (let i = 0; i < story.length; i++) {
        await new Promise((resolve) => setTimeout(resolve, story[i].length*50));
        controller.enqueue(
          encoder.encode(
            `${story[i]} \n`
          )
        );
      }

      // 关闭流
      controller.close();
    },
  });

  return new Response(readable, {
    headers: {
      "Content-Type": "text/plain",
      "Transfer-Encoding": "chunked",
    },
  });
}

PS:

ReadableStreamTransformStream 是 JavaScript 中用于处理流数据的接口。

ReadableStream

ReadableStream 用于从源读取数据,可以异步地获取数据。例如,可以用来读取文件、网络请求等。以下是一个基本的示例:

const readableStream = new ReadableStream({
  start(controller) {
    // 初始化数据
    controller.enqueue('Hello, ');
    controller.enqueue('World!');
    controller.close();
  },
  pull(controller) {
    // 当 consumer 请求更多数据时调用
  },
  cancel(reason) {
    // 当 consumer 取消读取时调用
    console.log('Stream cancelled, reason:', reason);
  }
});

const reader = readableStream.getReader();

reader.read().then(function processText({ done, value }) {
  if (done) {
    console.log('Stream complete');
    return;
  }
  console.log(value);
  return reader.read().then(processText);
});

TransformStream

TransformStream 用于对流数据进行转换。它接受一个输入流,并产生一个输出流。以下是一个简单的示例:

const transformStream = new TransformStream({
  start(controller) {
    // 初始化
  },
  transform(chunk, controller) {
    // 对每个数据块进行处理
    controller.enqueue(chunk.toUpperCase());
  },
  flush(controller) {
    // 在流结束时处理剩余的数据
    console.log('All chunks transformed.');
  }
});

const readable = new ReadableStream({
  start(controller) {
    controller.enqueue('Hello, ');
    controller.enqueue('world!');
    controller.close();
  }
});

const writable = new WritableStream({
  write(chunk) {
    console.log(chunk);
  },
  close() {
    console.log('All data written.');
  }
});

readable.pipeThrough(transformStream).pipeTo(writable);

以上示例展示了如何创建一个 ReadableStream 以及如何使用 TransformStream 对数据进行转换,然后通过 pipeThrough 将转换后的数据传输到一个 WritableStream

这两个接口可以非常灵活地处理流数据,适用于许多异步数据处理场景。

//前端调接口获取流式处理数据并返回数据主要代码
const response = await fetch('/api/aliyun/getApiResult', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + 'sk-5e304f3c097544958802d77fc2c7df82',
    'Content-Type': 'application/json',
    token: localStorage.getItem('TOKENES'),
    Accept: 'text/event-stream',
  },
  body: JSON.stringify({
    model: 'qwen-turbo',
    input: {
      messages,
    },
    parameters: {
      incremental_output: false,
    },
  }),
});
console.log('====================================');
console.log(response.body);
console.log('====================================');

const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
const encoder = new TextEncoder();
let content = '';

const readableStream = new ReadableStream({
  async start(controller) {
    function push() {
      reader
        .read()
        .then(({ done, value }) => {
          if (done) {
            controller.close();
            return;
          }
          const chunk = decoder.decode(value, { stream: true });
          chunk.split('\n').forEach(async (line) => {
            if (line && line.includes('data')) {
              console.log('line: ', line.replace(/^data:/, ''));
              const chatMessage = JSON.parse(line.replace(/^data:/, ''));
              content = chatMessage?.output?.text;
            }
          });

          controller.enqueue(encoder.encode(content));
          push();
        })
        .catch((err) => {
          console.error('读取流中的数据时发生错误', err);
          controller.error(err);
        });
    }
    push();
  },
});

return new Response(readableStream);
### SpringAI DeepSeek 中实现流式返回 在处理长时间运行的任务或大量数据传输时,采用流式返回可以显著提升用户体验和资源利用率。对于SpringAI DeepSeek框架而言,通过使用`Server-Sent Events (SSE)` 或 `WebSocket`协议能够实现实时的数据推送。 当利用HTTP协议下的服务器发送事件(SSE),客户端建立连接后保持打开状态直到一方关闭它,在此期间服务端可随时向客户端推送上新消息[^1]。 下面是一个基于Spring WebFlux的简单例子来展示如何配置并应用SSE: ```java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class StreamController { @GetMapping("/stream") public Flux<String> stream() { return Flux.interval(Duration.ofSeconds(1)) .map(sequence -> "Event at: " + LocalDateTime.now()); } } ``` 这段代码定义了一个RESTful API `/stream` ,每当有新的请求到达时会每隔一秒产生一个新的时间戳字符串作为响应的一部分持续不断地发给调用者直至连接被终止。 为了使上述功能正常工作还需要确保项目依赖中包含了Webflux模块的支持: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ``` 此外,如果希望更灵活地控制流的行为比如暂停/恢复数据流动,则可能需要引入Reactor库中的更多高级特性如`Sink.Many<T>`接口用于手动触发元素发射等操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值