• 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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    抛出的异常信息如下:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    为什么会出现这个错误呢?可以在 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;
                    })
            ;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    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;
    	    }
    	});
    	// ...其他配置
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

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

  • 相关阅读:
    使用Spring Data JPA 本机查询
    iOS包体积优化-图片优化
    Mysql(一) 索引底层数据结构
    C树和森林的研究学习随记【一】
    Linux/C 高级——shell脚本
    Java-GUI 编程之 Swing
    国有林场试点森林防火(资源监管)四位一体系统建设指南
    typedef复杂函数接口的解释
    RS232/RS485信号转12路模拟信号 隔离D/A转换器YL34
    每日OJ题_斐波那契dp①_力扣1137. 第 N 个泰波那契数
  • 原文地址:https://blog.csdn.net/Vampire69/article/details/126431753