关于feign对x

网友投稿 990 2022-10-24

关于feign对x

关于feign对x

目录对x-www-form-urlencode类型的encode和decode问题问题场景问题原因可能的解决办法(没来得及尝试)附:feign的调用栈feignx-www-form-urlencoded类型请求试了好多方式,最后用以下方式成功

对x-www-form-urlencode类型的encode和decode问题

记录一下开发过程中遇到的一个问题。

问题场景

使用feign调用另一服务b时,在feign-client包里跑单测能调用成功,在另一项目a引入该feign-client时使用同样的参数调用失败。content-type为application/x-www-form-urlencode  POST请求

问题原因

入参中有一个String,数据jsonArray,包含","和":",在打印请求的参数发现,feign-client包里对参数encode之后,“,” 和“:"不变,而项目a调用feign-client对参数encode会把“,” 和“:"encode成%2C和%3A,导致服务b decode失败。

后来debug对比两次的不同点,发现关键点在于feign中生成的RequestTemplate不同;一步一步调试发现,feign-client包中 feign-core版本是10.2.3,项目a的feign-core版本是9.5.1,两者在生成RequestTemplate中底层对参数encode的方法不同,低版本使用的JDK1.8的URLEncode,高版本使用的feign里的UriUtils.encodeReserved。

feign.template.UriUtils.encodeReserved对参数编码时,会将参数列表中key-value的value分割为byte数组,然后依次对每个byte进行encode,根据isAllowed方法判断是否需要encode,pctEncode(b, encoded)方法是真正去encode的地方。下面的代码可以看到UriUtils.encodeReserved保留了字母数字逗号冒号等字符。而java-.URLEncode的encode方法不会保留逗号冒号等字符。

private static String encodeChunk(String value, FragmentType type, Charset charset) {

byte[] data = value.getBytes(charset);

ByteArrayOutputStream encoded = new ByteArrayOutputStream();

// 依次对每个byte编码

for (byte b : data) {

// 对于一些字符不进行编码

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 == '=');

}

至于为什么服务b对URLEncode编码的参数解析不了,还待探索,因为我没看服务b的decode代码,不知道服务b是怎么解析的。

由于服务b已经对多方提供,不能让他们适应低版本去增加解决方案(事实上http://他们也不想动代码),所以只能从发起方来解决问题。

可能的解决办法(没来得及尝试)

1、版本升级,将项目a的feign-core版本升级到10.2.3,问题能解决(已尝试),但是项目a中已经使用低版本的feign与多个服务交互,虽然理论上feign会向下兼容,但是我不敢轻易升级版本,而且版本号跨度还挺大,风险太大 = =。

2、将高版本的encode方法提取出来,手动配置到feign.encode中

3、加一个interceptor,将低版本encode的template再特殊decode一次,保持和高版本的一致 (失败,template属性是unModifiable)

4、看能否让项目a调用b服务时使用高版本feign-core ,其他feign仍然使用低版本

5、放弃feign 用 httpclient调用 。。。。

附:feign的调用栈

1、 ReflectiveFeign 被反射实例化

2、SynchronousMethodHandler.invoke

2-1、先实例化RequestTemplate 此处encode参数

2-2、executeAndDecode方法,将RequestTemplate build为request,此处会先执行-

2-3、execute 执行 访问原程服务

2-4、将response decode

附上源码:

// 2、SynchronousMethodHandler.invoke

public Object invoke(Object[] argv) throws Throwable {

// 2-1、先实例化RequestTemplate 此处encode参数

RequestTemplate template = buildTemplateFromArgs.create(argv);

Retryer retryer = this.retryer.clone();

while (true) {

try {

return executeAndDecode(template);

} catch (RetryableException e) {

try {

retryer.continueOrPropagate(e);

http:// } 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 执行 访问原程服务

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) {

// 此处会先执行-

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 类型请求

spring发送 content-type=application/x-www-form-urlencoded 和普通请求不太一样。

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

@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 getIdCarInfo(@RequestBody MultiValueMap request);

}

Post请求,参数使用@RequestBody 并且使用 MultiValueMap。

// 测试代码

@Resource

private OcrClient ocrClient;

@GetMapping("getIdCardInfo")

public Message getIdCardInfo() {

MultiValueMap 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 idCarInfo = ocrClient.getIdCarInfo(req);

return Message.success(idCarInfo);

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:对okhttp网络框架的进一步封装
下一篇:Integer类之成员变量
相关文章

 发表评论

暂时没有评论,来抢沙发吧~