Merge pull request #1300 from jclouds/zero-length-put
set content-length on zero length payload
diff --git a/apis/s3/pom.xml b/apis/s3/pom.xml
index 364e233..17de087 100644
--- a/apis/s3/pom.xml
+++ b/apis/s3/pom.xml
@@ -87,6 +87,11 @@
<artifactId>log4j</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.mockwebserver</groupId>
+ <artifactId>mockwebserver</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<profiles>
diff --git a/apis/s3/src/test/java/org/jclouds/s3/S3ClientMockTest.java b/apis/s3/src/test/java/org/jclouds/s3/S3ClientMockTest.java
new file mode 100644
index 0000000..6931d49
--- /dev/null
+++ b/apis/s3/src/test/java/org/jclouds/s3/S3ClientMockTest.java
@@ -0,0 +1,84 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds 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.jclouds.s3;
+
+import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
+import static com.google.common.net.HttpHeaders.ETAG;
+import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static org.jclouds.s3.reference.S3Constants.PROPERTY_S3_VIRTUAL_HOST_BUCKETS;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.rest.RestContext;
+import org.jclouds.s3.domain.S3Object;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
+import com.google.mockwebserver.RecordedRequest;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test
+public class S3ClientMockTest {
+
+ private static final Set<Module> modules = ImmutableSet.<Module> of(
+ new ExecutorServiceModule(sameThreadExecutor(), sameThreadExecutor()));
+
+ static RestContext<? extends S3Client,? extends S3AsyncClient> getContext(URL server) {
+ Properties overrides = new Properties();
+ overrides.setProperty(PROPERTY_S3_VIRTUAL_HOST_BUCKETS, "false");
+
+ return ContextBuilder.newBuilder("s3")
+ .credentials("accessKey", "secretKey")
+ .endpoint(server.toString())
+ .modules(modules)
+ .overrides(overrides)
+ .build(S3ApiMetadata.CONTEXT_TOKEN);
+ }
+
+ public void testZeroLengthPutHasContentLengthHeader() throws IOException, InterruptedException {
+ MockWebServer server = new MockWebServer();
+ server.enqueue(new MockResponse().setBody("").addHeader(ETAG, "ABCDEF"));
+ server.play();
+
+ S3Client client = getContext(server.getUrl("/")).getApi();
+ S3Object nada = client.newS3Object();
+ nada.getMetadata().setKey("object");
+ nada.setPayload(new byte[] {});
+
+ assertEquals(client.putObject("bucket", nada), "ABCDEF");
+
+ RecordedRequest request = server.takeRequest();
+ assertEquals(request.getRequestLine(), "PUT /bucket/object HTTP/1.1");
+ assertEquals(request.getHeaders(CONTENT_LENGTH), ImmutableList.of("0"));
+ server.shutdown();
+ }
+}
diff --git a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java
index 21d4c9b..0816e20 100644
--- a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java
+++ b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java
@@ -214,27 +214,33 @@
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6755625
checkArgument(length < Integer.MAX_VALUE,
"JDK 1.6 does not support >2GB chunks. Use chunked encoding, if possible.");
- connection.setRequestProperty(CONTENT_LENGTH, length + "");
if (length > 0) {
writePayloadToConnection(payload, connection);
+ } else {
+ writeNothing(connection);
}
}
} else {
- connection.setRequestProperty(CONTENT_LENGTH, "0");
- // for some reason POST/PUT undoes the content length header above.
- if (ImmutableSet.of("POST", "PUT").contains(connection.getRequestMethod())) {
- connection.setFixedLengthStreamingMode(0);
- connection.setDoOutput(true);
- }
+ writeNothing(connection);
}
return connection;
}
+ protected void writeNothing(HttpURLConnection connection) {
+ connection.setRequestProperty(CONTENT_LENGTH, "0");
+ // for some reason POST/PUT undoes the content length header above.
+ if (ImmutableSet.of("POST", "PUT").contains(connection.getRequestMethod())) {
+ connection.setFixedLengthStreamingMode(0);
+ connection.setDoOutput(true);
+ }
+ }
+
void writePayloadToConnection(Payload payload, HttpURLConnection connection) throws IOException {
Long length = payload.getContentMetadata().getContentLength();
+ connection.setRequestProperty(CONTENT_LENGTH, length.toString());
+ connection.setRequestProperty("Expect", "100-continue");
connection.setFixedLengthStreamingMode(length.intValue());
connection.setDoOutput(true);
- connection.setRequestProperty("Expect", "100-continue");
CountingOutputStream out = new CountingOutputStream(connection.getOutputStream());
try {
payload.writeTo(out);
diff --git a/project/pom.xml b/project/pom.xml
index 6aaf69f..cfe83a5 100644
--- a/project/pom.xml
+++ b/project/pom.xml
@@ -271,6 +271,11 @@
<artifactId>logback-core</artifactId>
<version>1.0.7</version>
</dependency>
+ <dependency>
+ <groupId>com.google.mockwebserver</groupId>
+ <artifactId>mockwebserver</artifactId>
+ <version>20121111</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<dependencies>