RestTemplate介紹
是用于同步client端的核心類,簡化了與
Spring
服務的通信,并滿足
http
原則,程式代碼可以給它提供URL,并提取結果。預設情況下,
RestFul
預設依賴jdk的HTTP連接配接工具。當然你也可以 通過
RestTemplate
屬性切換到不同的HTTP源,比如
setRequestFactory
、
Apache HttpComponents
和
Netty
。
OkHttp
RestTemplate能大幅簡化了送出表單資料的難度,并且附帶了自動轉換JSON資料的功能,但隻有了解了HttpEntity的組成結構(header與body),且了解了與uriVariables之間的差異,才能真正掌握其用法。
其中:
預設使用
RestTemplate
執行個體将
HttpMessageConverter
消息轉換成
HTTP
或者從
POJO
轉換成
POJO
消息。預設情況下會注冊主
HTTP
類型的轉換器,但也可以通過
mime
注冊自定義轉換器。
setMessageConverters
使用了預設的
RestTemplate
,對40X
DefaultResponseErrorHandler
或50X
Bad Request
異常
internal
等錯誤資訊捕捉。
error
還可以使用攔截器
RestTemplate
,進行對請求連結跟蹤,以及統一head的設定。
interceptor
RestTemplate
類是在 Spring Framework 3.0 開始引入的,這裡我們使用的 Spring 版本為目前最新的 GA 版本 5.1.6。而在 5.0 以上,官方标注了更推薦使用非阻塞的響應式 HTTP 請求處理類
org.springframework.web.reactive.client.WebClient
來替代
RestTemplate
,尤其是對應異步請求處理的場景上 。
RestTemplate
類提供的 API 有哪些,
RestTemplate
提供了将近 30 個請求方法,其中多數是單個方法重載實作,這裡我主要參考官方文檔
rest-client-access進行如下分類:
方法 | 解析 |
delete() | 在特定的URL上對資源執行HTTP DELETE操作 |
exchange() | 在URL上執行特定的HTTP方法,傳回包含對象的ResponseEntity |
execute() | 在URL上執行特定的HTTP方法,傳回一個從響應體映射得到的對象 |
getForEntity() | 發送一個HTTP GET請求,傳回的ResponseEntity包含了響應體所映射成的對象 |
getForObject() | 發送一個HTTP GET請求,傳回的請求體将映射為一個對象 |
postForEntity() | POST 資料到一個URL,傳回包含一個對象的ResponseEntity |
postForObject() | POST 資料到一個URL,傳回根據響應體比對形成的對象 |
headForHeaders() | 發送HTTP HEAD請求,傳回包含特定資源URL的HTTP頭 |
optionsForAllow() | 發送HTTP OPTIONS請求,傳回對特定URL的Allow頭資訊 |
postForLocation() | POST 資料到一個URL,傳回新建立資源的URL |
put() | PUT 資源到特定的URL |
RestTemplate簡單使用
GET 請求
不帶任何參數 的 GET 請求
// 一個不帶任何參數 的 GET 請求
@Test
public void testGet_product1() {
String url = "http://localhost:8080/product/get_product1";
//方式一:GET 方式擷取 JSON 串資料
String result = restTemplate.getForObject(url, String.class);
System.out.println("get_product1傳回結果:" + result);
Assert.hasText(result, "get_product1傳回結果為空");
//方式二:GET 方式擷取 JSON 資料映射後的 Product 實體對象
Product product = restTemplate.getForObject(url, Product.class);
System.out.println("get_product1傳回結果:" + product);
Assert.notNull(product, "get_product1傳回結果為空");
//方式三:GET 方式擷取包含 Product 實體對象 的響應實體 ResponseEntity 對象,用 getBody() 擷取
ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class);
System.out.println("get_product1傳回結果:" + responseEntity);
Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product1響應不成功");
}

