最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于feign對(duì)x-www-form-urlencode類型的encode和decode問題

 更新時(shí)間:2022年03月07日 09:21:43   作者:灬CH灬  
這篇文章主要介紹了關(guān)于feign對(duì)x-www-form-urlencode類型的encode和decode問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

對(duì)x-www-form-urlencode類型的encode和decode問題

記錄一下開發(fā)過程中遇到的一個(gè)問題。

問題場(chǎng)景

使用feign調(diào)用另一服務(wù)b時(shí),在feign-client包里跑單測(cè)能調(diào)用成功,在另一項(xiàng)目a引入該feign-client時(shí)使用同樣的參數(shù)調(diào)用失敗。content-type為application/x-www-form-urlencode  POST請(qǐng)求

問題原因

入?yún)⒅杏幸粋€(gè)String,數(shù)據(jù)是jsonArray,包含","和":",在打印請(qǐng)求的參數(shù)發(fā)現(xiàn),feign-client包里對(duì)參數(shù)encode之后,“,” 和“:"不變,而項(xiàng)目a調(diào)用feign-client對(duì)參數(shù)encode會(huì)把“,” 和“:"encode成%2C和%3A,導(dǎo)致服務(wù)b decode失敗。

后來debug對(duì)比兩次的不同點(diǎn),發(fā)現(xiàn)關(guān)鍵點(diǎn)在于feign中生成的RequestTemplate不同;一步一步調(diào)試發(fā)現(xiàn),feign-client包中 feign-core版本是10.2.3,項(xiàng)目a的feign-core版本是9.5.1,兩者在生成RequestTemplate中底層對(duì)參數(shù)encode的方法不同,低版本使用的JDK1.8的URLEncode,高版本使用的feign里的UriUtils.encodeReserved。

feign.template.UriUtils.encodeReserved對(duì)參數(shù)編碼時(shí),會(huì)將參數(shù)列表中key-value的value分割為byte數(shù)組,然后依次對(duì)每個(gè)byte進(jìn)行encode,根據(jù)isAllowed方法判斷是否需要encode,pctEncode(b, encoded)方法是真正去encode的地方。下面的代碼可以看到UriUtils.encodeReserved保留了字母數(shù)字逗號(hào)冒號(hào)等字符。而java.net.URLEncode的encode方法不會(huì)保留逗號(hào)冒號(hào)等字符。

private static String encodeChunk(String value, FragmentType type, Charset charset) {
    byte[] data = value.getBytes(charset);
    ByteArrayOutputStream encoded = new ByteArrayOutputStream();
    // 依次對(duì)每個(gè)byte編碼
    for (byte b : data) {
      // 對(duì)于一些字符不進(jìn)行編碼
      if (type.isAllowed(b)) {
        encoded.write(b);
      } else {
        /* percent encode the byte */
        pctEncode(b, encoded);
      }
    }
    return new String(encoded.toByteArray());
  }
 
boolean isAllowed(int c) {
        return this.isPchar(c) || (c == '/');
      }
 
protected boolean isPchar(int c) {
      return this.isUnreserved(c) || this.isSubDelimiter(c) || c == ':' || c == '@';
    }
 
protected boolean isUnreserved(int c) {
      return this.isAlpha(c) || this.isDigit(c) || c == '-' || c == '.' || c == '_' || c == '~';
    }
 
protected boolean isAlpha(int c) {
      return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z');
    }
 
protected boolean isDigit(int c) {
      return (c >= '0' && c <= '9');
    }
 
protected boolean isSubDelimiter(int c) {
      return (c == '!') || (c == '$') || (c == '&') || (c == '\'') || (c == '(') || (c == ')')
          || (c == '*') || (c == '+') || (c == ',') || (c == ';') || (c == '=');
    }

至于為什么服務(wù)b對(duì)URLEncode編碼的參數(shù)解析不了,還待探索,因?yàn)槲覜]看服務(wù)b的decode代碼,不知道服務(wù)b是怎么解析的。

由于服務(wù)b已經(jīng)對(duì)多方提供,不能讓他們適應(yīng)低版本去增加解決方案(事實(shí)上他們也不想動(dòng)代碼),所以只能從發(fā)起方來解決問題。

可能的解決辦法(沒來得及嘗試)

1、版本升級(jí),將項(xiàng)目a的feign-core版本升級(jí)到10.2.3,問題能解決(已嘗試),但是項(xiàng)目a中已經(jīng)使用低版本的feign與多個(gè)服務(wù)交互,雖然理論上feign會(huì)向下兼容,但是我不敢輕易升級(jí)版本,而且版本號(hào)跨度還挺大,風(fēng)險(xiǎn)太大 = =。

2、將高版本的encode方法提取出來,手動(dòng)配置到feign.encode中  

3、加一個(gè)interceptor,將低版本encode的template再特殊decode一次,保持和高版本的一致 (失敗,template屬性是unModifiable)

4、看能否讓項(xiàng)目a調(diào)用b服務(wù)時(shí)使用高版本feign-core ,其他feign仍然使用低版本

5、放棄feign 用 httpclient調(diào)用 。。。。

附:feign的調(diào)用棧

1、 ReflectiveFeign 被反射實(shí)例化

