웹 커넥션 관리

본 장에서는 웹 엔진에서 제공하는 리스너 또는 커넥터의 관리 및 설정 방법 등에 대해 설명한다.

1. 개요

JEUS에서는 웹 리스너와 웹 커넥터를 통칭해서 웹 커넥션이라고 한다. 웹 엔진에서는 WebtoB 및 다른 웹 서버와의 커넥션, HTTP/TCP 클라이언트와의 직접적인 커넥션, 또는 Tmax와의 커넥션을 제공한다. 웹 서버는 클라이언트의 HTTP 요청을 받아 조건에 맞는 경우 웹 엔진으로 요청을 전달한다.

대표적인 웹 서버로 WebtoB와 Apache를 사용하고 연결을 위해 다음과 같은 각각의 커넥터를 제공한다.

  • WebtoB 커넥터

    WebtoB는 연결을 생성할 때 JEUS가 클라이언트 역할을 하기 때문에 WebtoB 커넥터를 제공한다.

  • AJP 리스너

    Apache는 JEUS가 서버 역할을 하기 때문에 AJP Listener를 제공한다. AJP의 경우 IIS, SunOne(Iplanet) 웹 서버와 같은 타사 상용 웹 서버에서도 지원하므로 AJP Listener를 통해서 해당 웹 서버와 연결할 수 있다.

웹 엔진은 클라이언트와 직접 커넥션을 맺고, 관리를 위해 각각의 클라이언트별로 다음과 같은 리스너 및 커넥터를 제공한다.

  • HTTP 리스너

    HTTP 클라이언트와의 직접적인 커넥션 관리를 위해 제공한다. Async Servlet 및 Sevlet NIO, Websocket의 기능을 사용하기 위해서는 HTTP 리스너를 사용해야 한다.

  • TCP 리스너

    TCP 클라이언트와의 커넥션 관리를 위해 제공한다.

  • Tmax 커넥터

    Tmax와의 연동을 위한 커넥터로 WebtoB 커넥터와 마찬가지로 연결을 생성할 때에는 JEUS가 클라이언트 역할을 한다.

리스너는 JEUS 서버의 설정을 기반으로 동작한다. 따라서 보안(SSL) 리스너를 사용하려면 JEUS 서버에 해당 설정을 하고 이를 각각의 웹 관련 리스너들이 사용하도록 설정한다.

JEUS 서버와 관련된 세부 설정 사항에 대한 자세한 내용은 JEUS Server 안내서의 Listener 설정을 참고한다.

2. 구성 요소

본 절에서는 웹 엔진에서 제공하는 리스너 및 커넥터에 대해 설명한다.

2.1. 리스너

리스너는 AJP 프로토콜을 따르는 웹 서버, HTTP/TCP 클라이언트가 접근할 수 있는 웹 엔진의 채널이다.

웹 엔진의 리스너와 각 클라이언트, 프로토콜의 연결을 나타내면 다음과 같다.

figure webserver and client listeners in web container
웹 엔진의 리스너

다음은 각 리스너에 대한 설명이다.

  • AJP 리스너

    WebtoB 이외의 다른 웹 서버(Apache, IIS, SunOne(Iplanet) 등)를 사용할 경우에도 JEUS 웹 애플리케이션과의 상호적인 연동이 가능하도록 하는 프로토콜이다. mod_jk module을 통해 지원하며 AJP 1.3 프로토콜을 사용한다. AJP 리스너는 SSL을 지원한다. AJP 리스너에 대한 자세한 내용은 AJP 리스너 설정부하 분산을 위한 웹 서버 설정을 참고하고, AJP 리스너에서 사용할 SSL 서버 리스너의 설정은 JEUS Server 안내서를 참고한다.

  • HTTP 리스너

    HTTP 요청을 웹 엔진이 직접 받을 때 사용한다. HTTP 리스너는 SSL을 지원한다. HTTP 리스너 설정의 자세한 내용은 HTTP 리스너 설정을 참고한다.

    HTTP 리스너에서 사용할 SSL 서버 리스너의 설정은 JEUS Server 안내서를 참고한다.

  • TCP 리스너

    HTTP 프로토콜이 아닌 커스텀 프로토콜로 동작하는 클라이언트에 대한 리스너이다. TCP 리스너에 대한 자세한 내용은 TCP 리스너 설정TCP 리스너 사용을 참고한다.

2.2. 커넥터

커넥터는 웹 엔진에서 WebtoB 및 Tmax에 연결하기 위한 채널이다.

웹 엔진의 커넥터와 각 클라이언트, 프로토콜의 연결을 나타내면 다음과 같다.

figure web connectors
웹 엔진의 커넥터

다음은 각 커넥터에 대한 설명이다.

  • WJP(WebtoB) 커넥터

    WJP는 WebtoB-JEUS Protocol을 의미한다. WebtoB는 TmaxSoft에서 제공하는 웹 서버이다. 별도의 제품으로 구매해서 JEUS와 함께 사용할 수도 있다.

    WebtoB 커넥터는 커넥션 생성 시점에 JEUS가 클라이언트 입장이기 때문에 직접 WebtoB로 접속하는 특징을 가진다. 이런 특징에 따라 방화벽 밖에 WebtoB 서버가 있을 경우에는 특별한 방화벽 설정 없이 연결을 맺을 수 있다. 이것은 방화벽이 주로 외부로부터의 연결 시도를 억제하고 내부로부터의 연결은 가능하게 하는 속성을 이용한 것이다. WebtoB 커넥터에 대한 자세한 내용은 부하 분산을 위한 웹 서버 설정을 참고한다.

  • Tmax 커넥터

    Tmax는 분산 환경에서 XA 트랜잭션을 관리하는 시스템 소프트웨어이다. Tmax 커넥터 역시 WebtoB 커넥터와 마찬가지로 JEUS가 클라이언트 역할을 한다. Tmax 커넥터는 JEUS와 Tmax 사이의 정보를 주고받거나, HTTP 요청을 Tmax의 게이트웨이를 통해 받음으로써 통신 채널을 일원화하는 용도로 사용할 수 있다. Tmax 커넥터에 대한 자세한 내용은 Tmax 커넥터 설정을 참고한다.

JEUS가 클라이언트 역할을 하는 것은 연결해서 커넥션을 맺을 때 뿐이다. 실제로 외부 클라이언트의 요청은 WebtoB, Tmax로부터 전달받는 것이며, JEUS 내부적으로 WebtoB나 Tmax에 요청을 보내는 경우는 없다. 즉, 실제 서비스 중에는 JEUS가 서버 역할을 한다.

2.3. Worker Thread Pool

리스너 또는 커넥터에는 클라이언트로의 요청을 처리하기 위한 Worker Thread Pool이 존재하는데, 이는 Worker Thread들을 관리하는 개체이다.

JEUS 21 이전에는 리스너 및 커넥터가 Worker Thread Pool을 관리하는 주체였지만, JEUS 21 FIX 1부터는 Web Connection, VirtualHost, Context가 주체가 되어 Worker Thread Pool을 관리한다. 자세한 내용은 스레드 풀을 참고한다.

리스너의 경우 요청이 오면 그 요청을 처리하기 위한 Worker Thread가 할당된다. HTTP, AJP13, TCP 리스너와 <use-nio>가 true인 WebtoB 커넥터인 경우에는 Worker Thread에 작업을 넘기기 전에 요청을 파싱하여 Context와 Host를 찾기 때문에, Context와 VirtualHost 레벨의 Worker Thread Pool에 넘길 수 있다. 커넥터의 경우 WebtoB 또는 Tmax와 JEUS가 client으로서 커넥션을 맺는다. <use-nio>가 false인 WebtoB와 Tmax 커넥터는 서버 리스너의 부재로 Worker Thread Pool에 할당하기 이전에 요청을 읽을 수 없기 때문에, 미리 커넥터에 종속되는 스레드 풀에서만 요청을 읽고 처리할 수 있다. 주의할 점은 커넥터의 Connection 개수와 스레드 개수가 동일한 구조로 되어 있어야 하기 때문에 따로 Web Connection의 스레드풀을 설정할 때 Connection, 스레드 풀 min, max가 동일한 값으로 설정되어야 한다.

Worker Thread Pool의 최솟값, 최댓값 등은 서비스 처리 성능에 큰 영향을 미치기 때문에 리스너 또는 커넥터를 설정할 때는 Worker Thread Pool을 주의해서 설정해야 한다.

JEUS는 각 Thread Pool이 자신의 상태를 직접 관리하며, 모니터링 Thread는 주기적으로 그 결과만 Logging해주는 방식을 사용한다. 따라서 로그에 남겨진 Thread 수 증감 변화와 실제 Thread Pool의 상태 변화가 서로 일치하지 않을 수 있으니 주의한다.

Active-Management와 상태 통보

