{"record":{"id":"952a758c857efd03","repo":"elunez/eladmin","slug":"s3-952a75","errorCode":null,"errorMessage":"读取 S3 输入流时出错: {}","messagePattern":"读取 S3 输入流时出错: (.+?)","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java","lineNumber":199,"sourceCode":"        // 创建 GetObjectRequest，指定存储桶和文件键\n        GetObjectRequest getObjectRequest = GetObjectRequest.builder()\n                .bucket(amzS3Config.getDefaultBucket())\n                .key(storage.getFilePath())\n                .build();\n        String base64Data;\n        // 使用 try-with-resources 确保流能被自动关闭\n        // s3Client.getObject() 返回一个 ResponseInputStream，它是一个包含S3对象数据的输入流\n        try (ResponseInputStream<GetObjectResponse> s3InputStream = s3Client.getObject(getObjectRequest)) {\n            // 使用 IOUtils.toByteArray 将输入流直接转换为字节数组\n            byte[] fileBytes = IOUtils.toByteArray(s3InputStream);\n            // 使用 Java 内置的 Base64 编码器将字节数组转换为 Base64 字符串\n            base64Data = Base64.getEncoder().encodeToString(fileBytes);\n        } catch (S3Exception e) {\n            // 处理 AWS 特定的异常\n            throw new BadRequestException(\"从 S3 下载文件时出错: \" + e.awsErrorDetails().errorMessage());\n        } catch (IOException e) {\n            // 处理通用的 IO 异常 (IOUtils.toByteArray 可能会抛出)\n            throw new BadRequestException(\"读取 S3 输入流时出错: \" + e.getMessage());\n        }\n        // 构造返回数据\n        Map<String, String> responseData = new HashMap<>();\n        // 文件名\n        responseData.put(\"fileName\", storage.getFileName());\n        // 文件类型\n        responseData.put(\"fileMimeType\", storage.getFileMimeType());\n        // 文件内容\n        responseData.put(\"base64Data\", base64Data);\n        return responseData;\n    }\n\n    /**\n     * 检查云存储桶是否存在\n     * @param bucketName 存储桶名称\n     */\n    @SuppressWarnings({\"all\"})\n    private boolean bucketExists(String bucketName) {","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/elunez/eladmin/blob/55fbf705956949697dbd68bf9003776609d3d029/eladmin-tools/src/main/java/me/zhengjie/service/impl/S3StorageServiceImpl.java#L181-L217","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the download once — transient stream resets are common and a fresh getObject usually succeeds.","For large files, stream to the response (or presigned URL) instead of byte[]+Base64 to avoid heap spikes and long single reads.","Raise socket/attempt timeouts on the S3Client builder if reads time out on big objects.","Check for proxies/LBs between the app and S3 with aggressive idle timeouts and bypass/raise them.","Log the IOException message: 'Connection reset' vs 'heap space' point to network vs memory fixes."],"exampleFix":"// before\nbyte[] fileBytes = IOUtils.toByteArray(s3InputStream);\nbase64Data = Base64.getEncoder().encodeToString(fileBytes);\n} catch (IOException e) {\n    throw new BadRequestException(\"读取 S3 输入流时出错: \" + e.getMessage());\n}\n\n// after: avoid full buffering — stream base64 directly to the caller\ntry (ResponseInputStream<GetObjectResponse> in = s3Client.getObject(getObjectRequest);\n     OutputStream out = response.getOutputStream()) {\n    Base64.getEncoder().wrap(out).transferTo? // JDK9+: use InputStream.transferTo\n    in.transferTo(Base64.getEncoder().wrap(out));\n} catch (IOException e) {\n    log.error(\"Streaming S3 object failed, key={}\", storage.getFilePath(), e);\n    throw new BadRequestException(\"读取 S3 输入流时出错: \" + e.getMessage());\n}","handlingStrategy":"retry","validationCode":"// fail fast on oversized objects before buffering to Base64\nlong maxBytes = 50L * 1024 * 1024;\nHeadObjectResponse head = s3Client.headObject(HeadObjectRequest.builder()\n    .bucket(amzS3Config.getDefaultBucket()).key(storage.getFilePath()).build());\nif (head.contentLength() > maxBytes) {\n    throw new BadRequestException(\"文件过大，请使用直链下载\");\n}","typeGuard":null,"tryCatchPattern":"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 */ } }","preventionTips":["Stream large objects to the HTTP response instead of byte[] + Base64 to shorten single-read window.","Tune S3Client socket/attempt timeouts for your largest object size.","Watch for proxies with short idle timeouts between the app and S3."],"tags":["s3","download","io","streaming","network"],"backgroundTag":null,"analyzedSha":"55fbf705956949697dbd68bf9003776609d3d029","analyzedAt":"2026-08-14T11:56:12.758Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}