帶有參數的 GET 請求
@Test
public void testGet_product2() {
String url = "http://localhost:8080/product/get_product2/id={id}";
//方式一:将參數的值存在可變長度參數裡,按照順序進行參數比對
ResponseEntity<Product> responseEntity = restTemplate.getForEntity(url, Product.class, 101);
System.out.println(responseEntity);
Assert.isTrue(responseEntity.getStatusCode().equals(HttpStatus.OK), "get_product2 請求不成功");
Assert.notNull(responseEntity.getBody().getId(), "get_product2 傳遞參數不成功");
//方式二:将請求參數以鍵值對形式存儲到 Map 集合中,用于請求時URL上的拼接
Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("id", 101);
Product result = restTemplate.getForObject(url, Product.class, uriVariables);
System.out.println(result);
Assert.notNull(result.getId(), "get_product2 傳遞參數不成功");
}
getForEntity()方法
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables){}
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables){}
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType){}
與getForObject()方法不同的是傳回的是對象,如果需要轉換成pojo,還需要json工具類的引入,這個按個人喜好用。不會解析json的可以百度
ResponseEntity
或者
FastJson
等工具類。然後我們就研究一下
Jackson
下面有啥方法。
ResponseEntity
ResponseEntity、HttpStatus、BodyBuilder結構
ResponseEntity.java
public HttpStatus getStatusCode(){}
public int getStatusCodeValue(){}
public boolean equals(@Nullable Object other) {}
public String toString() {}
public static BodyBuilder status(HttpStatus status) {}
public static BodyBuilder ok() {}
public static <T> ResponseEntity<T> ok(T body) {}
public static BodyBuilder created(URI location) {}
...
HttpStatus.java
public enum HttpStatus {
public boolean is1xxInformational() {}
public boolean is2xxSuccessful() {}
public boolean is3xxRedirection() {}
public boolean is4xxClientError() {}
public boolean is5xxServerError() {}
public boolean isError() {}
}
BodyBuilder.java
public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
//設定正文的長度,以位元組為機關,由Content-Length标頭
BodyBuilder contentLength(long contentLength);
//設定body的MediaType 類型
BodyBuilder contentType(MediaType contentType);
//設定響應實體的主體并傳回它。
<T> ResponseEntity<T> body(@Nullable T body);
}
可以看出來,ResponseEntity包含了HttpStatus和BodyBuilder的這些資訊,這更友善我們處理response原生的東西。
POST 請求
發送 Content-Type
為 application/x-www-form-urlencoded
的 POST 請求:
Content-Type
application/x-www-form-urlencoded
@Test
public void testPost_product1() {
String url = "http://localhost:8080/product/post_product1";
Product product = new Product(201, "Macbook", BigDecimal.valueOf(10000));
// 設定請求的 Content-Type 為 application/x-www-form-urlencoded
MultiValueMap<String, String> header = new LinkedMultiValueMap();
header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
//方式二: 将請求參數值以 K=V 方式用 & 拼接,發送請求使用
String productStr = "id=" + product.getId() + "&name=" + product.getName() + "&price=" + product.getPrice();
HttpEntity<String> request = new HttpEntity<>(productStr, header);
ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
System.out.println("post_product1: " + exchangeResult);
Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 請求不成功");
//方式一: 将請求參數以鍵值對形式存儲在 MultiValueMap 集合,發送請求時使用
MultiValueMap<String, Object> map = new LinkedMultiValueMap();
map.add("id", (product.getId()));
map.add("name", (product.getName()));
map.add("price", (product.getPrice()));
HttpEntity<MultiValueMap> request2 = new HttpEntity<>(map, header);
ResponseEntity<String> exchangeResult2 = restTemplate.exchange(url, HttpMethod.POST, request2, String.class);
System.out.println("post_product1: " + exchangeResult2);
Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product1 請求不成功");
}
Content-Type
application/json
Content-Type
application/json
@Test
public void testPost_product2() {
String url = "http://localhost:8080/product/post_product2";
// 設定請求的 Content-Type 為 application/json
MultiValueMap<String, String> header = new LinkedMultiValueMap();
header.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
// 設定 Accept 向伺服器表明用戶端可處理的内容類型
header.put(HttpHeaders.ACCEPT, Arrays.asList(MediaType.APPLICATION_JSON_VALUE));
// 直接将實體 Product 作為請求參數傳入,底層利用 Jackson 架構序列化成 JSON 串發送請求
HttpEntity<Product> request = new HttpEntity<>(new Product(2, "Macbook", BigDecimal.valueOf(10000)), header);
ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
System.out.println("post_product2: " + exchangeResult);
Assert.isTrue(exchangeResult.getStatusCode().equals(HttpStatus.OK), "post_product2 請求不成功");
}
RestTemplate源碼
1.1 預設調用鍊路
restTemplate
進行API調用時,預設調用鍊:
###########1.使用createRequest建立請求########
resttemplate->execute()->doExecute()
HttpAccessor->createRequest()
//擷取攔截器Interceptor,InterceptingClientHttpRequestFactory,SimpleClientHttpRequestFactory
InterceptingHttpAccessor->getRequestFactory()
//擷取預設的SimpleBufferingClientHttpRequest
SimpleClientHttpRequestFactory->createRequest()
#######2.擷取響應response進行處理###########
AbstractClientHttpRequest->execute()->executeInternal()
AbstractBufferingClientHttpRequest->executeInternal()
###########3.異常處理#####################
resttemplate->handleResponse()
##########4.響應消息體封裝為java對象#######
HttpMessageConverterExtractor->extractData()
1.2 restTemplate->doExecute()
在預設調用鍊中,
restTemplate
進行API調用都會調用
doExecute
方法,此方法主要可以進行如下步驟:
1)使用
createRequest
建立請求,擷取響應
2)判斷響應是否異常,處理異常
3)将響應消息體封裝為java對象
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
//使用createRequest建立請求
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
//擷取響應response進行處理
response = request.execute();
//異常處理
handleResponse(url, method, response);
//響應消息體封裝為java對象
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}finally {
if (response != null) {
response.close();
}
}
}
1.3 InterceptingHttpAccessor->getRequestFactory()
方法中,如果沒有設定
InterceptingHttpAccessor的getRequestFactory()
攔截器,就傳回預設的
interceptor
,反之,傳回
SimpleClientHttpRequestFactory
的
InterceptingClientHttpRequestFactory
,可以通過
requestFactory
設定自定義攔截器
resttemplate.setInterceptors
interceptor
//Return the request factory that this accessor uses for obtaining client request handles.
public ClientHttpRequestFactory getRequestFactory() {
//擷取攔截器interceptor(自定義的)
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
然後再調用
SimpleClientHttpRequestFactory的createRequest
建立連接配接:
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
1.4 resttemplate->handleResponse()
,響應處理,包括異常處理,而且異常處理可以通過調用
resttemplate的handleResponse
方法設定自定義的
setErrorHandler
,實作對請求響應異常的判别和處理。自定義的
ErrorHandler
需實作
ErrorHandler
接口,同時
ResponseErrorHandler
也提供了預設實作
Spring boot
,是以也可以通過繼承該類來實作自己的
DefaultResponseErrorHandler
ErrorHandler
預設對40X
DefaultResponseErrorHandler
Bad Request
internal
等錯誤資訊捕捉。如果想捕捉服務本身抛出的異常資訊,需要通過自行實作
error
RestTemplate
ErrorHandler
ResponseErrorHandler errorHandler = getErrorHandler();
//判斷響應是否有異常
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
int code = response.getRawStatusCode();
HttpStatus status = HttpStatus.resolve(code);
logger.debug("Response " + (status != null ? status : code));
}catch (IOException ex) {
// ignore
}
}
//有異常進行異常處理
if (hasError) {
errorHandler.handleError(url, method, response);
}
}
1.5 HttpMessageConverterExtractor->extractData()
HttpMessageConverterExtractor
中進行響應消息體封裝為
extractData
對象,就需要使用
java
轉換器,可以通過追加的方式增加自定義的
message
:先擷取現有的
messageConverter
,再将自定義的
messageConverter
添加進去。
messageConverter
根據
restTemplate
setMessageConverters
的源碼可得,使用追加的方式可防止原有的
messageConverter
丢失,源碼:
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
//檢驗
validateConverters(messageConverters);
// Take getMessageConverters() List as-is when passed in here
if (this.messageConverters != messageConverters) {
//先清除原有的messageConverter
this.messageConverters.clear();
//後加載重新定義的messageConverter
this.messageConverters.addAll(messageConverters);
}
}
HttpMessageConverterExtractor的extractData
源碼:
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
//擷取到response的ContentType類型
MediaType contentType = getContentType(responseWrapper);
try {
//依次循環messageConverter進行判斷是否符合轉換條件,進行轉換java對象
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//會根據設定的傳回類型responseType和contentType參數進行比對,選擇合适的MessageConverter
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
if (this.responseClass != null) {
if (messageConverter.canRead(this.responseClass, contentType)) {
if (logger.isDebugEnabled()) {
String className = this.responseClass.getName();
logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
}
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
}
.....
}
1.6 contentType與messageConverter之間的關系
在
HttpMessageConverterExtractor
extractData
方法中看出,會根據
contentType
與
responseClass
選擇
messageConverter
是否可讀、消息轉換。關系如下:
類名 | 支援的JavaType | 支援的MediaType |
ByteArrayHttpMessageConverter | byte[] | application/octet-stream, */* |
StringHttpMessageConverter | String | text/plain, */* |
ResourceHttpMessageConverter | Resource | */* |
SourceHttpMessageConverter | Source | application/xml, text/xml, application/*+xml |
AllEncompassingFormHttpMessageConverter | Map<K, List<?>> | application/x-www-form-urlencoded, multipart/form-data |
MappingJackson2HttpMessageConverter | Object | application/json, application/*+json |
Jaxb2RootElementHttpMessageConverter | ||
JavaSerializationConverter | Serializable | x-java-serialization;charset=UTF-8 |
FastJsonHttpMessageConverter |
springboot內建RestTemplate
根據上述源碼的分析學習,可以輕松,簡單地在項目進行對RestTemplate進行優雅地使用,比如增加自定義的異常處理、
MessageConverter
以及攔截器
interceptor
。本文使用示例
demo
,詳情請檢視接下來的内容。
1.1. 導入依賴:(RestTemplate內建在Web Start中)
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
1.2. RestTemplat配置:
- 使用
屬性配置RestTemplat參數,比如
ClientHttpRequestFactory
,
ConnectTimeout
;
ReadTimeout
- 增加自定義的
攔截器和異常處理;
interceptor
- 追加
轉換器;
message
- 配置自定義的異常處理.
@Configuration
public class RestTemplateConfig {
@Value("${resttemplate.connection.timeout}")
private int restTemplateConnectionTimeout;
@Value("${resttemplate.read.timeout}")
private int restTemplateReadTimeout;
@Bean
//@LoadBalanced
public RestTemplate restTemplate( ClientHttpRequestFactory simleClientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate();
//配置自定義的message轉換器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.add(new CustomMappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
//配置自定義的interceptor攔截器
List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new HeadClientHttpRequestInterceptor());
interceptors.add(new TrackLogClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
//配置自定義的異常處理
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
restTemplate.setRequestFactory(simleClientHttpRequestFactory);
return restTemplate;
}
@Bean
public ClientHttpRequestFactory simleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory reqFactory= new SimpleClientHttpRequestFactory();
reqFactory.setConnectTimeout(restTemplateConnectionTimeout);
reqFactory.setReadTimeout(restTemplateReadTimeout);
return reqFactory;
}
}
1.3. 元件(自定義異常處理、interceptor攔截器、message轉化器)
自定義攔截器,實作
interceptor
接口
ClientHttpRequestInterceptor
,記錄
TrackLogClientHttpRequestInterceptor
resttemplate
request
資訊,可進行追蹤分析;
response
,設定請求頭的參數。API發送各種請求,很多請求都需要用到相似或者相同的Http Header。如果在每次請求之前都把
HeadClientHttpRequestInterceptor
填入
Header
,這樣的代碼會顯得十分備援,可以在攔截器統一設定。
HttpEntity/RequestEntity
TrackLogClientHttpRequestInterceptor:
/**
* @Date: 2019/10/25 22:48,記錄resttemplate通路資訊
* @Description: 記錄resttemplate通路資訊
*/
@Slf4j
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
trackRequest(request,body);
ClientHttpResponse httpResponse = execution.execute(request, body);
trackResponse(httpResponse);
return httpResponse;
}
private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
log.info("============================response begin==========================================");
log.info("Status code : {}", httpResponse.getStatusCode());
log.info("Status text : {}", httpResponse.getStatusText());
log.info("Headers : {}", httpResponse.getHeaders());
log.info("=======================response end=================================================");
}
private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
log.info("======= request begin ========");
log.info("uri : {}", request.getURI());
log.info("method : {}", request.getMethod());
log.info("headers : {}", request.getHeaders());
log.info("request body : {}", new String(body, "UTF-8"));
log.info("======= request end ========");
}
}
HeadClientHttpRequestInterceptor:
@Slf4j
public class HeadClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
log.info("#####head handle########");
HttpHeaders headers = httpRequest.getHeaders();
headers.add("Accept", "application/json");
headers.add("Accept-Encoding", "gzip");
headers.add("Content-Encoding", "UTF-8");
headers.add("Content-Type", "application/json; charset=UTF-8");
ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, bytes);
HttpHeaders headersResponse = response.getHeaders();
headersResponse.add("Accept", "application/json");
return response;
}
}
自定義異常處理
可繼承
DefaultResponseErrorHandler
或者實作
ResponseErrorHandler
接口:
- 實作自定義
的思路是根據響應消息體進行相應的異常處理政策,對于其他異常情況由父類
ErrorHandler
來進行處理。
DefaultResponseErrorHandler
進行30x異常處理
CustomResponseErrorHandler
CustomResponseErrorHandler:
/**
* @Description: 30X的異常處理
*/
@Slf4j
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
if(statusCode.is3xxRedirection()){
return true;
}
return super.hasError(response);
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = response.getStatusCode();
if(statusCode.is3xxRedirection()){
log.info("########30X錯誤,需要重定向!##########");
return;
}
super.handleError(response);
}
}
自定義message轉化器
/**
* @Description: 将Content-Type:"text/html"轉換為Map類型格式
*/
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
public CustomMappingJackson2HttpMessageConverter() {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaType.TEXT_PLAIN);
mediaTypes.add(MediaType.TEXT_HTML); //加入text/html類型的支援
setSupportedMediaTypes(mediaTypes);// tag6
}
}
RestTemplate工具類
package com.fly.apigateway;
package com.devicemag.core.utils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
/**
* @Title: restTemplateUtils
* @ClassName: com.fly.apigateway.RestTemplateUtils.java
* @Description:
*
* @Copyright 2016-2019 - Powered By 研發中心
* @author: 王延飛
* @date: 2020/4/10 18:25
* @version V1.0
*/
@Slf4j
public class RestTemplateUtils {
/**
* 讀取時間,自定義預設8s,0表示沒有逾時時間
*/
public static final int READ_TIMEOUT = 1000*8;
/**
* 連接配接時間,自定義預設8s,0表示沒有逾時時間
*/
public static final int CONNEC_TIMEOUT = 1000*8;
/**
* 重試次數,自定義預設1
*/
public static final int RETRY_COUNT = 1;
/**
* http 請求 GET
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String getHttp(String url, JSONObject params) {
String result = getHttp(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* http 請求 GET
*
* @param url 位址
* @param params 參數
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String getHttp(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理
url = expandURL(url, params);
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.getForObject(url, String.class, params);
log.info("【GET/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【GET/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* https 請求 GET
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String getHttps(String url, JSONObject params) {
String result = getHttps(url, params, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* https 請求 GET
*
* @param url 位址
* @param params 參數
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String getHttps(String url, JSONObject params, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); //error處理
restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 繞過https
url = expandURL(url, params);
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.getForObject(url, String.class, params);
log.info("【GET/HTTPS請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【GET/HTTPS請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* http 請求 post/JSON
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String postHttp(String url, JSONObject params, Map headersMap) {
String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* http請求 post/JSON
*
* @param url 位址
* @param params 參數
* @param headersMap header
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String postHttp(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
//内部實際實作為 HttpClient
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
// 設定·header資訊
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAll(headersMap);
HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.postForObject(url, requestEntity, String.class);
log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【POST/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* http請求 post/MAP
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String postHttp(String url, MultiValueMap params, Map headersMap) {
String result = postHttp(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* http 普通請求 post/MAP
* @param url 位址
* @param params MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
* @param headersMap header
* @param connecTimeout 連接配接時間
* @param readTimeout 讀取時間
* @param retryCount 重試機制
* @return String 類型
*/
public static String postHttp(String url, MultiValueMap params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
//内部實際實作為 HttpClient
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
// 設定·header資訊
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAll(headersMap);
HttpEntity<Map> requestEntity = new HttpEntity<Map>(params, requestHeaders); // json utf-8 格式
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.postForObject(url, requestEntity, String.class);
log.info("【POST/HTTP請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【POST/HTTP請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* https 普通請求 post/JSON
*
* @param url 位址
* @param params 參數
* @return String 類型
*/
public static String postHttps(String url, JSONObject params, Map headersMap) {
String result = postHttps(url, params,headersMap, READ_TIMEOUT, CONNEC_TIMEOUT, RETRY_COUNT);
return result;
}
/**
* https 普通請求 post/JSON
* @param url 請求位址
* @param params 請求 josn 格式參數
* @param headersMap headers 頭部需要參數
* @param retryCount 重試機制
* @return 傳回string類型傳回值
*/
public static String postHttps(String url, JSONObject params, Map headersMap, int connecTimeout, int readTimeout, int retryCount) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); // 時間函數
requestFactory.setConnectTimeout(connecTimeout);
requestFactory.setReadTimeout(readTimeout);
//内部實際實作為 HttpClient
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 設定編碼集
restTemplate.setRequestFactory(new HttpsClientRequestFactory()); // 繞過https
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 異常處理的headers error 處理
// 設定·header資訊
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAll(headersMap);
HttpEntity<JSONObject> requestEntity = new HttpEntity<JSONObject>(params, requestHeaders); // josn utf-8 格式
String result = null; // 傳回值類型;
for (int i = 1; i <= retryCount; i++) {
try {
log.info("【POST/HTTPS請求資訊】,請求位址:{},請求參數:{}", url, params);
result = restTemplate.postForObject(url, requestEntity, String.class);
log.info("【POST/HTTPS請求資訊】,請求位址:{},請求參數:{},傳回結果:{}", url, params,result);
return result;
} catch (Exception e) {
log.error("【POST/HTTPS請求資訊】異常,重試count:{},請求位址:{},請求參數:{},異常資訊:{}", i, url, params,e);
e.printStackTrace();
}
}
return result;
}
/**
* @Title: URL拼接
* @MethodName: expandURL
* @param url
* @param jsonObject
* @Return java.lang.String
* @Exception
* @Description:
*
* @author: 王延飛
* @date: 2021/1/4 15:30
*/
private static String expandURL(String url,JSONObject jsonObject) {
StringBuilder sb = new StringBuilder(url);
sb.append("?");
Set<String> keys = jsonObject.keySet();
for (String key : keys) {
sb.append(key).append("=").append(jsonObject.getString(key)).append("&");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
/**
* 出現異常,可自定義
*/
private static class DefaultResponseErrorHandler implements ResponseErrorHandler {
/**
* 對response進行判斷,如果是異常情況,傳回true
*/
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().value() != HttpServletResponse.SC_OK;
}
/**
* 異常情況時的處理方法
*/
@Override
public void handleError(ClientHttpResponse response) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(response.getBody()));
StringBuilder sb = new StringBuilder();
String str = null;
while ((str = br.readLine()) != null) {
sb.append(str);
}
try {
throw new Exception(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.security.cert.X509Certificate;
/**
* @Title: Https 繞過
* @Description:
*
* @Copyright 2020-2021 - Powered By 研發中心
* @author: 王延飛
* @date: 2020/9/10 0010 9:01
* @version V1.0
*/
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
*/
// SSLSocketFactory用于建立 SSLSockets
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
// 傳回預設啟用的密碼套件。除非一個清單啟用,對SSL連接配接的握手會使用這些密碼套件。
// 這些預設的服務的最低品質要求保密保護和伺服器身份驗證
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
// 傳回的密碼套件可用于SSL連接配接啟用的名字
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port,
final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"});
return socket;
}
}
}
參考連結
https://juejin.im/post/5db99c285188257e435592ac https://docs.spring.io/spring-framework/docs/4.3.7.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html