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 package org.apache.hc.client5.http.impl.classic;
29
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Locale;
34
35 import org.apache.hc.client5.http.classic.ExecChain;
36 import org.apache.hc.client5.http.classic.ExecChainHandler;
37 import org.apache.hc.client5.http.config.RequestConfig;
38 import org.apache.hc.client5.http.entity.BrotliDecompressingEntity;
39 import org.apache.hc.client5.http.entity.BrotliInputStreamFactory;
40 import org.apache.hc.client5.http.entity.DecompressingEntity;
41 import org.apache.hc.client5.http.entity.DeflateInputStreamFactory;
42 import org.apache.hc.client5.http.entity.GZIPInputStreamFactory;
43 import org.apache.hc.client5.http.entity.InputStreamFactory;
44 import org.apache.hc.client5.http.protocol.HttpClientContext;
45 import org.apache.hc.core5.annotation.Contract;
46 import org.apache.hc.core5.annotation.Internal;
47 import org.apache.hc.core5.annotation.ThreadingBehavior;
48 import org.apache.hc.core5.http.ClassicHttpRequest;
49 import org.apache.hc.core5.http.ClassicHttpResponse;
50 import org.apache.hc.core5.http.Header;
51 import org.apache.hc.core5.http.HeaderElement;
52 import org.apache.hc.core5.http.HttpEntity;
53 import org.apache.hc.core5.http.HttpException;
54 import org.apache.hc.core5.http.HttpHeaders;
55 import org.apache.hc.core5.http.config.Lookup;
56 import org.apache.hc.core5.http.config.RegistryBuilder;
57 import org.apache.hc.core5.http.message.BasicHeaderValueParser;
58 import org.apache.hc.core5.http.message.MessageSupport;
59 import org.apache.hc.core5.http.message.ParserCursor;
60 import org.apache.hc.core5.util.Args;
61
62
63
64
65
66
67
68
69
70
71
72
73 @Contract(threading = ThreadingBehavior.STATELESS)
74 @Internal
75 public final class ContentCompressionExec implements ExecChainHandler {
76
77 private final Header acceptEncoding;
78 private final Lookup<InputStreamFactory> decoderRegistry;
79 private final boolean ignoreUnknown;
80
81 public ContentCompressionExec(
82 final List<String> acceptEncoding,
83 final Lookup<InputStreamFactory> decoderRegistry,
84 final boolean ignoreUnknown) {
85
86 final boolean brotliSupported = BrotliDecompressingEntity.isAvailable();
87 final List<String> encodings = new ArrayList<>(4);
88 encodings.add("gzip");
89 encodings.add("x-gzip");
90 encodings.add("deflate");
91 if (brotliSupported) {
92 encodings.add("br");
93 }
94 this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING, encodings);
95
96 if (decoderRegistry != null) {
97 this.decoderRegistry = decoderRegistry;
98 } else {
99 final RegistryBuilder<InputStreamFactory> builder = RegistryBuilder.<InputStreamFactory>create()
100 .register("gzip", GZIPInputStreamFactory.getInstance())
101 .register("x-gzip", GZIPInputStreamFactory.getInstance())
102 .register("deflate", DeflateInputStreamFactory.getInstance());
103 if (brotliSupported) {
104 builder.register("br", BrotliInputStreamFactory.getInstance());
105 }
106 this.decoderRegistry = builder.build();
107 }
108
109
110 this.ignoreUnknown = ignoreUnknown;
111 }
112
113 public ContentCompressionExec(final boolean ignoreUnknown) {
114 this(null, null, ignoreUnknown);
115 }
116
117
118
119
120
121
122
123
124
125
126 public ContentCompressionExec() {
127 this(null, null, true);
128 }
129
130
131 @Override
132 public ClassicHttpResponse execute(
133 final ClassicHttpRequest request,
134 final ExecChain.Scope scope,
135 final ExecChain chain) throws IOException, HttpException {
136 Args.notNull(request, "HTTP request");
137 Args.notNull(scope, "Scope");
138
139 final HttpClientContext clientContext = scope.clientContext;
140 final RequestConfig requestConfig = clientContext.getRequestConfigOrDefault();
141
142
143 if (!request.containsHeader(HttpHeaders.ACCEPT_ENCODING) && requestConfig.isContentCompressionEnabled()) {
144 request.addHeader(acceptEncoding);
145 }
146
147 final ClassicHttpResponse response = chain.proceed(request, scope);
148
149 final HttpEntity entity = response.getEntity();
150
151
152 if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) {
153 final String contentEncoding = entity.getContentEncoding();
154 if (contentEncoding != null) {
155 final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
156 final HeaderElement[] codecs = BasicHeaderValueParser.INSTANCE.parseElements(contentEncoding, cursor);
157 for (final HeaderElement codec : codecs) {
158 final String codecname = codec.getName().toLowerCase(Locale.ROOT);
159 final InputStreamFactory decoderFactory = decoderRegistry.lookup(codecname);
160 if (decoderFactory != null) {
161 response.setEntity(new DecompressingEntity(response.getEntity(), decoderFactory));
162 response.removeHeaders(HttpHeaders.CONTENT_LENGTH);
163 response.removeHeaders(HttpHeaders.CONTENT_ENCODING);
164 response.removeHeaders(HttpHeaders.CONTENT_MD5);
165 } else {
166 if (!"identity".equals(codecname) && !ignoreUnknown) {
167 throw new HttpException("Unsupported Content-Encoding: " + codec.getName());
168 }
169 }
170 }
171 }
172 }
173 return response;
174 }
175
176 }