本文译自OkHttp’s Gzip Compression,原文发布于2020年5月1日。本文假定读者对HTTP有基本的了解,以及使用过OkHttp,否则理解起来可能会一点困难。
译注: OkHttp确实会自动添加gzip并处理响应。但如果显示的给请求添加除了gzip之外的字段,如”Accept-Encoding: gzip, deflate, br”,那么就需要手动处理响应。其实一般时候我们并不需要手动设置,除非是模拟浏览器时(比如用了浏览器的UA),或者某些服务器强制deflate字段。更多的信息可以看这里。

压缩是一种简单有效的节省带宽和加快移动用户交互速度的方法。当用户点击你的页面屏幕时,会调用你的服务器来提供请求的响应。响应越大,屏幕上显示数据的时间就越长。通过压缩,即使你的访问者的互联网连接速度非常慢并且你的 API 响应过于繁重,他们也能享受快速加载。
这是如何工作的呢?
Gzip 会找到相似的字符串,并用一些占位符临时替换这些字符串,以缩小整体大小。如果你使用大量重复文本,并且有大量空格, 这也没有问题。你可以使用 Gzip 压缩你的响应主体以及请求主体。由于文件小得多,此操作可大大减少传输时间。
注意:如果你尝试使用postman,它会默认在header的隐藏部分中添加 Accept-Encoding: gzip字段。
重要提示:OkHttp 也会自动在请求中添加字段 Accept-Encoding 并自动识别响应中的 Content-Encoding,因此会自行解压缩响应数据,因此无需单独设置,但假设当我们必须将压缩的请求数据发送到服务器时,我们就必须编写自己的拦截器。
这种压缩的棘手之处在于请求者和服务器都知道可以发送压缩文件。你必须告诉服务器您接受这种编码,然后它才会提供。该协议分为两部分:
- 请求者发送一个header,告知服务器它接受压缩内容:Accept-Encoding:gzip
- 服务器使用此header确认你的请求:Content-Encoding:gzip
说得够多的了,我想你还有耐心,所以让我们看一些代码。
编写自己的拦截器
先决条件:了解 OkHttp 中的拦截器(Interceptors)。
- 解开 Gzip 响应:在请求header中添加 Accept-Encoding: gzip,并在获取响应时在其响应header中查找 Content-Encoding: gzip。如果存在则解压缩,否则直接返回响应。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| import java.io.IOException;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.GzipSource;
import okio.Okio;
public class GzipInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder newRequest = chain.request().newBuilder();
newRequest.addHeader("Accept-Encoding", "gzip");
Response response = chain.proceed(newRequest.build());
if (isGzipped(response)) {
return unzip(response);
} else {
return response;
}
}
private Response unzip(final Response response) throws IOException {
if (response.body() == null) {
return response;
}
GzipSource gzipSource = new GzipSource(response.body().source());
String bodyString = Okio.buffer(gzipSource).readUtf8();
ResponseBody responseBody = ResponseBody.create(response.body().contentType(), bodyString);
Headers strippedHeaders = response.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
return response.newBuilder()
.headers(strippedHeaders)
.body(responseBody)
.message(response.message())
.build();
}
private Boolean isGzipped(Response response) {
return response.header("Content-Encoding") != null && response.header("Content-Encoding").equals("gzip");
}
}
|
- 创建 Gzip 请求:如果你的请求过大,那么我们可以使用它来压缩我们的请求。为了通知服务器,我们将在请求header中添加 Content-Encoding: gzip。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;
public class GzipInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.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; // 事先不知道请求内容的长度
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
|
好了,就到这里吧,如果你愿意的话,可以赞一下。
参考资料:
- https://square.github.io/okhttp/
- https://www.apphp.com/tutorials/index.php?page=gzip-and-deflate-
compression-in-web-development
- https://en.wikipedia.org/wiki/Gzip
- https://www.youtube.com/watch?v=Mjab_aZsdxw