Feign 中 Header 的编码方式研究
请求头的构建
调用代理类 SynchronousMethodHandler#invoke()
当 FeignClient 发起 http 请求时,会从容器中获取对应的代理类,并调⽤ FeignInvocationHandler#invoke()
⽅
法,其最终实现在 SynchronousMethodHandler#invoke()
中:
public Object invoke(Object[] argv) throws Throwable {
// 解析请求参数,封装 RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 获取 Request.Options 对象,封装了请求的 connectTimeout/readTimeout 等信息
Options options = findOptions(argv);
// 重试机制
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 执⾏请求,并解析响应
return executeAndDecode(template, options);
} catch (RetryableException e) {
// 重试逻辑
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
调用 buildTemplateFromArgs.create(argv)
解析请求参数,封装成 RequestTemplate
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}
// 解析请求参数,封装成 RequestTemplate
RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}
if (metadata.headerMapIndex() != null) {
template =
addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
}
return template;
}
调用 resolve(argv, mutable, varBuilder)
方法解析请求参数
public RequestTemplate resolve(Map<String, ?> variables) {
StringBuilder uri = new StringBuilder();
/* create a new template form this one, but explicitly */
RequestTemplate resolved = RequestTemplate.from(this);
// ......
/* headers */
if (!this.headers.isEmpty()) {
/*
* same as the query string, we only want to keep resolved values, so clear the header map on
* the resolved instance
*/
resolved.headers(Collections.emptyMap());
for (HeaderTemplate headerTemplate : this.headers.values()) {
/* resolve the header */
String header = headerTemplate.expand(variables);
if (!header.isEmpty()) {
/* split off the header values and add it to the resolved template */
String headerValues = header.substring(header.indexOf(" ") + 1);
if (!headerValues.isEmpty()) {
/* append the header as a new literal as the value has already been expanded. */
// 解析请求头
resolved.header(headerTemplate.getName(), Literal.create(headerValues));
}
}
}
}
if (this.bodyTemplate != null) {
resolved.body(this.bodyTemplate.expand(variables));
}
/* mark the new template resolved */
resolved.resolved = true;
return resolved;
}
调用 resolved.header(headerTemplate.getName(), Literal.create(headerValues))
解析请求头参数
private RequestTemplate appendHeader(String name, List<TemplateChunk> chunks) {
if (chunks.isEmpty()) {
this.headers.remove(name);
return this;
}
this.headers.compute(name, (headerName, headerTemplate) -> {
if (headerTemplate == null) {
// 看哪个分支都一样,我们看这个分支
return HeaderTemplate.from(name, chunks);
} else {
return HeaderTemplate.appendFrom(headerTemplate, chunks);
}
});
return this;
}
调用 HeaderTemplate.from(name, chunks)
构造请求头模版
public static HeaderTemplate from(String name, List<TemplateChunk> chunks) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("name is required.");
}
if (chunks == null) {
throw new IllegalArgumentException("chunks are required.");
}
// 编码是 UTF-8
return new HeaderTemplate(name, Util.UTF_8, chunks);
}
可以看到请求头被编码成 UTF-8。
请求头的解析
executeAndDecode(template, options)
执行请求并解析响应:
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
// 打印⽇志
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 执⾏请求,拿到响应
response = client.execute(request, options);
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
// ...
}
// ...
if (decoder != null)
// 解析响应
return decoder.decode(response, metadata.returnType());
// ...
}
SpringDecoder
接口 Decoder
的 Object decode(Response response, Type type)
方法的实现类(Decoder.Default
、OptionalDecoder
、StreamDecoder
、StringDecoder
),都没有解析请求头,它们最后都会委托 spring-cloud-openfeign-core 包下的 SpringDecoder
进行解析:
public Object decode(final Response response, Type type) throws IOException, FeignException {
if (!(type instanceof Class) && !(type instanceof ParameterizedType) && !(type instanceof WildcardType)) {
throw new DecodeException(response.status(), "type is not an instance of Class or ParameterizedType: " + type, response.request());
} else {
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type, ((HttpMessageConverters)this.messageConverters.getObject()).getConverters());
// 解析响应
return extractor.extractData(new FeignResponseAdapter(response));
}
}
调用 extractor.extractData(new FeignResponseAdapter(response))
@Override
@SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
// 响应头中的 Content-Type,可指定格式和编码,根据其解析响应
MediaType contentType = getContentType(responseWrapper);
try {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
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);
}
}
}
}
catch (IOException | HttpMessageNotReadableException ex) {
throw new RestClientException("Error while extracting response for type [" +
this.responseType + "] and content type [" + contentType + "]", ex);
}
throw new UnknownContentTypeException(this.responseType, contentType,
response.getRawStatusCode(), response.getStatusText(), response.getHeaders(),
getResponseBody(response));
}
参考资料
Feign 底层原理分析 - 自动装载 & 动态代理
Feign 详解
Feign 的解码器处理响应数据
Springboot 解决 header 中的中文编码问题
认识 feign(二)请求流程解析