使用 quarkus 直接返回 InputStream 时如何避免 oom

how to avoid oom when direct return InputStream using quarkus

提问人:commonBoy 提问时间:9/14/2023 最后编辑:commonBoy 更新时间:9/19/2023 访问量:81

问:

首先,我像这样获得了要通过 Minio 下载的目标文件。

public GetObjectResponse getFile(String bucket, String object) {
        validateEnableMinio();
        try {
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(object)
                    .build();
            return minioClient.getObject(getObjectArgs);
        } catch (Exception e) {
            LOGGER.error("minio exception", e);
            throw new RuntimeException("get file error");
        }
    }

GetObjectResponse 是 FilterInputStream 的扩展。因此,我想到了使用 Response 类返回一个 BufferedInputStream 类,以避免加载内存中的所有字节。

 @GET
    @Path("/download")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Uni<Response> download1(@RestQuery String fileName) {
        var stat = minioService.statObject(fileName);
        try (GetObjectResponse getObjectResponse =  minioService.getFile(fileName)) {
            BufferedInputStream bufferedInputStream = new BufferedInputStream(getObjectResponse);
            Response.ResponseBuilder builder = Response.ok(bufferedInputStream);
            builder.type(MediaType.APPLICATION_OCTET_STREAM_TYPE);
            builder.header("Content-Disposition", "attachment; filename=" + stat.object());
            return Uni.createFrom().item(builder.build());
        } catch (Exception e) {
            LOGGER.error("error", e);
            return Uni.createFrom().item(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()) ;
        }
    }

但是我下载了一个 0 字节的空文件。

在此处输入图像描述

我可以下载带有如下代码的完整文件: 但是使用 readAllBytes() 方法

@GET
    @Path("/download")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Uni<Response> download1(@RestQuery String fileName) {
        var stat = minioService.statObject(fileName);
        try (GetObjectResponse getObjectResponse =  minioService.getFile(fileName)) {
            BufferedInputStream bufferedInputStream = new BufferedInputStream(getObjectResponse);
            Response.ResponseBuilder builder = Response.ok(bufferedInputStream.readAllBytes());
            builder.type(MediaType.APPLICATION_OCTET_STREAM_TYPE);
            builder.header("Content-Disposition", "attachment; filename=" + stat.object());
            return Uni.createFrom().item(builder.build());
        } catch (Exception e) {
            LOGGER.error("error", e);
            return Uni.createFrom().item(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()) ;
        }
    }

因此,我改变了方法,尝试直接下载本地文件,而不是通过HTTP返回的文件流。

@GET
    @Path("/download1")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Uni<Response> downloadFile() {
        String filePath = "D:\\xxx\\settings.xml"; // 替换为实际文件路径

        File file = new File(filePath);

        if (!file.exists() || !file.isFile()) {
            return Uni.createFrom().item(Response.status(Response.Status.NOT_FOUND).build());
        }

        try {
            InputStream is = new BufferedInputStream(new FileInputStream(file));

            return Uni.createFrom().item(Response.ok(is)
                    .header("Content-Disposition", "attachment; filename=" + file.getName())
                    .build());
        } catch (IOException e) {
            return Uni.createFrom().item(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("error").build());
        }
    }

它工作得很好。

我想到的第一个解决方案是将 HTTP 返回的 InputStream 写入本地文件,然后返回本地文件的流。

但是,如何在不使用文件传输作为中介的情况下直接将 InputStream 返回到前端?

后续行动

我尝试使用输入流返回 Response.ok,但它仍然没有 work.it 仍然返回 0 字节文件

@GET
    @Path("/download")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response download1(@RestQuery String fileName) {
        var stat = minioService.statObject(fileName);
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket("test1")
                .object(fileName)
                .build();
        try (InputStream inputStream =  minioClient.getObject(getObjectArgs)) {
            Response.ResponseBuilder builder = Response.ok(inputStream);
            builder.type(MediaType.APPLICATION_OCTET_STREAM_TYPE);
            builder.header("Content-Disposition", "attachment; filename=" + stat.object());
            return builder.build();
        } catch (Exception e) {
            LOGGER.error("exception", e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }
java http io 夸库斯

评论

1赞 geoand 9/14/2023
首先,您几乎肯定不应该返回 a,而只是使用 (或支持泛型)。其次,您应该能够使用从 Minio 获得并返回它Uni<Response>ResponseRestResponseInputStream.ok()
0赞 commonBoy 9/19/2023
我试过了,但它返回了一个空文件。

答:

0赞 geoand 9/19/2023 #1

执行如下操作:

@GET
@Path("/download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response download1(@RestQuery String fileName, @Context Closer closer) {
    var stat = minioService.statObject(fileName);
    GetObjectArgs getObjectArgs = GetObjectArgs.builder()
            .bucket("test1")
            .object(fileName)
            .build();

    InputStream inputStream;
    try {
        inputStream =  minioClient.getObject(getObjectArgs);
        closer.add(inputStream);
        Response.ResponseBuilder builder = Response.ok(inputStream);
        builder.type(MediaType.APPLICATION_OCTET_STREAM_TYPE);
        builder.header("Content-Disposition", "attachment; filename=" + stat.object());
        return builder.build();
    } catch (Exception e) {
        if (inputStream != null) {
           try {
              inputStream.close();
           } catch (Exception ignored) {}
        }
        LOGGER.error("exception", e);
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
}

原因是您不想在 JAX-RS 方法中关闭 InputStream