okhttp通过buffer读取request body请求体部分字节丢失问题

1、问题场景

调第三方接口验签失败,调试发现是请求体的字节数组部分丢失,原请求体字符长度10000+,转成字节数组后只有8135,导致用字节数组加密后的密文解密后与原文校验不一致。

2、相关代码

构建Request请求的url以及requestBody等参数,加密后发送http请求

Request request = new Request.Builder().method(HttpMethod.POST.name(), RequestBody.create(MEDIATYPE, reqBody)).url(new HttpUrl.Builder().scheme(config.getScheme()).host(config.getApiHost())
                                                                                                                                                .addPathSegments(CpichApiEnum.APPLY_ORDER.getUrl()).build()).build();
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request1 = signUtil.sign(request);
        Response response = client.newCall(request1).execute();
        ResponseBody responseBody = response.body();
        log.info(responseBody.string());

加密方法中处理请求体报文相关代码

Buffer buffer = new Buffer();
request.body().writeTo(buffer);
//请求体
byte[] bodyBytes = new byte[(int) request.body().contentLength()];
buffer.read(bodyBytes);

当request.body().contentLength()长度超过8192时,就会出现部分字节丢失

3、修复方法

方法1:

Buffer buffer = new Buffer();
request.body().writeTo(buffer);
//请求体
byte[] bodyBytes = buffer.readByteArray();

方法2:

Buffer buffer = new Buffer();
request.body().writeTo(buffer);
//请求体
byte[] bodyBytes = buffer.readString(StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);

方法3:

Buffer buffer = new Buffer();
request.body().writeTo(buffer);
byte[] bodyBytes = new byte[(int) request.body().contentLength()];
buffer.read(bodyBytes,0,bodyBytes.length);
// TODO 省略判断长度 循环分段添加
buffer.read(bodyBytes,8192,bodyBytes.length-8192);

4、原因分析

Buffer 里面保存了一个segment双向循环链表,从head segment读取,从tail segment写入。
Segment 真正保存数据的类,pos和limit保存了可以读写的位置,shared和owner表示是否可以修改此Segment里面的值。
SegmentPool 保存了一个单向Segment链表,最大包含有8个Segment。recylce方法加入SegmentPool和take从SegmentPool里面读取,都是从next节点(链表的头节点)开始的。

buffer.read源码:

    public int read(byte[] sink, int offset, int byteCount) {
        Util.checkOffsetAndCount((long)sink.length, (long)offset, (long)byteCount);
        Segment s = this.head;
        if (s == null) {
            return -1;
        } else {
            int toCopy = Math.min(byteCount, s.limit - s.pos);
            System.arraycopy(s.data, s.pos, sink, offset, toCopy);
            s.pos += toCopy;
            this.size -= (long)toCopy;
            if (s.pos == s.limit) {
                this.head = s.pop();
                SegmentPool.recycle(s);
            }

            return toCopy;
        }
    }

Segment:

static final int SIZE = 8192;
    static final int SHARE_MINIMUM = 1024;
    final byte[] data;
    int pos;
    int limit;
    boolean shared;
    boolean owner;
    Segment next;
    Segment prev;

    Segment() {
        this.data = new byte[8192];
        this.owner = true;
        this.shared = false;
    }

可见一个segment段长度只有8192,需要读取完整的字节,就需要分段读取。

就有了方法3的根据长度判断是否需要设置偏移量分段读取。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章