在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");
}
}