@@ -11,6 +11,10 @@ import org.openapitools.jackson.nullable.JsonNullableModule;
1111{ {/openApiNullable} }
1212
1313import java.io.InputStream;
14+ import java.io.IOException;
15+ { {#useGzipFeature} }
16+ import java.io.ByteArrayOutputStream;
17+ { {/useGzipFeature} }
1418import java.net.URI;
1519import java.net.URLEncoder;
1620import java.net.http.HttpClient;
@@ -25,6 +29,13 @@ import java.util.Collections;
2529import java.util.List;
2630import java.util.StringJoiner;
2731import 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} }
2839import java.util.stream.Collectors;
2940
3041import 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