Worker Thread Pool에는 Active-Management에 관한 설정이 포함되어 있다. Active-Management는 관리자가 지정한 특정 상태에 이르면 웹 엔진이 경고 메시지를 이메일(e-mail)로 통지하거나 웹 엔진이 속한 서버의 재시작을 권고하는 설정이다. 설정 가능한 값은 Worker Thread가 블록되었다고 판단하는 기준시간이다. 이에 따라 블록된 Worker Thread가 증가할 경우 몇 개 이상이면 경고 이메일 또는 엔진 재시작 권고 메시지 출력과 같은 특정 작업의 수행을 설정한다.

재시작 권고가 출력되면 서버의 요청 처리가 순조롭지 못할 가능성이 크기 때문에 관리자는 메시지를 확인하여 서버의 재시작 여부를 판단해야 한다.

3. 웹 커넥션 설정

AJP 리스너, WebtoB 커넥터, Tmax 커넥터를 사용하는 경우 연동 제품별로 설정이 필요하다. 모든 웹 커넥션은 웹 엔진에서 관리하므로 본 절에서는 웹 엔진의 설정에 대해서만 설명한다. WebtoB와 다른 웹 서버의 해당 설정에 대한 자세한 내용은 부하 분산을 위한 웹 서버 설정에서 설명한다.

  1. JEUS 6의 컨텍스트 그룹이 없어졌다. 컨텍스트 그룹이 관리하던 대부분의 사항들은 웹 엔진이 관리한다.

  2. 웹 리스너의 경우 서버에서 제공하는 통합된 서비스 리스너를 사용한다. 따라서 AJP, HTTP, TCP 리스너의 경우 서버에 리스너를 설정을 하고, 그 서버 리스너를 참조하는 방식으로 설정한다. 이에 따라 'Server Listener Ref'라는 설정이 추가되었다. 하지만 WebtoB, Tmax에 대한 커넥터는 JEUS가 클라이언트로서 직접 연결하므로 기존 설정 방식을 유지한다.

커넥터의 추가, 수정, 삭제는 콘솔 툴을 사용할 수 있다. 콘솔 툴을 사용하여 설정하는 방법은 JEUS Reference 안내서의 웹 엔진 관련 명령어를 참고한다.

3.1. 리스너 공통 설정

본 절에서는 각 리스너에서 공통적으로 사용하는 주요 설정이다.

  • Name

    • 웹 커넥션을 식별하는 유일한 이름이다. 웹 엔진 내에서 유일해야 하고, 반드시 설정해야 한다.

  • Server Listener Ref

    • 해당 리스너가 참조하는 서버 리스너를 나타낸다.

    • HTTP, AJP 리스너의 경우 서로 같은 서버 리스너를 공유할 수 있다. 단, HTTP 리스너를 관리 목적으로 사용하겠다고 설정한 경우에는 함께 사용할 수 없다. 설정하지 않은 경우에는 서버의 기본 리스너를 사용한다.

    • TCP 리스너는 함께 사용할 수 없다.

    • 2개 이상의 같은 프로토콜 리스너를 함께 사용할 수 없다. 예를 들어 AJP 리스너를 2개 설정했는데 둘 다 이 설정이 없으면 서버 기본 리스너를 공유하게 되므로 설정 에러가 발생한다. 둘 중 하나는 반드시 별도의 서버 리스너를 참조해야 한다.

    • 서버 리스너의 <keep-alive-timeout> 설정으로 아무런 요청이 없는 클라이언트 커넥션들을 끊어줄 수 있다. (단위: ms)

  • Connection Type

    • 각 리스너 또는 커넥터를 통해 나가는 응답의 커넥션 헤더를 강제로 설정한다.

    • 다음 중 하나를 설정할 수 있다.

      설정값 설명

      keep-alive

      응답을 보낸 후에도 연결을 계속 유지할 경우 설정한다.

      close

      응답을 보낸 후 연결을 끊을 경우 설정한다.

      none

      요청 헤더에 정의된 속성에 따라 응답의 커넥션 헤더를 따를 경우 설정한다. 웹 엔진은 아무 설정도 하지 않을 경우 'none’으로 설정된 것과 같이 동작한다.

      AJP 리스너의 경우 'Connection Type' 항목을 설정할 수 없다. AJP 리스너는 Apache와 같은 웹 서버의 설정을 따를 것을 권장한다.

  • Thread Pool

    • Web Connection 레벨의 Worker Thread Pool에 대한 설정이다.

    • 다음은 세부 사항을 설정하는 하위 항목에 대한 설명이다.

      항목 설명

      Min

      Pool에서 관리할 최소 Worker Thread의 개수이다. WebtoB 커넥터의 <use-nio>가 false인 경우에는 <Connection-Count>와 동일한 값으로 설정해야 한다.

      Max

      Pool에서 관리할 최대 Worker Thread의 개수이다. WebtoB 커넥터의 <use-nio>가 false인 경우에는 <Connection-Count>와 동일한 값으로 설정해야 한다.

      Maximum Idle Time

      Pool 내에 존재하던 Thread가 제거되기 전까지의 사용되고 있지 않은 시간을 지정한다. 그 결과 시스템 리소스는 증가한다.

      Max Queue

      Queue에 대기할 수 있는 요청의 최대 개수를 설정한다.

      값이 -1일 경우 Queue 크기에 제한을 두지 않는 것을 의미한다

      Thread State Notify

      블록되는 Worker Thread가 발생될 경우 이를 알려준다.

      각 Thread Pool은 장애가 발생했을 때 취하는 액션을 정의하는 'Thread State Notify' 항목을 가지고 있다. 이것에 대해서는 자동 Thread Pool 관리 설정(Thread 상태 통보)을 참고한다.

  • Output Buffer Size

    • 애플리케이션이 사용하는 응답 버퍼 크기값이다. 버퍼가 가득차면 해당 버퍼의 내용을 웹 엔진이 자동적으로 플러시한다. (단위: Byte)

    • AJP 리스너의 경우 이 값을 mod_jk의 workers.properties 파일의 max_packet_size와 일치시키는 것이 바람직하다. mod_jk의 설정에 대해서는 부하 분산을 위한 웹 서버 설정을 참고한다.

  • Postdata Read Timeout

    • 요청 바디를 읽어 들일 때 기다릴 수 있는 최대시간을 설정한다. ServletInputStream.read() 메소드에서 적용한다. (단위: ms)

    • HTTP 리스너, TCP 리스너에서는 타임아웃 적용 시점이 변경되었다.

      각 리스너마다 존재하는 I/O 컨트롤 스레드에서 요청 바디를 모두 읽기 때문에 실제 서블릿에게 넘기기 전에 이미 타임아웃 여부가 결정된다. 요청 바디를 읽는 도중에 타임아웃이 발생하면 500 에러를 내보낸다. 단, HTTP 요청이 Chunked 타입인 경우에는 기존과 마찬가지로 서블릿이 ServletInputStream.read()를 호출할 때 적용된다.

  • Max Post Size

    • 너무 큰 크기의 POST 요청의 데이터가 들어와 이 데이터를 읽고 분석하고 처리하는 데 많은 부하가 걸려 다른 요청들의 처리에 장애가 생길 경우를 위한 설정이다. 이 설정을 통해 특정 크기 이상의 요청에 대한 처리 부담을 줄일 수 있다. (기본값: -1)

    • POST 요청의 경우 요청의 Content-type에 따라 데이터의 최대 크기를 Byte 단위로 제한한다.

      • Content-Type이 x-www-form-urlencoded일 경우

        요청에 따라오는 데이터의 Byte 크기가 설정된 값을 초과할 경우, chunked일 경우, 들어온 데이터의 크기의 합이 설정된 값보다 클 경우에 JEUS는 해당 요청은 처리하지 않고 "413 Request entity too large" 응답을 보내고 해당 요청 처리를 완료한다.

      • Content-Type이 multipart/form-data일 경우

        이 설정으로 업로드 파일의 크기와 POST 요청의 전체 크기를 제한할 수 있다. 만약 업로드 파일을 포함한 크기를 제한하려면 Servlet의 multipart 관련 설정을 이용할 것을 권장한다.

    • 설정의 적용 여부는 Servlet 웹 애플리케이션 DD의 설정에 영향을 받는다. 즉, 설정에 따라 다음과 같이 동작하게 된다.

      • web.xml에 <multipart-config>의 설정 여부에 따른 동작

        web.xml에 <multipart-config> 설정이 존재할 경우 이 설정의 <max-file-size>와 <max-request-size>가 적용된다. <multipart-config> 설정이 없을 경우에는 이에 대한 제한을 웹 엔진에서는 제공하지 않는다. 따라서 각 애플리케이션에서 설정해서 사용하길 권장한다.

      • <content-length>의 설정 여부에 따른 동작

        <content-length>가 설정되어 있다면 이 값이 설정값을 초과할 경우 요청 처리가 제한이 된다. <content-length>가 설정되어 있지 않은 요청의 경우에는 이름/값 쌍의 파라미터의 Byte 합이 설정값을 초과할 때 요청 처리가 제한이 된다.

    • 음수 설정한 경우에는 기본값으로 설정한 것과 같이 동작하며 데이터 크기의 제한이 없다.

    • 해당 설정은 HTTP 요청을 받는 리스너 및 커넥터의 경우만 의미가 있다(TCP 리스너와 Tmax 커넥터의 경우 의미가 없다).

  • Max Parameter Count

    • 하나의 요청에 너무 많은 이름/값 쌍(파라미터)이 포함되어, 이 파라미터들을 분석 및 관리하는 부담이 증가하는 것을 방지하고자 할 경우 설정한다. (기본값: -1)

    • GET과 POST 요청에 포함된 '이름/값' 쌍 즉, 파라미터의 총 개수를 설정값으로 제한한다.

    • GET 요청의 Query String, POST 요청의 데이터나 multipart/form에 이름/값 쌍으로 들어온 모든 파라미터의 개수가 설정값 이상이 넘을 경우 "413 requesy entity too large" 응답을 보내고, 해당 요청의 처리를 중단한다.

    • 음수로 설정한 경우에는 기본값으로 설정한 것과 같이 동작하며 파라미터 개수의 제한이 없다.

    • 이 설정은 HTTP 요청을 받는 리스너 및 커넥터의 경우만 의미가 있다(TCP 리스너와 Tmax 커넥터의 경우 의미가 없다).

  • Max Header Count

    • 하나의 요청에 포함된 헤더가 무수히 많을 경우 이 요청을 읽는 부하가 많이 걸린다. 따라서 이럴 경우 해당 요청을 거부하여 서버의 부하를 줄일 수 있다. (기본값: -1)

    • 설정된 값 이상의 헤더 개수가 포함된 요청은 처리하지 않고, "400 Bad request" 응답을 보낸 후 해당 연결을 끊는다. 즉, 헤더 이후의 데이터는 읽지 않고 버림으로써 서버의 부담을 줄이게 된다.

    • 음수로 설정한 경우에는 기본값으로 설정한 것과 같이 동작하며, 헤더 개수의 제한이 없다.

    • 이 설정은 HTTP 요청을 받는 리스너 및 커넥터의 경우만 의미가 있다(TCP 리스너와 Tmax 커넥터의 경우 의미가 없다).

  • Max Header Size

    • 하나의 요청에 포함된 헤더가 제한 없이 클 경우 이 헤더를 읽고 처리하는 데 많은 부하가 걸린다. 따라서 이럴 경우 해당 요청을 거부하여 서버의 부하를 줄일 수 있다. (기본값: -1)

    • 설정값보다 Byte 크기(이때 헤더의 Byte는 요청의 헤더 하나를 타나내는 헤더 이름, 구분자, 헤더 값을 모두 포함한 크기)가 큰 헤더가 포함된 요청은 처리하지 않고 "400 Bad request" 응답을 보낸 후 해당 연결을 끊는다. 즉, 헤더 이후의 데이터는 읽지 않고 버림으로써 서버의 부담을 줄이게 된다.

    • 음수로 설정한 경우에는 기본값으로 설정한 것과 같이 동작하며, 헤더 Byte의 제한이 없다.

    • 해당 설정은 HTTP 요청을 받는 리스너 및 커넥터의 경우만 의미가 있다(TCP 리스너와 Tmax 커넥터의 경우 의미가 없다).

  • Max Query String Size

    • GET 요청의 Query String이 길 경우 이를 분석 및 관리하는 부하가 발생할 수 있다. 이런 경우 Query String의 크기를 제한하여 부하를 줄일 수 있다. (기본값: 8192, 단위: Byte)

    • 설정된 Byte 이상으로 큰 Query String을 포함한 요청이 들어올 경우 해당 요청에 대해 "400 Bad Rqeust" 응답을 보낸 후 해당 요청의 나머지 데이터들은 읽지 않고 버림으로써 서버의 부하를 줄인다.

    • 음수로 설정한 경우는 Query String의 크기에 제한을 두지 않는다. 그러나 JEUS는 내부적으로 request line(요청의 첫 줄)의 크기를 64KB로 제한하므로 그 이상은 의미가 없다.

    • 해당 설정은 HTTP 요청을 받는 리스너 및 커넥터의 경우만 의미가 있다(TCP 리스너와 Tmax 커넥터의 경우 의미가 없다).

