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