2. tomcat初心者のためのデプロイ入門

デプロイ(deploy)とは、tomcatで動作するためにプログラムを配置し、実行環境を整えることを言う。

WindowsPCでプログラムを作成、いざ、デプロイを、と思い、教科書1)を参考にしながら、 WARファイルを作成、Ubuntu Webサーバに転送後、tomcatシステムメニュー画面からデプロイをしてみた。

しかし、なぜかデプロイ失敗。原因不明。しばらく調査したが、tomcat初心者(私)には難しい問題となった。 おそらく、設定上のミスがあるのだろうが、深く追求せずにしばらく放置することにした。

そもそも、WindowsでコンパイルしてLinuxで動作させることに疑問がある。 Linux上でコンパイルし直す必要があるのではないかという疑問である。 昔、アプレットが全盛の時代、PC上で作ったJavaプログラムをLinuxサーバで動かしていた時、 Linuxサーバで再コンパイルしていた記憶がある。微妙に画面表示が狂ったり、エラーが発生したためである。 また、高速化のために再コンパイルした方が好ましい。 一部ネイティブコードで動作するらしいので、コンピュータのCPUに合致したコードが生成されるらしい。 (このへんのことは、Javaが思った以上に高速で動作するので、私自身がそのように思っていたというだけで、 確証があるわけではない。JavaコードはOSやCPUを選ばず、世界共通に動作するというJavaの崇高な理念に相反する話かもしれない。)

ということで、PC上でプログラムを作成、動作確認後、サーバへソースプログラムのみを転送後、再コンパイルすることにした。 このほうが、tomcat初心者にとって、tomcatを勉強するためには都合が良い。

しかし、多くの出版されている教科書を調べてみたが、eclipseを使う方法がメインで、手動でデプロイする方法を詳しく記述しているものを見つけることができなかった。 古い教科書は調べていないが、おそらく基本的なことなので、そこにあるのかもしれない。 幸いなことに、ネット検索すると簡単に見つかった。以下は参考にしたWebである。

どちらも、初心者向けに解説してあるので非常にわかり易く、tomcatの基本的ディレクトリ構成も覚えることができて、大いに学習の参考になる。

しかしながら、両者ともServlet3.0より導入されたアノテーションを使っていないので、最近の利用法としては説明不足の感がある。 ここでは、アノテーションを使った場合とそうでない場合の両方を解説する。

2.1 tomcatのドキュメントルート

基本的に、「webapps」ディレクトリに全てのサーブレット、JSPやWebsocketプログラムが配置される。 webappsはドキュメントルートにもなっており、ネットワークパス名の「/」に対応する。 直下にあるディレクトリ名がその後に続くパス名になる。(tomcat9に対応)

tomcat directories

「http://www.sample.jp:8080/test/...」のようにtestと言う名前のディレクトリ名が/の後に続く。

ここで、前章のnginxの設定でservletとwsのパス名2つのみをリバースプロキシ設定したので、https://....で利用できるtomcatのパス名はservletとwsのみであり、 testはhttpsモードでは利用できないことに注意しておく必要がある。 しかしながら、プライベートLANからは直接httpモードでアクセス可能であり、様々なテストプログラムをここで検証することができる。 他の、「・・・・」で記されているディレクトリはドキュメント、管理用プログラム、参考プログラム用などのディレクトリ群を省略して表したものである。

2.2 tomcatにおけるhtml

通常のhtmlファイルもそれぞれのディレクトリの中に保存し、「http://....:8080/test/sample/sample1.html」のようにブラウザ表示できるようにすることができる。 この場合、対応するサブディレクトリを作成する。htmlファイルのディレクトリ構成は以下の図のようになる。

tomcat html directories

htmlファイルの場合は、他のWebサーバと同じように、ただし、ドキュメントルートはwebappsではあるが、ディレクトリ構成がそのままネットワークパス名になる

以下は、testディレクトリ直下にsampleディレクトリを作成し、そこに簡単なhtmlファイルsample1.htmlを置いた場合の、ブラウザ表示である。

sample1 browser disp

しかしながら、tomcat管理画面のトップ表示は他の場所で設定されているので、webappsがドキュメントルートと厳密に言うことはできないようであるが、 管理画面は管理画面と言うことで、あまり気にせず、サクサク進めることにしよう。

2.3 簡単なServletのデプロイ

ServletやWebsocketは、htmlと扱いが異なり、単純にディレクトリ構成がネットワークパス名にはならない。 ただし、2.1で述べたwebapps直下のディレクトリはネットワークパス名になるので注意が必要である。 eclipse利用者にとっては、プロジェクト名がwebapps直下のディレクトリに相当すると考えると分かりやすいかもしれない。 しかし、必ずしもそう考える必要もなく、WebsocketプログラムはwsディレクトリにServletプログラムはservletディレクトリに入れる、 というようにプログラムの種類によって分けてもよいだろう。

