Skip to content

Commit ecb1e9e

Browse files
authored
[JAVA][NATIVE] Add gzip capability (#22358)
* add gzip capability * fixed test * added docstring * regenerated samples
1 parent 039de98 commit ecb1e9e

File tree

30 files changed

+2621
-1275
lines changed

30 files changed

+2621
-1275
lines changed

modules/openapi-generator/src/main/resources/Java/libraries/native/ApiClient.mustache

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import org.openapitools.jackson.nullable.JsonNullableModule;
1111
{{/openApiNullable}}
1212

1313
import java.io.InputStream;
14+
import java.io.IOException;
15+
{{#useGzipFeature}}
16+
import java.io.ByteArrayOutputStream;
17+
{{/useGzipFeature}}
1418
import java.net.URI;
1519
import java.net.URLEncoder;
1620
import java.net.http.HttpClient;
@@ -25,6 +29,13 @@ import java.util.Collections;
2529
import java.util.List;
2630
import java.util.StringJoiner;
2731
import java.util.function.Consumer;
32+
import java.util.Optional;
33+
import java.util.zip.GZIPInputStream;
34+
{{#useGzipFeature}}
35+
import java.util.function.Supplier;
36+
import java.util.Objects;
37+
import java.util.zip.GZIPOutputStream;
38+
{{/useGzipFeature}}
2839
import java.util.stream.Collectors;
2940

3041
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -54,7 +65,7 @@ public class ApiClient {
5465
protected String basePath;
5566
protected Consumer<HttpRequest.Builder> interceptor;
5667
protected Consumer<HttpResponse<InputStream>> responseInterceptor;
57-
protected Consumer<HttpResponse<String>> asyncResponseInterceptor;
68+
protected Consumer<HttpResponse<InputStream>> asyncResponseInterceptor;
5869
protected Duration readTimeout;
5970
protected Duration connectTimeout;
6071
@@ -378,7 +389,7 @@ public class ApiClient {
378389
* of null resets the interceptor to a no-op.
379390
* @return This object.
380391
*/
381-
public ApiClient setAsyncResponseInterceptor(Consumer<HttpResponse<String>> interceptor) {
392+
public ApiClient setAsyncResponseInterceptor(Consumer<HttpResponse<InputStream>> interceptor) {
382393
this.asyncResponseInterceptor = interceptor;
383394
return this;
384395
}
@@ -388,7 +399,7 @@ public class ApiClient {
388399
*
389400
* @return The custom interceptor that was set, or null if there isn't any.
390401
*/
391-
public Consumer<HttpResponse<String>> getAsyncResponseInterceptor() {
402+
public Consumer<HttpResponse<InputStream>> getAsyncResponseInterceptor() {
392403
return asyncResponseInterceptor;
393404
}
394405

@@ -448,4 +459,142 @@ public class ApiClient {
448459
public Duration getConnectTimeout() {
449460
return connectTimeout;
450461
}
462+
463+
/**
464+
* Returns the response body InputStream, transparently decoding gzip-compressed
465+
* payloads when the server sets {@code Content-Encoding: gzip}.
466+
*
467+
* @param response HTTP response whose body should be consumed
468+
* @return Original or decompressed InputStream for the response body
469+
* @throws IOException if the response body cannot be accessed or wrapping fails
470+
*/
471+
public static InputStream getResponseBody(HttpResponse<InputStream> response) throws IOException {
472+
if (response == null) {
473+
return null;
474+
}
475+
InputStream body = response.body();
476+
if (body == null) {
477+
return null;
478+
}
479+
Optional<String> encoding = response.headers().firstValue("Content-Encoding");
480+
if (encoding.isPresent()) {
481+
for (String token : encoding.get().split(",")) {
482+
if ("gzip".equalsIgnoreCase(token.trim())) {
483+
return new GZIPInputStream(body);
484+
}
485+
}
486+
}
487+
return body;
488+
}
489+
490+
{{#useGzipFeature}}
491+
/**
492+
* Wraps a request body supplier with a streaming GZIP compressor so large payloads
493+
* can be sent without buffering the entire contents in memory.
494+
*
495+
* @param bodySupplier Supplies the original request body InputStream
496+
* @return BodyPublisher that emits gzip-compressed bytes from the supplied stream
497+
*/
498+
public static HttpRequest.BodyPublisher gzipRequestBody(Supplier<InputStream> bodySupplier) {
499+
Objects.requireNonNull(bodySupplier, "bodySupplier must not be null");
500+
return HttpRequest.BodyPublishers.ofInputStream(() -> new GzipCompressingInputStream(bodySupplier));
501+
}
502+
503+
private static final class GzipCompressingInputStream extends InputStream {
504+
private final Supplier<InputStream> supplier;
505+
private final byte[] readBuffer = new byte[8192];
506+
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
507+
private InputStream source;
508+
private GZIPOutputStream gzipStream;
509+
private byte[] currentChunk = new byte[0];
510+
private int chunkPosition = 0;
511+
private boolean finished = false;
512+
513+
private GzipCompressingInputStream(Supplier<InputStream> supplier) {
514+
this.supplier = Objects.requireNonNull(supplier, "bodySupplier must not be null");
515+
}
516+
517+
private void ensureInitialized() throws IOException {
518+
if (source == null) {
519+
source = Objects.requireNonNull(supplier.get(), "bodySupplier returned null InputStream");
520+
gzipStream = new GZIPOutputStream(buffer, true);
521+
}
522+
}
523+
524+
private boolean fillBuffer() throws IOException {
525+
ensureInitialized();
526+
while (chunkPosition >= currentChunk.length) {
527+
buffer.reset();
528+
if (finished) {
529+
return false;
530+
}
531+
int bytesRead = source.read(readBuffer);
532+
if (bytesRead == -1) {
533+
gzipStream.finish();
534+
gzipStream.close();
535+
source.close();
536+
finished = true;
537+
} else {
538+
gzipStream.write(readBuffer, 0, bytesRead);
539+
gzipStream.flush();
540+
}
541+
currentChunk = buffer.toByteArray();
542+
chunkPosition = 0;
543+
if (currentChunk.length == 0 && !finished) {
544+
continue;
545+
}
546+
if (currentChunk.length == 0 && finished) {
547+
return false;
548+
}
549+
return true;
550+
}
551+
return true;
552+
}
553+
554+
@Override
555+
public int read() throws IOException {
556+
if (!fillBuffer()) {
557+
return -1;
558+
}
559+
return currentChunk[chunkPosition++] & 0xFF;
560+
}
561+
562+
@Override
563+
public int read(byte[] b, int off, int len) throws IOException {
564+
if (!fillBuffer()) {
565+
return -1;
566+
}
567+
int bytesToCopy = Math.min(len, currentChunk.length - chunkPosition);
568+
System.arraycopy(currentChunk, chunkPosition, b, off, bytesToCopy);
569+
chunkPosition += bytesToCopy;
570+
return bytesToCopy;
571+
}
572+
573+
@Override
574+
public void close() throws IOException {
575+
IOException exception = null;
576+
if (source != null) {
577+
try {
578+
source.close();
579+
} catch (IOException e) {
580+
exception = e;
581+
} finally {
582+
source = null;
583+
}
584+
}
585+
if (gzipStream != null) {
586+
try {
587+
gzipStream.close();
588+
} catch (IOException e) {
589+
exception = exception == null ? e : exception;
590+
} finally {
591+
gzipStream = null;
592+
}
593+
}
594+
if (exception != null) {
595+
throw exception;
596+
}
597+
}
598+
}
599+
{{/useGzipFeature}}
451600
}

0 commit comments

Comments
 (0)