restTemplate使用和踩坑總結

平常工做中確定會遇到服務之間的調用,尤爲是如今都是微服務的架構,因此總結一下restTemplate的最經常使用的用法以及本身踩過的坑。html

restTemplate的使用

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

或者其餘掘金好文。

相關文章
相關標籤/搜索