HTTP chunked 인 코딩 데이터 흐름 분석 알고리즘

HTTP 데이터 전송의 내용 길이
HTTP 전송 과정 에서 Content - Length 는 응답 메시지 의 크기 를 나타 내 는 데 사 용 됩 니 다. 보통 Content - Length 가 있 는 데 이 터 는 그림, 미디어 자원, 정적 텍스트, 웹 페이지 이 고 문서 도 있 습 니 다.보통 Content - Length 는 클 라 이언 트, 서버 에서 이용 할 수 있 습 니 다.
1. Content - Length 를 클 라 이언 트 에 사용 할 때 POST, PUT, DELETE 등 과 함께 서버 에 데 이 터 를 보 내 고 데이터 길 이 를 서버 에 보고 합 니 다.
2. Content - Length 를 서버 에 사용 할 때 HEAD, HTTP 304 등 을 제외 하고 Client 에 데이터 길 이 를 보고 합 니 다.
3. 물론 문 제 는 일부 데 이 터 는 동적 변화 이다. 예 를 들 어 동적 페이지 처리 데이터, Server 자체 가 데이터 의 총 길 이 를 확정 하지 못 하고 세 션 길이 만 알 기 때문에 Content - Length 를 사용 하지 않 았 다.이때 Transfer - Encoding: Chunked 는 무대 에 올 라 세 션 을 보 내 고 세 션 을 전달 하 는 Content - Length 에 따라 데 이 터 를 보 냈 다.마찬가지 로 Content - Length 는 영원히 chunked 와 함께 사용 할 수 없다 는 것 을 알 수 있다.
HTTP chunked编码数据流解析算法_第1张图片
2. Transfer - Encoding: Chunked 인 코딩 의 데이터 형식 입 니 다.
HTTP/1.1 200 OK\r
   Content-Type: text/plain\r
   Transfer-Encoding: chunked\r
Connection:keep-Alive\r
\r
  25\r
;           This is the data in the first chunk\r
\r
1A\r
and this is the second one \r
0\r
\r

보기 에는 매우 평범 하 다. 사실 우 리 는 3, 5, 7, 9 행 에 중점 을 두 었 다.
세 번 째 줄 은 그의 전송 인 코딩 이 chunked, 다섯 번 째, 일곱 번 째, 아홉 번 째 는 chunked 인 코딩 의 다음 부분 인 Content - Length 지시 로 16 진 데이터 로 표시 되 었 음 을 설명 했다.
     ,  \r
,0x25 10 37, “This is the data in the first chunk\r
” 37 ,0x1A=26,“and this is the second one” 26
       ,      0x0=0,              ,  Client    ,             -1
\r
0\r
\r

예제 도 를 사용 하여 이 사이 의 관 계 를 설명 하 다.
HTTP chunked编码数据流解析算法_第2张图片
3. Transfer - Encoding: Chunked 인 코딩 분석
표준 해석 위조 코드
length := 0//             
read chunk-size, chunk-extension (if any) and CRLF//        
while (chunk-size > 0) {//    ,         0
read chunk-data and CRLF//      ,     
append chunk-data to entity-body//              
length := length + chunk-size//          
read chunk-size and CRLF//       
}
read entity-header//            
while (entity-header not empty) {
append entity-header to existing header fields
read entity-header
}
Content-Length := length//          
Remove "chunked" from Transfer-Encoding//      Transfer-Encoding

