使用http的Accept-Ranges实现json报文分段传输

运维 作者:bt资源下载站 2023-03-24 18:53:51

因为公司业务需求,对接一款终端设备,需要下载黑名单。可是设备的性能较差,每次下载的报文大小有限制。所以需要采用报文分段下载来实现。

当然其实这种需求最好是用查询分页来达到分段下载是最好的,可是因为终端那边实现问题还有以及其他各种原因,不得已采用这种分段下载方式。

找了很多帖子比较少有关于这种分段返回报文的内容,可是有发现可以利用断点下载的思想,去实现这个分段报文返回。有断点下载经验的同学,应该很快能理解本文介绍的内容,其实都是很皮毛的东西。正题

分段下载我们主要用到两个HTTP头信息:

http 响应头 Accept-Rangeshttp 请求头 Range

服务器使用 HTTP 响应头Accept-Ranges标识自身支持范围请求(partial requests)。字段的具体值用于定义范围请求的单位。当浏览器/客户端发现 Accept-Ranges 头时,可以尝试继续中断了的下载,而不是重新开始。用法:Accept-Ranges: bytes

Range用来标志本次请求,获取数据的范围。如:Range: bytes=100-200步骤

客户端请求头都带上Range,第一个请求Range:bytes=0-99,第二个Range:bytes=100-199,以此类推。服务端除了响应头带上Accept-Ranges: bytes,还需要根据请求中的Range来切割报文,返回Range范围内的数据。http响应码设置为206http响应头新增Content-Range,标识本次响应的内容的范围,和整个完整报文的长度需要注意一点的是,Range单位是byte而不是字符数,所以计算长度的时候要要将字符串换算成byte计算。

接下来展示controller层部分代码:

   // 省略业务代码    //rsp为黑名单的响应dto对象,全量的黑名单    String returnJson = JsonUtil.Object2Json(rsp);    httpResp.setContentType("application/json");    //无缓存    httpResp.setHeader("Cache-Control", "no-cache");    //设置UTF-8编码    httpResp.setCharacterEncoding(Constant.CHARACTER_ENCODING);    //可以断点下载    httpResp.setHeader("Accept-Ranges", "bytes");    //设置http响应码为206    httpResp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);    //获取头信息Range,并且去掉bytes=    String range = httpReq.getHeader("Range").replaceAll("bytes=", "");    String[] rangeSplit = range.split("-");    //整段报文的总长度,要用utf-8的编码字节长度计算    int returnJsonLength = returnJson.getBytes("UTF-8").length;    //offset 第一次请求默认从0开始    int offset = Integer.parseInt(rangeSplit[0]);    // 初始化 length 和 endIndex    // length:本次报文返回的长度;endIndex:本次报文结束的下标位置,默认是报文的总长度所以是returnJsonLength - 1    int length = returnJsonLength - offset;    int endIndex = returnJsonLength - 1;    //设置 length 和 endIndex 值    if (rangeSplit.length > 1) {        //strSplit数组长度大于1,说明Range是一个范围,而不是一个固定值        endIndex = Integer.parseInt(rangeSplit[1]);        //endIndex不允许大于等于报文长度        if (endIndex >= returnJsonLength) {            endIndex = returnJsonLength - 1;        }        //因为endIndex最大值比报文长度小于1的,而length是报文返回长度,所以要+1        length = endIndex - offset + 1;    }    httpResp.setHeader("Content-Length", Long.toString(returnJsonLength));    //Content-Range:bytes[json的开始字节]-[json的结束字节]/[报文的总大小]    httpResp.setHeader("Content-Range", "bytes " + offset + "-" + endIndex + "/" + returnJsonLength);    // 获得写出流    try {        OutputStream outputStream = httpResp.getOutputStream();        //重点来了,输出报文,只返回原完整报文的offset至offset + length的字节内容        outputStream.write(returnJson.getBytes("utf-8"), offset, length);        outputStream.flush();        outputStream.close();    } catch (Exception e) {        LogUtil.addProclog(procid, "返回报文错误:" + CommonUtil.getTrace(e));    }

缺点:

每次请求只返回部分的报文,可是每次都是获取全量的黑名单,数据库查询多次。可以用redis缓存黑名单,减少数据库查询。仅以记录本次的解决方案,如果有更好的思路不妨一起交流一下,不才献丑了。

关注公众号:拾黑(shiheibook)了解更多

友情链接:

下软件就上简单下载站:https://www.jdsec.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接