[Window] Apache에서 mod_jk와 mod_proxy차이, apache에서 tomcat으로 websocket 프록시 포워딩하는 방법


Development note/Window  2020. 1. 24. 09:00

안녕하세요. 명월입니다.

 

이 글은 Apache에서 mod_jk와 mod_proxy차이, apache에서 tomcat의 websocket 프록시 포워딩하는 방법에 대한 글입니다.

 

예전에 Tomcat과 apache를 연동하는 방법에 대해서 설명한 적이 있습니다.

링크 - [CentOS] apache-tomcat 연동하기

링크 - [PHP] Apache 환경의 같은 호스트 안에서 PHP와 Java(Servlet)를 동시에 기동, 운영하는 방법

 

위 링크를 보시면 apache와 tomcat을 연동하는 방법에 대해서 mod_jk를 이용하는 방법이 있고 mod_proxy를 이용하는 방법이 있습니다.

첫번째 링크는 mod_jk로 apache와 tomcat를 연동하는 방법을 설명했고 두번째는 mod_proxy로 apache와 tomcat를 연동하여 complex language를 사용하는 방법에 대해 설명했습니다.

 

두가지 연동 방법에는 각각 특징이 있는데 mod_jk로 연동하는 건 apache와 tomcat을 완전히 연결하는 것입니다.

즉, 가상 디렉토리를 만든다고 하면 apache만 설정하는 것이 아니라 tomcat에서 설정을 해야합니다. 물론 apache에서도 가상 디렉토리를 설정할 수 있습니다만, 여러가지 문제가 발생할 수 있습니다.

