Skip to content

Commit d385907

Browse files
author
Yaniv Inbar
committed
[http issue 115] NetHttpTransport drops Content-Length header when posting 0-length data
http://codereview.appspot.com/6463064/
1 parent a67c3d6 commit d385907

File tree

11 files changed

+317
-36
lines changed

11 files changed

+317
-36
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.http;
16+
17+
import java.io.IOException;
18+
import java.io.OutputStream;
19+
20+
/**
21+
* Empty HTTP content of length zero just to force {@link HttpRequest#execute()} to add the header
22+
* {@code Content-Length: 0}.
23+
*
24+
* <p>
25+
* Note that there is no {@code Content-Length} header if the HTTP request content is {@code null} .
26+
* However, when making a request like PUT or POST without a {@code Content-Length} header, some
27+
* servers may respond with a {@code 411 Length Required} error. Specifying the
28+
* {@code Content-Length: 0} header may work around that problem.
29+
* </p>
30+
*
31+
* @since 1.11
32+
* @author Yaniv Inbar
33+
*/
34+
public class EmptyContent implements HttpContent {
35+
36+
public long getLength() throws IOException {
37+
return 0;
38+
}
39+
40+
public String getEncoding() {
41+
return null;
42+
}
43+
44+
public String getType() {
45+
return null;
46+
}
47+
48+
public void writeTo(OutputStream out) throws IOException {
49+
out.flush();
50+
}
51+
52+
public boolean retrySupported() {
53+
return true;
54+
}
55+
}

