feign
feign copied to clipboard
[Default Client] Error responses are not deflated when using GZIP Encoding
Hi,
We've found a bug in FeignException.errorStatus(methodKey, feignResponse)
that in some cases, when the response body is gzip compressed, the parsed exception message contains raw byte array output which is illegible. This is due to the httpUrlConnection.getErrorStream();
being stored raw in the response object instead of being uncompressed before this storage happens.
Wouldn't it make more sense to either store the uncompressed stream in the response (and the rest of the code would work transparently) or, as an alternative, to at least adapt the FeignException.errorStatus
method to perform this decompression before constructing the exception message (if body is present and header "content-encoding" is "gzip")?
We we're forced to hack an ugly solution to take advantage of the exception message building aspects of FeignException
(since it's mostly static code and we can't set the body
in the response instead of rebuilding the whole object) and would like to avoid this entirely:
object FeignExceptionMapper {
private const val CONTENT_ENCODING_HEADER = "content-encoding"
private const val GZIP_ENCODING = "gzip"
fun toFeignException(methodKey: String, response: Response): FeignException {
var feignResponse = response
// If response body stream is compressed, needs to be uncompressed for legible exception message parsing
if (feignResponse.body() != null && feignResponse.headers().containsKey(CONTENT_ENCODING_HEADER) && feignResponse.headers()[CONTENT_ENCODING_HEADER]!!.contains(GZIP_ENCODING)) {
var gzipBodyInputStream : GZIPInputStream? = null
var uncompressedBodyOutputStream : ByteArrayOutputStream? = null
try {
gzipBodyInputStream = GZIPInputStream(feignResponse.body().asInputStream())
uncompressedBodyOutputStream = ByteArrayOutputStream()
var bytesRead = 0
val byteArray = ByteArray(1024)
while (bytesRead >= 0) {
bytesRead = gzipBodyInputStream.read(byteArray, 0, byteArray.size)
if (bytesRead > 0) {
uncompressedBodyOutputStream.write(byteArray, 0, bytesRead)
}
}
val uncompressedBody = uncompressedBodyOutputStream.toByteArray()
feignResponse = Response.builder().status(feignResponse.status()).request(feignResponse.request()).reason(feignResponse.reason()).headers(feignResponse.headers()).body(uncompressedBody).build()
} catch (e: IOException) {
logger.error(e) { "Could not uncompress gzipped response body for ${feignResponse.request().httpMethod()} request ${feignResponse.request().url()}" }
} finally {
Util.ensureClosed(gzipBodyInputStream)
Util.ensureClosed(uncompressedBodyOutputStream)
}
}
return FeignException.errorStatus(methodKey, feignResponse)
}
}
Hi Joao,
I think this is a problem with the client. I just noticed that the default client, only unzip body for success responses https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Client.java#L129
Do you think that is the case for you too?
Cheers
Yes, it seems it's related to that client behaviour! Nice find 👍
Hello there, I created a MR about this issue. See #2184 for details. Made some tests, based on other test case for gzip / deflate.