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

Quarkus REST流式接口:流处理崩溃时如何返回错误?

在Quarkus中实现流式CSV导出并处理错误状态码

核心问题分析

当流式输出开始后,HTTP响应头(包括200状态码)已经发送给客户端,此时无法再修改状态码。因此处理思路分为两种:

  1. 确保流处理中抛出的异常被JAX-RS容器捕获,在响应头未发送前返回500;
  2. 使用Quarkus/Vert.x的低级API,控制响应发送时机,在出错时灵活处理状态码。

方案一:增强StreamingOutput的异常处理

直接在StreamingOutput的写入逻辑中捕获异常,抛出标准的WebApplicationException,让JAX-RS容器自动处理为500状态码。如果响应头还未发送,客户端会收到500;如果已发送部分数据,客户端会收到中断的文件并感知传输错误。

@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/export/csv")
public Response streamCsv() {
    StreamingOutput streamingOutput = os -> {
        try {
            productManager.exportCsv(new RequestContext(1, 1), os);
        } catch (Exception e) {
            // 抛出带状态码的WebApplicationException,触发容器返回500
            throw new WebApplicationException("CSV导出失败", e, Response.Status.INTERNAL_SERVER_ERROR);
        }
    };

    return Response.ok(streamingOutput)
            .header("Content-Disposition", "attachment; filename=product_export.csv")
            .build();
}

方案二:使用Quarkus ResponseEmitter(推荐)

ResponseEmitter是Quarkus提供的异步流式响应API,能更灵活地控制响应发送时机,在流处理出错时,若响应头未发送则返回500,若已发送则中断连接。

@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/export/csv")
public void streamCsv(@Context ResponseEmitter emitter) {
    // 先准备响应头,暂不发送
    MultivaluedMap<String, Object> headers = Response.ok()
            .header("Content-Disposition", "attachment; filename=product_export.csv")
            .build().getHeaders();

    emitter.onChunk(() -> {
        try (OutputStream os = emitter.createOutputStream()) {
            productManager.exportCsv(new RequestContext(1, 1), os);
            return false; // 返回false表示流结束
        } catch (Exception e) {
            // 出错时发送错误响应
            emitter.error(Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("导出失败:" + e.getMessage())
                    .build());
            return true; // 返回true表示处理终止
        }
    });

    // 发送响应头(此时才正式向客户端发送200状态码)
    emitter.sendHeaders(headers);
}

方案三:直接使用Vert.x RoutingContext

如果需要最底层的控制,可以直接操作Vert.x的RoutingContext,手动管理响应状态和流输出:

@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/export/csv")
public void streamCsv(@Context RoutingContext ctx) {
    HttpServerResponse response = ctx.response();
    response.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM);
    response.putHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=product_export.csv");

    try {
        OutputStream os = response.outputStream();
        productManager.exportCsv(new RequestContext(1, 1), os);
        os.flush();
        response.end();
    } catch (Exception e) {
        if (!response.headWritten()) {
            // 响应头未发送,设置500状态码并返回错误信息
            response.setStatusCode(500);
            response.end("CSV导出失败:" + e.getMessage());
        } else {
            // 响应头已发送,只能关闭连接,客户端会感知到传输异常
            response.close();
        }
    }
}

关键注意事项

  • 无论哪种方案,若已经向客户端写入了部分数据,都无法再修改已发送的200状态码,只能中断连接,客户端会收到不完整的文件并感知传输错误;
  • 必须确保productManager.exportCsv方法不会内部吞掉异常,所有错误都要抛出,才能被上层处理;
  • 所有方案都遵循“不加载全部数据到内存”的要求,仅流式处理小数据块。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 17:54:57