在Feign中,Client是一个非常重要的组件,Feign最终发送Request请求以及接收Response响应都是由Client组件来完成的。Client在Feign源码中是一个接口,在默认情况下,Client的实现类是Client.Default。Client.Default是由HttpURLConnection来实现网络请求的。另外,Client还支持HttpClient和OkHttp3来进行网络请求。
HttpURLConnection没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection 。我们可以用Apache的HTTP Client替换Feign原始的http client, 从而获取连接池、超时时间等与性能息息相关的控制能力。
但是做Android的小伙伴早已经淘汰该库了,就是因为其API数量过多过于繁重,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,因而团队不愿意去维护该库.本章介绍的是由 Square 公司开发的OkHttp,是一个专注于性能和易用性的 HTTP 客户端。
OkHttp的优点
okhttp 的设计初衷就是简单和高效,这也是我们选择它的重要原因之一。它的优势如下:
- 支持 HTTP/2 协议。
- 允许连接到同一个主机地址的所有请求,提高请求效率。
- 共享Socket,减少对服务器的请求次数。
- 通过连接池,减少了请求延迟。
- 缓存响应数据来减少重复的网络请求。
- 减少了对数据流量的消耗。
- 自动处理GZip压缩。
Feign使用Okhttp
maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.1.0</version>
</dependency>
配置文件
feign.httpclient.enabled=false
feign.okhttp.enabled=true
配置
package com.haier.uhome.iot.api.config;
import com.haier.uhome.iot.api.interceptors.OkHttpInterceptor;
import feign.Feign;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@Slf4j
public class FeignClientConfig {
private OkHttpClient okHttpClient;
@Bean
public OkHttpInterceptor okHttpInterceptor(){
return new OkHttpInterceptor();
}
//注入okhttp
@Bean
public okhttp3.OkHttpClient okHttpClient(OkHttpClientFactory okHttpClientFactory,
FeignHttpClientProperties httpClientProperties) {
this.okHttpClient = okHttpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout(httpClientProperties.getConnectionTimeout(),TimeUnit.SECONDS)
.followRedirects(httpClientProperties.isFollowRedirects())
.addInterceptor(okHttpInterceptor())
.build();
return this.okHttpClient;
}
}
配置拦截器
package com.haier.uhome.iot.api.interceptors;
import com.haier.uhome.iot.api.utils.CommonConstant;
import com.haier.uhome.iot.api.utils.ElasticsearchClient;
import com.haier.uhome.iot.core.util.Jackson;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.Buffer;
import okio.BufferedSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
@Slf4j
public class OkHttpInterceptor implements HandlerInterceptor, Interceptor {
@Autowired
private ElasticsearchClient elasticsearchClient;
@Override
public Response intercept(Chain chain) throws IOException {
log.info("进入okhttp拦截器");
//编码设为UTF-8
Charset charset = Charset.forName("UTF-8");
try {
HashMap esCmdLogMap = new HashMap();
Request request = chain.request();
HashMap requestMap = this.getRequestBody(request, charset);
Response response = chain.proceed(request);
if(request.url().toString().contains("manage/command")) {
log.info(request.url().toString());
HashMap responseMap = this.getResponseBody(response, charset);
esCmdLogMap.put("requestUrl", request.url().toString());
esCmdLogMap.put("request", requestMap);
esCmdLogMap.put("deviceId", requestMap.get("deviceId"));
esCmdLogMap.put("productCode", requestMap.get("productCode"));
esCmdLogMap.put("response", responseMap);
long startTime = System.currentTimeMillis();
// LocalDateTime rightNow = LocalDateTime.now();
// String createTime = rightNow.format(DateTimeFormatter.ISO_DATE);
esCmdLogMap.put("cmdTime", startTime);
elasticsearchClient.postRquestAsync(CommonConstant.INDEX_DEVICE_CMD, CommonConstant.TYPE_DEVICE_HIST, Jackson.object2JsonStr(esCmdLogMap));
}
return response;
}catch (Exception e){
throw e;
}
}
private HashMap getResponseBody(Response response,Charset charset) throws IOException {
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
MediaType mediaType = responseBody.contentType();
if(isPlaintext(buffer)){
String result = buffer.clone().readString(mediaType.charset(charset));
HashMap resultMap = Jackson.jsonStr2Object(result,HashMap.class);
log.info("result:"+result);
return resultMap;
}
return null;
}
private HashMap getRequestBody(Request request ,Charset charset) {
RequestBody requestBody = request.body();
Buffer reqbuffer = new Buffer();
try {
requestBody.writeTo(reqbuffer);
} catch (IOException e) {
e.printStackTrace();
}
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(Charset.forName("UTF-8"));
}
//拿到request
return Jackson.jsonStr2Object(reqbuffer.readString(charset),HashMap.class);
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) throws EOFException {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
}