Http Trunked协议使用

1为什么要使用Http Trunked协议?

      一般http通信时会使用content_length头信息来表示服务器发送的文档内容长度,这是因为我们已经提前知道了文档内容的长度,但

有时候我们无法提前知道我们需要传输的文档的长度,这时我们就要采用分块传输的方式来发送内容,也就是通过我们的http trunked协议。

Http1.1x协议的chunked编码方式,可以确保接收端能够准确的判断不定长内容收取是否完整。

2.  http RFC文档中的chunked编码格式

chunked编码一般使用若干个chunk串联而成,最后一个chunk的长度为0,表示chunk数据结束。每个chunked分为头部和正文,头部指

定下一段正文的长度,正文只的是实际内容。通过/r/n分隔符来分隔各个部分。

Chunked编码格式:

      Chunked-Body   = *chunk

                        last-chunk

                        trailer

                        CRLF

 

       chunk          = chunk-size [ chunk-extension ] CRLF

                        chunk-data CRLF

       chunk-size     = 1*HEX

       last-chunk     = 1*("0") [ chunk-extension ] CRLF

 

       chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )

       chunk-ext-name = token

       chunk-ext-val  = token | quoted-string

       chunk-data     = chunk-size(OCTET)

       trailer        = *(entity-header CRLF)

Chunked-Body表示经过chunked编码后的报文体。报文体可以分为chunk, last-chunk,trailer和结束符四部分。chunk的数量在报文

体中最少可以为0,无上限;每个chunk的长度是自指定的,即,起始的数据必然是16进制数字的字符串,代表后面chunk-data的长度

(字节数)。这个16进制的字符串第一个字符如果是“0”,则表示chunk-size为0,该chunk为last-chunk,无chunk-data部分。可选的

chunk-extension由通信双方自行确定,如果接收者不理解它的意义,可以忽略。
    trailer是附加的在尾部的额外头域,通常包含一些元数据,后文中会给出具体的例子。

3.   Http RFC文档中chunked协议解码

    length := 0         //长度计数器置0
    read chunk-size, chunk-extension (if any) and CRLF    

 //读取chunk-size, chunk-extension和CRLF
    while(chunk-size > 0 )   {            //表明不是last-chunk
          read chunk-data and CRLF            //读chunk-size大小的chunk-data,skip CRLF
          append chunk-data to entity-body     //将此块chunk-data追加到entity-body后
          read chunk-size and CRLF          //读取新chunk的chunk-size 和 CRLF
    }
    read entity-header      //entity-header的格式为name:valueCRLF,如果为空即只有CRLF
    while (entity-header not empty)   //即,不是只有CRLF的空行
    {
       append entity-header to existing header fields
       read entity-header
    }
    Content-Length:=length      //将整个解码流程结束后计算得到的新报文体length
                                 //作为Content-Length域的值写入报文中
    Remove "chunked" from Transfer-Encoding  //同时从Transfer-Encoding中域值去除   chunked这个标记

从这段伪代码中,我们可以看出chunked协议还是比较简单的,用任何一门语言实现起来都很方便。下面我们给出Java版实现的例子。

