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的根據長度判斷是否需要設置偏移量分段讀取。

 

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