【问题标题】:Android - OKHttp: how to enable gzip for POSTAndroid - OKHttp:如何为 POST 启用 gzip
【发布时间】:2024-01-07 11:34:01
【问题描述】:

在我们的 Android 应用中,我正在向我们的 (NGINX) 服务器发送相当大的文件,因此我希望将 gzip 用于我的 Retrofit POST 消息。

有很多关于 OkHttp 的文档透明地使用 gzip 或更改标头以接受 gzip(即在 GET 消息中)。

但是如何启用此功能以从我的设备发送 gzip http POST 消息? 我是否必须编写自定义拦截器或其他东西?或者只是在标题中添加一些内容?

【问题讨论】:

标签: android nginx retrofit gzip okhttp


【解决方案1】:

根据以下recipe: gzip 的正确流程是这样的:

OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(new GzipRequestInterceptor())
      .build();

/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
  static class GzipRequestInterceptor implements Interceptor {
    @Override public Response intercept(Chain chain) throws IOException {
      Request originalRequest = chain.request();
      if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
        return chain.proceed(originalRequest);
      }

      Request compressedRequest = originalRequest.newBuilder()
          .header("Content-Encoding", "gzip")
          .method(originalRequest.method(), gzip(originalRequest.body()))
          .build();
      return chain.proceed(compressedRequest);
    }

    private RequestBody gzip(final RequestBody body) {
      return new RequestBody() {
        @Override public MediaType contentType() {
          return body.contentType();
        }

        @Override public long contentLength() {
          return -1; // We don't know the compressed length in advance!
        }

        @Override public void writeTo(BufferedSink sink) throws IOException {
          BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
          body.writeTo(gzipSink);
          gzipSink.close();
        }
      };
    }
  }

【讨论】:

  • 好的,这就是我从 OkHttp 示例中假设的内容。感谢您的肯定!
  • @Jason 这种方法在我的情况下由于传输编码失败:分块。我无法将内容长度设置为 -1
  • 这行得通,谢谢。仅供参考:1. 使用 addInterceptor() 而不是 addNetworkInterceptor() 添加此拦截器 2. Retrofit 自动处理“Accept-Encoding: gzip”标头,即用于解压缩 gzip 响应。你不必担心它。 3. 另一方面,你需要添加上面的 GzipRequestInterceptor() 来发送压缩请求
  • 您可能不想压缩每个请求主体,因为小的请求主体最终可能会更大,请参阅webmasters.stackexchange.com/questions/31750/…
  • @Leanid Vouk 我尝试了您的解决方案,但不幸的是,http 日志拦截器的日志中省略了响应正文,但我尝试自己记录请求正文,但压缩文本太奇怪了。原始请求正文:{"data":{"lng":"-122.0840926","lat":"37.4219524"},"device_id":"XXXXX"} 压缩请求正文:���������� ����9 ��������h.�\���$�x����L�0��D��x��������=����ኤӤ�� r�Z*w�{D�A��J�ՒNX?��r�Q������ 那么你认为为什么会发生这种情况?
【解决方案2】:

除了接受的答案:首先,声明这个类(来自@Jason)

// to compress the body of request which is injected as interceptor.
public class GzipInterceptor implements Interceptor {
    @Override public okhttp3.Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();

        // do not set content encoding in negative use case

        if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
            return chain.proceed(originalRequest);
        }

        Request compressedRequest = originalRequest.newBuilder()
                .header("Content-Encoding", "gzip")
                .method(originalRequest.method(), gzip(originalRequest.body()))
                .build();
        return chain.proceed(compressedRequest);
    }

    private RequestBody gzip(final RequestBody body) {
        return new RequestBody() {
            @Override public MediaType contentType() {
                return body.contentType();
            }

            @Override public long contentLength() {
                return -1; // We don't know the compressed length in advance!
            }

            @Override public void writeTo(BufferedSink sink) throws IOException {
                BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
                body.writeTo(gzipSink);
                gzipSink.close();
            }
        };
    }
}

然后:

 //Encryption Interceptor
GzipInterceptor gzipInterceptor = new GzipInterceptor();

// OkHttpClient. Be conscious with the order
OkHttpClient okHttpClient = new OkHttpClient()
        .newBuilder()
        //httpLogging interceptor for logging network requests
        .addInterceptor(gzipInterceptor)
        .build();

最后将它作为客户添加到您的改造中:(无需进一步更改!)

 Retrofit retrofit = new Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(Constants.serveraddress)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
Serverinterface serverinterface = retrofit.create(Serverinterface.class);

【讨论】:

  • 为了报contentLength看看forceContentLength方法的实现here