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

接口 DecoderObject decode(Response response, Type type) 方法的实现类(Decoder.DefaultOptionalDecoderStreamDecoderStringDecoder),都没有解析请求头,它们最后都会委托 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(二)请求流程解析

posted @ 2022-09-07 15:57  ageovb  阅读(314)  评论(0编辑  收藏  举报