diff --git a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BrokerResponse.java b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BrokerResponse.java index 1a47f8012285..6f3f4248b7ca 100644 --- a/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BrokerResponse.java +++ b/sdks/java/io/solace/src/main/java/org/apache/beam/sdk/io/solace/broker/BrokerResponse.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,10 +36,14 @@ public BrokerResponse(int responseCode, String message, @Nullable InputStream co this.code = responseCode; this.message = message; if (content != null) { - this.content = - new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.joining("\n")); + // Use try-with-resources so the underlying InputStream is always closed once the + // response body has been read; otherwise the HTTP connection stream leaks. + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))) { + this.content = reader.lines().collect(Collectors.joining("\n")); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read broker response content", e); + } } } diff --git a/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/broker/BrokerResponseTest.java b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/broker/BrokerResponseTest.java new file mode 100644 index 000000000000..086142194236 --- /dev/null +++ b/sdks/java/io/solace/src/test/java/org/apache/beam/sdk/io/solace/broker/BrokerResponseTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.beam.sdk.io.solace.broker; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import org.junit.Test; + +public class BrokerResponseTest { + + /** An {@link java.io.InputStream} that records whether {@code close()} was invoked. */ + private static class CloseTrackingInputStream extends ByteArrayInputStream { + private boolean closed = false; + + CloseTrackingInputStream(String data) { + super(data.getBytes(StandardCharsets.UTF_8)); + } + + boolean wasClosed() { + return closed; + } + + @Override + public void close() { + closed = true; + } + } + + @Test + public void testContentIsReadAndStreamIsClosed() { + CloseTrackingInputStream stream = new CloseTrackingInputStream("line1\nline2"); + + BrokerResponse response = new BrokerResponse(200, "OK", stream); + + assertEquals("line1\nline2", response.content); + assertTrue( + "the content InputStream must be closed after the response body has been read", + stream.wasClosed()); + } + + @Test + public void testNullContentIsHandled() { + BrokerResponse response = new BrokerResponse(204, "No Content", null); + + assertNull(response.content); + } +}