Java 11 HttpClient转OkHttp实现POST获取PDF是否正确?
需求:通过POST请求获取PDF文件并展示给用户
尝试过程:
- 已有基于Java 11 HttpClient API的示例代码,可向labelary发送POST请求将ZPL字符串转换为PDF文件:
var zpl = "^xa^cfa,50^fo100,100^fdHello World^fs^xz"; // 调整打印密度(8dpmm)、标签宽度(4英寸)、标签高度(6英寸)和标签索引(0) var uri = URI.create("http://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/"); var request = HttpRequest.newBuilder(uri) .header("Accept", "application/pdf") // 注释此行可获取PNG图片 .POST(BodyPublishers.ofString(zpl)) .build(); var client = HttpClient.newHttpClient(); var response = client.send(request, BodyHandlers.ofByteArray()); var body = response.body(); if (response.statusCode() == 200) { var file = new File("label.pdf"); // 获取PNG时修改文件名 Files.write(file.toPath(), body); } else { var errorMessage = new String(body, StandardCharsets.UTF_8); System.out.println(errorMessage); }
- 由于该代码无法在Android中运行,改用Squareup OkHttp库实现:
fun fetch(completion: (InputStream?) -> Unit) { val url = "https://api.labelary.com/v1/printers/8dpmm/labels/4x6/0/" val postBody = "^xa^cfa,50^fo100,100^fdHello World^fs^xz" val text = MediaType.parse("text/plain;charset=utf-8") val body = RequestBody.create(text, postBody) val labelFetch = OkHttpClient() val request = Request.Builder() .header("Accept", "application/pdf") .url(url) .post(body) .build() labelFetch.newCall(request).enqueue(object : Callback { override fun onFailure(request: Request?, e: IOException?) { } override fun onResponse(response: Response?) { val pdfData = response?.body()?.byteStream() completion(pdfData) } }) }
问题:获取到InputStream后用pdfViewer展示时,出现错误:
java.io.IOException: file not in PDF format or corrupted
请问上述OkHttp的转换实现是否正确?
分析与解决
你的OkHttp实现存在几个潜在问题,是导致PDF格式错误的主要原因:
未校验响应状态
代码里没有检查响应是否成功,如果接口返回非200状态码(比如400参数错误、500服务异常),返回的是错误文本而非PDF流,直接传给pdfViewer必然报格式错误。必须先判断响应状态,失败时读取错误信息排查问题:override fun onResponse(response: Response?) { if (response?.isSuccessful == true) { val pdfData = response.body()?.byteStream() completion(pdfData) } else { val errorMsg = response?.body()?.string() // 打印错误信息,排查接口返回的具体问题 completion(null) } }缺少Content-Type请求头
Java版本的代码虽然没显式设置Content-Type,但BodyPublishers.ofString()会默认带上合适的文本类型头,而OkHttp的实现仅声明了RequestBody的MediaType,未在请求头中明确传递。部分接口对这个头的校验严格,需手动添加:val request = Request.Builder() .header("Accept", "application/pdf") .header("Content-Type", "text/plain;charset=utf-8") // 新增此行 .url(url) .post(body) .build()InputStream处理不规范
OkHttp的ResponseBody.byteStream()是一次性资源,且需确保完整读取。如果直接传递给pdfViewer时出现线程问题或读取不完整,会导致数据截断。建议先将流转换为字节数组或临时文件,再传给阅读器:override fun onResponse(response: Response?) { if (response?.isSuccessful == true) { response.body()?.let { body -> val byteArray = body.bytes() // 转为临时文件(避免流失效问题) val tempFile = File(context.cacheDir, "temp_label.pdf") FileOutputStream(tempFile).use { it.write(byteArray) } completion(tempFile.inputStream()) } ?: completion(null) } else { completion(null) } }HTTP/HTTPS差异
Java版本用的是http://协议,OkHttp版本改用了https://,部分环境下HTTPS证书校验问题可能导致响应异常。可以先换回HTTP测试,确认是否是协议差异导致的问题。
按上述步骤调整后,就能解决PDF格式错误的问题。
内容的提问来源于stack exchange,提问作者F.Mysir