3.2. AJP 리스너 설정

콘솔 툴을 사용하여 AJP 리스너를 추가하거나 수정 및 삭제할 수 있다. AJP 리스너는 AJP 버전 1.3을 준수한다.

콘솔 툴 사용

다음은 콘솔 툴을 사용해서 AJP 리스너를 추가, 수정, 삭제하는 방법이다.

  • 추가

    콘솔 툴을 사용하여 AJP 리스너를 추가하려면 add-ajp-listener 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 add-ajp-listener를 참고한다.

    add-ajp-listener [-cluster <cluster-name> | -server <server-name>]
                     [-f, --forceLock]
                     -name <web-connection-name>
                     -tmin <minimum-thread-num>
                     [-tmax <maximum-thread-num>]
                     [-tidle <max-idle-time>]
                     [-qs <max-queue-size>]
                     -slref <server-listener-ref-name>
  • 수정

    콘솔 툴을 사용하여 AJP 리스너를 수정하려면 modify-web-listener 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 modify-web-listener를 참고한다.

    modify-web-listener [-cluster <cluster-name> | -server <server-name>]
                        [-f, --forceLock]
                        -name <web-connection-name>
                        [-tmin <minimum-thread-num>]
                        [-tmax <maximum-thread-num>]
                        [-tidle <max-idle-time>]
                        [-http2 <enable-http2>]
                        [-obuf <output-buffer-size>]
  • 삭제

    콘솔 툴을 사용하여 AJP 리스너를 삭제하려면 remove-web-listener 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 remove-web-listener를 참고한다.

    remove-web-listener [-cluster <cluster-name> | -server <server-name>]
                        [-f, --forceLock]
                        <web-connection-name>

3.3. HTTP 리스너 설정

콘솔 툴을 사용하여 HTTP 리스너를 추가하거나 수정 및 삭제할 수 있다. HTTP 리스너는 내부 관리 용도로만 사용할 것을 권장한다.

JEUS 9는 HTTP/2를 지원한다. HTTP/2 사용 방법에 대해서는 HTTP/2 사용을 참조한다.

콘솔 툴 사용

콘솔 툴을 사용하여 HTTP 리스너를 추가, 수정, 삭제하는 방법은 AJP 리스너의 방법과 동일하나 HTTP 리스너를 추가, 수정할 경우에 대해서만 HTTP/2를 사용할지 결정할 수 있는 옵션을 제공한다. 자세한 내용은 AJP 리스너 설정"콘솔 툴 사용"을 참고한다.

3.4. TCP 리스너 설정

TCP 리스너는 커스텀 프로토콜로 상호 간에 통신할 수 있도록 제공하는 특수한 리스너이다. TCP 리스너는 기본적으로 서버 리스너를 다른 웹 리스너들과 공유할 수 없기 때문에 사용을 위해서는 서버에 TCP 리스너를 위한 전용 리스너를 추가해야 한다. 콘솔 툴을 사용하여 TCP 리스너를 추가하거나 수정 및 삭제할 수 있다.

콘솔 툴 사용

콘솔 툴을 사용하여 TCP 리스너를 추가, 수정, 삭제하는 방법은 AJP 리스너의 방법과 동일하다. 자세한 내용은 AJP 리스너 설정"콘솔 툴 사용"을 참고한다.

3.5. WebtoB 커넥터 설정

WebtoB 커넥터는 커넥션을 생성할 때 JEUS가 클라이언트 역할을 하게 된다. 따라서 WebtoB 주소와 연결 포트가 필요하다. 콘솔 툴을 사용하여 WebtoB 커넥터를 추가하거나 수정 및 삭제할 수 있다.

WebtoB와 JEUS 사이의 통신 프로토콜인 WJP 버전이 1에서 2(이하 WJPv1 및 WJPv2)로 업그레이드되었다. WJPv2의 경우 WJPv1에 비교해서 좀더 적은 사이즈의 패킷을 사용하며, 여러 가지 부가 기능들을 제공한다. JEUS에서 연결할 WebtoB가 WJPv2를 지원하지 않는 버전일 경우에는 WJPv1을 사용하면 된다. WJP 버전 정보의 경우 WebtoB에서 자동으로 파악할 수 없기 때문에 JEUS에 <wjp-version>으로 설정해야 한다. WJP는 WebtoB-JEUS Protocol을 의미한다.

콘솔 툴 사용

