feign icon indicating copy to clipboard operation
feign copied to clipboard

Support Response Interceptors

Open Guoyin-Wen opened this issue 5 years ago • 12 comments

In my project, I use the request interceptor to sign the request. I thought that there would be a corresponding response interceptor to verify the signature, but I didn't find it. Is there a better way?

Guoyin-Wen avatar Nov 30 '19 13:11 Guoyin-Wen

Short answer is cause nobody implemented it.

As workaround, you could use a customized decoded to validate your response and then delegate decoding to whatever decoder you need.

velo avatar Dec 04 '19 01:12 velo

I also could use a response interceptor: my use case is to capture some response headers and run some logic on them, possibly only if the response is successful (e.g. only when the status is 200 and the decoding throws no exceptions).

What would be the appropriate place to do such change, design-wise? Maybe in SynchronousMethodHandler.executeAndDecode(...), right after the decoder runs the method decoder.decode(response, metadata.returnType());? (I am not considering the asyncResponseHandler because it's marked as @Experimental)

mox601 avatar Apr 07 '20 07:04 mox601

@Experimental is just for the sake of informing API may change in a unpredictable way and break compatibility between minor releases.

Just to give us a little extra flexibility =)

So, RequestInterceptor happens right after encoder and before client is invoked... ResponseInterceptor then would happen after client, before decoder. So yes, SynchronousMethodHandler.executeAndDecode seems to be the right place, just not after decoder, but before.

Now, what are you trying to do that you can't do by chain decoders?

class MyResponseValidatorDecoder implements Decoder {
  private final Decoder realDecoder; //populated by constructor

  // inside decode method
{
  if(response.getStatus() == 302) return new RedirectObject(response);
  return realDecoder.decode();
}

}

//usage
FeignBuilder.decoder(new MyResponseValidatorDecoder(new JacksonDecoder()));

velo avatar Apr 07 '20 23:04 velo

How can i send another request when response in some status, and then retry request.

The steps like this.

request: biz -> reponse: access_token expire -> request: refresh access_token ->request: retry biz

qrqhuang avatar Aug 28 '20 08:08 qrqhuang

I've updated the title of this issue to better reflect the ask and added the proposal label. If this receives enough support we will consider this enhancement.

kdavisk6 avatar Dec 29 '20 19:12 kdavisk6

This would be a great feature if we can get it.

kpramesh2212 avatar Jan 11 '21 12:01 kpramesh2212

I too have a use case where I can leverage ResponseInterceptor functionality. Will be good to see this implemented, instead of performing a workaround.

naushadmohammed avatar Mar 09 '21 22:03 naushadmohammed

I too have a need for this kind of functionality even though my work around is working as well. The response interceptor just would need to happen before decoder. In this case all data is moved around in encrypted string so it is needed to be decrypted before say GsonDecoder can access and decode the json.

My current decryption decoder is like so if someone finds it useful:

`public class GsonDecryptionDecoder extends GsonDecoder {

@Override
public Object decode(Response response, Type type) throws IOException, FeignException {

    try (Reader reader = response.body().asReader(Charset.defaultCharset())) {

        String encryptedMessage = CharStreams.toString(reader);
        String decryptedBody = decryptJsonResp(encryptedMessage);

        Response decryptedResponse = Response.builder()
                .body(decryptedBody, Charset.defaultCharset())
                .headers(response.headers())
                .reason(response.reason())
                .status(response.status())
                .request(response.request())
                .build();

        return super.decode(decryptedResponse, type);
    } catch (Exception e) {
        throw new DecodeException(response.status(), "Failed to decode response", response.request(), e);
    }
}

}`

Bloof avatar Dec 07 '21 13:12 Bloof

I also need ResponseInterceptor, especially because decoder workaround is not usable for me - the decoder is not called for methods returning void. I need to validate response headers and ignore the response if it does not contain certain header.

ondrejpar avatar Mar 29 '22 22:03 ondrejpar

Hi @kdavisk6 , I also need ResponseInterceptor. Could you please consider enhancing it?

I need to catch a header datum in Response to validate it. The following solution is working, but It is ugly. Instead of this I want to write ResponseInterceptor to catch the header rate-limit datum.

At the moment, I appreciate any workaround helps.

Response response = myService.callMethod(request);

// for catching header data
Integer rateLimit = Integer.valueOf(response.headers().get("x-rate-limit-remaining").stream().findFirst().orElse("0")); 

// For getting body
Decoder.Default decoder = new Decoder.Default();
        CustomResponse customResponse;
        try {
            String body = (String) decoder.decode(response, String.class);
            ObjectMapper mapper = new ObjectMapper();
            customResponse = mapper.readValue(body, CustomResponse.class);
        } catch (Exception e) {
            log.error("Error occured during realty transfer response decoding. Error: {}", e.getMessage());
            return;
        }

Huge thnx

atesibrahim avatar Apr 09 '22 14:04 atesibrahim

We are open to evaluating any proposals for this.

Feel free to open a PR and tag me to review.

velo avatar Apr 09 '22 18:04 velo

Would be good to also be able to throw custom exceptions from decoder or response interceptor, currently all exceptions get wrapped in DecodeException.

PankajChandaTide avatar Aug 25 '22 06:08 PankajChandaTide