我正在尝试处理经过 GZIP 处理的服务器响应。响应带有标题
Content-Type: application/x-gzip
但没有标题
Content-Encoding: gzip
如果我使用代理添加该标头,响应就会被很好地解析。 我对服务器没有任何控制权,因此无法添加标头。
我可以强制 Retrofit 将其视为 GZIP 内容吗?有没有更好的办法? 服务器的 URL 是: http://crowdtorch.cms.s3.amazonaws.com/4474/Updates/update-1.xml
我想通了。这个想法是添加一个自定义拦截器,它将获取尚未解压缩的响应,并“手动”解压缩它 - 执行与 OkHttp 基于 Content-Encoding 标头自动执行的操作相同的操作,但不需要该标头。
就像这样:
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.addInterceptor(new UnzippingInterceptor());
OkHttpClient client = clientBuilder.build();
拦截器就像这样:
private class UnzippingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return unzip(response);
}
}
解压功能就像dis:
// copied from okhttp3.internal.http.HttpEngine (because is private)
private Response unzip(final Response response) throws IOException {
if (response.body() == null) {
return response;
}
GzipSource responseBody = new GzipSource(response.body().source());
Headers strippedHeaders = response.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
return response.newBuilder()
.headers(strippedHeaders)
.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)))
.build();
}
有比重新发明轮子更好的方法。只需自己添加
Content-Encoding
标题即可。
.addNetworkInterceptor((Interceptor.Chain chain) -> {
Request req = chain.request();
Headers.Builder headersBuilder = req.headers().newBuilder();
String credential = Credentials.basic(...);
headersBuilder.set("Authorization", credential);
Response res = chain.proceed(req.newBuilder().headers(headersBuilder.build()).build());
return res.newBuilder()
.header("Content-Encoding", "gzip")
.header("Content-Type", ""application/json")
.build();
})
事实上,您的代码是使用内部代码(如 JDK 中的
com.sun
包)的弊端的典型示例。 RealResponseBody
不再有那个构造函数了。
这对我的 gzip 文件有用:
private fun decompressGzip(responseBody: ResponseBody): String {
try {
// Create a GZIPInputStream to decompress the data
val gzipInputStream = GZIPInputStream(responseBody.byteStream())
// Use a ByteArrayOutputStream to hold the decompressed data
val byteArrayOutputStream = ByteArrayOutputStream()
// Buffer for reading data
val buffer = ByteArray(1024)
var bytesRead: Int
// Read from the GZIPInputStream and write to the ByteArrayOutputStream
while (gzipInputStream.read(buffer).also { bytesRead = it } != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead)
}
// Convert the ByteArrayOutputStream to a byte array
val decompressedData = byteArrayOutputStream.toByteArray()
// Optionally convert the byte array to a string or other format as needed
return String(decompressedData, StandardCharsets.UTF_8)
} catch (e: Exception) {
e.printStackTrace()
// Handle exceptions
}
return ""
}
fun getGzip(
country: String,
onRadioMutableListGetCallback: OnRadioListGetCallback
) {
val call = apiServiceBackup.MutableListByCountryBackup(
country.toLowerCase(),
)
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
val res = response.body() ?: return
val json = decompressGzip(res)
val gson = Gson()
val myObjectListType = object : TypeToken<List<RadioData>>() {}.type
val stations = gson.fromJson<MutableList<RadioData>>(json, myObjectListType)
onRadioMutableListGetCallback.onRadioListGet(stations)
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
onRadioMutableListGetCallback.onRadioListGet(ArrayList())
}
})
}
@GET("{country}.json.gz")
fun MutableListByCountryBackup(
@Path("country") user: String?
): Call<ResponseBody>