<VirtualHost *:80>
  # 루트(/)를 기준으로 tomcat과 연동했다. 
  JkMount /* WebSocketEx
  ServerName localhost
</VirtualHost>
worker.list=WebSocketEx
 
worker.WebSocketEx.port=8009
worker.WebSocketEx.host=localhost
worker.WebSocketEx.type=ajp13
worker.WebSocketEx.lbfactor=1

mod_jk로 연동한 VirtualHost인데 여기서 제가 「JkMount /* WebSocketEx」을 「JkMount /java/* WebSocketEx」으로 설정을 바꾸면 「localhost/java」가 「localhost:8009/」를 포워딩하는 것이 아니라 「localhost:8009/java」를 가르키게 됩니다.

즉, tomcat에서도 가상 디렉토리를 맞추어야 합니다. 이렇게 되면 꽤나 복잡해 집니다. 왜냐하면 루트는 가상 디렉토리로 사용할 수 없어졌기 때문입니다.

<VirtualHost *:80>
  ServerName localhost
  ProxyRequests Off
  ProxyPreserveHost On
  ## java와 연결할 ajp 주소.
  ProxyPass /java  ajp://localhost:8009/
  ProxyPassReverse /java  ajp://localhost:8009/
</VirtualHost>

그러나 complex language에서는 루트(/)를 php로 사용하고 「ProxyPass /java ajp://localhost:8009/」를 사용할 경우 「localhost/java」가 「localhost:8009/」로 포워딩이 됩니다.

즉, tomcat의 ajp 주소만 맞는다면 그 외에 tomcat에서는 별도의 가상 디렉토리 설정이 필요가 없습니다.

 

그럼 여기서 mod_jk보다tomcat 설정이 필요없는 mod_proxy를 연결하는 게 더 편하지 않을까 생각이 됩니다.

(mod_proxy는 apache의 기본 모듈이고 mod_jk의 경우는 connector를 다운 받아야 하는 번거로움도 있습니다.)

 

그러나 mod_jk의 경우는 apache와 tomcat간의 2진 패킷 전송 크기가 64kb이고 mod_proxy는 8kb입니다. 즉, 성능 면에서 mod_jk가 mod_proxy보다 압도적으로 좋고, 로드 벨런싱에서도 훨씬 유연하게 적용을 할 수 있다는 특징입니다.

(ajp는 apache과 tomcat간의 2진 패킷 전송 프로토콜인데 결국 apache과 tomcat간에 내부 통신을 통해 파일을 송수신을 하는 것입니다.)

참조 - http://javafatihk.blogspot.com/2014/11/modjk-modproxy-and-modproxyajp.html

 

단일 서버에서 여러가지 언어의 특성을 사용하기 위해서는 mod_proxy가 유리하지만, 많은 트래픽으로 인한 다중 서버와 성능을 중시해야 한다면 mod_jk를 사용하는 것이 좋습니다. (트래픽이 큰 웹 서비스에서 1kb차이가 어마어마합니다.)

그런데 제가 이렇게 인지하고 있는 건 apache 2.2때의 일입니다. 현재는 apache가 2.4버전으로 예전보다 많이 발전되었다고 합니다.(최근에는 성능상의 차이가 크게 없다고 하네요... 그럼 뭐 mod_proxy가 편하니깐 선택의 여지가 없는데...)

링크 - https://cwiki.apache.org/confluence/display/TOMCAT/Connectors

 

그럼 apache와 tomcat간에 websocket을 연결해 보겠습니다.

사실 이걸 설명하기 위해서 mod_jk와 mod_proxy의 특성까지 설명한 것입니다.

 

websocket은 웹서버와 브라우져간에 같은 http프로토콜을 이용하는 것은 맞는데, 웹은 비동기(웹 요청 응답 후에 소켓이 끊깁니다.), 웹소켓은 동기(접속 후에도 접속 유지) 소켓 통신 방식입니다.

apache와 tomcat간의 ajp프로토콜은 동기 소켓 방식이 아닙니다. 그래서 이걸 따로 연결하는 방법이 필요합니다.

 

먼저 톰켓 측의 자바 소스는 예전에 WebSocket에 대해 설명한 적이 있는 그 소스를 그대로 사용하겠습니다.

링크 - [Java] Servlet에서 사용하는 웹 소켓 (WebSocket)

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.server.ServerEndpoint;
// WebSocket의 호스트 주소 설정
@ServerEndpoint("/websocket")
public class Websocket {
  // WebSocket으로 브라우저가 접속하면 요청되는 함수	
  @OnOpen
  public void handleOpen() {
    // 콘솔에 접속 로그를 출력한다.
    System.out.println("client is now connected...");
  }
  // WebSocket으로 메시지가 오면 요청되는 함수
  @OnMessage
  public String handleMessage(String message) {
    // 메시지 내용을 콘솔에 출력한다.
    System.out.println("receive from client : " + message);
    // 에코 메시지를 작성한다.
    String replymessage = "echo " + message;
    // 에코 메시지를 콘솔에 출력한다.
    System.out.println("send to client : "+replymessage);
    // 에코 메시지를 브라우저에 보낸다.
    return replymessage;
  }
  // WebSocket과 브라우저가 접속이 끊기면 요청되는 함수
  @OnClose
  public void handleClose() {
    // 콘솔에 접속 끊김 로그를 출력한다.
    System.out.println("client is now disconnected...");
  }
  // WebSocket과 브라우저 간에 통신 에러가 발생하면 요청되는 함수.
  @OnError
  public void handleError(Throwable t) {
    // 콘솔에 에러를 표시한다.
    t.printStackTrace();
  }
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head><title>Web Socket Example</title></head>
  <body>
    <form>
      <!-- 송신 메시지를 작성하는 텍스트 박스 -->
      <input id="textMessage" type="text">
      <!-- 메시지 송신을 하는 버튼 -->
      <input onclick="sendMessage()" value="Send" type="button">
      <!-- WebSocket 접속 종료하는 버튼 -->
      <input onclick="disconnect()" value="Disconnect" type="button">
    </form>
    <br />
    <!-- 콘솔 메시지의 역할을 하는 로그 텍스트 에리어.(수신 메시지도 표시한다.) -->
    <textarea id="messageTextArea" rows="10" cols="50"></textarea>
    <script type="text/javascript">
      // 「WebSocketEx」는 프로젝트 명
      // 「websocket」는 호스트 명
      // WebSocket 오브젝트 생성 (자동으로 접속 시작한다. - onopen 함수 호출)
      // var webSocket = new WebSocket("ws://localhost:8080/WebSocketEx/websocket");
      // apache에서 연결하기 때문에 8080이 아닌 80(apache에서 설정한 포트)로 접속한다.
      var webSocket = new WebSocket("ws://localhost/WebSocketEx/websocket");
      // 콘솔 텍스트 에리어 오브젝트
      var messageTextArea = document.getElementById("messageTextArea");
      // WebSocket 서버와 접속이 되면 호출되는 함수
      webSocket.onopen = function(message) {
        // 콘솔 텍스트에 메시지를 출력한다.
        messageTextArea.value += "Server connect...\n";
      };
      // WebSocket 서버와 접속이 끊기면 호출되는 함수
      webSocket.onclose = function(message) {
        // 콘솔 텍스트에 메시지를 출력한다.
        messageTextArea.value += "Server Disconnect...\n";
      };
      // WebSocket 서버와 통신 중에 에러가 발생하면 요청되는 함수
      webSocket.onerror = function(message) {
        // 콘솔 텍스트에 메시지를 출력한다.
        messageTextArea.value += "error...\n";
      };
      // WebSocket 서버로 부터 메시지가 오면 호출되는 함수
      webSocket.onmessage = function(message) {
        // 콘솔 텍스트에 메시지를 출력한다.
        messageTextArea.value += "Recieve From Server => "+message.data+"\n";
      };
     // Send 버튼을 누르면 호출되는 함수
    function sendMessage() {
      // 송신 메시지를 작성하는 텍스트 박스 오브젝트를 취득한다.
      var message = document.getElementById("textMessage");
      // 콘솔 텍스트에 메시지를 출력한다.
      messageTextArea.value += "Send to Server => "+message.value+"\n";
      // WebSocket 서버에 메시지를 송신한다.
      webSocket.send(message.value);
      // 송신 메시지를 작성하는 텍스트 박스를 초기화한다.
      message.value = "";
    }
    // Disconnect 버튼을 누르면 호출되는 함수
    function disconnect() {
      // WebSocket 접속 해제
      webSocket.close();
    }
  </script>
</body>
</html>

저는 tomcat을 기본 포트 http는 8080, ajp는 8009를 사용했습니다. apache의 경우는 80으로 설정하였습니다.

웹 소켓에서 접속하는 주소는 apache를 통해서 tomcat에 접속을 해야하기 때문에 80포트로 접속합니다.

 

다시 apache의 설정으로 돌아오겠습니다.

여기서는 mod_jk로 설정을 할 생각인데 예전에 작성한 포스트는 Window가 아니고 CentOS에서 mod_jk를 연동하는 방법에 대해 기술을 했었네요.

(참고로 mod_proxy를 사용하는 분이 계신다면 websocket은 설정은 mod_jk와 mod_proxy가 설정이 같기 때문에 mod_jk 설정을 건너뛰고 아래의 websocket 설정 부분부터 보시면 됩니다. (링크 - apache websocket설정)

 

이번에는 Window에서 설정을 하겠습니다.

Window 버전 mod_jk는 따로 컴파일할 필요없이 apachelounge에서 다운이 가능합니다.

링크 - https://www.apachelounge.com/download/

다운을 받으면 압축 파일 안에 mod_jk.so 파일이 있는데 apache의 modules폴더에 복사합니다.

그 후 부터는 CentOS와 설정이 같습니다.

링크 - [CentOS] apache-tomcat 연동하기

LoadModule jk_module modules/mod_jk.so
<IfModule mod_jk.c>	
  JkWorkersFile conf/workers.properties
  JkLogFile logs/mod_jk.log
  JkLogLevel info
  JkLogStampFormat "[%y %m %d %H:%M:%S]"
</IfModule>
<VirtualHost *:80>
  JkMount /* WebSocketEx
  ServerName localhost
</VirtualHost>
worker.list=WebSocketEx
 	
worker.WebSocketEx.port=8009	
worker.WebSocketEx.host=localhost
worker.WebSocketEx.type=ajp13	
worker.WebSocketEx.lbfactor=1

이렇게 설정하고 apache와 톰켓(eclipse에서 디버깅 환경)으로 기동하겠습니다.

 

그럼 웹 환경을 보면 websocket이 접속이 되지 않습니다.

eclipse에서 콘솔 로그를 봐도 에러가 발생했습니다.

 

「java.lang.UnsupportedOperationException: HTTP upgrade is not supported by this protocol」에러가 발생했네요. 사실 이 문제 때문에 설정이 필요합니다.

 

여기서 위에 설명한 것처럼 apache에서 websocket 설정을 추가해야 합니다.

httpd.conf에서 mod_proxy.so와 mod_proxy_wstunnel.so의 주석을 해제합니다.

그리고 가장 하단 부분에 websocket 주소를 입력합니다.

<LocationMatch "/WebSocketEx/websocket">
  ProxyPass ws://localhost:8080/WebSocketEx/websocket retry=0
</LocationMatch>

그리고 apache를 재기동하고 브라우져에 접속하면 websocket이 접속되는 것을 확인할 수 있습니다.

참조 - https://community.bitnami.com/t/tomcat-8-websockets/39477

 

여기까지 Apache에서 mod_jk와 mod_proxy차이, apache에서 tomcat으로 websocket 프록시 포워딩하는 방법에 대한 설명이었습니다.

 

궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.