From bc02603f77439a678ee0d110c42fa4a8b2824edc Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Mon, 12 May 2025 18:40:42 -0400 Subject: [PATCH 1/3] Optimize DialogueFeignClient small response reader Avoid InputStreamReader / HeapByteBuffer overhead for small (less than 8KiB) inputs, see https://github.com/FasterXML/jackson-core/pull/1081 --- .../client/jaxrs/DialogueFeignClient.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java index 6dc077ab7..dc081afd9 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java @@ -50,7 +50,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; -import java.io.UnsupportedEncodingException; +import java.io.StringReader; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -143,11 +143,7 @@ private static boolean includeRequestHeader(String headerName) { } private static String urlDecode(String input) { - try { - return URLDecoder.decode(input, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new SafeUncheckedIoException("Failed to decode path segment", e, UnsafeArg.of("encoded", input)); - } + return URLDecoder.decode(input, StandardCharsets.UTF_8); } private static Optional requestBody(Request request) { @@ -239,6 +235,17 @@ public InputStream asInputStream() { @Override public Reader asReader() { + Integer maybeLength = length(); + if (maybeLength != null && maybeLength < 8192) { + // Avoid InputStreamReader / HeapByteBuffer overhead for small (less than 8KiB) inputs, + // see https://github.com/FasterXML/jackson-core/pull/1081 + try (InputStream inputStream = asInputStream()) { + return new StringReader(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new SafeUncheckedIoException( + "Failed to read response body", e, SafeArg.of("length", maybeLength)); + } + } return new InputStreamReader(asInputStream(), StandardCharsets.UTF_8); } From 9423be25716fcc2487f38d93a7103f25d4b00487 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Wed, 20 Aug 2025 15:38:24 -0400 Subject: [PATCH 2/3] Improve handling of response larger than content-length --- .../client/jaxrs/DialogueFeignClient.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java index dc081afd9..7d95e9abb 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java @@ -50,6 +50,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; +import java.io.SequenceInputStream; import java.io.StringReader; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -235,18 +236,32 @@ public InputStream asInputStream() { @Override public Reader asReader() { + InputStream inputStream = asInputStream(); Integer maybeLength = length(); - if (maybeLength != null && maybeLength < 8192) { - // Avoid InputStreamReader / HeapByteBuffer overhead for small (less than 8KiB) inputs, - // see https://github.com/FasterXML/jackson-core/pull/1081 - try (InputStream inputStream = asInputStream()) { - return new StringReader(new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new SafeUncheckedIoException( - "Failed to read response body", e, SafeArg.of("length", maybeLength)); + if (maybeLength != null) { + int length = maybeLength; + if (length < 8192) { + // Avoid InputStreamReader / HeapByteBuffer overhead for small (less than 8KiB) inputs, + // see https://github.com/FasterXML/jackson-core/pull/1081 + // try to read an extra byte to determine if more bytes were provided than actual content-length + int toRead = length + 1; + byte[] bytes = new byte[toRead]; + try { + int read = inputStream.readNBytes(bytes, 0, toRead); + if (read == length) { + // fully read input + inputStream.close(); + return new StringReader(new String(bytes, 0, read, StandardCharsets.UTF_8)); + } + // input was larger than provided content length, fallback to stream path + inputStream = new SequenceInputStream(new ByteArrayInputStream(bytes), inputStream); + } catch (IOException e) { + throw new SafeUncheckedIoException( + "Failed to read response body", e, SafeArg.of("length", maybeLength)); + } } } - return new InputStreamReader(asInputStream(), StandardCharsets.UTF_8); + return new InputStreamReader(inputStream, StandardCharsets.UTF_8); } @Override From cb10bd867db2f58b4e86c82bc972b1dfa037f1f2 Mon Sep 17 00:00:00 2001 From: David Schlosnagle Date: Wed, 20 Aug 2025 15:40:45 -0400 Subject: [PATCH 3/3] handle shorter content --- .../palantir/conjure/java/client/jaxrs/DialogueFeignClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java index 7d95e9abb..de98b3118 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java @@ -248,7 +248,7 @@ public Reader asReader() { byte[] bytes = new byte[toRead]; try { int read = inputStream.readNBytes(bytes, 0, toRead); - if (read == length) { + if (read <= length) { // fully read input inputStream.close(); return new StringReader(new String(bytes, 0, read, StandardCharsets.UTF_8));