다음은 콘솔 툴을 사용해서 WebtoB 커넥터를 추가, 수정, 삭제하는 방법이다.

  • 추가

    콘솔 툴을 사용하여 WebtoB 커넥터를 추가하려면 add-webtob-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 add-webtob-connector를 참고한다.

    add-webtob-connector [-cluster <cluster-name> | -server <server-name>]
                         [-f,--forceLock]
                         -name <web-connection-name>
                         -tmin <minimum-thread-num>
                         [-tmax <maximum-thread-num>]
                         [-tidle <max-idle-time>]
                         [-qs <max-queue-size>]
                         -conn <thread-number>
                         -regid <registration-id>
                         [-ver <wjp-version>]
                         [-useNio <use-nio>]
                         [-addr <WebtoB address>]
                         [-dsocket | -port <WebtoB port>]
                         [-wbhome <webtob-home>]
                         [-ipcport <ipc-base-port>]
                         [-sndbuf <send-buffer-size>]
                         [-rcvbuf <receive-buffer-size>]
                         [-hth <set-hth-count.>]
  • 수정

    콘솔 툴을 사용하여 WebtoB 커넥터를 수정하려면 modify-webtob-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 modify-webtob-connector를 참고한다.

    modify-webtob-connector [-cluster <cluster-name> | -server
                            <server-name>]
                            [-f,--forceLock]
                            -name <web-connection-name>
                            [-tmin <minimum-thread-num>]
                            [-tmax <maximum-thread-num>]
                            [-tidle <max-idle-time>]
                            [-conn <thread-number>]
                            [-obuf <output-buffer-size>]
                            [-ver <wjp-version>]
                            [-addr <WebtoB address>]
                            [-dsocket | -port <WebtoB port>]
                            [-wbhome <webtob-home>]
                            [-cloud]
                            [-ipcport <ipc-base-port>]
                            [-regid <registration-id>]
                            [-sndbuf <send-buffer-size>]
                            [-rcvbuf <receive-buffer-size>]
                            [-hth <set-hth-count.>]
  • 삭제

    콘솔 툴을 사용하여 WebtoB 커넥터를 삭제하려면 remove-webtob-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 remove-webtob-connector를 참고한다.

    remove-webtob-connector [-cluster <cluster-name> | -server
                            <server-name>]
                            [-f,--forceLock]
                            <web-connection-name>

3.6. Tmax 커넥터 설정

Tmax 커넥터 역시 WebtoB 커넥터와 마찬가지로 커넥션을 생성할 때 JEUS가 클라이언트 역할을 하게 된다. 따라서 Tmax 주소와 연결 포트가 필요하다. 콘솔 툴을 사용하여 Tmax 커넥터를 추가하거나 설정 및 삭제할 수 있다.

콘솔 툴 사용

다음은 콘솔 툴을 사용해서 Tmax 커넥터를 추가, 수정, 삭제하는 방법이다.

  • 추가

    콘솔 툴을 사용하여 Tmax 커넥터를 추가하려면 add-tmax-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 add-tmax-connector를 참고한다.

    add-tmax-connector [-cluster <cluster-name> | -server <server-name>]
                       [-f,--forceLock]
                       -name <web-connection-name>
                       -tmin <minimum-thread-num>
                       [-tmax <maximum-thread-num>]
                       [-tidle <max-idle-time>]
                       [-qs <max-queue-size>]
                       -addr <server-address>
                       -port <server-port>
                       -svrg <server-group-name>
                       -svr <server-name>
                       -dcc <dispatcher-config-class>
  • 수정

    콘솔 툴을 사용하여 Tmax 커넥터를 수정하려면 modify-tmax-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 modify-tmax-connector를 참고한다.

    modify-tmax-connector [-cluster <cluster-name> | -server <server-name>]
                          [-f,--forceLock]
                          -name <web-connection-name>
                          -tmin <minimum-thread-num>
                          [-tmax <maximum-thread-num>]
                          [-tidle <max-idle-time>]
                          [-qs <max-queue-size>]
                          -addr <server-address>
                          -port <server-port>
                          -svrg <server-group-name>
                          -svr <server-name>
                          -dcc <dispatcher-config-class>
  • 삭제

    콘솔 툴을 사용하여 Tmax 커넥터를 삭제하려면 remove-tmax-connector 명령어를 실행한다. 명령어에 대한 자세한 내용은 JEUS Reference 안내서의 remove-tmax-connector를 참고한다.

    remove-tmax-connector [-cluster <cluster-name> | -server <server-name> |
                            -f,--foreceLock]
                            <web-connection-name>

4. 부하 분산을 위한 웹 서버 설정

본 절에서는 JEUS와의 연동 사례가 많은 웹 서버들의 설정 방법과 웹 엔진을 웹 커넥션과 연동할 수 있는 방법에 대해서 설명한다.

웹 엔진은 시스템의 HTTP 처리의 성능 향상을 위해 웹 서버와 웹 엔진(서블릿 엔진)으로 구성하여 서비스할 수 있다. 각 웹 엔진으로의 HTTP 요청을 웹 엔진 앞의 웹 서버에서 1차로 부하를 분산시킨다. 이후 같은 연결이나 세션의 요청에 대해 HTTP 세션 클러스터링 서비스를 이용하여 처음 요청을 처리한 웹 엔진으로 할당하여 서비스 처리 효율을 높인다.

처음 요청을 처리한 웹 엔진에 장애가 발생한 경우, 장애 발생 이후에 들어온 요청은 웹 엔진 앞의 웹 서버에서 장애가 발생하지 않은 웹 엔진으로 전달하여 끊김 없는 서비스를 가능하게 한다. 이 서비스는 HTTP 세션 클러스터링을 사용한다는 전제 조건이 필요하다. HTTP 세션 클러스터링에 대한 자세한 내용은 JEUS 세션 관리 안내서의 분산 세션 서버를 참고한다.

만약 웹 서버를 사용하지 않고 웹 엔진만을 사용할 경우에 요청을 처리한 웹 엔진으로 이후의 요청을 전달할 수 있는 장비나 소프트웨어를 사용하면 웹 서버를 사용할 때와 같이 효율적인 서비스가 가능할 것이다. 그러나 JEUS에서는 이에 필요한 소프트웨어를 제공하지 않고, 이런 사용을 권장하지 않는다.

각 웹 서버들의 설정과 mod_jk 설정은 웹 서버 버전에 따라서 차이가 있을 수 있다. 본 절의 설정 방법은 JEUS 사용자들의 이해를 돕기 위해서 제공하는 것이므로 실제로 설정할 때에는 반드시 각 웹 서버들의 문서, 국내외 커뮤니티에서 제공하는 설정 사례들을 참고해야 한다.

4.1. 부하 분산 구조

서비스 요청이 많은 웹 사이트는 한 대의 웹 서버와 웹 엔진으로는 서비스를 제공하기 어렵기 때문에 부하 분산을 위해서 여러 개의 웹 서버와 웹 엔진이 필요하다.

다음은 2대의 웹 서버와 4대의 웹 엔진이 연결되어 있는 부하 분산 구조를 나타낸다.

figure small cluster with two webservers connected to two web containers each
소규모 웹 사이트에서의 부하 분산 구조

각 웹 엔진에는 동일한 웹 컨텍스트들이 deploy되어 있어야 한다. 이러한 환경에서 중요한 것은 세션의 공유 여부이다. 애플리케이션을 좀 더 편리하게 관리하고 싶거나 분산 세션 서비스가 필요하다면 웹 엔진들을 하나의 클러스터로 묶어야 한다. 세션 클러스터에 대한 자세한 내용은 JEUS 세션 관리 안내서의 세션 트래킹을 참고한다.

4.2. Apache 웹 서버

Apache를 웹 엔진과 연동하려면 다음과 같은 과정을 수행해야 한다.

  1. Apache에 mod_jk 라이브러리를 설치한다.

  2. JEUS 웹 엔진에 AJP13 리스너를 설정한다.

  3. workers.properties 파일을 생성하고 편집한다.

  4. Apache 웹 서버의 httpd.conf 파일에 연동 구절을 삽입한다.

  5. Apache 웹 서버를 재시작한다.

Apache의 경우 2.2.4, mod_jk의 경우 1.2.20을 기준으로 설명한다.

mod_jk 라이브러리 설치

Apache 웹 서버를 웹 엔진의 앞 단에서 사용하려면 "mod_jk"라는 모듈을 Apache 설치 모듈에 추가해야 한다. "mod_jk" 모듈은 서버와 엔진 간의 통신 프로토콜을 구현한 것으로, 이 프로토콜은 Apache JServ Protocol 1.3(AJP 1.3)이다.

현재 Windows 바이너리를 제공하고 있는 Apache Tomcat 커넥터 다운로드 페이지에서 소스를 다운로드받아서 해당 머신에서 컴파일한다.

AJP13 리스너 설정

