Skip to content

Commit 71fc36c

Browse files
committed
Content coding support
1 parent 238edf3 commit 71fc36c

4 files changed

Lines changed: 194 additions & 33 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.http.impl;
29+
30+
import java.util.ArrayList;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Locale;
34+
35+
import org.apache.hc.core5.annotation.Internal;
36+
import org.apache.hc.core5.http.EntityDetails;
37+
import org.apache.hc.core5.http.message.MessageSupport;
38+
import org.apache.hc.core5.http.message.ParserCursor;
39+
40+
/**
41+
* @since 5.6
42+
*/
43+
@Internal
44+
public final class ContentCodingSupport {
45+
46+
private ContentCodingSupport() {
47+
}
48+
49+
public static List<String> parseContentCodecs(final EntityDetails entityDetails) {
50+
if (entityDetails == null || entityDetails.getContentEncoding() == null) {
51+
return Collections.emptyList();
52+
}
53+
final String contentEncoding = entityDetails.getContentEncoding();
54+
final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
55+
final List<String> codecs = new ArrayList<>();
56+
MessageSupport.parseTokens(contentEncoding, cursor, token -> {
57+
final String codec = token.toLowerCase(Locale.ROOT);
58+
if (!codec.isEmpty() && !"identity".equals(codec)) {
59+
codecs.add(codec);
60+
}
61+
});
62+
return codecs;
63+
}
64+
65+
}

httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import java.io.IOException;
3030
import java.util.Arrays;
3131
import java.util.LinkedHashMap;
32-
import java.util.Locale;
32+
import java.util.List;
3333
import java.util.Set;
3434
import java.util.function.UnaryOperator;
3535

@@ -39,21 +39,19 @@
3939
import org.apache.hc.client5.http.async.methods.InflatingAsyncDataConsumer;
4040
import org.apache.hc.client5.http.async.methods.InflatingGzipDataConsumer;
4141
import org.apache.hc.client5.http.entity.compress.ContentCoding;
42+
import org.apache.hc.client5.http.impl.ContentCodingSupport;
4243
import org.apache.hc.client5.http.protocol.HttpClientContext;
4344
import org.apache.hc.core5.annotation.Contract;
4445
import org.apache.hc.core5.annotation.Internal;
4546
import org.apache.hc.core5.annotation.ThreadingBehavior;
4647
import org.apache.hc.core5.http.EntityDetails;
47-
import org.apache.hc.core5.http.HeaderElement;
4848
import org.apache.hc.core5.http.HttpException;
4949
import org.apache.hc.core5.http.HttpHeaders;
5050
import org.apache.hc.core5.http.HttpRequest;
5151
import org.apache.hc.core5.http.HttpResponse;
5252
import org.apache.hc.core5.http.config.Lookup;
5353
import org.apache.hc.core5.http.config.RegistryBuilder;
54-
import org.apache.hc.core5.http.message.BasicHeaderValueParser;
5554
import org.apache.hc.core5.http.message.MessageSupport;
56-
import org.apache.hc.core5.http.message.ParserCursor;
5755
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
5856
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
5957
import org.apache.hc.core5.util.Args;
@@ -119,30 +117,24 @@ public AsyncDataConsumer handleResponse(final HttpResponse rsp,
119117
return cb.handleResponse(rsp, details);
120118
}
121119

