近来好多人亮相自已发出HTTP请求,我也正好也在做HTTP代理,还可以看看我写的一个文章也有关于HttpClient的内容
http://feixing2008.iteye.com/blog/569927
其中我没有使用HttpClient这个东西,我想更直接地透传数据。下边写了几个工具方法,引出内容。还有出遇到的一些问题。
public static byte[] getDataByInputStream(InputStream in)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[BUF_SIZE];
int len = 0;
while ((len = in.read(b, 0, BUF_SIZE)) != -1) {
baos.write(b, 0, len);
}
baos.flush();
byte[] bytes = baos.toByteArray();
return bytes;
}
上边的方法是从流中取出所有数据,放在数组中。BUF_SIZE常量自已设定,意义为缓冲区的大小。
在以上的基础上加增多一个方法
public static String getStrByInputStream(InputStream in, String outEncode) throws IOException{
byte[] btyStr = getDataByInputStream(in);
String str = new String(btyStr, outEncode);
return str;
}
这个方法主要是将响应的数据转成字符串。其中outEncode是字符器的编码。这里我要说一个问题
就是Content-Length这个头的主要含义是上边btye[]的长度,而不是String的length()的长度。
Content-Length反映了字节流的长度,这个受操作系统编码的影响。所以需要得到响应的编码形式。
继续再看看这个方法。
public static String getStrEveryLine(InputStream in, String encode)
throws IOException {
StringBuilder rs = new StringBuilder();
Scanner inScn = new Scanner(in);
String buf = "";
while(inScn.hasNextLine()){
buf = inScn.nextLine();
if(buf.length() < 1){
rs.append("\r\n");
break;
}
rs.append(buf);
rs.append("\r\n");
}
return rs.toString();
这个方法是是通过Scanner来处理流数据。本来使用getStrByInputStream这个方法好像已满足需求,其实非也,因为出现一个问题。
问题:
浏览器发出请求时,在完成请求头后关不会将socket关闭的,也就是说
in.read()是不会返回-1,只是线程会一直阻塞在那里。
再具体一点就是这样
GET / HTTP/1.0
Host: www.taobao.com
User-Agent: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.0.11) Gecko/2009060308 Ubuntu/9.04 (jaunty) Firefox/3.0.11
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: text/html
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: t=31d7b3ed1734e98759c7009c7cc5d492; cna=G2ZHBE1o0hUCATjEb3H2nl0g
<---------这里有个空行,读完这个空行再读的话会阻塞。所以读到这个空间就要跳出,不要再read
在请求头遇到这个情况还算好,因为可以通过一个空行可以发现头已经结束。
PS:不过这里还有个问题,如果是POST的话,在这个空行后边还是会有参数请求,这个时间就需要处理。然而我还没有实现。
在响应接收也是存在这个情况,这个问题更麻烦,因为是在响应数据流中读完一个字节流后阻塞,一般服务器是不会马上关闭Socket的,因为它不知你什么时间关闭,一般是等到10秒后才关闭。天吖,这个很影响我代理的性能。
其中一个解决方法是得到响应头的中Content-Length,再一次说明,这个头的值指的是字节流大小, 不是字符串大小。有了这个大小便可以根据大小读数据,知道何时数据已经完整,而不需要等服务器来关闭连接。
然而有时服务器是不会传Content-Length这个值。我也没有什么好方法,给合我代理的功能,我直接将流指向真实客服端的输出流当中。
BufferedInputStream bufIn = new BufferedInputStream(in);
ArrayList<Byte> bys = new ArrayList<Byte>();
BufferedOutputStream outBuf = new BufferedOutputStream(outToClient);
int c = 0;
int cCnt = 0;
while(( c = bufIn.read()) != -1){
++cCnt;
byte bc = (byte)c;
bys.add(bc);
outBuf.write(c);
if((cCnt % 256) == 0){
outBuf.flush();
}
}
outBuf.close();
这代码比较笨拙,其实不用细看,只是说明直接输到真实的客户端的输出流中。这个效率大大提升。
其中还有一个地方是线程池,这个是我需要改进的另一块,继续关注HTTP协议的相关开发。