大きなプロジェクトが複数ある場合、プロジェクト毎に分類した方が管理が容易かもしれない。 その場合、nginxの設定もそれに対応したものになるだろう。

さて、testディレクトリに テスト用のServletをデプロイすることにする。 以下のようにディレクトリを作成し、「Hello!」と表示するだけの簡単なテスト用Servletプログラムhello.javaを保存する。

servlet sample directories

hello.javaは以下のとおりである。

hello.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;

@WebServlet("/hello")
public class hello extends HttpServlet {

  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws IOException, ServletException
  {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<html>");
    out.println("<head>");
    out.println("<title>Hello</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>Hello!</h1>");
    out.println("</body>");
    out.println("</html>");
  }
}

hello.javaはWindowsPCでeclipseを使って作成、動作確認を行って後、サーバへ転送したが、 簡単なプログラムなので、サーバで直接エディタを使って記述し、保存してもよいだろう。

eclipseを使うと、import文やアノテーション(@WebServlet)を自動挿入してくれるので、非常に便利である。

次に、tomcatインストールディレクトリの目的位置にcdコマンドで移動し、コンパイルを行う。 コンパイルが正常終了したら、tomcatの再起動を行う。

sudo su
cd /opt/tomcat/latest/webapps/test/WEB-INF/classes
javac -classpath /opt/tomcat/latest/lib/servlet-api.jar hello.java
systemctl restart tomcat

tomcatインストールディレクトリは、systemctl status tomcat で確認できる。適宜、上記を変更すること。

tomcatインストール領域はtomcatユーザのみ閲覧許可になっているので、root権限で行う。 作成したディレクトリやファイルは、所有者がrootになっているので、tomcatに変えた方が良いだろう。

chown tomcat:tomcat ファイル名またはディレクトリ名
chmod 750 ディレクトリ名
chmod 640 ファイル名

上記処理をしておいた方が無難だろう。 上記処理をしなくても動作するが、何らかの不具合が発生するかもしれない。

アノテーション @WebServlet("/hello") は、ネットワークパス名を示している。この場合、

http://サーバアドレス:8080/test/hello

ブラウザ表示は以下のようになる。

hello browser disp

これまではアノテーションを使った場合の例であるが、アノテーションを使わない方法もある。

プロジェクト単位でweb.xmlファイルを作成し、クラスファイルとネットワークパス名の関連付けを行う。 詳しい解説は、この章の冒頭部分で紹介した参考Webにあるので割愛する。

注意すべき点は、プロジェクト内でweb.xmlファイルがあると、そのプロジェクト内ではそれが優先され、アノテーションは無視される。 つまり、一つのプロジェクト内でweb.xmlとアノテーションは共存できず、 404 file not found のエラーになる場合があるので要注意。

解決策は、web.xmlを利用しているプロジェクトの場合は、その内部のすべてのプログラムはアノテーションを使わないことであり、 アノテーションを使いたい場合は別のプロジェクトにすることである。この場合、web.xmlファイルは存在できない。あれば削除しておくこと。

管理のしやすさの観点から、web.xmlを使ってパス名との関連付けを行っているところも多いらしい。 しかし、web.xmlの設定は微妙に回りくどく、シンプルさが不足しているように思う。 ソースプログラムの中から@WebServlet部分を抽出し、一覧表を作った方が見やすくなりそうであり、 管理もしやすいのではないだろうか? linuxコマンドでそのようなことができるものがあったように思うので、暇があれば挑戦してみようと思う。 しかし、クラスファイルを変更せずにパス名を変更できるweb.xmlも捨て難い。

Servletの場合のURLは、

http://サーバ名/アプリケーション名/URLパターン

の形式で表現される。 ここで、testはアプリケーション名、helloはURLパターンに分類されることがわかる。 アプリケーションは一般的によく使われる言葉なので、意味する範囲が広くあまりピンとこないが、tomcatにおいてはこのような呼び方が正式名称のようである。 ディレクトリtestは、testアプリケーションと言った方がよいのかもしれない。 ともかく、パス名の最初はアプリケーション名と覚えるとよい。

2.4 簡単なWebSocketサーバのデプロイ

WebSocketもサーブレットと同様にデプロイできる。 今回は、ディレクトリclassesの中にディレクトリwebsocketを新規に作成し、そこにWebSocketサーバプログラムを保存することにする。

テスト用ということで、今回作成するWebSocketサーバプログラムは非常にシンプルなエコープログラムである。 このプログラムは、ブラウザからのメッセージに「echo:」を先頭に付けて返信するだけである。

WebSocketEchoServer.java
package websocket;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/WebSocketEchoServer")
public class WebSocketEchoServer {
    @OnMessage
    public String onMessage(String message) {
        return "echo: " + message;
    }
}

上記プログラムをサーバへ転送し、

websocket directories

のようなディレクトリ構成になるように配置する。