1. chunked 일반적인 해석
우 리 는 아래 코드 를 직접 볼 수 있다.
 
	    URL url=new URL("http://localhost:8080/test/ios.php");
	    EchoURLConnection connection=(EchoURLConnection)url.openConnection();
	    connection.setDoOutput(true);
	    connection.setDoInput(true);
	 
	    PrintWriter pw = new PrintWriter(new OutputStreamWriter(connection.getOutputStream()));
	    pw.write("name=zhangsan&password=123456");
	    pw.flush();
	   
	    InputStream stream = connection.getInputStream();
	    
	    int len = -1;
	    byte[] buf = new byte[1];
	    byte[] outBuf = new byte[]{0x3A}; //     “  ”,    http Response      
	    int count = 0;
	    //  Header  
	    while((len=stream.read(buf, 0, buf.length))>-1)
	    {
	    	
	    	int outLength = outBuf.length+len;
	    	byte[] tempBuf = outBuf;
	    	outBuf = new byte[outLength];
	    	System.arraycopy(tempBuf, 0, outBuf,0, tempBuf.length);
	    	System.arraycopy(buf, 0, outBuf,outBuf.length-1, len);
	    		
	    	if(buf[0]==0x0D || buf[0]==0x0A)
	    	{
	    		count++;
	    		if(count==4)
		    	{
		    		break;
		    	}
	    	}else{
	    		count = 0;
	    	}
	 
	    }
	    String headerString = new String(outBuf, 0, outBuf.length);
	    String[] splitLine = headerString.trim().split("\r
");          // header Map         Map<String, String> headerMap = new HashMap<String, String>();     boolean isCkunked = false;     for (String statusLine : splitLine) {      String[] nameValue = statusLine.split(":");      if(nameValue[0].equals(""))      {      headerMap.put("", nameValue[1]);      }else{      headerMap.put(nameValue[0], nameValue[1]);      }           if("Transfer-Encoding".equalsIgnoreCase(nameValue[0]))      {      String value = nameValue[1].trim();      isCkunked = value.equalsIgnoreCase("chunked"); // chunked      } }          System.out.println(headerMap);         if(isCkunked)     {      int chunkedNumber = 0; // , CRLF( )      byte[] readBuf = null; // chunked Content-Length      byte[] contentBuf = new byte[0]; //      long currentLength = 0;           while((len=stream.read(buf, 0,buf.length))>=0)      {      if(readBuf==null)      {      readBuf  = new byte[]{buf[0]};      }      else      {      int outLength = readBuf.length+len;           byte[] tempBuf = readBuf;           readBuf = new byte[outLength];           System.arraycopy(tempBuf, 0, readBuf,0, tempBuf.length);           System.arraycopy(buf, 0, readBuf,readBuf.length-1, len);      }           if(buf[0]==0x0D|| buf[0]==0x0A) //      {      chunkedNumber++;       //readBuf.length>currentLength chunked Content-Length      if(chunkedNumber==2&&readBuf.length>currentLength)      {           byte[] tmpContentBuf = contentBuf;      contentBuf = new byte[(int) (tmpContentBuf.length+currentLength)];      System.arraycopy(tmpContentBuf, 0, contentBuf,0, tmpContentBuf.length);                System.arraycopy(readBuf, 0, contentBuf,tmpContentBuf.length, (int) currentLength);                               String lineNo = "0x"+ (new String(readBuf,(int)currentLength, readBuf.length-(int)currentLength)).trim();                if(lineNo.equals("0x"))                 {                currentLength = 0; // chunked Content-Length                }else{                currentLength = Long.decode(lineNo); // chunked Content-Length                if(currentLength==0) // currentLength 0,                {                System.out.println(new String(contentBuf, 0,contentBuf.length));                break;                }                }                System.out.println(currentLength+"--"+lineNo);      chunkedNumber = 0;      readBuf = null;      }           }else{      chunkedNumber = 0; // CRLF, 0      }      }     }else{              // chunked      StringBuilder sb = new StringBuilder();           while((len=stream.read(buf, 0,buf.length))>-1)       {      sb.append(new String(buf, 0, len));      }      System.out.println(sb.toString());     }     pw.close();     stream.close();

2. 해석 클래스 로 봉인
물론 위의 분석 방식 은 이식 성 이 좋 지 않 기 때문에 개선 이 필요 합 니 다. 우 리 는 대리 방식 을 통 해 InputStream 을 분석 합 니 다.
public class HttpInputStream extends InputStream {

	private  InputStream hostStream; //    input 
	private  boolean isCkunked = false;
	private  int readMetaSize = 0; //        
	private  long contentLength = 0; //        
	private  long chunkedNextLength = -1L; //           
	private  long chunkedCurrentLength = 0L; //              
	
	private final Map<String, Object> httpHeaders = new HashMap<String, Object>();
	
	public HttpInputStream(InputStream inputStream) throws IOException 
	{
		this.hostStream = inputStream;
		parseHeader();
	}
	public int getReadMetaSize() {
		return readMetaSize;
	}
	public long getContentLength() 
	{
		return contentLength;
	}
	/**
	 *      
	 * @throws IOException
	 */
	private void parseHeader() throws IOException
	{
		if(this.hostStream!=null)
		{
			    int len = -1;
			    byte[] buf = new byte[1];
			    byte[] outBuf = new byte[]{0x3A};
			    int count = 0;
			    while((len=read(buf, 0, buf.length))>-1)
			    {
			    	int outLength = outBuf.length+len;
			    	byte[] tempBuf = outBuf;
			    	outBuf = new byte[outLength];
			    	System.arraycopy(tempBuf, 0, outBuf,0, tempBuf.length);
			    	System.arraycopy(buf, 0, outBuf,outBuf.length-1, len);
			    		
			    	if(buf[0]==0x0D || buf[0]==0x0A)
			    	{
			    		count++;
			    		if(count==4)
				    	{
			    			break;
				    	}
			    	}else{
			    		count = 0;
			    	}
			 
			    }
			    String headerString = new String(outBuf, 0, outBuf.length);
			    String[] splitLine = headerString.trim().split("\r
");          for (String statusLine : splitLine) {      String[] nameValue = statusLine.split(":");      if(nameValue[0].equals(""))      {      httpHeaders.put("", nameValue[1]);      }else{      httpHeaders.put(nameValue[0], nameValue[1]);      }           if("Transfer-Encoding".equalsIgnoreCase(nameValue[0]))      {      String value = nameValue[1].trim();      isCkunked = value.equalsIgnoreCase("chunked");      } } } } public Map<String, Object> getHttpHeaders() { return httpHeaders; } /**  *   */ @Override public int read() throws IOException { if(!isCkunked) { /**  *  chunked  */ return hostStream.read(); } /**  * chunked  */ return  readChunked(); } private int readChunked() throws IOException { byte[] chunkedFlagBuf  = new byte[0]; // chunked length int crlf_nums = 0; int byteCode = -1; if(chunkedNextLength==-1L) // -1  chunkedNextLength , chunked length {  byteCode = hostStream.read();  readMetaSize++; while(byteCode!=-1) { int outLength = chunkedFlagBuf.length+1;      byte[] tempBuf = chunkedFlagBuf;      chunkedFlagBuf = new byte[outLength];      System.arraycopy(tempBuf, 0, chunkedFlagBuf,0, tempBuf.length);      System.arraycopy(new byte[]{(byte) byteCode}, 0, chunkedFlagBuf,chunkedFlagBuf.length-1, 1);           if(byteCode==0x0D || byteCode==0x0A) //      {      crlf_nums++;      if(crlf_nums==2) // 2,      {      String lineNo = "0x"+ (new String(chunkedFlagBuf,0,chunkedFlagBuf.length)).trim();      chunkedNextLength = Long.decode(lineNo);      contentLength+=chunkedNextLength;           if(chunkedNextLength>0)      {       byteCode = hostStream.read();       readMetaSize++;      }           break;      }           }else{      crlf_nums=0;      }      byteCode = hostStream.read(); readMetaSize++; } } else if(chunkedNextLength>0) // { if(chunkedCurrentLength<chunkedNextLength) { byteCode = hostStream.read(); readMetaSize++; chunkedCurrentLength++; // , } if(chunkedCurrentLength==chunkedNextLength){  // , chunkedCurrentLength chunkedNextLength = -1L; chunkedCurrentLength = 0L; } }else{         // , header getHttpHeaders().put("Content-Length", ""+contentLength); return -1;//chunked -1, chunkedLength=0 -1 } return byteCode; } @Override public long skip(long n) throws IOException { return hostStream.skip(n); } @Override public boolean markSupported() { return hostStream.markSupported(); } @Override public int available() throws IOException { return hostStream.available(); } @Override public synchronized void mark(int readlimit) { hostStream.mark(readlimit); } @Override public void close() throws IOException { hostStream.close(); } @Override public synchronized void reset() throws IOException { hostStream.reset(); }

아주 간단 하지 않 습 니까? 이 단계 까지 우 리 는 사실 HttpResponse 의 가장 70% 기능 을 완 성 했 습 니 다. 나머지 30% 는 캐 시, 방향 변경, httpCookie 와 같은 구성 요소 입 니 다.
사용 방식


	    URL url=new URL("http://localhost:8080/test/ios.php");
	    EchoURLConnection connection=(EchoURLConnection)url.openConnection();
	    connection.setDoOutput(true);
	    connection.setDoInput(true);
	 
	    PrintWriter pw = new PrintWriter(new OutputStreamWriter(connection.getOutputStream()));
	    pw.write("name=zhangsan&password=123456");
	    pw.flush();
	   
	    InputStream stream = connection.getInputStream();
	    HttpInputStream his = new HttpInputStream(stream);
	    System.out.println(his.getHttpHeaders());
	    int len = 0;
	    byte[] buf = new byte[127];
	    StringBuilder sb = new StringBuilder();
	    while((len=his.read(buf, 0, buf.length))!=-1)
	    {
	    	sb.append(new String(buf, 0, len));
	    }
	    
	    System.out.println(sb.toString());

좋은 웹페이지 즐겨찾기