Quarkus使用StreamingOutput返回文件附件报错,求正确实现方式
问题:Quarkus中返回S3文件下载时出现Jackson序列化错误
尝试将S3文件作为响应附件返回供用户下载时,遇到Jackson序列化错误:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.fanthreesixty.audience.resource.S3ClientResource$$Lambda$2984/0x0000000801f44000 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
当前实现代码:
public Response downloadFile(final String bucketName, final String objectKey, final String fileExtension) { try { CompletableFuture<ResponseInputStream<GetObjectResponse>> response = s3.getObject(GetObjectRequest.builder() .bucket(bucketName) .key(objectKey) .build(), AsyncResponseTransformer.toBlockingInputStream()); final String fileName = objectKey.contains("/") ? objectKey.split("/")[1] : objectKey; // StreamingOutput stream = outputStream -> { // try (InputStream is = response.join()) { // byte[] buffer = new byte[4096]; // int bytesRead; // while ((bytesRead = is.read(buffer)) != -1) { // outputStream.write(buffer, 0, bytesRead); // } // } // }; StreamingOutput stream = output -> output.write(response.join().readAllBytes()); return Response.ok(stream) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .build(); } catch (Exception e) { LOGGER.error("Could not create FileOutputStream due to error", e); } return null; }
问题原因
在Quarkus的Resteasy Reactive模式下,直接返回StreamingOutput会被Jackson当作普通Java Bean尝试序列化,而StreamingOutput的Lambda实现没有可序列化的属性,因此抛出该错误。
可行解决方案
方案一:直接返回S3输入流(同步方式)
替换StreamingOutput,直接将S3获取到的ResponseInputStream作为响应体,并指定正确的媒体类型:
public Response downloadFile(final String bucketName, final String objectKey, final String fileExtension) { try { CompletableFuture<ResponseInputStream<GetObjectResponse>> responseFuture = s3.getObject( GetObjectRequest.builder() .bucket(bucketName) .key(objectKey) .build(), AsyncResponseTransformer.toBlockingInputStream() ); ResponseInputStream<GetObjectResponse> inputStream = responseFuture.join(); final String fileName = objectKey.contains("/") ? objectKey.split("/")[1] : objectKey; // 根据实际文件类型调整Content-Type,比如application/pdf、image/png等 String contentType = "application/octet-stream"; if (fileExtension != null) { contentType = switch(fileExtension.toLowerCase()) { case "pdf" -> "application/pdf"; case "jpg", "jpeg" -> "image/jpeg"; case "png" -> "image/png"; default -> "application/octet-stream"; }; } return Response.ok(inputStream) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .header("Content-Type", contentType) .build(); } catch (Exception e) { LOGGER.error("下载文件失败", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("文件下载失败").build(); } }
方案二:使用Mutiny Uni实现异步响应(推荐,符合Reactive风格)
避免阻塞join(),利用Quarkus原生的Mutiny异步API处理S3请求:
import io.smallrye.mutiny.Uni; import jakarta.ws.rs.core.Response; public Uni<Response> downloadFile(final String bucketName, final String objectKey, final String fileExtension) { return Uni.createFrom().completionStage(() -> s3.getObject( GetObjectRequest.builder() .bucket(bucketName) .key(objectKey) .build(), AsyncResponseTransformer.toBlockingInputStream() ) ) .map(inputStream -> { final String fileName = objectKey.contains("/") ? objectKey.split("/")[1] : objectKey; String contentType = "application/octet-stream"; if (fileExtension != null) { contentType = switch(fileExtension.toLowerCase()) { case "pdf" -> "application/pdf"; case "jpg", "jpeg" -> "image/jpeg"; case "png" -> "image/png"; default -> "application/octet-stream"; }; } return Response.ok(inputStream) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .header("Content-Type", contentType) .build(); }) .onFailure().invoke(e -> LOGGER.error("下载文件失败", e)) .onFailure().recoverWithItem(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("文件下载失败").build()); }
注意事项
- 不要在Reactive端点中使用
CompletableFuture.join(),这会阻塞线程,破坏Reactive模式的非阻塞特性,方案二更适合Quarkus的Reactive架构。 - 务必根据实际文件类型设置正确的
Content-Type,帮助浏览器正确识别文件格式。 - 原代码中返回
null会导致服务器返回500错误,建议在异常分支返回明确的错误响应。
内容的提问来源于stack exchange,提问作者ShowAndTell
相关产品推荐
相关产品推荐

