Skip to content

Commit e4e4aab

Browse files
swallezHakky54
andauthored
Enable support for compressed response within RestHighLevelClient (#63087)
Co-authored-by: Hakky54 <[email protected]>
1 parent 6a92ef5 commit e4e4aab

File tree

3 files changed

+126
-7
lines changed

3 files changed

+126
-7
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.http.Header;
2323
import org.apache.http.HttpEntity;
24+
import org.apache.http.client.entity.GzipDecompressingEntity;
2425
import org.elasticsearch.ElasticsearchException;
2526
import org.elasticsearch.ElasticsearchStatusException;
2627
import org.elasticsearch.action.ActionListener;
@@ -2037,11 +2038,18 @@ protected final ElasticsearchStatusException parseResponseException(ResponseExce
20372038
return elasticsearchException;
20382039
}
20392040

2040-
protected final <Resp> Resp parseEntity(final HttpEntity entity,
2041+
protected final <Resp> Resp parseEntity(final HttpEntity httpEntity,
20412042
final CheckedFunction<XContentParser, Resp, IOException> entityParser) throws IOException {
2042-
if (entity == null) {
2043+
if (httpEntity == null) {
20432044
throw new IllegalStateException("Response body expected but not returned");
20442045
}
2046+
2047+
final HttpEntity entity = Optional.ofNullable(httpEntity.getContentEncoding())
2048+
.map(Header::getValue)
2049+
.filter("gzip"::equalsIgnoreCase)
2050+
.map(gzipHeaderValue -> (HttpEntity) new GzipDecompressingEntity(httpEntity))
2051+
.orElse(httpEntity);
2052+
20452053
if (entity.getContentType() == null) {
20462054
throw new IllegalStateException("Elasticsearch didn't return the [Content-Type] header, unable to parse response body");
20472055
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client;
20+
21+
import org.apache.http.HttpHeaders;
22+
import org.apache.http.client.methods.HttpPost;
23+
import org.apache.http.client.methods.HttpPut;
24+
import org.elasticsearch.action.search.SearchRequest;
25+
import org.elasticsearch.action.search.SearchResponse;
26+
27+
import java.io.IOException;
28+
29+
import static org.hamcrest.Matchers.equalTo;
30+
31+
public class HighLevelRestClientCompressionIT extends ESRestHighLevelClientTestCase {
32+
33+
private static final String GZIP_ENCODING = "gzip";
34+
private static final String SAMPLE_DOCUMENT = "{\"name\":{\"first name\":\"Steve\",\"last name\":\"Jobs\"}}";
35+
36+
public void testCompressesResponseIfRequested() throws IOException {
37+
Request doc = new Request(HttpPut.METHOD_NAME, "/company/_doc/1");
38+
doc.setJsonEntity(SAMPLE_DOCUMENT);
39+
client().performRequest(doc);
40+
client().performRequest(new Request(HttpPost.METHOD_NAME, "/_refresh"));
41+
42+
RequestOptions.Builder requestOptionsBuilder = RequestOptions.DEFAULT.toBuilder();
43+
requestOptionsBuilder.addHeader(HttpHeaders.ACCEPT_ENCODING, GZIP_ENCODING);
44+
RequestOptions requestOptions = requestOptionsBuilder.build();
45+
46+
SearchRequest searchRequest = new SearchRequest("company");
47+
SearchResponse searchResponse = execute(searchRequest, highLevelClient()::search, highLevelClient()::searchAsync, requestOptions);
48+
49+
assertThat(searchResponse.status().getStatus(), equalTo(200));
50+
assertEquals(1L, searchResponse.getHits().getTotalHits());
51+
assertEquals(SAMPLE_DOCUMENT, searchResponse.getHits().getHits()[0].getSourceAsString());
52+
}
53+
54+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

+62-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.http.message.BasicStatusLine;
3535
import org.apache.http.nio.entity.NByteArrayEntity;
3636
import org.apache.http.nio.entity.NStringEntity;
37+
import org.apache.lucene.util.BytesRef;
3738
import org.elasticsearch.Build;
3839
import org.elasticsearch.ElasticsearchException;
3940
import org.elasticsearch.Version;
@@ -89,10 +90,12 @@
8990
import org.hamcrest.Matchers;
9091
import org.junit.Before;
9192

93+
import java.io.ByteArrayOutputStream;
9294
import java.io.IOException;
9395
import java.lang.reflect.Method;
9496
import java.lang.reflect.Modifier;
9597
import java.net.SocketTimeoutException;
98+
import java.nio.charset.StandardCharsets;
9699
import java.util.ArrayList;
97100
import java.util.Arrays;
98101
import java.util.Collections;
@@ -106,6 +109,7 @@
106109
import java.util.concurrent.atomic.AtomicReference;
107110
import java.util.stream.Collectors;
108111
import java.util.stream.Stream;
112+
import java.util.zip.GZIPOutputStream;
109113

110114
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
111115
import static org.hamcrest.CoreMatchers.endsWith;
@@ -273,6 +277,59 @@ public void testParseEntity() throws IOException {
273277
}
274278
}
275279

280+
public void testParseCompressedEntity() throws IOException {
281+
CheckedFunction<XContentParser, String, IOException> entityParser = parser -> {
282+
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
283+
assertEquals(XContentParser.Token.FIELD_NAME, parser.nextToken());
284+
assertTrue(parser.nextToken().isValue());
285+
String value = parser.text();
286+
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
287+
return value;
288+
};
289+
290+
HttpEntity jsonEntity = createGzipEncodedEntity("{\"field\":\"value\"}", ContentType.APPLICATION_JSON);
291+
assertEquals("value", restHighLevelClient.parseEntity(jsonEntity, entityParser));
292+
HttpEntity yamlEntity = createGzipEncodedEntity("---\nfield: value\n", ContentType.create("application/yaml"));
293+
assertEquals("value", restHighLevelClient.parseEntity(yamlEntity, entityParser));
294+
HttpEntity smileEntity = createGzipEncodedEntity(SmileXContent.contentBuilder(), ContentType.create("application/smile"));
295+
assertEquals("value", restHighLevelClient.parseEntity(smileEntity, entityParser));
296+
HttpEntity cborEntity = createGzipEncodedEntity(CborXContent.contentBuilder(), ContentType.create("application/cbor"));
297+
assertEquals("value", restHighLevelClient.parseEntity(cborEntity, entityParser));
298+
}
299+
300+
private HttpEntity createGzipEncodedEntity(String content, ContentType contentType) throws IOException {
301+
byte[] gzipEncodedContent = compressContentWithGzip(content.getBytes(StandardCharsets.UTF_8));
302+
NByteArrayEntity httpEntity = new NByteArrayEntity(gzipEncodedContent, contentType);
303+
httpEntity.setContentEncoding("gzip");
304+
305+
return httpEntity;
306+
}
307+
308+
private HttpEntity createGzipEncodedEntity(XContentBuilder xContentBuilder, ContentType contentType) throws IOException {
309+
try (XContentBuilder builder = xContentBuilder) {
310+
builder.startObject();
311+
builder.field("field", "value");
312+
builder.endObject();
313+
314+
BytesRef bytesRef = BytesReference.bytes(xContentBuilder).toBytesRef();
315+
byte[] gzipEncodedContent = compressContentWithGzip(bytesRef.bytes);
316+
NByteArrayEntity httpEntity = new NByteArrayEntity(gzipEncodedContent, contentType);
317+
httpEntity.setContentEncoding("gzip");
318+
319+
return httpEntity;
320+
}
321+
}
322+
323+
private static byte[] compressContentWithGzip(byte[] content) throws IOException {
324+
ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length);
325+
GZIPOutputStream gzip = new GZIPOutputStream(bos);
326+
gzip.write(content);
327+
gzip.close();
328+
bos.close();
329+
330+
return bos.toByteArray();
331+
}
332+
276333
private static HttpEntity createBinaryEntity(XContentBuilder xContentBuilder, ContentType contentType) throws IOException {
277334
try (XContentBuilder builder = xContentBuilder) {
278335
builder.startObject();
@@ -763,12 +820,12 @@ public void testApiNamingConventions() throws Exception {
763820
Collectors.mapping(Tuple::v2, Collectors.toSet())));
764821

765822
// TODO remove in 8.0 - we will undeprecate indices.get_template because the current getIndexTemplate
766-
// impl will replace the existing getTemplate method.
823+
// impl will replace the existing getTemplate method.
767824
// The above general-purpose code ignores all deprecated methods which in this case leaves `getTemplate`
768-
// looking like it doesn't have a valid implementatation when it does.
825+
// looking like it doesn't have a valid implementatation when it does.
769826
apiUnsupported.remove("indices.get_template");
770-
771-
827+
828+
772829

773830
for (Map.Entry<String, Set<Method>> entry : methods.entrySet()) {
774831
String apiName = entry.getKey();
@@ -803,7 +860,7 @@ public void testApiNamingConventions() throws Exception {
803860
apiName.startsWith("index_lifecycle.") == false &&
804861
apiName.startsWith("ccr.") == false &&
805862
apiName.endsWith("freeze") == false &&
806-
// IndicesClientIT.getIndexTemplate should be renamed "getTemplate" in version 8.0 when we
863+
// IndicesClientIT.getIndexTemplate should be renamed "getTemplate" in version 8.0 when we
807864
// can get rid of 7.0's deprecated "getTemplate"
808865
apiName.equals("indices.get_index_template") == false) {
809866
apiNotFound.add(apiName);

0 commit comments

Comments
 (0)