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的根据长度判断是否需要设置偏移量分段读取。