由于工作原因,想采用NIO技术,研究了一下[url=http://crmky.blogdriver.com/crmky/index.html]Crmky[/url]的[url=http://cindy.sourceforge.net/]Cindy[/url]框架。由于使用长连接方式,数据协议采用定长包头+不定长保体的方式,因而主要研究了其中的HTTP范例。在net.sf.cindy.example.http的HttpRequestDecoder中,发现了一个BUG。原始代码如下:
[code] public Object decode(Session session, Packet packet) throws Exception {
Buffer buffer = packet.getContent();
int index = buffer.indexOf(TOKEN);
if (index >= 0) {
String[] headers = buffer.getString(Charset.UTF8,
index + TOKEN.length).split("\r\n");
HttpRequest request = new HttpRequest();
parse(request, headers);
String contentLen = request.getParam("Content-Length");
if (contentLen != null) {
int len = Integer.parseInt(contentLen);
if (buffer.remaining() >= len) {
byte[] content = new byte[len];
buffer.get(content);
request.setContent(content);
} else {
return null;
}
}
return request;
}
return null;
}[/code]
假设:
第一步:http头已经读完,但http报文体长度不够,此时return null,等待下次读取;
第二步:在第二次下次读取时候http报文体时,又会重新检查TOKEN(\r\n\r\n),此时(index>=0)返回false,直接return null了。
此时,不能不确读取HTTP报文。其实,Cindy的接口中,定义了Attributes相关操作,因而可以把第一次读取的内容,通过session.setAttribute(HTTP_HEAD, request)缓存起来,下次读取是再继续。通过检查HTTP_HEAD的attribute是否存在,来绝对读取报文头还是报文体。修改后的代码如下:
[code] public Object decode(Session session, Packet packet) throws Exception {
Buffer buffer = packet.getContent();
final String HTTP_HEAD = null;
Object request_obj = session.getAttribute(HTTP_HEAD);
HttpRequest request_inst = null;
if (request_obj==null) { //读取HTTP头
int index = buffer.indexOf(TOKEN);
if (index >= 0) {
String[] headers = buffer.getString(Charset.UTF8,
index + TOKEN.length).split("\r\n");
request_inst = new HttpRequest();
parse(request_inst, headers);
}else return null; //读取HTTP头不完整,留到下次读取;
}else { //上次已经读取了HTTP头,本次只读HTTP报文体
request_inst = (HttpRequest)request_obj;
}
String bodylen_str = request_inst.getParam("Content-Length");
if (bodylen_str!=null || (! "0".equals(bodylen_str))) {
int bodylen_int = Integer.parseInt(bodylen_str);
if (buffer.remaining() >= bodylen_int) {
byte[] content = new byte[bodylen_int];
buffer.get(content);
request_inst.setContent(content);
} else {
return null; //等待下次再继续读取
}
}
//完整读取了一个HTTP报文(可能BodyLen==0)
session.removeAttribute(HTTP_HEAD);
return request_inst;
}[/code]
[code] public Object decode(Session session, Packet packet) throws Exception {
Buffer buffer = packet.getContent();
int index = buffer.indexOf(TOKEN);
if (index >= 0) {
String[] headers = buffer.getString(Charset.UTF8,
index + TOKEN.length).split("\r\n");
HttpRequest request = new HttpRequest();
parse(request, headers);
String contentLen = request.getParam("Content-Length");
if (contentLen != null) {
int len = Integer.parseInt(contentLen);
if (buffer.remaining() >= len) {
byte[] content = new byte[len];
buffer.get(content);
request.setContent(content);
} else {
return null;
}
}
return request;
}
return null;
}[/code]
假设:
第一步:http头已经读完,但http报文体长度不够,此时return null,等待下次读取;
第二步:在第二次下次读取时候http报文体时,又会重新检查TOKEN(\r\n\r\n),此时(index>=0)返回false,直接return null了。
此时,不能不确读取HTTP报文。其实,Cindy的接口中,定义了Attributes相关操作,因而可以把第一次读取的内容,通过session.setAttribute(HTTP_HEAD, request)缓存起来,下次读取是再继续。通过检查HTTP_HEAD的attribute是否存在,来绝对读取报文头还是报文体。修改后的代码如下:
[code] public Object decode(Session session, Packet packet) throws Exception {
Buffer buffer = packet.getContent();
final String HTTP_HEAD = null;
Object request_obj = session.getAttribute(HTTP_HEAD);
HttpRequest request_inst = null;
if (request_obj==null) { //读取HTTP头
int index = buffer.indexOf(TOKEN);
if (index >= 0) {
String[] headers = buffer.getString(Charset.UTF8,
index + TOKEN.length).split("\r\n");
request_inst = new HttpRequest();
parse(request_inst, headers);
}else return null; //读取HTTP头不完整,留到下次读取;
}else { //上次已经读取了HTTP头,本次只读HTTP报文体
request_inst = (HttpRequest)request_obj;
}
String bodylen_str = request_inst.getParam("Content-Length");
if (bodylen_str!=null || (! "0".equals(bodylen_str))) {
int bodylen_int = Integer.parseInt(bodylen_str);
if (buffer.remaining() >= bodylen_int) {
byte[] content = new byte[bodylen_int];
buffer.get(content);
request_inst.setContent(content);
} else {
return null; //等待下次再继续读取
}
}
//完整读取了一个HTTP报文(可能BodyLen==0)
session.removeAttribute(HTTP_HEAD);
return request_inst;
}[/code]