elunez/eladmin · error · BadRequestException

读取 S3 输入流时出错: {}

Error message

读取 S3 输入流时出错: {}

What it means

BadRequestException passthrough from catch (IOException e) around IOUtils.toByteArray(s3InputStream) in privateDownload — thrown when reading the already-open S3 response stream fails mid-transfer. Unlike error 94 (service-level S3Exception), this is transport/stream level: connection reset while streaming, socket timeout, or truncated response while Base64-encoding a large file in memory.

Source

Thrown at eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java:199

        // 创建 GetObjectRequest,指定存储桶和文件键
        GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                .bucket(amzS3Config.getDefaultBucket())
                .key(storage.getFilePath())
                .build();
        String base64Data;
        // 使用 try-with-resources 确保流能被自动关闭
        // s3Client.getObject() 返回一个 ResponseInputStream,它是一个包含S3对象数据的输入流
        try (ResponseInputStream<GetObjectResponse> s3InputStream = s3Client.getObject(getObjectRequest)) {
            // 使用 IOUtils.toByteArray 将输入流直接转换为字节数组
            byte[] fileBytes = IOUtils.toByteArray(s3InputStream);
            // 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串
            base64Data = Base64.getEncoder().encodeToString(fileBytes);
        } catch (S3Exception e) {
            // 处理 AWS 特定的异常
            throw new BadRequestException("从 S3 下载文件时出错: " + e.awsErrorDetails().errorMessage());
        } catch (IOException e) {
            // 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出)
            throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage());
        }
        // 构造返回数据
        Map<String, String> responseData = new HashMap<>();
        // 文件名
        responseData.put("fileName", storage.getFileName());
        // 文件类型
        responseData.put("fileMimeType", storage.getFileMimeType());
        // 文件内容
        responseData.put("base64Data", base64Data);
        return responseData;
    }

    /**
     * 检查云存储桶是否存在
     * @param bucketName 存储桶名称
     */
    @SuppressWarnings({"all"})
    private boolean bucketExists(String bucketName) {

View on GitHub (pinned to 55fbf70595)

Solutions

  1. Retry the download once — transient stream resets are common and a fresh getObject usually succeeds.
  2. For large files, stream to the response (or presigned URL) instead of byte[]+Base64 to avoid heap spikes and long single reads.
  3. Raise socket/attempt timeouts on the S3Client builder if reads time out on big objects.
  4. Check for proxies/LBs between the app and S3 with aggressive idle timeouts and bypass/raise them.
  5. Log the IOException message: 'Connection reset' vs 'heap space' point to network vs memory fixes.

Example fix

// before
byte[] fileBytes = IOUtils.toByteArray(s3InputStream);
base64Data = Base64.getEncoder().encodeToString(fileBytes);
} catch (IOException e) {
    throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage());
}

// after: avoid full buffering — stream base64 directly to the caller
try (ResponseInputStream<GetObjectResponse> in = s3Client.getObject(getObjectRequest);
     OutputStream out = response.getOutputStream()) {
    Base64.getEncoder().wrap(out).transferTo? // JDK9+: use InputStream.transferTo
    in.transferTo(Base64.getEncoder().wrap(out));
} catch (IOException e) {
    log.error("Streaming S3 object failed, key={}", storage.getFilePath(), e);
    throw new BadRequestException("读取 S3 输入流时出错: " + e.getMessage());
}
Defensive patterns

Strategy: retry

Validate before calling

// fail fast on oversized objects before buffering to Base64
long maxBytes = 50L * 1024 * 1024;
HeadObjectResponse head = s3Client.headObject(HeadObjectRequest.builder()
    .bucket(amzS3Config.getDefaultBucket()).key(storage.getFilePath()).build());
if (head.contentLength() > maxBytes) {
    throw new BadRequestException("文件过大,请使用直链下载");
}

Try / catch

int attempts = 0; while (true) { try { return privateDownload(id); } catch (BadRequestException e) { if (++attempts >= 2 || !e.getMessage().contains("输入流")) throw e; /* brief backoff, retry once for transient stream drop */ } }

Prevention

When it happens

Trigger: Downloading a large S3 file where the HTTP stream drops mid-read (network reset, proxy idle timeout, S3 closing the connection), or the app/heap pressure causing stream read failures while loading the whole object into a byte[] for Base64.

Common situations: Big files (the whole object is buffered then Base64-encoded — roughly 1.33x size in heap); flaky networks / NAT idle timeouts on long reads; HTTP proxies between app and S3 cutting long responses; SDK socket timeout defaults too low for the object size.

Related errors


AI-assisted analysis of elunez/eladmin@55fbf70595 (2026-08-14). Data as JSON: /api/errors/952a758c857efd03. Report an issue: GitHub.