ここで注意事項として、websocketサブディレクトリに保存するので、プログラムの先頭行に「package websocket;」を記述する必要がある。 これを忘れると、起動できなくなる。

その後、

sudo su
cd /opt/tomcat/latest/webapps/test/WEB-INF/classes/websocket
javac -classpath /opt/tomcat/latest/lib/websocket-api.jar WebSocketEchoServer.java
systemctl restart tomcat

を実行することでデプロイは完了する。

以下のhtmlはクライアント側でWebSocket通信テストを行うものである。

socket_test.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>socket test html</title>
</head>
<body>
	<h2>エコーサーバ ソケット通信テストページ</h2>
	<button onclick="socket_open()" >ソケットオープン</button>
	<button onclick="socket_close()">ソケットクローズ</button><br>
	<h3>送信メッセージ</h3>
	<textarea rows="5" id="send_box" style="width:100%;"></textarea><br>
	<button onclick="send()">送信</button><br>
	<h3>受信メッセージ</h3>
	<textarea rows="15" id="receive_box" style="width:100%;"></textarea><br>
	<h3>ログ</h3>
	<textarea rows="15" id="log_box" style="width:100%;"></textarea><br>
</body>
<script type="text/javascript">
	const send_box = document.getElementById("send_box");
	const receive_box = document.getElementById("receive_box");
	const log_box = document.getElementById("log_box");
	let network_socket = "ws://192.xxx.xxx.xxx:8080/test/WebSocketEchoServer";
	let socket = null;

	function socket_open() {
	    socket = new WebSocket(network_socket);
	    socket.onopen = function() {
    		log("通信接続イベント受信、通信の準備完了");
	    };
	    socket.onerror = function(error) {
	    	log("エラー発生イベント受信");
	    	log(error.data);
	    };
	    socket.onmessage = function(event) {
	        log("メッセージ受信: "+event.data);
	        receive_out(event.data);
	    };
	    socket.onclose = function() {
	        log("通信切断イベント受信");
	    };
	    log("WebSocketをオープンしました。socket="+network_socket);
	}
	function socket_close() {
		socket.close();
		log("WebSocketをクローズしました。");
	}
	function send() {
		socket.send(send_box.value);
		log("データを送信: "+send_box.value);
		send_box.value = "";
	}
	function log(log_message) {
		log_box.value += log_message + "\n";
	}
	function receive_out(received_data) {
		receive_box.value += received_data + "\n";
	}
</script>
</html>

正常に通信ができると下記のように表示される。

socket test html

蛇足かもしれないが、Javascriptのsocket_open()関数内で、先にイベントリスナーを定義してからlog("WebSocketをオープンしました・・・")を実行している。 しかし、上図ログを見ると、逆の順番になっており、イベントリスナーの応答が遅れていることがわかる。 通信のタイムラグがあるので当たり前なのだが、なるほどなと実感させられる。

WebSocketにおいては、URLパターンのことをエンドポイントと呼ぶようである。 ServletとWebSocketでアノテーションが異なるのは、少々面倒である。 また、WebSocketにおいては、web.xmlがあってもアノテーションは無視されない。これは、Servletとは異なる。 どちらが優先されるのかは未確認だが、2重定義にならないよう注意しておいたほうがよいだろう。

testアプリケーションではnginxを通過できないので、今のところhttpsモードで利用できない。 wsアプリケーションに変更して動作確認をする予定。

WebSocketの場合のURLは、

http://サーバ名/アプリケーション名/エンドポイント(URLパターン)

の形式で表現される。Servletの場合と用語に違いがあるだけで、全く変わらない。

【web.xmlの配置について】

web.xmlの配置は以下のとおりであるが、設定方法は参考書や他のWebで詳しく説明してあるので割愛する。 アノテーション@WebServletを使う場合は、web.xmlを削除すること。

tomcat directories web.xml

上図のlibは参照jarファイルを格納する場所であるが、小さなプロジェクトの場合、通常、空である。 eclipseでWARファイルを生成、それを使ってtomcatサーバにデプロイすると、使っていなくても空のlibが作られる。 なくてもよいので、手動デプロイの場合は無視してよい。

2.5 jspについて

jspは、基本的にhtmlの場合と同じである。 webappsディレクトリをドキュメントルートとし、ディレクトリの階層構造がそのままネットワークパス名になる。

アクセス要求があった場合、それが最初の要求であった場合にコンパイルされクラスファイルが作成されるが、 2度目以降は処理が省略され、高速処理されるようになる。

【参考文献】
  1. 国本 大悟 著、「スッキリわかるサーブレット&JSP入門 第2版」、2019年3月21日、インプレス

比較的新しい教科書として、2014年に出版された、「藤野圭一著、詳解Tomcat、オライリージャパン」があるが、 レベルが高すぎて初心者にはお勧めできない。正直、私もチンプンカンプンであった。 最近の新しい入門書が見当たらない印象である。

作成日: 更新日:


次は、WebSocketについて パート3である。

back    next