Okhttps解决异步解析非Json数据异常问题

问题描述

在使用 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;
	    }
	});
	// ...其他配置
}

最后,记得注册一下这个配置类哈~

最后问题就得以解决了~
在这里插入图片描述

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值