google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) {
8888
* value is set to {@code false} then " " is set as the content with Content-Length {@code 1} for
8989
* empty contents. Defaults to {@code true}.
9090
*/
91+
@Deprecated
9192
private boolean allowEmptyContent = true;
9293

9394
/**
@@ -594,9 +595,13 @@ public HttpRequest setUnsuccessfulResponseHandler(
594595
* empty contents. Defaults to {@code true}.
595596
*
596597
* @since 1.7
598+
* @deprecated (scheduled to be removed in 1.12) Use {@code setContent(new EmptyContent())}
597599
*/
600+
@Deprecated
598601
public HttpRequest setAllowEmptyContent(boolean allowEmptyContent) {
599602
this.allowEmptyContent = allowEmptyContent;
603+
HttpTransport.LOGGER.warning("setAllowEmptyContent is deprecated and will be removed in 1.12;"
604+
+ " use setEmptyContent instead (if needed)");
600605
return this;
601606
}
602607

@@ -606,8 +611,11 @@ public HttpRequest setAllowEmptyContent(boolean allowEmptyContent) {
606611
* empty contents. Defaults to {@code true}.
607612
*
608613
* @since 1.7
614+
* @deprecated (scheduled to be removed in 1.12)
609615
*/
616+
@Deprecated
610617
public boolean isAllowEmptyContent() {
618+
HttpTransport.LOGGER.warning("isAllowEmptyContent is deprecated and will be removed in 1.12");
611619
return allowEmptyContent;
612620
}
613621

google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpRequest.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
import com.google.api.client.http.HttpContent;
1818
import com.google.api.client.http.LowLevelHttpRequest;
1919
import com.google.api.client.http.LowLevelHttpResponse;
20+
import com.google.common.base.Preconditions;
2021

2122
import java.io.IOException;
2223
import java.io.OutputStream;
2324
import java.net.HttpURLConnection;
25+
import java.net.ProtocolException;
2426
import java.net.URL;
2527

2628
/**
@@ -32,8 +34,11 @@ final class NetHttpRequest extends LowLevelHttpRequest {
3234
private HttpContent content;
3335

3436
NetHttpRequest(String requestMethod, String url) throws IOException {
35-
HttpURLConnection connection =
36-
this.connection = (HttpURLConnection) new URL(url).openConnection();
37+
this(requestMethod, (HttpURLConnection) new URL(url).openConnection());
38+
}
39+
40+
NetHttpRequest(String requestMethod, HttpURLConnection connection) throws ProtocolException {
41+
this.connection = connection;
3742
connection.setRequestMethod(requestMethod);
3843
connection.setUseCaches(false);
3944
connection.setInstanceFollowRedirects(false);
@@ -67,8 +72,8 @@ public LowLevelHttpResponse execute() throws IOException {
6772
if (contentLength >= 0) {
6873
addHeader("Content-Length", Long.toString(contentLength));
6974
}
70-
if (contentLength != 0) {
71-
// setDoOutput(true) will change a GET method to POST, so only if contentLength != 0
75+
String requestMethod = connection.getRequestMethod();
76+
if ("POST".equals(requestMethod) || "PUT".equals(requestMethod)) {
7277
connection.setDoOutput(true);
7378
// see http://developer.android.com/reference/java/net/HttpURLConnection.html
7479
if (contentLength >= 0 && contentLength <= Integer.MAX_VALUE) {
@@ -82,6 +87,11 @@ public LowLevelHttpResponse execute() throws IOException {
8287
} finally {
8388
out.close();
8489
}
90+
} else {
91+
// cannot call setDoOutput(true) because it would change a GET method to POST
92+
// for HEAD, OPTIONS, DELETE, or TRACE it would throw an exceptions
93+
Preconditions.checkArgument(
94+
contentLength == 0, "%s with non-zero content length is not supported", requestMethod);
8595
}
8696
}
8797
// connect

google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
* globally-shared instance of the HTTP transport.
3434
* </p>
3535
*
36+
* <p>
37+
* Upgrade warning: in prior version 1.10 when using GET method with non-zero content, it
38+
* automatically changed the method to POST. However, starting with version 1.11 it now throws an
39+
* {@link IllegalArgumentException} instead. Instead, use POST.
40+
* </p>
41+
*
3642
* @since 1.0
3743
* @author Yaniv Inbar
3844
*/
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.testing.http.javanet;
16+
17+
import com.google.common.base.Preconditions;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.net.HttpURLConnection;
23+
import java.net.URL;
24+
import java.net.UnknownServiceException;
25+
26+
/**
27+
* Mock for {@link HttpURLConnection}.
28+
*
29+
* <p>
30+
* Implementation is not thread-safe.
31+
* </p>
32+
*
33+
* @since 1.11
34+
* @author Yaniv Inbar
35+
*/
36+
public class MockHttpURLConnection extends HttpURLConnection {
37+
38+
/** Whether {@link #doOutput} was called. */
39+
private boolean doOutputCalled;
40+
41+
/**
42+
* Output stream or {@code null} to throw an {@link UnknownServiceException} when
43+
* {@link #getOutputStream()} is called.
44+
*/
45+
private OutputStream outputStream = new ByteArrayOutputStream(0);
46+
47+
/**
48+
* @param u the URL or {@code null} for none
49+
*/
50+
public MockHttpURLConnection(URL u) {
51+
super(u);
52+
}
53+
54+
@Override
55+
public void disconnect() {
56+
}
57+
58+
@Override
59+
public boolean usingProxy() {
60+
return false;
61+
}
62+
63+
@Override
64+
public void connect() throws IOException {
65+
}
66+
67+
@Override
68+
public int getResponseCode() throws IOException {
69+
return responseCode;
70+
}
71+
72+
@Override
73+
public void setDoOutput(boolean dooutput) {
74+
doOutputCalled = true;
75+
}
76+
77+
@Override
78+
public OutputStream getOutputStream() throws IOException {
79+
if (outputStream != null) {
80+
return outputStream;
81+
}
82+
return super.getOutputStream();
83+
}
84+
85+
/** Returns whether {@link #doOutput} was called. */
86+
public final boolean doOutputCalled() {
87+
return doOutputCalled;
88+
}
89+
90+
/**
91+
* Sets the output stream or {@code null} to throw an {@link UnknownServiceException} when
92+
* {@link #getOutputStream()} is called.
93+
*
94+
* <p>
95+
* By default it is {@code null}.
96+
* </p>
97+
*/
98+
public MockHttpURLConnection setOutputStream(OutputStream outputStream) {
99+
this.outputStream = outputStream;
100+
return this;
101+
}
102+
103+
/** Sets the HTTP response status code. */
104+
public MockHttpURLConnection setResponseCode(int responseCode) {
105+
Preconditions.checkArgument(responseCode >= -1);
106+
this.responseCode = responseCode;
107+
return this;
108+
}
109+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
/**
16+
* Testing utilities used for writing tests based on the {@code java.net} package.
17+
*
18+
* <p>
19+
* <b>Warning: this package is experimental, and its content may be changed in incompatible ways or
20+
* possibly entirely removed in a future version of the library</b>
21+
* </p>
22+
*
23+
* @since 1.11
24+
* @author Yaniv Inbar
25+
*/
26+
27+
package com.google.api.client.testing.http.javanet;
28+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.http;
16+
17+
18+
import junit.framework.TestCase;
19+
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
22+
23+
/**
24+
* Tests {@link EmptyContent}.
25+
*
26+
* @author Yaniv Inbar
27+
*/
28+
public class EmptyContentTest extends TestCase {
29+
30+
public void test() throws IOException {
31+
EmptyContent content = new EmptyContent();
32+
assertEquals(0L, content.getLength());
33+
assertNull(content.getEncoding());
34+
assertNull(content.getType());
35+
assertTrue(content.retrySupported());
36+
ByteArrayOutputStream out = new ByteArrayOutputStream();
37+
content.writeTo(out);
38+
assertEquals(0, out.size());
39+
}
40+
}

google-http-client/src/test/java/com/google/api/client/http/HttpRequestTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,8 @@ public void testUserAgent() {
805805
assertTrue(HttpRequest.USER_AGENT_SUFFIX.contains("gzip"));
806806
}
807807

808-
public void testExecute_emptyContent() throws IOException {
808+
@Deprecated
809+
public void testExecute_allowEmptyContent() throws IOException {
809810
class MyTransport extends MockHttpTransport {
810811
String expectedContent;
811812

google-http-client/src/test/java/com/google/api/client/http/HttpResponseTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ class DisconnectLowLevelHttpResponse extends MockLowLevelHttpResponse {
244244
boolean disconnectCalled;
245245
DisconnectByteArrayInputStream content;
246246

247+
@SuppressWarnings("resource")
247248
@Override
248249
public MockLowLevelHttpResponse setContent(String stringContent) {
249250
content = stringContent == null ? null : new DisconnectByteArrayInputStream(StringUtils

0 commit comments

Comments
 (0)