mod_jk 라이브러리 설치가 완료되면 JEUS 웹 엔진에 AJP13 리스너를 설정한다. AJP13 리스너 설정 방법의 자세한 내용은 AJP 리스너 설정을 참고한다.

workers.properties 설정

workers.properties 파일은 mod_jk를 위한 설정 파일이다. 예를 들어 JEUS는 server1, server2라는 2대의 서버가 있다. 호스트 이름이 각각 server1, server2라고 가정한다. 각 웹 엔진에 설정된 AJP 리스너들은 9901, 9902로 설정했다고 가정하자.

다음은 가정한 JEUS 환경에서의 workers.properties 예제이다.

mod_jk 설정 파일 예제 : <workers.properties>
worker.list=jeus_load_balancer_workers
worker.jeus_load_balancer_workers.type=lb
worker.jeus_load_balancer_workers.sticky_session=true

###########################################
# listener specific configuration
###########################################
worker.jeus_load_balancer_workers.balance_workers=server1
worker.server1.reference=worker.template
worker.server1.host=192.168.0.101
worker.server1.port=9901
worker.server1.route=ZG9tYWluMS9zZXJ2MQ==

worker.jeus_load_balancer_workers.balance_workers=server2
worker.server1.reference=worker.template
worker.server1.host=192.168.0.102
worker.server1.port=9902
worker.server1.route=ZG9tYWluMS9zZXJ2Mg==


###########################################
# common config
###########################################
worker.template.type=ajp13
worker.template.socket_connect_timeout=5000
worker.template.socket_keepalive=true
worker.template.ping_mode=A
worker.template.ping_timeout=10000
worker.template.connection_pool_minsize=0
worker.template.connection_pool_timeout=600
worker.template.reply_timeout=300000
worker.template.recovery_options=3

workers.properties 파일은 몇몇 정의를 제외하고 일반적으로 "worker.<worker_name>.<directive>=<value>"의 형식으로 정의한다.

다음은 중요한 설정에 대한 설명이다.

  • worker.list

    해당 worker를 정의하는 항목이다. worker의 이름들은 콤마(,)로 구분되며 여러 worker들을 나열할 수 있다.

    다음은 "jeus_load_balancer_workers"라는 이름의 worker를 하나 정의한 예제이다.

    worker.list=jeus_load_balancer_workers
  • worker.<worker_name>.type

    worker의 타입을 정의한다. 'ajp13', 'ajp14', 'jni', 'lb', 'status' 등을 선택할 수 있다. AJP13을 지원하기 위해 JEUS에서는 'ajp13', 'lb’만의 설정으로도 충분하다.

    다음은 "jeus_load_balancer_workers"를 Load Balancer type으로 정의한 예제이다.

    worker.jeus_load_balancer_workers.type=lb
  • worker.<worker_name>.balance_workers

    콤마(,)로 구분하여 Load Balance를 원하는 worker들을 나열할 수 있다. 위의 woker.list에 적은 worker들이 나타나서는 안 된다.

    JEUS의 AJP13 리스너와 연동하는 경우 올바른 Load Balancer 및 sticky session 역할을 하려면 worker의 이름을 JEUS의 서블릿 엔진 이름으로 해야 한다. JEUS의 경우 Session ID의 라우팅 정보로 서블릿 엔진 이름을 이용하며 mod_jk에서는 worker의 이름을 이용하기 때문이다.

  • worker.<worker_name>.sticky_session

    Session ID를 기반으로 라우팅을 지원할지 여부를 설정한다. true(or 1) 또는 false(or 0)로 설정할 수 있다. JEUS에서는 Sticky Session을 기본으로 지원하므로 항상 true로 설정한다. (기본값: true)

    다음은 "jeus_load_balancer_workers"는 Sticky Session을 지원한다는 예제이다.

    worker.jeus_load_balancer_workers.sticky_session=true
  • worker.<worker_name>.host

    JEUS의 AJP13 리스너가 존재하는 호스트 이름 또는 IP 주소를 입력한다.

  • worker.<worker_name>.port

    JEUS의 AJP13 리스너의 포트 번호를 설정한다.

  • worker.<worker_name>.reference

    많은 Load Balancer worker들을 설정할 때 유용하며 지정된 값에 해당하는 worker의 설정값을 reference할 수 있는 설정이다. 예를 들어 "worker.castor.reference=worker.template"라고 설정했다면 명시적으로 castor worker에서 설정한 값을 제외하고 모든 worker.template의 설정들을 상속받는다.

  • worker.<worker_name>.route

    설정값에 해당하는 값이 sticky로 요청이 왔을 경우에 해당 worker가 처리하는 설정이다. JEUS의 세션 매니저에서 생성하는 세션에 붙이는 세션 라우팅 기술로 해당하는 worker를 매치시킴으로서 로컬 세션의 활용도를 높일 수 있다. 세션 라우팅에 대한 자세한 내용은 JEUS 세션 관리 안내서의 세션 트래킹 구조를 참고한다.

    해당 값은 domain_name/server_name의 Base64 알고리듬으로 인코딩한 값이다. 해당 인코딩 값은 JEUS_HOME/bin의 encryption 명령을 통해 얻을 수 있다.

    다음은 사용 예제이다.

    ${JEUS_HOME}/bin/encryption -algorithm base64 -text domain1/server1

    설정할 때 "domain1/server1", "domain1/server2"와 같이 "도매인 이름/서버 이름"을 사용하는 것에 유의한다.

    이 설정들은 mod_jk 버전에 따라 deprecated될 수도 있으므로 본 절에서 제시한 내용은 참고 용도로만 사용한다. 반드시 Tomcat 홈 페이지의 mod_jk 관련 설명에 따라 설정한다.

httpd.conf 설정

httpd.conf 파일에는 mod_jk 모듈을 사용하기 위해 다음 사항을 추가해야 한다.

