이 글은, KT RP 프로젝트 live 전환 중 발생한 에러를 해결하며 알게 된 내용을 토대로 정리하였다.

전송 방식이 JEUS6 (chunked) , JEUS7(content-length) 방식이었고 결론은 이 차이로 인하여 live 전환에 실패했다.

오류를 설명하기 전에 해당 전송 방식에 대한 기본적인 정보를 먼저 알아보자.

Chunked Transfer encoding

  • chunked 인코딩 전송방식 이란 HTTP 1.1 version 에서 사용가능한 스트리밍 데이터 전송 방식이다.
  • chunked 인코딩 방식에서 데이터는 각각의 덩어리들로 나누어 진다.
  • 각 덩어리들은 독립적으로 송신 및 수신 된다.
  • 각 chunk의 앞에는 해당 chunk의 크기가 온다. 형식은 byte 형식이다.
  • 길이가 0인 chunk가 수신되면 전송이 종료된다.
  • Transfer-Encoding 헤더에 chunked는 위의 방식으로 통신한다는 의미다.

 

Chunked 전송 방식의 장점

  • 서버는 동적으로 생성된 콘텐츠에 대해 HTTP 연결을 영구적으로 유지할 수 있다.
  • 해당 방식에서는 콘텐츠 크기가 아직 알려지지 않았기 때문에 HTTP Content-Length 헤더를 사용해서 콘텐츠와 다음 HTTP 요청 및 응답을 구분 할 수 없다.
  • chunked 방식은 헤더를 쓰기 전에 전체 컨텐츠를 생성할 필요가 없다.  컨텐츠를 청크로 스트리밍하고 콘텐츠의 끝을 명시적으로 신호를 보내서, 다음 HTTP요청, 응답에 연결을 사용할 수 있도록 하기 때문이다.
  • chunked 방식에서는 보낸 사람이 메세지 본문 뒤에  헤더 필드를 추가 할 수 있다. 내용이 생성되기 전까지 해당 필드의 내용을 알 수 없는 경우에 특히 중요하다.
  • 만약 chunked 인코딩 표시가 없다면, sender(발신자)는 콘텐츠를 보내기 전에 해당 필드의 값을 계산하여 먼저 보내야 하며 해당 컨텐츠가 전송 완료될 때까지 버퍼링 해야한다.

 

 

Chunked 전송 방식의 적용

  • HTTP 1.1 버전의 경우 chunked 전송 방식은 어디서나 사용 가능했다. 요청 필드에 transfer encoding이 없을 때에도.
  • 다른 전송 방식과 함께 사용할 수 있었으며, chunked 는 항상 마지막에 적용해야 한다.
  • client가 요청시 transfer encoding에 trailers 를 매개변수로 사용할 경우 마지막 청크 이후 추가 헤더 필드를 전송 할 수 있도록 했다.
  • server에서도 굳이 client가 trailers를  header에 요청하지 않더라도 추가 헤더 필드를 전송 가능했다.
  • trailer가 사용될 때마다 서버는 추가된 trailer 헤더 필드에 이름을 나열해야 한다.
  • 다음 세가지 헤더 필드 유형은 트레일러 필드로 표시되지 않으니 주의한다. : Transfer-Encoding, Content-Length, Trailer

 

Chunked 방식 format

  • HTTP 통신 방식(서버 또는 클라이언트에서 설정)에서 Transfer-Encoding 필드가 chunked인 경우 메세지 본문은 지정되지 않은 수의 청크로 구성된다.
  • 각 청크는 포함 된 데이터의 octets 수로 시작하여 ASCII의 16 진수 숫자로 표시되고, 그 뒤에 선택적 매개 변수 (청크 확장), 종료 CRLF 시퀀스, 청크 데이터가 이어진다.
  • 청크 확장이 제공되는 경우 청크의 크기는 세미콜론으로 끝나게 되고 그 뒤에 매개변수가 온다. 매개 변수도 각각 세미콜론으로 구분된다.
    해당 매개 변수들은 확장자 이름 뒤에 등호 및 값으로 인코딩된다.
    이런 매개변수는 디지털 서명을 실행하거나 예상 전송 진행률을 나타내는 데 사용할 수 있다.
  • 종료 청크는 길이가 0이라는 점을 제외하면, 일반적인 청크와 같다. 그 뒤에는 entity header 부분에 시퀀스로 구성된 트레일러가 온다. ( 비어있을 수도 있다.)
    일반적으로 이러한 헤더 필드는 메세지의 헤더로 전송된다. 그러나, 전체 메세지 엔터티를 처리한 후에 확인하는 것이 더 효율적일 수도 있다.
    이 경우에는 트레일러에서 해당 헤더를 보내는 것이 더 유용하다.
  • 트레일러 사용을 규제하는 헤더 필드는 transfer encoding( 요청시에 사용) 하거나, trailer ( 응답시에 사용) 부분에 설정하면 된다.

 

