diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequest.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequest.java index 922e32d5..ef79c245 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequest.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequest.java @@ -37,6 +37,7 @@ import lombok.Getter; public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable { private final String method; + private Long contentLength; private final MultiValueMap headers; @@ -44,11 +45,18 @@ public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable private final InputStream requestEntity; + public RibbonApacheHttpRequest(final String method, final URI uri, + final Boolean retryable, final MultiValueMap headers, + final MultiValueMap params, final InputStream requestEntity) { + this(method, uri, retryable, headers, params, requestEntity, null); + } + public RibbonApacheHttpRequest(final String method, final URI uri, final Boolean retryable, final MultiValueMap headers, - final MultiValueMap params, final InputStream requestEntity) { + final MultiValueMap params, final InputStream requestEntity, Long contentLength) { this.method = method; + this.contentLength = contentLength; this.uri = uri; this.isRetriable = retryable; this.headers = headers; @@ -77,6 +85,11 @@ public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable final BasicHttpEntity entity; entity = new BasicHttpEntity(); entity.setContent(this.requestEntity); + // if the entity contentLength isn't set, transfer-encoding will be set + // to chunked in org.apache.http.protocol.RequestContent. See gh-1042 + if (contentLength != null) { + entity.setContentLength(this.contentLength); + } builder.setEntity(entity); } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java index ca564d54..1182cb33 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/apache/HttpClientRibbonCommand.java @@ -35,6 +35,7 @@ import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.zuul.constants.ZuulConstants; import com.netflix.zuul.context.RequestContext; +import org.springframework.util.StringUtils; /** * @author Christian Lohmann @@ -100,10 +101,15 @@ public class HttpClientRibbonCommand extends HystrixCommand protected ClientHttpResponse forward() throws Exception { final RequestContext context = RequestContext.getCurrentContext(); + Long contentLength = null; + String contentLengthHeader = context.getRequest().getHeader("Content-Length"); + if (StringUtils.hasText(contentLengthHeader)) { + contentLength = new Long(contentLengthHeader); + } URI uriInstance = new URI(this.uri); RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(this.method, uriInstance, this.retryable, this.headers, this.params, - this.requestEntity); + this.requestEntity, contentLength); final RibbonApacheHttpResponse response = this.client .executeWithLoadBalancer(request); context.set("ribbonResponse", response); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequestTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequestTests.java new file mode 100644 index 00000000..0f0e266a --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonApacheHttpRequestTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013-2016 the original author or authors. + * + * Licensed 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.springframework.cloud.netflix.ribbon.apache; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.charset.Charset; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpUriRequest; +import org.junit.Test; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.StreamUtils; + +/** + * @author Spencer Gibb + */ +public class RibbonApacheHttpRequestTests { + + @Test + public void testNullEntity() throws Exception { + URI uri = URI.create("http://example.com"); + LinkedMultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("my-header", "my-value"); + LinkedMultiValueMap params = new LinkedMultiValueMap<>(); + params.add("myparam", "myparamval"); + RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest("GET", uri, false, + headers, params, null); + + HttpUriRequest request = httpRequest.toRequest(RequestConfig.custom().build()); + + assertThat("request is wrong type", request, is(not(instanceOf(HttpEntityEnclosingRequest.class)))); + assertThat("uri is wrong", request.getURI().toString(), startsWith(uri.toString())); + assertThat("my-header is missing", request.getFirstHeader("my-header"), is(notNullValue())); + assertThat("my-header is wrong", request.getFirstHeader("my-header").getValue(), is(equalTo("my-value"))); + assertThat("myparam is missing", request.getURI().getQuery(), is(equalTo("myparam=myparamval"))); + } + + + @Test + public void testNotNullEntity() throws Exception { + URI uri = URI.create("http://example.com"); + LinkedMultiValueMap headers = new LinkedMultiValueMap<>(); + headers.add("Content-Length", "4"); + String entityValue = "abcd"; + RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest("POST", uri, false, + headers, new LinkedMultiValueMap(), new ByteArrayInputStream(entityValue.getBytes()), + (long) entityValue.length()); + + HttpUriRequest request = httpRequest.toRequest(RequestConfig.custom().build()); + + assertThat("request is wrong type", request, is(instanceOf(HttpEntityEnclosingRequest.class))); + assertThat("uri is wrong", request.getURI().toString(), startsWith(uri.toString())); + assertThat("Content-Length is missing", request.getFirstHeader("Content-Length"), is(notNullValue())); + assertThat("Content-Length is wrong", request.getFirstHeader("Content-Length").getValue(), is(equalTo("4"))); + + HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request; + assertThat("entity is missing", entityRequest.getEntity(), is(notNullValue())); + HttpEntity entity = entityRequest.getEntity(); + assertThat("contentLength is wrong", entity.getContentLength(), is(equalTo(4L))); + assertThat("content is missing", entity.getContent(), is(notNullValue())); + String string = StreamUtils.copyToString(entity.getContent(), Charset.forName("UTF-8")); + assertThat("content is wrong", string, is(equalTo(entityValue))); + } +}