From 66e64441a5a4e64a0f655d2971066757896080d7 Mon Sep 17 00:00:00 2001 From: Louis Christopher Date: Sun, 4 Jan 2026 20:47:17 +0530 Subject: [PATCH] chore: normalize host URL in MultipartUploadHttpRequestManager --- .../MultipartUploadHttpRequestManager.java | 6 +++- .../google/cloud/storage/FakeHttpServer.java | 7 +++- ...MultipartUploadHttpRequestManagerTest.java | 34 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java index 5cb5f0e558..f890bd1025 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/MultipartUploadHttpRequestManager.java @@ -253,7 +253,11 @@ static MultipartUploadHttpRequestManager createFrom(HttpStorageOptions options) storage.getRequestFactory(), new XmlObjectParser(new XmlMapper()), options.getMergedHeaderProvider(FixedHeaderProvider.create(stableHeaders.build())), - URI.create(options.getHost())); + URI.create(ensureTrailingSlash(options.getHost()))); + } + + private static String ensureTrailingSlash(String host) { + return host.endsWith("/") ? host : host + "/"; } private void addChecksumHeader(@Nullable Crc32cLengthKnown crc32c, HttpHeaders headers) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java index 71bf4a2dac..566a7765eb 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java @@ -93,6 +93,10 @@ public void close() throws Exception { } static FakeHttpServer of(HttpRequestHandler server) { + return of(server, true); + } + + static FakeHttpServer of(HttpRequestHandler server, boolean trailingSlash) { // based on // https://github.com/netty/netty/blob/59aa6e635b9996cf21cd946e64353270679adc73/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServer.java InetSocketAddress address = new InetSocketAddress("localhost", 0); @@ -124,7 +128,8 @@ protected void initChannel(SocketChannel ch) { Channel channel = b.bind(address).syncUninterruptibly().channel(); InetSocketAddress socketAddress = (InetSocketAddress) channel.localAddress(); - URI endpoint = URI.create("http://localhost:" + socketAddress.getPort() + "/"); + String suffix = trailingSlash ? "/" : ""; + URI endpoint = URI.create("http://localhost:" + socketAddress.getPort() + suffix); HttpStorageOptions httpStorageOptions = HttpStorageOptions.http() .setHost(endpoint.toString()) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java index 5a6a0096fe..91973d61d9 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITMultipartUploadHttpRequestManagerTest.java @@ -1364,6 +1364,40 @@ public void sendListMultipartUploadsRequest_withUserProject() throws Exception { } } + @Test + public void hostWithoutTrailingSlash_urlConstructedCorrectly() throws Exception { + HttpRequestHandler handler = + req -> { + assertThat(req.uri()).startsWith("/test-bucket/test-key"); + CreateMultipartUploadResponse response = + CreateMultipartUploadResponse.builder() + .bucket("test-bucket") + .key("test-key") + .uploadId("test-upload-id") + .build(); + ByteBuf buf = Unpooled.wrappedBuffer(xmlMapper.writeValueAsBytes(response)); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/xml; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler, false)) { + MultipartUploadHttpRequestManager manager = + MultipartUploadHttpRequestManager.createFrom(fakeHttpServer.getHttpStorageOptions()); + CreateMultipartUploadRequest request = + CreateMultipartUploadRequest.builder() + .bucket("test-bucket") + .key("test-key") + .contentType("application/octet-stream") + .build(); + + CreateMultipartUploadResponse response = manager.sendCreateMultipartUploadRequest(request); + + assertThat(response.bucket()).isEqualTo("test-bucket"); + } + } + private void forceSetUploads( ListMultipartUploadsResponse response, java.util.List uploads) { try {