데이터를 압축하여 전송하는 경우

  • HTTP 서버는 종종 압축을 사용하여 전송을 최적화 한다.(예 : Content-Encoding : gzip 또는 Content-Encoding : deflate).
  • 압축 및 청크 인코딩이 모두 활성화 된 경우 콘텐츠 스트림이 먼저 압축 된 다음 청크된다.
    따라서 청크 인코딩 자체는 압축되지 않으며 각 청크의 데이터는 개별적으로 압축되지 않는다. 그런 다음 원격 엔드 포인트는 청크를 연결하고 결과를 압축 해제하여 스트림을 디코딩하게된다.

 

 

설정 방식

  • 응답시 설정하는 response.setBufferSize ()는 응답 크기의 Content-Length 헤더를 설정한다.
    응답 크기가 bufferSize를 초과하면 Transfer-Encoding : Chunked로 대체된다. 따라서 버퍼 크기는 적절한 값으로 설정해야 하는데 JEUS7의 경우 8192 byte 였다.
    더 높은 값으로 설정하면 플러시 하기 전에 모든 응답을 메모리에 버퍼링 합니다. 따라서 값은 적절하게 설정해야 한다.
    기본적으로 Tomcat 버퍼 크기는 8K(8192byte)로 설정되며, 해당 설정 옵션은 서비스 및 원하는 세트 헤더에 응답 필터를 추가 할 수 있다.


  • 서버는 기본적으로 요청 http 헤더에 따라 원하는 방식으로 응답한다.
    요청 http 헤더에 다음이 포함 된 경우( Accept-Encoding : chunked)가 있으면 서버 앱은 헤더 및 콘텐츠 Transfer-Encoding : chunked로 응답한다.
    따라서 클라이언트 기능을 나타내는 올바른 http 요청을 할 수없는 경우 리버스 프록시를 추가하여 Accept-Encoding : 청크 헤더를 변경하거나 추가하여 요청을 수정할 수도 있다.

 

 

Contents-Length 방식과 Chunked방식의 차이

HTTP 1.1 에서는 커넥션이 끊어지지 않고 유지되는 Keep Alive connection(persistent connection)을 지원한다. apache 에서 httpd.conf 에 Keepalive on으로 설정하면 Keep alive connection을 지원하게 된다.

웹페이지를 가져오는 프로그램을 만들 때를 생각해보자. 하나의 커넥션이 계속 유지된 상태라면 정확한 본문의 내용이 어디까지 인지, 그리고 길이는 얼마인지
어떻게 확인 해야 할까? 또한 connection close형태로 요청을 하더라도 HTTP 1.1에서는 본문내용을 chunked encoding형식으로 표시하는 경우도 있다.
이런 것처럼 HTTP 1.1로 요청을 할 때는 Content Length헤더로 표시되는 경우와 chunked encoding을 사용하는 경우 등 2가지 response 형식을 잘 구분해서 처리해주어야 한다.

1. Content-Length방식 

Content-Length ( keep alive off)

$ telnet coffeenix.net 80
Trying 211.xxx.xxx.xx...
Connected to coffeenix.net (211.xxx.xxx.xx).
Escape character is '^]'.
GET /truefeel/files/mail_log.pl.txt HTTP/1.1      <-- HTTP/1.1 형태로 요청
Host: coffeenix.net
Connection: close                                 <-- 요청 후 접속을 바로 끊음


HTTP/1.1 200 OK                                   <-- 여기 부터는 서버의 응답
Date: Sat, 03 Dec 2005 15:10:17 GMT               <-- RFC 1123을 따르는 날짜 표시
Server: Secured                                   <-- 웹서버명 ^^
Last-Modified: Sun, 18 Jan 2004 20:26:49 GMT
ETag: "64151-7dc-400aec09"
Accept-Ranges: bytes
Content-Length: 2012                              <-- contents의 길이. 2012bytes
Connection: close
Content-Type: text/plain


#!/usr/bin/perl                                   <-- 여기 부터는 contents
#
# procmail을 통해 넘겨온 메일 수신 정보를 DB로.
#
... 생략 ...
Connection closed by foreign host.

 

아래와 같이 요청하면 connection은 계속 유지된 상태로 있게 된다.

Content-Length( keep alive on)

$ telnet www.cyworld.nate.com 80
Trying 211.115.11.30...
Connected to www.cyworld.nate.com (211.115.11.30).
Escape character is '^]'.
GET /main2/index.htm HTTP/1.1                     <-- HTTP/1.1 형태로 요청
Host: www.cyworld.nate.com


HTTP/1.1 200 OK                                   <-- 여기 부터는 서버의 응답
Cache-Control: max-age=31536000
Content-Length: 162368                            <-- contents의 길이. 162368bytes
Content-Type: text/html
Last-Modified: Sat, 03 Dec 2005 14:58:50 GMT
Accept-Ranges: bytes
ETag: "fee1c0111af8c51:d63"
Server: Microsoft-IIS/6.0
Date: Sat, 03 Dec 2005 15:24:06 GMT

 


위처럼 'Content-Length' 헤더가 있다면 body부분에서 해당 길이만큼만 받아오고, close하면 된다.

 

