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的源代码。

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

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

 

 

 

你可能感兴趣的:(trunk)