Apache에 mod_jk 설정 예제 : <httpd.conf>
. . .
LoadModule    jk_module  "/usr/local/apache/modules/mod_jk.so"
JkWorkersFile "/usr/local/apache/conf/workers.properties"
JkLogFile     "/usr/local/apache/logs/mod_jk.log"
JkLogLevel    info
JkMount /examples/* jeus_load_balancer_workers
. . .

Apache 버전에 따라 deprecated될 수도 있으므로 여기에서 제시한 것은 참고 용도로만 사용한다. 반드시 Apache 매뉴얼에 따라 설정한다.

4.3. IIS 웹 서버 및 Iplanet 웹 서버 설정

IIS 웹 서버와 Iplanet 웹 서버도 AJP13 프로토콜을 지원한다. 따라서 workers.properties와 JEUS 설정 방식은 모두 동일하다. 단, 각 제품에 따라 mod_jk 모듈을 설치하는 방법과 mod_jk 사용 설정을 하는 방법이 다르다. 이러한 설정 방법은 각 웹 서버에서 제공하는 매뉴얼을 참고한다.

4.4. WebtoB 부하 분산 설정

본 절에서는 예제를 통하여 WebtoB와 JEUS의 부하 분산 처리 환경을 구성한다.

다음은 예제의 서버 구조를 나타낸다. 각 엔진이 각각의 WebtoB 서버를 갖는 구조에 대한 설정으로 예제는 2대의 WebtoB가 각각 2대의 웹 엔진에 연결된다.

figure two webtob webservers connected in parallel with two web containers each
부하 분산 서버 구조 - 2대의 WebtoB가 각각 2대의 웹 엔진에 연결된 경우

Server A부터 D에는 동일한 웹 컨텍스트를 deploy해야 한다. 위와 같은 구성에서는 일반적으로 Server A부터 D가 하나의 클러스터를 이루게 되며, 그에 따라 분산식 세션 서버를 사용하게 된다. 만약 사용자 세션이 필요 없는 경우에는 클러스터를 구성하지 않아도 무방하다.

다음은 WebtoB A와 Server A, Server B가 연결된 모습을 좀 더 자세히 표현한 것으로 각각의 WebtoB 커넥터 설정 상태를 보여준다.

figure two webtob connectors connected to one webtob server
WebtoB 커넥터 설정 상태

설정할 때는 WebtoB의 HTH 프로세스 수를 고려해야 한다.

WebtoB HTH 프로세스는 몇 개의 하위 프로세스를 가지고 있는 엔진처럼 행동한다. 이는 WebtoB 커넥터의 Worker Thread와 1대 1로 연결된다. 따라서 WebtoB 커넥터의 'connection-count' 설정값과 WebtoB 설정의 'MaxProc' 값을 주의해서 설정해야 한다.

HTH 프로세스의 개수는 WebtoB 설정에 존재하는 HTH 프로세스 개수를 그대로 WebtoB 커넥터의 'Hth Count' 설정에 반영한다. WebtoB의 'MaxProc' 값과 WebtoB 커넥터의 'connection-count' 값은 다음과 같은 공식으로 표현될 수 있다.

MinProc = Listener A connection-counts + Listener B connection-counts + … + Listener X connection-counts setting.
MaxProc = Listener A connection-counts + Listener B connection-counts + … + Listener X connection-counts setting.

WebtoB 커넥터의 'Hth Count' 값은 WebtoB 설정 파일의 NODE 절의 요소 중 HTH 프로세스 개수와 반드시 동일해야 한다. WebtoB 커넥터 설정은 WebtoB 커넥터 설정을 참고한다.

다음은 WebtoB A가 위의 예제와 같이 설정되기 위한 http.m 파일의 설정 예이다.

WebtoB 설정 예 : <http.m>
*NODE
foo     …
   HTH = 2,
   JSVPORT = 9900,
   …
*SVRGROUP
jsvg    NODENAME = foo, SVRTYPE = JSV

*SERVER
default    SVGNAME = jsvg, MinProc = 6, MaxProc = 25

*URI
uri1  Uri = "/examples/", Svrtype = JSV
uri2  Uri = "/test/", Svrtype = JSV

*EXT
jsp  MimeType = "application/jsp", SvrType = JSV

connection count는 6과 25 사이여야 한다.

5. TCP 리스너 사용

TCP 리스너는 통신 프로토콜을 직접 정의해서 TCP 클라이언트와 TCPServlet 간에 통신할 수 있다. 단, TCP 리스너는 HTTP 또는 HTTPS 프로토콜로 만족할 수 없는 사항이 있을 경우에만 사용하기를 권장한다.

TCP 리스너를 사용하기 위해서는 다음과 같은 과정을 수행해야 한다.

  1. 맞춤 통신 프로토콜을 정의한다.

  2. Dispatcher Config Class를 구현한다(프로토콜 설정 정보).

  3. TCP 핸들러 서블릿을 구현한다(프로토콜 구현).

  4. 맞춤 프로토콜 구현을 위한 TCP 리스너를 설정한다.

  5. TCP 클라이언트를 구현한다.

  6. TCP 클라이언트를 컴파일하고 구동한다.

본 절에서 사용할 용어는 다음과 같다.

용어 설명

프로토콜(통신 프로토콜)

TCP 클라이언트와 TCP 핸들러 사이에서 (TCP 리스너가 중간역할) 교환되는 메시지의 구조와 내용을 정의한다.

메시지

TCP 클라이언트와 TCP 핸들러 사이에서 교환되는 데이터이다. 프로토콜에서 정의한 구조에 맞아야 한다.

TCP 클라이언트

TCP 리스너와 교신하며 메시지를 주고받는 외부의 애플리케이션(TCP 핸들러에 의해 처리된다)이다. 메시지를 주고받는 것은 표준 소켓을 사용한다.

TCP 핸들러

TCP 리스너를 통해 메시지를 받고 구현된 방법대로 메시지를 처리한다.

TCP 핸들러는 jeus.servlet.tcp.TCPServlet의 하위 클래스의 형태로 구현되고 웹 엔진에 보통 서블릿처럼 등록된다. TCP 핸들러는 TCP 프로토콜 위에 존재하는 맞춤 프로토콜의 "제공자" 또는 "구현체"처럼 동작한다고 할 수 있다.

TCP Dispatcher Configuration Class

TCP Dispatcher Configuration Class는 jeus.servlet.tcp.TCPDispatcherConfig를 확장한 클래스이다. 이 클래스는 맞춤 프로토콜에 대한 정보를 TCP 리스너로 전달하여 이 non-HTTP 메시지를 해석하여 처리한다.

이 클래스는 DOMAIN_HOME/lib/application 디렉터리 아래에 위치시키고, 웹 엔진 설정 하위의 TCP 리스너 설정 항목 중 'Dispatcher Config Class' 항목에 설정한다(TCP 리스너 설정 참조).

TCP 리스너

맞춤 메시지에 대한 해석과 라우팅 인프라를 제공한다. TCP 클라이언트와 TCP 핸들러 사이에서 non-HTTP 중간 매개체로 역할을 한다.

이것은 다른 HTTP 리스너와 마찬가지로 웹 엔진 내에 존재한다.

5.1. 맞춤 통신 프로토콜 정의

TCP 리스너는 TCP 클라이언트와 TCP 핸들러 간에 통신되는 모든 메시지들을 두 부분으로 나누는데, 헤더와 바디가 그것이다. 일반적으로 헤더는 기본적이면서도 표준 정보를 전달하고 크기도 정해져 있다. 바디는 전송될 임의의 사용자 데이터가 포함된다. (예: HTTP 응답의 HTML 코드)

TCP 리스너의 사용을 설명하기 위해 간단한 프로토콜(메시지 구조)을 정의한다(이것은 나중에 사용할 것이다).

맞춤 통신 프로토콜(맞춤 메시지 구조)은 다음과 같은 내용을 가진다.

  • 헤더

    • 헤더는 4Bytes의 magic 수로 시작한다. 이는 사용되는 프로토콜을 식별한다. '7777’로 설정한다.

    • 1Byte의 type 필드가 magic 수 다음에 쓰인다. '0’은 요청을 '1’은 응답을 의미한다.

    • 세 번째 항목은 4Bytes의 body length이다. 이 항목은 메시지의 바디 부분의 Byte 수를 기록한다. 예제에서는 크기가 128Bytes로 고정되어 있다.

    • 마지막 항목은 32Bytes의 string으로 service name을 기록한다. 예제에서는 그 값으로 요청을 처리하는 TCPServlet의 이름을 입력한다.

  • 바디

    • 메시지의 바디 부분은 128Bytes 길이를 가진 문자 데이터를 넣는다. 예제에서는 이 128Byte 블록을 2개의 임의의 64Bytes 텍스트 string으로 넣는다.

5.2. Dispatcher Config Class 구현

Dispatcher Config Class는 jeus.servlet.tcp.TCPDispatcherConfig Class의 하위 클래스이다. 이 클래스의 abstract 메소드는 TCP 리스너가 알맞은 TCP 핸들러에게 메시지를 전달하기 위해 필요한 여러 가지 정보들이 구현되어야 한다. 이 메소드들은 JEUS_HOME/docs/api/jeus-servlet/index.html에 설명되어 있다.

JEUS 7 Fix#1부터 getBodyLengthLong 메소드가 추가되었다. TCP 바디의 크기가 2GB 이상이라면 해당 메소드를 사용해서 바디 길이를 8Byte로 나타내면 된다.

다음은 정의된 프로토콜에 기반하여 Dispatcher Config Class를 어떻게 구현했는지를 보여준다. 특정 메소드들이 어떻게 사용되었는지는 주석을 참고한다.

TCPDispatcherConfig 구현 예 : <SampleConfig.java>
package sample.config;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jeus.servlet.tcp.*;

/*
  This class extends the abstract class jeus.servlet.tcp.
  TCPDispatcherConfig class. This implementation provides
  routing and handling information to the TCP listener
  according to our defined communications protocol.
*/
public class SampleConfig extends TCPDispatcherConfig {
    /*
      Any init code goes into this method. This method will be
      Called once after starting the TCP listener.
      We leave this method empty for our simple example.
    */
    public void init() {}

    /*
      This method returns the fixed-length of the header so
      that the TCP listener knows when to stop
      reading the header. The header length for
      our example is 41 (bytes): 4 (magic) + 1
      (type) + 4 (body length) + 32 (service name).
    */
    public int getHeaderLength() {
        return 41;
    }

    /*
      This method must return the length of the body.
      For our example, this length is represented as an
      “int” in the header, starting at position “5”
      (see the protocol definition above).
    */
    public int getBodyLength(byte[] header) {
        return getInt(header, 5);
    }

    /**)
     * Returns the long-size length of request body of
     * incoming request packet.
     * If you don't need to support long, you may map to
     * {@link #getBodyLength(byte[])}.
     */
    public long getBodyLengthLong(byte[] header) {
        return getBodyLength(header);
    }

    /*
      This method must return the context path so that the
      request can be routed by the TCP listener to the context
      that contains the TCP handler (TCPServlet implementation).
      For our example, we always use the context path “/tcptest”.
    */
    public String getContextPath(byte[] header) {
        return "/tcptest";
    }

    /*
      This method must return the name (path) of the TCP
      handler(TCPServlet) relative to the context path.
      For our example, we fetch this name from the 9th position
      in the header.
    */
    public String getServletPath(byte[] header) {
        return "/" + getString(header, 9, 32);
    }

    /*
      This method returns some path info from the header.
      It is not used in our example and thus returns “null”.
    */
    public String getPathInfo(byte[] header) {
        return null;
    }

    /*
      This method returns any session ID embedded in the header.
      It is not used in our example and thus returns “null”.
    */
    public String getSessionId(byte[] header) {
        return null;
    }