122-
final String coding = details != null ? details.getContentEncoding() : null;
123-
124-
if (coding != null) {
120+
final List<String> codecs = ContentCodingSupport.parseContentCodecs(details);
121+
if (!codecs.isEmpty()) {
125122
AsyncDataConsumer downstream = cb.handleResponse(rsp, wrapEntityDetails(details));
126-
127-
final HeaderElement[] el = BasicHeaderValueParser.INSTANCE
128-
.parseElements(coding, new ParserCursor(0, coding.length()));
129-
for (int i = el.length - 1; i >= 0; i--) {
130-
final String token = el[i].getName().toLowerCase(Locale.ROOT);
131-
if ("identity".equals(token) || token.isEmpty()) {
132-
continue;
133-
}
134-
final UnaryOperator<AsyncDataConsumer> op = decoders.lookup(token);
123+
for (int i = codecs.size() - 1; i >= 0; i--) {
124+
final String codec = codecs.get(i);
125+
final UnaryOperator<AsyncDataConsumer> op = decoders.lookup(codec);
135126
if (op != null) {
136127
downstream = op.apply(downstream);
137128
} else {
138-
throw new HttpException("Unsupported Content-Encoding: " + token);
129+
throw new HttpException("Unsupported Content-Encoding: " + codec);
139130
}
140131
}
141132
rsp.removeHeaders(HttpHeaders.CONTENT_ENCODING);
142133
rsp.removeHeaders(HttpHeaders.CONTENT_LENGTH);
143134
rsp.removeHeaders(HttpHeaders.CONTENT_MD5);
144135
return downstream;
145136
}
137+
146138
return cb.handleResponse(rsp, details);
147139
}
148140

httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.util.ArrayList;
3232
import java.util.EnumMap;
3333
import java.util.List;
34-
import java.util.Locale;
3534
import java.util.Map;
3635
import java.util.function.UnaryOperator;
3736

@@ -40,22 +39,20 @@
4039
import org.apache.hc.client5.http.config.RequestConfig;
4140
import org.apache.hc.client5.http.entity.compress.ContentCodecRegistry;
4241
import org.apache.hc.client5.http.entity.compress.ContentCoding;
42+
import org.apache.hc.client5.http.impl.ContentCodingSupport;
4343
import org.apache.hc.client5.http.protocol.HttpClientContext;
4444
import org.apache.hc.core5.annotation.Contract;
4545
import org.apache.hc.core5.annotation.Internal;
4646
import org.apache.hc.core5.annotation.ThreadingBehavior;
4747
import org.apache.hc.core5.http.ClassicHttpRequest;
4848
import org.apache.hc.core5.http.ClassicHttpResponse;
4949
import org.apache.hc.core5.http.Header;
50-
import org.apache.hc.core5.http.HeaderElement;
5150
import org.apache.hc.core5.http.HttpEntity;
5251
import org.apache.hc.core5.http.HttpException;
5352
import org.apache.hc.core5.http.HttpHeaders;
5453
import org.apache.hc.core5.http.config.Lookup;
5554
import org.apache.hc.core5.http.config.RegistryBuilder;
56-
import org.apache.hc.core5.http.message.BasicHeaderValueParser;
5755
import org.apache.hc.core5.http.message.MessageSupport;
58-
import org.apache.hc.core5.http.message.ParserCursor;
5956
import org.apache.hc.core5.util.Args;
6057

6158
/**
@@ -130,22 +127,20 @@ public ClassicHttpResponse execute(
130127
// entity can be null in case of 304 Not Modified, 204 No Content or similar
131128
// check for zero length entity.
132129
if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) {
133-
final String contentEncoding = entity.getContentEncoding();
134-
if (contentEncoding != null) {
135-
final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
136-
final HeaderElement[] codecs = BasicHeaderValueParser.INSTANCE.parseElements(contentEncoding, cursor);
137-
for (final HeaderElement codec : codecs) {
138-
final String codecname = codec.getName().toLowerCase(Locale.ROOT);
139-
final UnaryOperator<HttpEntity> decoder = decoderRegistry.lookup(codecname);
130+
final List<String> codecs = ContentCodingSupport.parseContentCodecs(entity);
131+
if (!codecs.isEmpty()) {
132+
for (int i = codecs.size() - 1; i >= 0; i--) {
133+
final String codec = codecs.get(i);
134+
final UnaryOperator<HttpEntity> decoder = decoderRegistry.lookup(codec);
140135
if (decoder != null) {
141136
response.setEntity(decoder.apply(response.getEntity()));
142-
response.removeHeaders(HttpHeaders.CONTENT_LENGTH);
143-
response.removeHeaders(HttpHeaders.CONTENT_ENCODING);
144-
response.removeHeaders(HttpHeaders.CONTENT_MD5);
145-
} else if (!"identity".equals(codecname)) {
146-
throw new HttpException("Unsupported Content-Encoding: " + codec.getName());
137+
} else {
138+
throw new HttpException("Unsupported Content-Encoding: " + codec);
147139
}
148140
}
141+
response.removeHeaders(HttpHeaders.CONTENT_LENGTH);
142+
response.removeHeaders(HttpHeaders.CONTENT_ENCODING);
143+
response.removeHeaders(HttpHeaders.CONTENT_MD5);
149144
}
150145
}
151146
return response;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.impl;
28+
29+
import java.util.Set;
30+
31+
import org.apache.hc.core5.http.EntityDetails;
32+
import org.apache.hc.core5.http.impl.BasicEntityDetails;
33+
import org.hamcrest.MatcherAssert;
34+
import org.hamcrest.Matchers;
35+
import org.junit.jupiter.api.Test;
36+
37+
class ContentCodingSupportTest {
38+
39+
static class MockEntityDetails implements EntityDetails {
40+
41+
private final String contentEncoding;
42+
43+
MockEntityDetails(final String contentEncoding) {
44+
this.contentEncoding = contentEncoding;
45+
}
46+
47+
@Override
48+
public long getContentLength() {
49+
return -1;
50+
}
51+
52+
@Override
53+
public String getContentType() {
54+
return null;
55+
}
56+
57+
@Override
58+
public String getContentEncoding() {
59+
return contentEncoding;
60+
}
61+
62+
@Override
63+
public boolean isChunked() {
64+
return false;
65+
}
66+
67+
@Override
68+
public Set<String> getTrailerNames() {
69+
return null;
70+
}
71+
72+
}
73+
74+
@Test
75+
void testNoEntity() {
76+
MatcherAssert.assertThat(
77+
ContentCodingSupport.parseContentCodecs(null),
78+
Matchers.empty());
79+
}
80+
81+
@Test
82+
void testNotEncoded() {
83+
MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
84+
new BasicEntityDetails(-1, null)),
85+
Matchers.empty());
86+
}
87+
88+
@Test
89+
void testNotEncodedNoise() {
90+
MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
91+
new MockEntityDetails(", ,, , ")),
92+
Matchers.empty());
93+
}
94+
95+
@Test
96+
void testIdentityEncoded() {
97+
MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
98+
new MockEntityDetails("identity,,,identity")),
99+
Matchers.empty());
100+
}
101+
102+
@Test
103+
void testEncodedMultipleCodes() {
104+
MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
105+
new MockEntityDetails("This,,that, \"This and That\"")),
106+
Matchers.contains("this", "that", "\"this and that\""));
107+
}
108+
109+
}

0 commit comments

Comments
 (0)