Browse Source
* Refactor so that ResponseInterceptor intercepts the response (in the same manner that RequestInterceptor does) rather than intercepting the decoding process. Signed-off-by: Iain Henderson <Iain.henderson@mac.com> * Add a default RedirectionInterceptor as an implementation of ResponseInterceptor and include unit tests for redirection interception, error interception, and void decoding in FeignTest. * Update README to include ResponseInterceptor * Add copyright notice to RedirectionInterceptor * Correct formatting using maven * Updates in response to CodeRabbit * more CodeRabbitAI suggestions * Add unit tests for chained ResponseInterceptor instances * fixing formatting * formatting and responding to CodeRabbitAI comment * Reverting Feign-core pom * Cleanup Javadocs in ResponseInterceptor and RedirectionInterceptor --------- Signed-off-by: Iain Henderson <Iain.henderson@mac.com> Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>pull/2166/head
Iain Henderson
1 year ago
committed by
GitHub
8 changed files with 398 additions and 142 deletions
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* Copyright 2012-2023 The Feign 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 feign; |
||||
|
||||
import static feign.FeignException.errorReading; |
||||
import static feign.Util.ensureClosed; |
||||
import feign.codec.DecodeException; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.ErrorDecoder; |
||||
import java.io.IOException; |
||||
import java.lang.reflect.Type; |
||||
|
||||
public class InvocationContext { |
||||
private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L; |
||||
private final String configKey; |
||||
private final Decoder decoder; |
||||
private final ErrorDecoder errorDecoder; |
||||
private final boolean dismiss404; |
||||
private final boolean closeAfterDecode; |
||||
private final boolean decodeVoid; |
||||
private final Response response; |
||||
private final Type returnType; |
||||
|
||||
InvocationContext(String configKey, Decoder decoder, ErrorDecoder errorDecoder, |
||||
boolean dismiss404, boolean closeAfterDecode, boolean decodeVoid, Response response, |
||||
Type returnType) { |
||||
this.configKey = configKey; |
||||
this.decoder = decoder; |
||||
this.errorDecoder = errorDecoder; |
||||
this.dismiss404 = dismiss404; |
||||
this.closeAfterDecode = closeAfterDecode; |
||||
this.decodeVoid = decodeVoid; |
||||
this.response = response; |
||||
this.returnType = returnType; |
||||
} |
||||
|
||||
public Decoder decoder() { |
||||
return decoder; |
||||
} |
||||
|
||||
public Type returnType() { |
||||
return returnType; |
||||
} |
||||
|
||||
public Response response() { |
||||
return response; |
||||
} |
||||
|
||||
public Object proceed() throws Exception { |
||||
if (returnType == Response.class) { |
||||
return disconnectResponseBodyIfNeeded(response); |
||||
} |
||||
|
||||
try { |
||||
final boolean shouldDecodeResponseBody = |
||||
(response.status() >= 200 && response.status() < 300) |
||||
|| (response.status() == 404 && dismiss404 |
||||
&& !isVoidType(returnType)); |
||||
|
||||
if (!shouldDecodeResponseBody) { |
||||
throw decodeError(configKey, response); |
||||
} |
||||
|
||||
if (isVoidType(returnType) && !decodeVoid) { |
||||
ensureClosed(response.body()); |
||||
return null; |
||||
} |
||||
|
||||
try { |
||||
return decoder.decode(response, returnType); |
||||
} catch (final FeignException e) { |
||||
throw e; |
||||
} catch (final RuntimeException e) { |
||||
throw new DecodeException(response.status(), e.getMessage(), response.request(), e); |
||||
} catch (IOException e) { |
||||
throw errorReading(response.request(), response, e); |
||||
} |
||||
} finally { |
||||
if (closeAfterDecode) { |
||||
ensureClosed(response.body()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static Response disconnectResponseBodyIfNeeded(Response response) throws IOException { |
||||
final boolean shouldDisconnectResponseBody = response.body() != null |
||||
&& response.body().length() != null |
||||
&& response.body().length() <= MAX_RESPONSE_BUFFER_SIZE; |
||||
if (!shouldDisconnectResponseBody) { |
||||
return response; |
||||
} |
||||
|
||||
try { |
||||
final byte[] bodyData = Util.toByteArray(response.body().asInputStream()); |
||||
return response.toBuilder().body(bodyData).build(); |
||||
} finally { |
||||
ensureClosed(response.body()); |
||||
} |
||||
} |
||||
|
||||
private Exception decodeError(String methodKey, Response response) { |
||||
try { |
||||
return errorDecoder.decode(methodKey, response); |
||||
} finally { |
||||
ensureClosed(response.body()); |
||||
} |
||||
} |
||||
|
||||
private boolean isVoidType(Type returnType) { |
||||
return returnType == Void.class |
||||
|| returnType == void.class |
||||
|| returnType.getTypeName().equals("kotlin.Unit"); |
||||
} |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
/* |
||||
* Copyright 2012-2023 The Feign 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 feign; |
||||
|
||||
import java.lang.reflect.ParameterizedType; |
||||
import java.lang.reflect.Type; |
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* An implementation of {@link ResponseInterceptor} the returns the value of the location header |
||||
* when appropriate. |
||||
*/ |
||||
public class RedirectionInterceptor implements ResponseInterceptor { |
||||
@Override |
||||
public Object intercept(InvocationContext invocationContext, Chain chain) throws Exception { |
||||
Response response = invocationContext.response(); |
||||
int status = response.status(); |
||||
Object returnValue = null; |
||||
if (300 <= status && status < 400 && response.headers().containsKey("Location")) { |
||||
Type returnType = rawType(invocationContext.returnType()); |
||||
Collection<String> locations = response.headers().get("Location"); |
||||
if (Collection.class.equals(returnType)) { |
||||
returnValue = locations; |
||||
} else if (String.class.equals(returnType)) { |
||||
if (locations.isEmpty()) { |
||||
returnValue = ""; |
||||
} else { |
||||
returnValue = locations.stream().findFirst().orElse(""); |
||||
} |
||||
} |
||||
} |
||||
if (returnValue == null) { |
||||
return chain.next(invocationContext); |
||||
} else { |
||||
response.close(); |
||||
return returnValue; |
||||
} |
||||
} |
||||
|
||||
private Type rawType(Type type) { |
||||
return type instanceof ParameterizedType ? ((ParameterizedType) type).getRawType() : type; |
||||
} |
||||
} |
Loading…
Reference in new issue