    /*
      This method determines whether the TCP listener
      should keep the socket connection open after the TCP handler
      has delivered its response. If it returns “false”, the
      connection will be dropped like in HTTP communications.
      If it returns “true” the connection will be kept open
      like in the Telnet or FTP protocols. For our example,
      we choose to make it persistent (connection not closed
      by the TCP listener).
    */
    public boolean isPersistentConnection() {
        return true;
    }
}

5.3. TCP 서블릿 구현

TCP 서블릿은 항상 jeus.servlet.tcp.TCPServlet의 하위 클래스이다. 여기에는 항상 overridden되는 abstract void service(TCPServletRequest req, TCPServletResponse res) 메소드가 존재한다.

service 메소드는 맞춤 프로토콜에 준하는 메시지를 처리하도록 구현해야 한다. 웹 컨테이너는 헤더를 읽어서 TCPServletRequest 객체에 전달하며, TCP 서블릿에서는 TCPServletResponse 객체의 output stream으로 응답을 쓴다.

다음 예제는 맞춤 프로토콜에 준하는 메시지를 처리하는 TCP 서블릿의 구현 코드이다.

TCP 서블릿 구현 예 : <SampleTCPServlet.java>
package sample.servlet;

import java.io.*;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jeus.servlet.tcp.*;

/**
 * Sample TCPServlet implementation
 *
 * Protocol scheme:
 *
 * common header (for request and response) : total 41 byte
 *
 *   magic field: length = 4 byte, value = 7777
 *   type field : length = 1 byte, 0 : request, 1:response
 *   body length field : length = 4, value = 128
 *   service name field : length = 32
 *
 * request and response body
 *
 *   message1 field : length = 64
 *   message2 field : length = 64
 *
 */

public class SampleTCPServlet extends TCPServlet {

    public void init(ServletConfig config) throws ServletException {
    }

    public void service(TCPServletRequest req, TCPServletResponse res)
        throws ServletException, IOException {
        ServletContext context = req.getServletContext();
        byte[] header = req.getHeader();
        byte[] body = new byte[req.getContentLength()];
        int read = req.getInputStream().read(body);
        if (read < body.length) {
            throw new IOException("The client sent the wrong content.");
        }

        String encoding = res.getCharacterEncoding();
        if (encoding == null)
            encoding = "euc-kr";

        DataInputStream in = new DataInputStream(new ByteArrayInputStream(header));
        int magic = in.readInt();
        context.log("[SampleTCPServlet] received magic = " + magic);

        byte type = (byte)in.read();
        context.log("[SampleTCPServlet] received type = " + type);

        int len = in.readInt();
        context.log("[SampleTCPServlet] received body length = " + len);

        byte[] svcname = new byte[32];
        in.readFully(svcname);
        context.log("[SampleTCPServlet] received service name = "
                     + (new String(svcname)).trim());

        String rcvmsg = null;
        rcvmsg = (new String(body, 0, 64)).trim();
        context.log("[SampleTCPServlet] received msg1 = " + rcvmsg);

        try {
            rcvmsg = (new String(body, 64, 64, encoding)).trim();
        } catch (Exception e) {}
        context.log("[SampleTCPServlet] received msg2 = " + rcvmsg);

        String msg1 = "test response";
        String msg2 = "test response2";

        byte[] result1 = null;
        byte[] result2 = null;
        if (encoding != null) {
            try {
                result1 = msg1.getBytes(encoding);
                result2 = msg2.getBytes(encoding);
            } catch (UnsupportedEncodingException uee) {
                result1 = msg1.getBytes();
                result2 = msg2.getBytes();
            }
        } else {
            result1 = msg1.getBytes();
            result2 = msg2.getBytes();
        }

        header[4] = (byte)1; // mark as response
        ServletOutputStream out = res.getOutputStream();
        out.write(header);

        byte[] buf1 = new byte[64];
        System.arraycopy(result1, 0, buf1, 0, result1.length);
        out.write(buf1);

        byte[] buf2 = new byte[64];
        System.arraycopy(result2, 0, buf2, 0, result2.length);
        out.write(buf2);

        out.flush();
    }

    public void destroy() {
    }
}

JEUS 7 Fix#2부터는 TCPServletRequest.getBody() 메소드 사용을 권장하지 않는다. 이 메소드는 요청 바디가 매우 큰 경우 메모리 문제를 일으킬 수 있다. 따라서 서블릿 표준에 정의된 ServletInputStream을 사용하는 것을 권장한다. 이는 TCPServletRequest.getInputStream()으로 얻을 수 있다.

5.4. 맞춤 프로토콜 코드를 위한 TCP 리스너 설정

구현한 코드(SampleConfig.java와 SampleTCPServlet.java)를 기반으로 TCP 리스너를 설정한다. 설정 과정은 다음과 같다.

  1. 웹 엔진에 TCP 리스너 정보를 설정한다. TCP 리스너 정보 설정에 대한 자세한 내용은 TCP 리스너 설정을 참고한다. 단, TCP 리스너에서 참조하는 서버 리스너의 포트는 '5555', 'Dispatcher Config Class'는 'sample.config.SampleConfig’를 입력해야 한다.

  2. JEUS 서버를 시작하고 위에서 작성한 TCP 서블릿을 포함시킨 웹 애플리케이션을 deploy한다. 이때 웹 애플리케이션의 DD의 예는 다음과 같다.

    TCP 핸들러 DD : <web.xml>
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
             metadata-complete="false"
             version="5.0">
        <display-name>test</display-name>
        <distributable/>
        <servlet>
            <servlet-name>SampleServlet</servlet-name>
            <servlet-class>sample.servlet.SampleTCPServlet</servlet-class>
            <load-on-startup>0</load-on-startup>
            <async-supported>true</async-supported>
        </servlet>
        <servlet-mapping>
            <servlet-name>SampleServlet</servlet-name>
            <url-pattern>/sample</url-pattern>
        </servlet-mapping>
        <login-config>
            <auth-method>BASIC</auth-method>
        </login-config>
    </web-app>

5.5. TCP 클라이언트 구현

TCP 클라이언트는 TCP 리스너와 소켓 연결을 맺고 이 연결을 통하여 메시지를 전송한다. 이 메시지는 맞춤 통신 프로토콜 정의에서 정의한 맞춤 통신 프로토콜에 준하는 Byte 스트림이다.

설정된 TCP 리스너는 메시지를 받아서 SampleConfig 클래스에 정의된 dispatch 정보를 바탕으로 SampleTCPServlet의 service() 메소드를 호출한다. SampleTCPServlet은 클라이언트로부터 전달된 데이터를 바탕으로 응답을 생성하여 전송한다. 이 응답은 클라이언트가 받아 System.out으로 출력한다.

다음은 그에 대한 예이다.

TCP 클라이언트 구현 예 : <Client.java>
package sample.client;

import java.io.;
import java.net.;

public class Client {
    private String address;
    private int port;

    private int magic = 7777;
    private byte type = 0;
    private int bodyLength = 128;
    private byte[] serviceName="sample".getBytes();

    public Client(String host, int port) {
        this.address = host;
        this.port = port;
    }

    public void test()
        throws IOException, UnsupportedEncodingException {
        Socket socket = new Socket(address, port);
        DataOutputStream out = new DataOutputStream(
            new BufferedOutputStream(socket.getOutputStream()));
        DataInputStream in = new DataInputStream(
            new BufferedInputStream(socket.getInputStream()));

        out.writeInt(7777);
        out.write(type);
        out.writeInt(bodyLength);
        byte[] buf = new byte[32];
        System.arraycopy(serviceName, 0, buf, 0, serviceName.length);
        out.write(buf);
        byte[] msg1 = "test request".getBytes();
        byte[] msg2 = "test request2".getBytes();
        buf = new byte[64];
        System.arraycopy(msg1, 0, buf, 0, msg1.length);
        out.write(buf);
        buf = new byte[64];
        System.arraycopy(msg2, 0, buf, 0, msg2.length);
        out.write(buf);

        out.flush();

        // rx msg
        int magic = in.readInt();
        System.out.println("[Client] received magic = " + magic);

        byte type = (byte)in.read();
        System.out.println("[Client] received type = " + type);

        int len = in.readInt();
        System.out.println("[Client] received body length = " + len);

        byte[] svcname = new byte[32];
        in.readFully(svcname);
        System.out.println("[Client] received service name = " +
                           (new String(svcname)).trim());

        byte[] body = new byte[128];
        in.readFully(body);
        String rcvmsg = null;
        rcvmsg = (new String(body, 0, 64)).trim();
        System.out.println("[Client] received msg1 = " + rcvmsg);
        rcvmsg = (new String(body, 64, 64, "euc-kr")).trim();
        System.out.println("[Client] received msg2 = " + rcvmsg);

        out.close();
        in.close();
        socket.close();
    }

    public static void main(String[] argv) throws Exception {
        Client client = new Client("localhost", 5555);
        client.test();
    }
}