2. Transfer-Encoding 헤더 Chunked 방식

Chunked encoding 헤더에는 Transfer-Encoding: chunked가 표시되고, 본문은 chunk size+내용+chunk size+내용... 형식으로 표시된다.

Trasfer-Encoding = chunked

$ telnet coffeenix.net 80
Trying 211.xxx.xxx.xx...
Connected to coffeenix.net (211.xxx.xxx.xx).
Escape character is '^]'.
GET / HTTP/1.1                                    <-- HTTP/1.1 형태로 요청
Host: coffeenix.net


HTTP/1.1 200 OK                                   <-- 여기 부터는 서버의 응답
Date: Sat, 03 Dec 2005 16:29:25 GMT
Server: Secured
Transfer-Encoding: chunked                        <-- chunked encoding
Content-Type: text/html


e22                         <-- chunked 된 size. 16진수로 표시되며, bytes 단위
... 내용 ...                <-- 해당 e22(3618 bytes) 길이의 contents
192                         <-- 16진수 192
... 내용 ...                <-- 192(402 bytes) 길이의 contents
f76
... 내용 ...
101
... 이하 생략 ..
0                           <-- 끝을 표시한다

보다 정확하게 보자면 chunk size+CRLF문자(CRLF는 ASCII코드 16진수로 0d0a)가 표시되고,내용+CRLF문자가 오는 형태가 반복되며 0+CRLF문자로 마무리를 한다.
따라서 웹페이지 읽어올 때는 처음 chunk size를 알아낸 후 해당 길이만큼 내용을 분리하고 다시 chunk size을 확인, 내용 분리하는 과정을 반복한 후 내용만 합치는 과정이 필요하다.

 

 

JEUS6 - JEUS7 간의 통신오류 문제

  • JEUS6(chunked) - JEUS6(chunked) : 문제 없음
  • JEUS7(content-length) - JEUS7(content-length) :  문제없음
  • JEUS7(content-length) - JEUS6(chunked) : 문제없음
  • JEUS6(chunked) - JEUS7(content-length) : 문제발생

 

이번 RP live 전환을 하는 도중 발생한 오류는 위와 같다. 정말 찾기 어려웠다.. 단말(STB)에서 오류 처리하는 케이스도 중구난방이었고

전혀 생각지도 못한 부분에서 오류였기 때문에 .. 참 난감했다. 그래도 이번 기회에 단말 로그도 보고 wireshark 로 네트워크 통신에 대해서 조금이나마,,? 보게 되었다. 너무나 어려운 것..

먼저 오류를 이해하기 전에 두 가지 방식의 차이에 대해서 이해할 필요가 있었다.

content-length 방식은 데이터의 길이를 서버에서 미리 알려 주기 때문에 client는 해당 길이만큼 데이터를 받고 종료한다.

반면 chunked 방식은 각각의 덩어리로 나누어진 데이터들을 각 길이만큼 한 부분 한 부분 읽어, chunk size가 0이 되면 종료한다.

일단 의심스러운 부분은, 위에서 언급했던 아래 글귀이다.

컨텐츠를 청크로 스트리밍하고 콘텐츠의 끝을 명시적으로 신호를 보내서, 다음 HTTP요청, 응답에 연결을 사용할 수 있도록 하기 때문이다.

chunked 방식의 경우 한 번 통신을 하게 되면  다음 요청 응답에도 해당 연결을 끊지 않고( 0이라는 부분으로 종료를 인식) 이후에도 기존에 연결된 통신으로 다음 요청을 진행하는 것으로 볼 수 있다.

따라서, 한번 chunked 방식으로 통신하게 되면 그 이후 요청도 당연히 chunked 방식으로 진행될 것이라고 client는 예측하는 것이다.

그래서 chunked 응답을 기대하고 있던 client에게 갑자기 JEUS7에서는 chunked 응답을 주지 않았고, 해당 정보를 이해하지 못한 client에서는 오류가 발생한 것이다.

다시 JEUS6으로 가게 되면  chunked 응답이 올 것이니 당연히 성공 처리되며, 해당 연결을 끊지 않고 계속 chunked 방식으로 통신할 수 있는 것이다..

요청과 응답을 종료 했는데 다음 HTTP요청 응답을 연결한다는 게 썩 좋은 로직 같진않다..

아무튼 진짜 찾기 힘든 오류였지만, 또 이렇게 배워간다..

 

 

참고자료

 

나의 노동력 with tmax engineer...

https://en.wikipedia.org/wiki/Chunked_transfer_encoding

http://coffeenix.net/board_view.php?bd_code=1280

https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Transfer-Encoding

https://b.pungjoo.com/entry/Transfer-Encoding-chunked-VS-Content-Length

 

Transfer-Encoding: chunked VS Content-Length

0. 들어가면서 Network에서는 전송하고자 하는 콘텐츠(content, 또는 data) 길이를 헤더에 기술하던가 콘텐츠의 끝이라고 서로간에 약속한 데이터를 마지막에 기술하던가 이도 저도 아니면 open된 strea

b.pungjoo.com

 

+ Recent posts