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

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

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

  • 相关阅读:
    利用freopen()函数和fc命令简化程序调试
    A Guide to the Multiboot Process
    略谈cpu架构种类
    RHEL与Centos
    九度 1470 调整方阵
    九度 1481 Is It A Tree?
    九度 1548 平面上的点(技巧题)
    九度 1547 出入栈(递推DP)
    2014年王道论坛研究生机试练习赛(一) set 1 GrassLand密码
    13年10月 月赛第一场 set 4 迷宫问题
  • 原文地址:https://www.cnblogs.com/cstar/p/2567370.html
Copyright © 2020-2023  润新知