위의 클라이언트 코드에서 프로토콜에 필요한 다양한 헤더의 필드들의 설정을 주의 깊게 확인한다.

'magic' 수는 '7777’로, 'type’은 '0'(요청), 'body length’는 '128Bytes'(고정 길이), 'service name’은 'sample’로 설정되어 있다(SampleServlet의 이름은 web.xml에 설정되어 있다). 그리고 2개의 메시지들을 생성하여 헤더 정보를 전송한 후에 TCP 리스너에게 그들을 전송한다. 마지막으로 'hostname’을 'localhost’로 포트 번호는 '5555’로 설정하였다.

5.6. TCP 클라이언트 컴파일과 실행

TCP 리스너가 설정되어 'localhost’에 '5555' 포트를 이용하여 운영되고 있다고 가정한다. 이런 환경에서 TCP 클라이언트를 컴파일하고 실행하는 과정은 다음과 같다.

  1. Client.java를 컴파일한다.

    javac -classpath ${JEUS_HOME}/lib/system/jeus.jar -d . Client.java
  2. Client.class를 다음과 같이 실행한다.

    java -classpath ${JEUS_HOME}/lib/system/jeus.jar:. sample.client.Client
  3. JEUS 관리자의 콘솔 화면은 다음과 같이 TCP 핸들러의 결과값을 보여줘야 한다(SampleServlet 클래스).

    [SampleServlet] received magic = 7777
    [SampleServlet] received type = 0
    [SampleServlet] received body length = 128
    [SampleServlet] received service name = sample
    [SampleServlet] received msg1 = test request
    [SampleServlet] received msg2 = test request2
  4. 다음의 내용이 클라이언트의 실행 화면에 표시된다.

    [Client] received magic = 7777
    [Client] received type = 1
    [Client] received body length = 128
    [Client] received service name = sample
    [Client] received msg1 = test response
    [Client] received msg2 = test response2

6. HTTP/2 사용

HTTP/2는 HTTP/1.1에서 성능 향상을 위하여 새로 나온 HTTP 표준의 차기 버전이다.

다음은 HTTP/2의 특징에 대한 설명이다.

  • Header Compression

    HTTP/2에서는 기본적으로 헤더를 허프만 코딩으로 압축을 하여 보낸다. 또한, 반복되는 헤더가 여러 번 전송되지 않도록 서버, 클라이언트가 각자 헤더 테이블에 인덱싱을 해둔다. 얼마나 많은 양의 헤더를 인덱싱을 해둘지는 Settings Header Table Size로 설정할 수 있다.

  • Request/Response Multiplexing

    HTTP/2에서는 일련의 요청/응답 쌍을 하나의 스트림이라 부르며 하나의 스트림은 여러 개의 프레임(헤더 프레임, 데이터 프레임, …​)으로 이루어져 있다. 실제로 연결을 통해 전송되는 단위는 프레임이다. HTTP/1.1까지는 하나의 요청/응답이 완료된 후에 다시 요청을 보낼 수 있었지만 HTTP/2에서는 여러 개의 스트림을 생성하여 동시에 여러 요청을 보낼 수 있다.

    몇 개의 스트림을 동시에 사용할지는 Settings Max Concurrent Streams 설정을 통해 할 수 있다. 실제 데이터(body)가 전송되는 프레임인 데이터 프레임의 크기는 Settings Max Frame Size로 설정할 수 있다.

  • Server Push

    Server Push에 관한 내용은 Server Push를 참고한다.

본 안내서는 JEUS에서 HTTP/2 사용에 관한 내용만을 주로 다루고 있으며 HTTP/2에 관한 더 자세한 내용은 "RFC 문서"를 참고한다.

6.1. HTTP/2 사용 설정

HTTP/2는 h2ch2 두 가지 식별자를 정의하고 있다. 각각의 의미는 아래와 같다.

구분 설명

h2c

HTTP/2를 가리킨다. 'http’로 서비스된다.

h2

TLS(Transport Layer Security)를 사용해 동작하는 HTTP/2를 가리킨다. 'https’로 서비스된다.

대부분의 브라우저는 h2c를 지원하지 않고 h2만 지원한다.

HTTP/2를 h2c로 사용하기 위해서는 HTTP 리스너 설정을 참고하여 HTTP/2 사용 항목을 체크한다. HTTP/2를 h2로 사용하기 위해서 TLS(Transport Layer Security)를 사용해 동작하는데 TLS handshake 과정중에 ALPN(Application Layer Protocol Negotiation) 기능을 이용해야 한다. h2는 TLS를 사용해 동작하므로 당연히 보안 리스너로 설정한 포트를 사용해야 한다.

다음은 ALPN을 사용하기 위해 필요한 작업이다.

OpenJDK 8u252 버전 이후부터는 JDK 자체에서 ALPN을 지원하므로, JVM 옵션으로 Jetty ALPN 설정이 필요하지 않다. 각 JDK의 지원 기준이 다를 수 있으므로 이를 참고하여 설정한다.

  1. 다음 주소에서 사용할 JDK 버전에 맞는 alpn-boot 라이브러리를 확인한 후 "MVN Repository"에서 다운받는다.

    http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions

    alpn-boot 라이브러리는 OpenJDK, OracleJDK만 지원한다. 따라서 현재 IBM JDK에서는 HTTP/2를 사용할 수 없다.

  2. 다운받은 alpn-boot 라이브러리를 bootclasspath에 추가하는 <jvm-option>을 domain.xml에 추가한다.

    <jvm-config>
        <jvm-option>-Xbootclasspath/p:<path_to_alpn_boot_jar> ...</jvm-option>
    </jvm-config>
  1. HTTP/2 스펙에서는 TLS 통신에 필요한 Cipher Suites 중 권장하지 않는 Cipher Suites를 명시하고 있다(). 스펙에 따르면 권장하지 않는 Cipher Suites를 사용할 경우 연결이 끊어질 수 있으며 대부분의 브라우저는 이에 따라 연결을 끊고 있다. JDK 7에서 제공하는 Cipher Suites는 모두 권장하지 않고 있기 때문에 더 강력한 암호화 Suites를 사용하는 JDK 8 버전 사용이 필수이다.

    HTTP/2 스펙에서 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256는 필수로 지원하도록 명시하고 있기 때문에 이 암호화 Suite를 Listener SSL 설정에서 설정하길 권장한다.

  2. JEUS 서버와 관련된 세부 설정 사항에 대한 자세한 내용은 JEUS Server 안내서의 Listener 설정을 참고한다.

6.2. Server Push

Server Push는 지정된 리소스에 대하여 클라이언트가 요청을 하지 않아도 서버에서 응답을 주는 기능으로 요청 전송에 필요한 시간을 줄여 더 빠른 응답을 받을 수 있다. Server Push 기능을 사용하기 위해서는 웹 애플리케이션을 개발할 때에 Server Push할 리소스를 지정해야 한다. 본 절에서는 해당 기능의 사용법에 대해서 설명한다.

Servlet 4.0 API를 기반으로 Server Push를 구현하였다.

Server Push 사용의 기본적인 흐름은 PushBuilder 객체를 얻은 후 리소스의 경로를 지정하고 push() 메소드를 호출한다.

다음은 Server Push의 기본 흐름에 대한 예제 소스이다. 이외에도 Push될 리소스에 전달된 Query String, Cookie, Header 등을 바꿀 수 있다.

Server Push 사용 예 : <ServerPushServlet.java>
package sample.serverpush

...

@WebServlet("/ServerPush")
public class ServerPushServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        PushBuilder builder = req.newPushBuilder();
        String pushResourcePath = "resources/a.txt";
        
        // Push할 리소스의 패스를 넣어준 후 push()함수를 호출한다.
        builder.path(pushResourcePath);
        builder.push();

        resp.getWriter().write("main page\n");
    }
}

Server Push를 사용하지 않겠다고 설정한 경우 위 예제는 아무 영향을 미치지 않는다.

7. 리스너 튜닝

최적의 성능을 위하여 리스너를 설정할 때 다음의 몇 가지 사항을 고려해야 한다.

  • 시스템 리소스를 많이 사용하거나 대기시간을 길게 하여 출력 버퍼의 크기를 증가시킨다.

  • 일반적으로 Worker Thread Pool의 min, max 값을 크게 부여하면 웹 엔진에 많은 클라이언트가 접근할 때 Pool이 좋은 성능을 가지게 된다. 시스템 메모리를 적게 사용하기 위해서는 이들의 값을 낮게 설정한다.

  • 'Server Access Control' 옵션을 비활성화하면 성능 개선을 기대할 수 있다.

  • WebtoB 커넥터에서는 앞에서 언급했던 공식에 따라 웹 엔진의 WebtoB 커넥터의 Worker Thread 개수와 http.m의 값들을 동일하게 설정한다. 이렇게 해야 가장 좋은 성능을 기대할 수 있다.