Android - OKHttp:如何在POST请求中启用gzip

7
在我们的Android应用中,我要发送很大的文件到我们的(NGINX)服务器,所以我希望在我的Retrofit POST消息中使用gzip。
有很多关于OkHttp如何透明地使用gzip或者更改头部信息以接受gzip(例如在GET请求中)的文档。
但是,我该如何在设备上启用这个特性来发送gzip的HTTP POST消息呢? 我需要编写一个自定义的拦截器或者添加一些头部信息吗?

1
我在这里找到了一个GzipRequestInterceptor的源代码:https://insight.io/github.com/square/okhttp/blob/HEAD/samples/guide/src/main/java/okhttp3/recipes/RequestBodyCompression.java?line=73 这是我需要的吗? - Tobias Reich
2个回答

22
根据以下 示例: 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示例中所推断出来的。感谢您的确认! - Tobias Reich
@Jason 这种方法在我的情况下会因为传输编码:chunked而失败。我无法将内容长度设置为-1。 - anshul
这个可行,谢谢。FYI:
  1. 使用 addInterceptor() 而不是 addNetworkInterceptor() 添加拦截器。
  2. Retrofit 自动处理 'Accept-Encoding: gzip' 头部,即用于解压缩 gzip 压缩的响应。您不需要担心它。
  3. 另一方面,您需要添加 GzipRequestInterceptor() 以发送 gzip 压缩的请求。
- Vipul Kumar
1
你可能不想压缩每个请求主体,因为小的请求主体很可能会变得更大,请参见https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits。 - satur9nine
@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������那么您认为这是为什么呢? - Ashraf Atef

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 retrofit = new Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(Constants.serveraddress)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
Serverinterface serverinterface = retrofit.create(Serverinterface.class);

为了报告 contentLength,请查看 forceContentLength 方法的实现此处 - William Grand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接