Add support for streaming request and response bodies by trumpetinc · Pull Request #2750 · OpenFeign/feign · GitHub
Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static feign.Feign.configKey;

import feign.HttpBodyFactory;
import feign.Request;
import feign.Response;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -47,7 +48,7 @@ Response testResponse(int status, String body, Map<String, Collection<String>> h
.headers(headers)
.request(
Request.create(
Request.HttpMethod.GET, "http://test", headers, Request.Body.empty(), null))
Request.HttpMethod.GET, "http://test", headers, HttpBodyFactory.empty(), null))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static org.assertj.core.api.Assertions.assertThat;

import feign.HttpBodyFactory;
import feign.Request;
import feign.codec.Decoder;
import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors;
Expand Down Expand Up @@ -59,7 +60,7 @@ public class AnnotationErrorDecoderExceptionConstructorsTest
feign.Request.HttpMethod.GET,
"http://test",
Collections.emptyMap(),
Request.Body.empty(),
HttpBodyFactory.empty(),
null);
private static final feign.Request NO_REQUEST = null;
private static final Map<String, Collection<String>> NON_NULL_HEADERS = new HashMap<>();
Expand Down
44 changes: 30 additions & 14 deletions core/src/main/java/feign/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import static java.lang.String.format;

import feign.Request.Options;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -38,28 +37,49 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.InflaterInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

import feign.HttpBodyFactory.HttpBody;
import feign.Request.Options;

/** Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe. */
public interface Client {

/**
* Executes a request against its {@link Request#url() url} and returns a response.
*
*
* @param request safe to replay.
* @param options options to apply to this request.
* @return connected response, {@link Response.Body} is absent or unread.
* @return connected response, {@link HttpBody} is absent or unread.
* @throws IOException on a network error connecting to {@link Request#url()}.
*/
Response execute(Request request, Options options) throws IOException;

class ForceRequestCloseDecoratorClient implements Client {
private final Client delegate;

public ForceRequestCloseDecoratorClient(Client delegate) {
this.delegate = delegate;
}

// TODO: KD - get rid of null check if we can agree to not allow null bodies...
@Override
public Response execute(Request request, Options options) throws IOException {
Response resp = delegate.execute(request, options);
Optional.ofNullable(request.httpBody()).ifPresent(HttpBody::close);
return resp;
}
}

class Default implements Client {

private final SSLSocketFactory sslContextFactory;
Expand Down Expand Up @@ -129,10 +149,6 @@ Response convertResponse(HttpURLConnection connection, Request request) throws I
}
}

Integer length = connection.getContentLength();
if (length == -1) {
length = null;
}
InputStream stream;
if (status >= 400) {
stream = connection.getErrorStream();
Expand All @@ -149,7 +165,7 @@ Response convertResponse(HttpURLConnection connection, Request request) throws I
.reason(reason)
.headers(headers)
.request(request)
.body(stream, length)
.body(stream, connection.getContentLengthLong())
.build();
}

Expand All @@ -175,20 +191,22 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
connection.setInstanceFollowRedirects(options.isFollowRedirects());
connection.setRequestMethod(request.httpMethod().name());

HttpBody body = request.httpBody();

Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean gzipEncodedRequest = this.isGzip(contentEncodingValues);
boolean deflateEncodedRequest = this.isDeflate(contentEncodingValues);

boolean hasAcceptHeader = false;
Integer contentLength = null;
Long contentLength = null;
for (String field : request.headers().keySet()) {
if (field.equalsIgnoreCase("Accept")) {
hasAcceptHeader = true;
}
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
if (!gzipEncodedRequest && !deflateEncodedRequest) {
contentLength = Integer.valueOf(value);
contentLength = Long.valueOf(value);
connection.addRequestProperty(field, value);
}
}
Expand All @@ -206,9 +224,7 @@ else if (field.equals(ACCEPT_ENCODING)) {
connection.addRequestProperty("Accept", "*/*");
}

byte[] body = request.body();

if (body != null) {
if (body != null && body.getLength() != 0) {
/*
* Ignore disableRequestBuffering flag if the empty body was set, to ensure that internal
* retry logic applies to such requests.
Expand All @@ -228,7 +244,7 @@ else if (field.equals(ACCEPT_ENCODING)) {
out = new DeflaterOutputStream(out);
}
try {
out.write(body);
Util.copy(body.asInputStream(), out);
} finally {
try {
out.close();
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/feign/Feign.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Builder contract(Contract contract) {
}

public Builder client(Client client) {
this.client = client;
this.client = new Client.ForceRequestCloseDecoratorClient(client);

return this;
}
Expand Down
68 changes: 40 additions & 28 deletions core/src/main/java/feign/FeignException.java
Loading