View Code
 1 private ByteBuffer m_chunkbody = ByteBuffer.allocate(100*1024);
 2     public byte[] m_buffer;
 3     protected BufferedInputStream m_bis = getInputStream();
 4     BisBuffer bb = new BisBuffer(m_bis, 100);//BisBuffer每次读一百个字节
 5     int contentLength = 0;
 6     boolean isDataOverLimit = false;
 7     byte b;
 8     while (true) {  // 头判断
 9         b = (byte)bb.read();//每次读取一个字节
10         if (b == 'T' || b == 't') {
11             byte[] keyBuffer = new byte[50];
12             idx = 0;
13             while ((keyBuffer[idx++] = (byte)bb.read()) != ' ') // "transfer-Encoding: "
14                 if (keyBuffer[idx - 1] == '\n')
15                     continue;
16             String encoding = new String(keyBuffer, 0, idx - 1);
17             if (encoding.equalsIgnoreCase("ransfer-Encoding:")) {
18                 byte[] chuckBuffer = new byte[8];
19                 idx = 0;
20                 while ((chuckBuffer[idx++] = (byte)bb.read()) != '\r');
21                 String chunked = new String(chuckBuffer, 0, idx - 1); // chunked协议标记
22                 logger.info(chunked);
23                 if (!chunked.equalsIgnoreCase("chunked"))
24                     throw new Exception("not chunked!");
25                 b = (byte)bb.read(); // \n
26             } else if(encoding.equalsIgnoreCase("railer:")){
27                 byte[] trailerBuffer = new byte[50];
28                 idx = 0;
29                 while ((trailerBuffer[idx++] = (byte)bb.read()) != '\r');
30                 String trailer = new String(trailerBuffer, 0, idx - 1); // trailer
31                 String[] trailerArr = trailer.split(",");
32                 if (!(trailerArr[0].trim()).equalsIgnoreCase("data_over_limit"))
33                     throw new Exception("trailer doesn't have data_over_limit!");
34                 b = (byte)bb.read(); // \n
35             }else {
36                 while ((b = (byte)bb.read()) != '\n') ; // \n
37             }
38         } else if (b == '\r') {
39             //到此说明接下来是chunked-body相关内容,。
40             b = (byte)bb.read();  // \n
41             byte[] lensize = new byte[Integer.SIZE];
42             idx = 0;
43             while((lensize[idx++] = (byte)bb.read()) != '\r');
44             b = (byte)bb.read();  // \n
45             int chunksize = Integer.parseInt(new String(lensize,0,idx-1),16);
46             //n个chunked包的解析
47             while(chunksize > 0){
48                 contentLength += chunksize;//add len
49                 if(contentLength < 0 || contentLength > 100*1024)
50                     throw new Exception(contentLength+" LENGTH TOO LARGE!");
51 
52                 byte[] temp = new byte[chunksize];
53                 idx = 0;
54                 while(idx != chunksize){
55                     temp[idx++] = bb.read();
56                 }
57                 m_chunkbody.put(temp);//append chunk-data
58                 //读取下一个chunk-data
59                 idx = 0;    
60                 b = (byte)bb.read();  // \r
61                 b = (byte)bb.read();  // \n
62                 while((lensize[idx++] = (byte)bb.read()) != '\r');
63                 chunksize = Integer.parseInt(new String(lensize,0,idx-1),16);
64                 b = (byte)bb.read();  // \n
65             }
66             b = (byte)bb.read();  // \r
67             b = (byte)bb.read();  // \n
68         } else {
69             if(b == 's'){
70                 //end 读取完chunk-body,最后将trailer数据读取出来
71                 byte[] trailerBuffer = new byte[50];
72                 idx = 0;
73                 while ((trailerBuffer[idx++] = (byte)bb.read()) != '\r');
74                 String trailer = new String(trailerBuffer, 0, idx - 1); // trailer
75                 int length = trailer.length();
76                 trailer = "s" + trailer;
77                 String tailerKey = "data_over_limit: ";
78                 if (!trailer.startsWith(tailerKey))
79                     throw new Exception("data_over_limit ERROR!");
80                 String isOverLimit = trailer.substring(tailerKey.length(),length+1);
81                 if(isOverLimit.equalsIgnoreCase("true")){
82                     isDataOverLimit = true;
83                 }else
84                     isDataOverLimit = false;
85                 b = (byte)bb.read();  // \n
86                 break;
87             }
88             while ((b = (byte)bb.read()) != '\n') ; // 其他头字段
89         }
90     }
91     //组装chunk-body的内容,即chunk-size对应的chunk-data的所有块的组合。
92     m_chunkbody.flip();//反转
93     m_buffer = m_chunkbody.array();
94     m_chunkbody.clear();//清空缓冲区

 4 .   Chunked协议发送端数据组装

首先来看一下http普通协议和http trunked协议header头部信息的异同。普通http头部信息如下所示:

     Post  xxx http/1.1

     Accept-Language: en-us

     Accept: */*

     Host: xxx.xxx

     User-Agent: xxx HTTP Client

     Content-Length: 1024

Http Trunked协议头部信息:

   Post  xxx http/1.1

   Accept-Language: en-us

   Accept: */*

   Host: xxx.xxx

   User-Agent: xxx HTTP Client

   Transfer-Encoding: chunked

   Trailer: data_over_limit

      从上面我们可以看到普通http协议header包含了长度信息,chunked协议是没有长度的,需要再客户端全部chunk数据解析后才

能得到传输信息的具体长度。

头部信息的组装通过java代码来实现如下:

View Code
 1 public byte[] creatHttpHeader(){
 2         StringBuilder sb = new StringBuilder(100);
 3         sb.append("POST xxx http/1.1").append("\r\n");
 4         sb.append("Accept-Language: en-us").append("\r\n");
 5         sb.append("Accept: */*").append("\r\n");
 6         sb.append("Host: xxx.xxx").append("\r\n");
 7         sb.append("User-Agent: xxx HTTP Client").append("\r\n");
 8         sb.append("Transfer-Encoding: chunked").append("\r\n");
 9         sb.append("Trailer: data_over_limit").append("\r\n");
10         sb.append("\r\n"); // mark header over
11         return sb.toString().getBytes("US-ASCII");
12     }

 Trailer信息Java代码实现:

View Code
1 public byte[] createChunkedTrailer(){
2         StringBuilder sb = new StringBuilder(100);
3         sb.append("data_over_limit: true\r\n");
4         return sb.toString().getBytes("US-ASCII");
5     }

 传输内容chunked组装java代码实现:

View Code
 1 public void send(InputStream fileInputStream) throws IOException {
 2         OutputStream requestStream = socket.getOutputStream();
 3         ChunkedOutputStream chunkedBodyStream = new ChunkedOutputStream(requestStream);
 4         int chunkSize = 2048;
 5         this.requestStream.write(createHttpHeader());
 6         
 7         byte[] buf = new byte[chunkSize];
 8         int readed = 0;
 9         int size = 0;
10         while ((readed = fileInputStream.read(buf)) != -1) {
11             size += readed;
12         }
13         this.chunkedBodyStream.finish();
14         this.requestStream.write(createChunkedTrailer());
15     }

     在介绍一下ChunkedOutputStream这个类,这个类是httpclient-3.0.1.jar里面的一个类,源代码我们可以拿到,代码实现的很简

洁,有兴趣的同学可以好好看看,可以去网上获取httpclient的源代码。

      传输内容组装好之后,就可以通过套接字发送到客户端去了,第三节中的代码就可以解析从这里发送过去的数据,怎么样,很简单吧

,看过之后大家都会使用了吧。

 

 

 

posted @ 2012-06-28 12:57  cstar(小乐)  阅读(7972)  评论(8编辑  收藏  举报