2、SynchronousMethodHandler.invoke 

      2-1、先實(shí)例化RequestTemplate 此處encode參數(shù)

      2-2、executeAndDecode方法,將RequestTemplate build為request,此處會(huì)先執(zhí)行攔截器

      2-3、execute 執(zhí)行 訪問原程服務(wù)

      2-4、將response decode 

附上源碼:

// 2、SynchronousMethodHandler.invoke 
public Object invoke(Object[] argv) throws Throwable {
    // 2-1、先實(shí)例化RequestTemplate 此處encode參數(shù)
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);
      } 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;
      }
    }
  }
 
  Object executeAndDecode(RequestTemplate template) throws Throwable {
    // 2-2、executeAndDecode方法,將RequestTemplate build為request
    Request request = targetRequest(template);
 
    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }
 
    Response response;
    long start = System.nanoTime();
    try {
      // 2-3、execute 執(zhí)行 訪問原程服務(wù)
      response = client.execute(request, options);
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
 
    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }
  }
 
  long elapsedTime(long start) {
    return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
  }
 
  Request targetRequest(RequestTemplate template) {
    // 此處會(huì)先執(zhí)行攔截器
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }
 
  Object decode(Response response) throws Throwable {
    try {
      // 2-4、將response decode 
      return decoder.decode(response, metadata.returnType());
    } catch (FeignException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new DecodeException(response.status(), e.getMessage(), e);
    }
  }

feign x-www-form-urlencoded 類型請(qǐng)求

spring發(fā)送 content-type=application/x-www-form-urlencoded 和普通請(qǐng)求不太一樣。

試了好多方式,最后用以下方式成功

@FeignClient(
?? ?name = "ocr-api",
?? ?url = "${orc.idcard-url}",
?? ?fallbackFactory = OcrClientFallbackFactory.class
)
public interface OcrClient {
?
?? ?@PostMapping(
?? ??? ?value = "/v1/demo/idcard",
?? ??? ?headers = {"content-type=application/x-www-form-urlencoded"}
?? ?)
?? ?OcrBaseResponse<IdCardResponse> getIdCarInfo(@RequestBody MultiValueMap<String, Object> request);
}

Post請(qǐng)求,參數(shù)使用@RequestBody 并且使用 MultiValueMap。

? ? // 測(cè)試代碼
?? ?@Resource
?? ?private OcrClient ocrClient;
?? ?@GetMapping("getIdCardInfo")
?? ?public Message getIdCardInfo() {
?? ??? ?MultiValueMap<String, Object> req = new LinkedMultiValueMap<>();
?? ??? ?req.add("request_id", 12343531123L);
?? ??? ?req.add("img_url", "xxx.jpg");
?? ??? ?req.add("source", -1);
?? ??? ?req.add("out_business_id", 1321434234L);
?? ??? ?OcrBaseResponse<IdCardResponse> idCarInfo = ocrClient.getIdCarInfo(req);
?? ??? ?return Message.success(idCarInfo);
?? ?}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Maven中錯(cuò)誤使用parent.relativePath導(dǎo)致構(gòu)建失敗問題

    Maven中錯(cuò)誤使用parent.relativePath導(dǎo)致構(gòu)建失敗問題

    這篇文章主要介紹了Maven中錯(cuò)誤使用parent.relativePath導(dǎo)致構(gòu)建失敗問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀

    Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀

    這篇文章主要介紹了Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀,CGLib是一個(gè)強(qiáng)大的、高性能、高質(zhì)量的 Code 生成類庫(kù),它可以在運(yùn)行期擴(kuò)展 Java 類與實(shí)現(xiàn) Java 接口,需要的朋友可以參考下
    2023-11-11
  • MyBatis中的關(guān)聯(lián)關(guān)系配置與多表查詢的操作代碼

    MyBatis中的關(guān)聯(lián)關(guān)系配置與多表查詢的操作代碼

    本文介紹了在MyBatis中配置和使用一對(duì)多和多對(duì)多關(guān)系的方法,通過合理的實(shí)體類設(shè)計(jì)、Mapper接口和XML文件的配置,我們可以方便地進(jìn)行多表查詢,并豐富了應(yīng)用程序的功能和靈活性,需要的朋友可以參考下
    2023-09-09
  • java中如何調(diào)用js

    java中如何調(diào)用js

    Nashorn是Java8中引入的一個(gè)新的JavaScript引擎,它允許在JVM上運(yùn)行JavaScript代碼,并且可以與Java代碼相互調(diào)用,Nashorn遵循JSR233規(guī)范,是一個(gè)純Java實(shí)現(xiàn)的JavaScript引擎,可以與Java程序無縫集成,提供動(dòng)態(tài)腳本執(zhí)行和靈活性
    2025-01-01
  • 最新評(píng)論

    韩城市| 驻马店市| 武定县| 河南省| 竹山县| 龙江县| 黑龙江省| 南皮县| 治县。| 麦盖提县| 双桥区| 亚东县| 连城县| 衡阳县| 新龙县| 工布江达县| 新龙县| 云阳县| 雷波县| 祥云县| 柳州市| 云龙县| 胶南市| 浮梁县| 镶黄旗| 博湖县| 宝兴县| 如皋市| 乌鲁木齐县| 玛纳斯县| 姚安县| 尚志市| 昆明市| 庆元县| 铁力市| 河池市| 建平县| 眉山市| 同心县| 曲靖市| 凤庆县|