- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在使用 spring feign 压缩请求和响应
服务器端:
server:
servlet:
context-path: /api/v1/
compression:
enabled: true
min-response-size: 1024
'Accept-Encoding': "gzip, deflate, br"
server:
port: 8192
servlet:
context-path: /api/demo
feign.compression.response.enabled: true
feign.client.config.default.loggerLevel: HEADERS
logging.level.com.example.feigndemo.ManagementApiService: DEBUG
eureka:
client:
enabled: false
management-api:
ribbon:
listOfServers: localhost:8080
Accept-Encoding: deflate
Accept-Encoding: gzip
plugins {
id 'org.springframework.boot' version '2.1.8.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "Greenwich.SR2")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compile ('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
// https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient
// https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient
//compile group: 'io.github.openfeign', name: 'feign-httpclient', version: '9.5.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
最佳答案
几周前我遇到了同样的问题,我开始知道没有富有成效/直接的方法可以做到这一点。我也知道,当@patan 向 spring 社区报告问题时@patan reported issue1和 @patan reported issue2为 tomcat 端创建了一张票来尝试解决该问题 ( issue link )。 Jetty 端也有一张票( ticket link )与此相关。最初,我计划使用 github 中建议的方法。但是后来才知道这个库已经被合并到spring-cloud-openfeign-core
了 jar 下 org.springframework.cloud.openfeign.encoding
包裹。然而,我们无法按预期实现压缩,并面临以下两个挑战:
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingInterceptor
( code-link ) 类添加了 Accept-Encoding
标题为 gzip
和 deflate
但由于问题( ticket ),tomcat 服务器无法将其解释为压缩信号的标志。作为解决方案,我们必须添加手动 Feign 解释器来覆盖FeignAcceptGzipEncodingInterceptor
功能并连接标题。 Client calling microservice and that microservice calling another microservice through feign
然后 feign 无法处理压缩的响应,因为 Spring cloud open feign 解码器默认不解压响应( default spring open feign decoder ),最终以问题( issue link )结束。所以我们必须自己编写解码器来实现解压。 spring:
http:
encoding:
enabled: true
#to enable server side compression
server:
compression:
enabled: true
mime-types:
- application/json
min-response-size: 2048
#to enable feign side request/response compression
feign:
httpclient:
enabled: true
compression:
request:
enabled: true
mime-types:
- application/json
min-request-size: 2048
response:
enabled: true
注意 : 上面的feign配置我默认启用对所有feign客户端的压缩。
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
public class CustomGZIPResponseDecoder implements Decoder {
final Decoder delegate;
public CustomGZIPResponseDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
Collection<String> values = response.headers().get(HttpEncoding.CONTENT_ENCODING_HEADER);
if(Objects.nonNull(values) && !values.isEmpty() && values.contains(HttpEncoding.GZIP_ENCODING)){
byte[] compressed = Util.toByteArray(response.body().asInputStream());
if ((compressed == null) || (compressed.length == 0)) {
return delegate.decode(response, type);
}
//decompression part
//after decompress we are delegating the decompressed response to default
//decoder
if (isCompressed(compressed)) {
final StringBuilder output = new StringBuilder();
final GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressed));
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gis, StandardCharsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
output.append(line);
}
Response uncompressedResponse = response.toBuilder().body(output.toString().getBytes()).build();
return delegate.decode(uncompressedResponse, type);
}else{
return delegate.decode(response, type);
}
}else{
return delegate.decode(response, type);
}
}
private static boolean isCompressed(final byte[] compressed) {
return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8));
}
}
Feign自定义配置
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.optionals.OptionalDecoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomFeignConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
//concatenating headers because of https://github.com/spring-projects/spring-boot/issues/18176
@Bean
public RequestInterceptor gzipInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
template.header("Accept-Encoding", "gzip, deflate");
}
};
}
@Bean
public CustomGZIPResponseDecoder customGZIPResponseDecoder() {
OptionalDecoder feignDecoder = new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
return new CustomGZIPResponseDecoder(feignDecoder);
}
}
其他提示
feign-core
构建 CustomDecoder图书馆
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpMessageConverterExtractor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import static java.util.zip.GZIPInputStream.GZIP_MAGIC;
public class CustomGZIPResponseDecoder implements Decoder {
private final Decoder delegate;
public CustomGZIPResponseDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException {
Collection<String> values = response.headers().get("Content-Encoding");
if (Objects.nonNull(values) && !values.isEmpty() && values.contains("gzip")) {
byte[] compressed = Util.toByteArray(response.body().asInputStream());
if ((compressed == null) || (compressed.length == 0)) {
return delegate.decode(response, type);
}
if (isCompressed(compressed)) {
Response uncompressedResponse = getDecompressedResponse(response, compressed);
return getObject(type, uncompressedResponse);
} else {
return getObject(type, response);
}
} else {
return getObject(type, response);
}
}
private Object getObject(Type type, Response response) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (response.status() == 404 || response.status() == 204)
return Util.emptyValueOf(type);
if (Objects.isNull(response.body()))
return null;
if (byte[].class.equals(type))
return Util.toByteArray(response.body().asInputStream());
if (isParameterizeHttpEntity(type)) {
type = ((ParameterizedType) type).getActualTypeArguments()[0];
if (type instanceof Class || type instanceof ParameterizedType
|| type instanceof WildcardType) {
@SuppressWarnings({"unchecked", "rawtypes"})
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
type, Collections.singletonList(new MappingJackson2HttpMessageConverter(mapper)));
Object decodedObject = extractor.extractData(new FeignResponseAdapter(response));
return createResponse(decodedObject, response);
}
throw new DecodeException(HttpStatus.INTERNAL_SERVER_ERROR.value(),
"type is not an instance of Class or ParameterizedType: " + type);
} else if (isHttpEntity(type)) {
return delegate.decode(response, type);
} else if (String.class.equals(type)) {
String responseValue = Util.toString(response.body().asReader());
return StringUtils.isEmpty(responseValue) ? Util.emptyValueOf(type) : responseValue;
} else {
String s = Util.toString(response.body().asReader());
JavaType javaType = TypeFactory.defaultInstance().constructType(type);
return !StringUtils.isEmpty(s) ? mapper.readValue(s, javaType) : Util.emptyValueOf(type);
}
}
public static boolean isCompressed(final byte[] compressed) {
return (compressed[0] == (byte) (GZIP_MAGIC)) && (compressed[1] == (byte) (GZIP_MAGIC >> 8));
}
public static Response getDecompressedResponse(Response response, byte[] compressed) throws IOException {
final StringBuilder output = new StringBuilder();
final GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressed));
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gis, StandardCharsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
output.append(line);
}
return response.toBuilder().body(output.toString().getBytes()).build();
}
public static String getDecompressedResponseAsString(byte[] compressed) throws IOException {
final StringBuilder output = new StringBuilder();
final GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressed));
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gis, StandardCharsets.UTF_8));
String line;
while ((line = bufferedReader.readLine()) != null) {
output.append(line);
}
return output.toString();
}
private boolean isParameterizeHttpEntity(Type type) {
if (type instanceof ParameterizedType) {
return isHttpEntity(((ParameterizedType) type).getRawType());
}
return false;
}
private boolean isHttpEntity(Type type) {
if (type instanceof Class) {
Class c = (Class) type;
return HttpEntity.class.isAssignableFrom(c);
}
return false;
}
private <T> ResponseEntity<T> createResponse(Object instance, Response response) {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
for (String key : response.headers().keySet()) {
headers.put(key, new LinkedList<>(response.headers().get(key)));
}
return new ResponseEntity<>((T) instance, headers, HttpStatus.valueOf(response
.status()));
}
private class FeignResponseAdapter implements ClientHttpResponse {
private final Response response;
private FeignResponseAdapter(Response response) {
this.response = response;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.valueOf(this.response.status());
}
@Override
public int getRawStatusCode() throws IOException {
return this.response.status();
}
@Override
public String getStatusText() throws IOException {
return this.response.reason();
}
@Override
public void close() {
try {
this.response.body().close();
} catch (IOException ex) {
// Ignore exception on close...
}
}
@Override
public InputStream getBody() throws IOException {
return this.response.body().asInputStream();
}
@Override
public HttpHeaders getHeaders() {
return getHttpHeaders(this.response.headers());
}
private HttpHeaders getHttpHeaders(Map<String, Collection<String>> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
for (Map.Entry<String, Collection<String>> entry : headers.entrySet()) {
httpHeaders.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return httpHeaders;
}
}
}
如果您打算构建自己的 Feign 构建器,那么您可以像下面这样配置
Feign.builder().decoder(new CustomGZIPResponseDecoder(new feign.optionals.OptionalDecoder(new feign.codec.StringDecoder())))
.target(SomeFeignClient.class, "someurl");
更新上述答案:
spring-cloud-openfeign-core
的依赖版本至
'org.springframework.cloud:spring-cloud-openfeign-core:2.2.5.RELEASE'
然后意识到
FeignContentGzipEncodingAutoConfiguration class 中的以下变化.
FeignContentGzipEncodingAutoConfiguration
类的签名
ConditionalOnProperty
注释从
@ConditionalOnProperty("feign.compression.request.enabled", matchIfMissing = false)
至
@ConditionalOnProperty(value = "feign.compression.request.enabled")
, 所以默认情况下
FeignContentGzipEncodingInterceptor
如果您有应用程序属性
feign.request.compression=true
,bean 将被注入(inject)到 spring 容器中如果超出默认/配置的大小限制,则在您的环境中压缩请求正文。如果您的服务器没有处理压缩请求的机制,这会导致问题,在这种情况下,添加/修改属性为
feign.request.compression=false
关于Spring Feign 不压缩响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57831707/
有什么区别: spring-cloud-starter-openfeign ( https://github.com/spring-cloud/spring-cloud-openfeign ) 和 s
我们正在使用netflix feign来调用 Restful Web服务。对于补丁请求,看来不支持PATCH请求。 Caused by: feign.RetryableException: Inval
据我了解,当请求响应状态码!= 2xx 时,将调用 feign ErrorDecoder 的 decode() 方法。通过调试我的测试,我发现我的 CustomErrorDecoder 的 decod
我有一个像这样构建的假客户端服务: Feign.Builder builder = Feign.builder() .contract(new SpringMvcContract())
使用spring cloud feign调用我的服务,当服务返回401异常时,respose.body()一片空白。 当我抛出异常时 throw new BadRequestException(400
我使用 ErrorDecoder 返回正确的异常而不是 500 状态代码。 有没有办法在解码器中检索原始消息。我可以看到它在 FeignException 中,但不在 decode 方法中。我所拥有的
通过学习曲线,遇到了这个场景: 鉴于90%的调用都是JSON,在构建客户端时添加了GSON解码器。不过接口(interface)中有些方法调用应该是支持raw return without decod
我正在尝试使用以下 Feign 客户端在 Spring Boot 应用程序中检索在线图像内容。 @FeignClient(name = "image") public interface ImageC
我正在使用需要设置几个字段的 REST api。我的应用程序应始终将某些字段设置为相同的值。是否可以在带有 feign 定义(或其他地方)的界面中使这些值“硬编码”? 我的假声明看起来像这个例子。假设
我是新来的 Spring 和假装和探索几天以来。我能够向我们的 protected 资源(用户名/密码)发出身份验证请求,并在后续请求 header 中使用身份验证服务返回的 JWT token 。但
我尝试在我的 Spring Boot 应用程序上配置 OpenFeign,我使用 pokeapi 进行测试。 我编写了这段代码: @FeignClient(value = "pokeapi", url
快速浏览了Feign的源码,发现Feign使用的是JDK的HttpUrlConnection在不使用连接池的情况下发出 HTTP 请求并在请求完成时关闭它。我怀疑这种方式的效率。然后我看了Spring
我为 feignClients 启用了我的 spring 云,如下所示: @Configuration @EnableAutoConfiguration @RestController @Enable
当我发帖时 Map使用 Feign Client,我收到错误消息: feign.FeignException: status 400 reading MAp . 代码 //Client side @C
目录 使用HttpClient和OkHttp 使用HttpClient 使用OkHttp OpenFeign替换为OkHtt
需求 最近小编的项目中出现了很多feign 调用出现 Read Time out 的异常,但因为没有集成链路追踪的第三方框架,查不到原因。 所以想到打印请求的ip地址,判断是指定的服务器出现的问
我正在使用 spring feign 压缩请求和响应 服务器端: server: servlet: context-path: /api/v1/ compression: en
我面临以下情况,令我惊讶的是,我找不到太多文档:有一项服务仅通过一一获取项目详细信息来提供休息调用。总共有 1000 多个项目。 出于响应能力的原因,我想将这些数据保留在我的一端,而不是懒惰地获取它。
我正在尝试实现一个涉及 FeignClient 调用的单元测试,该调用应返回 404 Not Found。 由于 Feign 抛出了 404 异常,那么实现此测试用例的正确方法是什么? 我正在尝试这样
当使用spring-cloud-feign连接到本地主机时,Feign找不到url,而是给我以下错误消息。 2019-11-13 12:01:55.553 ERROR 23798 --- [n
我是一名优秀的程序员,十分优秀!