问题描述
在使用 okhttps 框架请求第三方网站接口时,异步使用 setOnMapper
解析数据时,如果第三方接口的响应数据不属于 JSON 格式,就会抛出 JSON 解析异常
OkHttps.async("https://xxx.com/api/buy")
.addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36")
.addHeader("token", v)
.setBodyPara("{\"payType\": "alipay",\"order_sn\":\"" + orderSn + "\",\"buy_num\":1}")
// 此处,如果异步获取到的响应数据不是 JSON 格式的话,会抛出异常
.setOnResMapper(mapper -> {
int resCode = m.getInt("code");
if (resCode == 200) {
System.out.println(mapper);
}
})
.post();
抛出的异常信息如下:
Exception in thread "OkHttp Dispatcher" com.ejlchina.okhttps.OkHttpsException: 转换失败
at com.ejlchina.okhttps.TaskExecutor.doMsgConvert(TaskExecutor.java:169)
at com.ejlchina.okhttps.TaskExecutor.doMsgConvert(TaskExecutor.java:141)
at com.ejlchina.okhttps.internal.AbstractBody.toMapper(AbstractBody.java:28)
at com.ejlchina.okhttps.AHttpTask.lambda$complexOnResponse$9(AHttpTask.java:488)
at com.ejlchina.okhttps.TaskExecutor.lambda$executeOnResponse$0(TaskExecutor.java:66)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalStateException: Jackson 解析异常
at com.ejlchina.data.jackson.JacksonDataConvertor.toMapper(JacksonDataConvertor.java:39)
at com.ejlchina.okhttps.MsgConvertor$FormConvertor.toMapper(MsgConvertor.java:55)
at com.ejlchina.okhttps.internal.AbstractBody.lambda$toMapper$0(AbstractBody.java:28)
at com.ejlchina.okhttps.TaskExecutor.doMsgConvert(TaskExecutor.java:157)
... 7 more
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('<' (code 60)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (InputStreamReader); line: 1, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1851)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:707)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:632)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:1947)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:776)
at com.fasterxml.jackson.databind.ObjectMapper._readTreeAndClose(ObjectMapper.java:4555)
at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:2964)
at com.ejlchina.data.jackson.JacksonDataConvertor.toMapper(JacksonDataConvertor.java:37)
... 10 more
为什么会出现这个错误呢?可以在 okhttps 源码中找到答案,可以看到调用 toMapper
方法失败时,会抛出异常,这就是产生该异常的原因
解决这个异常,是不能够使用框架提供的 setOnException
方法解决的,因为它的发生时期是在解析 JSON 数据时,而 setOnException
捕捉的是回调之后的数据
解决问题
法一:使用 okhttps 的拦截器判断是否为 JSON,不是则构建一个返回
(法二会简单一些~)
解决这个问题,可以使用框架提供的拦截器去解决,具体思路是判断其返回数据是否为 JSON 格式,如果不是 JSON,我们可以构造一个 JSON 数据交给拦截器返回即可
想要实现 okhttps 的拦截器,需要编写 okhttps 的配置类
具体步骤如下:
此处有需要注意的地方,一定要做完下面几个步骤,okhttps 的配置才能生效
也可直接参考官方文档:官方文档 is here
1. 创建 OkHttpsConfig 配置类
import com.ejlchina.okhttps.Config;
import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import com.ejlchina.okhttps.HttpTask;
import com.ejlchina.okhttps.OkHttps;
import com.ejlchina.okhttps.jackson.JacksonMsgConvertor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Configuration
public class OkHttpsConfig implements Config {
@Autowired
private ObjectMapper objectMapper;
@Override
public void with(HTTP.Builder builder) {
AtomicInteger count = new AtomicInteger();
// 在这里对 HTTP.Builder 做一些自定义的配置
//builder.baseUrl("https://api.domo.com");
//如果项目中添加了 okhttps-fastjson 或 okhttps-gson 或 okhttps-jackson 依赖
//OkHttps 会自动注入它们提供的 MsgConvertor
//所以这里就不需要再配置 MsgConvertor 了 (内部实现自动注入的原理也是 SPI)
//但如果没有添加这些依赖,那还需要自定义一个 MsgConvertor
builder.charset(StandardCharsets.UTF_8)
.addMsgConvertor(new JacksonMsgConvertor(objectMapper))
.config((OkHttpClient.Builder client) -> {
// 配置连接池 最小10个连接(不配置默认为 5)
client.connectionPool(new ConnectionPool(25, 5L, TimeUnit.MINUTES));
// 连接超时时间(默认10秒)
client.connectTimeout(10, TimeUnit.SECONDS);
// 写入超时时间(默认10秒)
client.writeTimeout(10, TimeUnit.SECONDS);
// 读取超时时间(默认10秒)
client.readTimeout(10, TimeUnit.SECONDS);
// 使用拦截器解决json解析失败的问题
client.addInterceptor((Interceptor.Chain chain) -> {
// 获取连接
Request request = chain.request();
// 获取请求后的响应
Response proceed = chain.proceed(request);
// 获取响应体
ResponseBody body = proceed.body();
if (body != null) {
// 获取媒体类型
MediaType mediaType = body.contentType();
assert mediaType != null;
// 如果媒体类型是json格式,则响应成功
if ("json".equals(mediaType.subtype())) {
return proceed;
}
}
// 否则响应失败,自己构建一个响应返回
ResponseBody responseBody = ResponseBody.create("{\"code\": 6666, \"success\": false}", MediaType.parse(OkHttps.JSON));
return new Response.Builder()
.code(6666)
.request(request)
.body(responseBody)
.protocol(Protocol.HTTP_2)
.message("解析失败!")
.build();
});
})
.responseListener((HttpTask<?> task, HttpResult result) -> {
if (result.isSuccessful()) {
// 继续接口的业务处理
return true;
}
log.error("崩溃第{}次...", count.incrementAndGet());
return false;
})
.completeListener((HttpTask<?> task, HttpResult.State state) -> {
// 完成回调,无论成功失败都会执行,并且在 响应|异常回调 之前执行
// 所有异步请求(包括 WebSocket)执行完都会走这里
//log.info("请求完成");
// 返回 true 表示继续执行 task 的 OnComplete 回调,
// 返回 false 则表示不再执行,即 阻断
return true;
})
.exceptionListener((HttpTask<?> task, IOException error) -> {
// 所有异步请求(包括 WebSocket)发生异常都会走这里
log.error("框架异常={}", error.getMessage());
// 返回 true 表示继续执行 task 的 OnException 回调,
// 返回 false 则表示不再执行,即 阻断
return true;
})
;
}
}
2. 注册自定义的配置类
指定为你配置类的全限定路径名即可
法二:在 responseListener 中 catch JsonProcessingException 异常
// 其他代码略...参考上面的配置类即可
// 以下是核心代码
@Override
public void with(HTTP.Builder builder) {
builder.charset(StandardCharsets.UTF_8)
// ...其他配置
.responseListener((HttpTask<?> task, HttpResult result) -> {
HttpResult.Body body = result.getBody().cache();
try {
objectMapper.readTree(body.toString());
return true;
} catch (JsonProcessingException e) {
// log.error("解析错误: {}", e.getMessage().substring(0, 20));
log.error("崩溃第{}次...", count.incrementAndGet());
return false;
}
});
// ...其他配置
}
最后,记得注册一下这个配置类哈~
最后问题就得以解决了~