平常工做中确定会遇到服务之间的调用,尤为是如今都是微服务的架构,因此总结一下restTemplate的最经常使用的用法以及本身踩过的坑。html
restTemplate底层调用的是Execute方法,而Execute底层调用的是doExecute,它是基于http协议的,底层仍是httpClient 的使用。java
/**
* Execute the given method on the provided URI.
* <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
* the response with the {@link ResponseExtractor}.
* @param url the fully-expanded URL to connect to
* @param method the HTTP method to execute (GET, POST, etc.)
* @param requestCallback object that prepares the request (can be {@code null})
* @param responseExtractor object that extracts the return value from the response (can be {@code null})
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
@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 {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
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();
}
}
}
复制代码
咱们通常都是用的restTepmlate的exchange方法,这个方法比较灵活,能够接受可变参数,重载方法也有不少。 固然 restTemplate还有其余不少方法,并且遵循restFul风格,像PUT POST GET PATCH DELETE 等都有对应的方法,按需使用。这里就不贴源码了。git
而后就贴一个使用案例代码上来:github
public YourResponse sampleRestTepmlate (YourRequest request) throws Exception {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.serviceUrl);
builder.path("urlpath");
log.info("url : {}, request : {}", builder.toUriString(), JsonUtils.toJson(request));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("headername","headervalue");
headers.add("anotherway", "value");
HttpEntity<YourRequest> requestEntity = new HttpEntity<>(request, headers);
ResponseEntity<YourResponse> responseEntity = null;
try {
responseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, requestEntity,
YourResponse.class);
return responseEntity.getBody();
} catch (Exception e) {
log.error("exception:{}",e.getMessage());
}
}
复制代码
这里就要说一下我遇到的坑了。 在使用restTemplate的时候,当你的call没有成功返回200的时候,好比返回400 500之类的,restTemplate里面有一个DefaultResponseErrorHandler,他会自动拦截住这些httpstatus 为400 500的response而后给你抛出一个异常。这就意味着,当你也想拿到带有错误信息的response的时候,他不会给你!它会给你抛出exception而且只是给你返回一个简单的相似500 Internal error! WTF!spring
贴上这段坑爹的代码:bash
/**
* Handle the error in the given response with the given resolved status code.
* <p>This default implementation throws a {@link HttpClientErrorException} if the response status code
* is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException}
* if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR},
* and a {@link RestClientException} in other cases.
* @since 5.0
*/
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
}
}
复制代码
遇到了坑就不要惧怕,这个问题能够这么解决:架构
1.不用restTemplate去请求,能够采用httpClient底层去实现socket
2.重写handleError方法,自定义ErrorHandle继承DefaultResponseErrorHandleride
在已经写完实现以后,我选择方式2 : )微服务
@Builder
@Slf4j
public class MyErrorHandle extends DefaultResponseErrorHandler {
@Override
public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
int status = statusCode.value();
if (status == 200 || status == 400 || status == 500) {
//do what u want to do
} else {
super.handleError(response,statusCode);
}
}
}
复制代码
而后在初始化restTemplate的时候调用setErrorHandle方法就能够了。
restTemplate.setErrorHandler(YourErrorHandle).
复制代码
至于方式一这里不提了。
有的时候当咱们调用对方的server时,基于https 的协议是须要导入证书的,那咱们该怎么把证书融入到restTemplate中呢?(又一个坑)
@Bean
public RestTemplate buildRestTemplateWithinSSl(@Value("${service.connectTimeout}") int connectTimeout,
@Value("${service.readTimeout}") int readTimeout,
@Value("${service.sslFilePath}") String filePath,
@Value("${service.sslpassword}") String sslPassword) throws Exception{
RestTemplate template = restTemplateBuilder.setConnectTimeout(connectTimeout).setReadTimeout(readTimeout).build();
String workingDirectory = BeanUtility.getWorkingDirectory();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new File(workingDirectory + "/" + filePath), sslPassword.toCharArray()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
template.setRequestFactory(factory);
return template;
}
复制代码
至关于从新给RequestFactory值,构造一个已经带有ssl证书的factory给他。
这里注意两个地方:
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
复制代码
这里有个参数是NoopHostnameVerifier.INSTANCE, 这里是能够无视ip的,也就是ip或者域名形式均可以。 (适用于对方给我提供证书和 ip地址,试了半天死活不通的状况。。)
第二个就是一个工具类的使用,我相信不少时候new file的时候很容易被路径绕晕。
String workingDirectory = BeanUtility.getWorkingDirectory();
复制代码
这个工具类得到的路径不用你去担忧,只要你的jks文件和你的jar包同级就行。管他什么环境什么路径,很方便。
贴上地址: github.com/AnsonCong/A…
本地调试证书导入jdk就行。
记录下导入证书的方法:
keytool -import -alias {别名} -file {路径\证书名}.cer -keystore "{jdk路径\jre\lib\security\cacerts}" -storepass {password} -trustcacerts
复制代码
删除证书:
keytool -delete -alias {别名} -keystore "C:\Program Files\Java\jdk1.7.0_25\jre\lib\security\cacerts" -storepass {password}
复制代码
查看全部安装证书列表
keytool -list -v -keystore "C:\Users\1580977\Downloads\jdk1.8.0_101\jre\lib\security\cacerts" -storepass {password} >> C:\Desktop\abcd.txt
复制代码
生成jks文件 (没有默认生存,有就导入)
keytool -import -alias {别名} -file {证书名}.cer -keystore {命名}.jks
复制代码
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,可以大大提升客户端的编写效率。
更多restTemplate详细资料,能够参考: juejin.im/post/5b88b1… www.zifangsky.cn/1221.html
或者其余掘金好文。