閒聊:
這次需求需要後臺服務以http方式調用另一個服務,我用的spring的RestTemplate,先是通過本地配置host方法成功調成功,但是後面接入公司的mesh網關後(調用方所在服務器不需要配置host),連接失敗了,排查了下發現是RestTemplate的header格式爲key:[value1,value2]導致,而我們公司的網關對header格式有強要求導致。。。
在使用RestTemplate的時候,除非直接使用execute方法,其他的如post…,get…,exchange,put,patchForObject等方法,其header都會是 key:[value1,value2] 的格式,而不是一般的 key:value 格式
爲什麼格式會是key:[value1,value2]格式
這些方法最終都是調用的execute方法,在調用前,會對請求參數 RequestCallback 進行配置後再傳入execute方法,用postForObject舉個例子,httpEntityCallback方法就是配置RequestCallback,再傳入到execute方法
@Override
@Nullable
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType,
Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
而 RequestCallback 定義了 doWithRequest 方法,用於操作 ClientHttpRequest 其中就包含了設置headers
/**
* Callback interface for code that operates on a {@link ClientHttpRequest}. Allows to manipulate the request
* headers, and write to the request body.
*
* <p>Used internally by the {@link RestTemplate}, but also useful for application code.
*
* @author Arjen Poutsma
* @see RestTemplate#execute
* @since 3.0
*/
@FunctionalInterface
public interface RequestCallback {
/**
* Gets called by {@link RestTemplate#execute} with an opened {@code ClientHttpRequest}.
* Does not need to care about closing the request or about handling errors:
* this will all be handled by the {@code RestTemplate}.
* @param request the active HTTP request
* @throws IOException in case of I/O errors
*/
void doWithRequest(ClientHttpRequest request) throws IOException;
}
在調用post…,get…,exchange,put,patchForObject等方法時,會使用默認提供的RequestCallback實現類:
一個無配置header的AcceptHeaderRequestCallback
一個有配置header的HttpEntityRequestCallback,
來關注下有header的,裏面header的保存格式:
1.header的參數是從其中的 HttpEntity 取出來的
2.HttpEntity中負責保存header的是HttpHeaders
3.HttpHeaders是實現的MultiValueMap接口(key:[value, value]的格式),
最後,從設置代碼中看出
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
是直接把這個list設置進去,這導致了最後header裏面的格式是key:[value, value]
/**
* Request callback implementation that writes the given object to the request stream.
*/
private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback {
private final HttpEntity<?> requestEntity;
public HttpEntityRequestCallback(@Nullable Object requestBody) {
this(requestBody, null);
}
public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) {
super(responseType);
if (requestBody instanceof HttpEntity) {
this.requestEntity = (HttpEntity<?>) requestBody;
}
else if (requestBody != null) {
this.requestEntity = new HttpEntity<>(requestBody);
}
else {
this.requestEntity = HttpEntity.EMPTY;
}
}
@Override
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
Object requestBody = this.requestEntity.getBody();
if (requestBody == null) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
}
if (httpHeaders.getContentLength() < 0) {
httpHeaders.setContentLength(0L);
}
}
else {
Class<?> requestBodyClass = requestBody.getClass();
Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
...
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new LinkedList<>(values)));
}
...
}
}
}
public class HttpEntity<T> {
/**
* The empty {@code HttpEntity}, with no body or headers.
*/
public static final HttpEntity<?> EMPTY = new HttpEntity<>();
private final HttpHeaders headers;
@Nullable
private final T body;
}
public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
...
}
解決方案
換http工具
1.用apache的HttpClient
2.用okhttp3
自己實現RequestCallback
因爲execute方法可以直接傳入RequestCallback,因此可以通過自己重寫RequestCallback來自己進行header的設置