0. まえがき

tomcatおよびWebSocketについて勉強中であり、メモを残しながら、忘れても思い出せるようにしておきたいという思いで記述する。 正直、最近、忘れっぽいのでメモしておく必要に駆られている。 メモ書きも普通のメモの場合、どこに置いたか忘れてしまうので、Web上でデータベース化しておくことにする。
私自身、Java言語に関してはエキスパートと自負しているが、tomcatやWebSocketは手掛け始めたばかりであり、知識不足の点は否めない。 それゆえ、多くの誤解や間違いがあるに違いない。少しずつ改定を重ね、修正をしていこうと思う。

1. tomcatとnginxとの連携

WebSocketを使って高速に動作する動的Webページを制作したい。 そのためには何がベストなのかいろいろ考えていたらかなりの時間が経過してしまった。 ネット検索するとNode.jsが数多く引っかかる。 Node.jsがベストなのかなと思い、その勉強を開始したが、javascriptって本当に早いの?という疑問にぶつかり、再調査することにした。 2014年ごろはNode.jsが結構早かったようだが、2016年頃Java言語によるtomcatが8.5以降においてノンブロッキングI/0に全面改訂され、高速化された。 JavaのノンブロッキングI/Oは2000年代ごろからあったように思うが、tomcatはマルチスレッドが主流だったようだ。 マルチスレッドはC10K問題をクリアできないので、ノンブロッキングに変更する必要があったわけだが、時間がかかり過ぎたように思う。

現時点2021年では、javaのほうに軍配が上がっているように思えたので、急遽、Java言語によるWebSocket開発に切り替えることにした。 私自身、長年Java言語を使った経験があるので、そちらの方がより好ましい。

ノンブロッキングI/Oを使ったプログラミングでWebSocketを作ることも可能なようだが、かなり骨の折れるプログラミングになるらしく、 既にあるtomcatを使えば、WebSocketプログラミングは非常に容易になり、Servletも利用できるというおまけもついてくる。

私自身は、その昔、CGIをPerlで記述していた時代にせっせとログインなどを書いていた。 CGIは毎回コマンド形式で起動し、スクリプト言語Perlでゆっくりと実行するので、動作が遅かった。 そのため、ソケット通信で高速にサーバと通信するプログラムを作成し、アプレットとサーバ間の高速通信を行なうシステムを開発した記憶がある。 その後、アプレットの利用に制限がかかるようになり、代替手段を模索していたが、他の仕事に時間を取られ、いつしかそのままに長い間放置していた。

他の選択肢として、C、C++などがあるが、メモリリークの問題解決が難しいという難点がある。 その点Javaは独自のガベージコレクションにより、メモリリークの問題がほとんど発生しない。 と言っても、メモリリークが起きるケースがたまにあるらしいので、そのケースのみ注意喚起しておけばよいだろう。

プログラミングの注意点としては、メインメソッドで全ての必要メモリをアロケートしておき、内部メソッドでメモリアロケートしないよう工夫することが重要であろう。 空きメモリをメインでコントロールし、不要になったメモリを使いまわすようにするのが好ましい。

ハッシュテーブルは自動アロケートになっているが、初期メモリサイズと上限のメモリサイズをコンピュータの性能からある程度決めておいた方がよいだろう。 メモリ管理ツール(ヒープ管理)を使いながら、システムクラッシュが起きないよう監視プログラムを作成する必要もあるかもしれない。

蛇足だが、ハッシュテーブルは非常に高速なツールだが、リハッシュが発生すると全てのハッシュを再計算するので、その間、コンピュータ性能が低下する。 頻繁にリハッシュが発生しないよう、適切な初期メモリサイズを設定する必要がある。

1.1 Javaのインストール

近年、Javaを提供しているOracle社がJavaの商用利用を有償化することになり、今まで無料で使っていたJavaが使えなくなってしまった。 と思いきや、今年9月(2021)再び無料化が宣言された。えっ!、IBMやMicrosoft・Slackなどが支援しているAdoptOpenJDKをインストールしたばかりなのに!! ということで、現在、Java最新版(バージョン17LTS)をインストールするかどうか迷っている。

さて、UbuntuサーバにおけるAdoptOpenJDKのインストール手順を記しておく。 AdoptOpenJDKの公開ページ(https://adoptopenjdk.net/installation.html#linux-pkg)を参考にインストール作業を行なった。

  1. 必要パッケージのインストール
    sudo apt-get install -y wget apt-transport-https gnupg
  2. GPGキーの入手
    wget https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public
  3. キーをaptで利用できるようにする。
    gpg --no-default-keyring --keyring ./adoptopenjdk-keyring.gpg --import <dowloaded-keyfile-name><.ext>
    gpg --no-default-keyring --keyring ./adoptopenjdk-keyring.gpg --export --output adoptopenjdk-archive-keyring.gpg
    (ブラケット表記のところは、--import public になるはず。古いpublicがあると、public.1などになっている。そのときは、--import public.1となる。)
  4. クリーンアップ
    rm adoptopenjdk-keyring.gpg
  5. キーファイルをルートディレクトリへ移動
    sudo mv adoptopenjdk-archive-keyring.gpg /usr/share/keyrings && sudo chown root:root /usr/share/keyrings/adoptopenjdk-archive-keyring.gpg
  6. AdoptOpenJDKのaptリポジトリへの登録
    echo "deb [signed-by=/usr/share/keyrings/adoptopenjdk-archive-keyring.gpg] https://adoptopenjdk.jfrog.io/adoptopenjdk/deb <codename> main" | sudo tee /etc/apt/sources.list.d/adoptopenjdk.list
    <codename>のところは、実際のUbuntuマシンのOSのコードネーム、Ubuntu 20.04 LTSの場合、focalとなる。
  7. パッケージのリフレッシュ
    sudo apt-get update
  8. インストール可能なAdoptOpenJDKの種類を確認
    sudo apt-cache search adoptopenjdk
    最新LTSはTemurin 17(LTS)であるが、インストール可能となっていない。 Adoptiumサイト(https://adoptium.net)からのインストール手順もあるだろうが、今後の課題。
  9. AdoptOpenJDKのインストール
    sudo apt-get install adoptopenjdk-11-hotspot
    hotspotとopenj9の2種類がある。好きな方を選択した。また、インストール可能な最新LTSを選択した。

AdoptOpenJDKのサイトは今年(2021)7月にAdoptiumのサイトに移動した。新バージョンはすべてAdoptiumで公開されるらしい。

<codename>は、cat /etc/os-release を実行し、UBUNTU_CODENAME=....のところを見るとよい。

このようにして、Oracle社以外のJavaをインストールしたが、早急にJavaのバージョンアップする必要がある。 と思うのだが、Oracleにするかその他にするか・・・・

1.2 tomcatのインストール

ubuntu Webサーバにtomcatをインストールしたが、開発環境の端末として利用しているWindows PCにもtomcatをインストールすることにした。 その理由として、サーバ端末では日本語環境が好ましくなく、開発環境が著しく悪いことが上げられる。 また、Webサーバに余計な負荷を掛けたくないということもある。

ubuntuで「sudo apt install tomcat9 tomcat9-admin」を実行すると、かなり古いバージョンのtomcatがインストールされてしまう問題があった。 DOS攻撃に対するセキュリティ上の問題があるバージョンがインストールされていた。 そこで、最新版のtomcatを直接ダウンロードする方法に切り替えることにした。

まず、古いバージョンのtomcatを削除(purge)した。

sudo apt purge tomcat9*

最新版のインストールは「Ubuntu20.04 tomcat9をインストールする手順」を参考にした。

全く同じ(Javaのインストールのみ異なる)手順でインストールしたので、上記の参考ページを見ればインストール手順がわかり易い。 ここで、手順の紹介は割愛する。

問題は、apt登録されていないので、「sudo apt update」でアップデートされないことである。 頻繁にtomcatのダウンロードサイトを見に行き、最新版が出ていないか確かめる必要がある。

ブラウザで http://サーバIP:8080 を開き、下記の画面が表示されれば、インストール完了となる。

tomcat main view

1.3 nginxとtomcatの連携

nginxは本Webサイトの構築に使っているものであり、Let's Encrypt認証でhttpsのみ接続可能となっている。nginxのインストールについては、ここでは割愛する。

通常の静的Webページはnginxで、動的Webページはサーブレット(Servlet)とウェブソケット(WebSocket)が担当するように設定することにした。 この設定は意外と簡単で、nginxのconfファイルにリバースプロキシの設定を追加するだけでよい。

基本構成は下図のようになる。

nginx proxy image

nginxはhttpsプロトコルのみ受付、暗号解読を行ない、httpプロトコルに変換してtomcatの8080ポートに接続する。

https://サーバIP/servlet/....の接続要求の場合、Servletが応答し、https://サーバIP/ws/....の接続要求の場合、WebSocketが応答する。 その他の接続要求の場合、nginx内で用意されている静的Webページが応答する。

ServletやWebSocketからの応答メッセージは、nginxが暗号化し、クライアントに送信される。 このようにして、外部からはhttpsサーバとして動作し、内部的には、httpプロトコルでアプリケーションプログラムとの通信が行なわれることになる。

1.4 nginxの設定(リバースプロキシ設定)

まずrootユーザになり、

sudo su
cd /etc/nginx/
cp nginx.conf nginx.conf.org
cd conf.d
cp default.conf default.[today's date].org

を実行し、

/etc/nginx/nginx.conf
	  ............
	  ............
  # 以下のコードを追加する
  map $http_upgrade $connection_upgrade {
     default upgrade;
     ''      close;
  }
 # include /etc/nginx.conf.d/*.conf の前に挿入

  include /etc/nginx/conf.d/*.conf;
}

上記のようにnginx.confファイルを書き換える。

/etc/nginx/conf.d/default.conf
    ....................
    ....................
  listen 443 ssl; # managed by Certbot
  ssl_certificate ...... # managed by Certbot
  ssl_certificate_key ..... # managed by Certbot
  include ......; # managed by Certbot
  ssl_dhparam ..... # managed by Certbot

  # listen 443 があるところの最後のところに以下を追加する
  location /servlet/ {
    proxy_pass http://127.0.0.1:8080/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $hostname;
    proxy_set_header X-Real-IP $remote_addr;
  }
  location /ws/ {
    proxy_pass http://127.0.0.1:8080/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $hostname;
    proxy_set_header X-Real-IP $remote_addr;
  }
  # 追加はここまで

}

上記のように/etc/nginx/conf.d/default.confを書き換える。 (注:上記設定は、tomcat付属のサンプルプログラムなどが外部からアクセス可能となっているので、下記を参考に修正することを推奨する。)

ところで、下記の部分はあってもなくてもよい。

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $hostname;
proxy_set_header X-Real-IP $remote_addr;

この部分は、servletやWebsocket側で、クライアントのIPアドレスなどのクライアント情報を知りたい場合に必要になるかもしれないということで追加した。 もしかすると、あまり意味がないかもしれない。今後の課題である。(要再調査)

servletはproxy_passが設定されていれば動作するが、WebSocketは http_version 1.1でなければ動作しない。 また、WebSocketは、header情報にhttpプロトコル→websocketプロトコルへのupgrade情報を保持していなければならない。

tomcatはnginx Webサーバにインストールしたので、同じサーバ内に同居しているため、「proxy_pass http://127.0.0.1:8080/;」となっている。 同じプライベートLAN内の他のサーバにインストールした場合、「proxy_pass http://192.168.1.199:8080/;」のように設定することになる。 なお、最後の「/」がある場合とない場合で動作が異なるので要注意である。最後に/がある場合、 選別用に使った/servlet/や/ws/の文字列をhttp://以下の文字列から削除されて送信される。ない場合は削除されない。

proxy_pass設定の最後に/がある場合、クライアントからサーバにアドレスを指定する際、実際のディレクトリ構成にさらに追加して送信することになるので、いくらか冗長になってしまう。 また、外部からtomcat付属のサンプルなどをディレクトリ構成を知っていれば動かせる問題もあり、/を外すことにした。 さらに、サーバのファイアウォール設定で8080ポートはプライベートLAN内のみ利用可としているため、外部から直接アクセスできないようにしている。

しかし、htmlのソースコードを見れば、送信先を知ることができる可能性が十分高いので、外部からの攻撃に対してこれで安全かと言うとそうでもない。 今のところ、外部からインターネット経由でtomcat付属のドキュメントを見たり、サンプルプログラムの実行は避けられるだけである。 ServletやWebSocketのアドレスを隠す方法があればと思うが、様々な外部からの攻撃に対するセキュリティ対策は今後の課題である。 (例えば、バッファオーバフロー攻撃対策として、受信データのサイズチェックを必ず行うなど。 バッファオーバフロー攻撃に対する脆弱性はCやC++のみとされているが、予想外の入力データに対するサーバの応答を調査しておくことは必要だろうと思っている。 文字データがバイナリコードだったらどうなるかなど、チェックしておきたい。)

wss:サーバIP/ws/../samples
と「../」を途中に入れれば、既存のディレクトリも見ることができるのではないか?
という疑問も湧くが、ブラウザ側でサーバIP/ws/../samples → サーバIP/samplesと自動修正されるので、 実際は他のディレクトリはアクセスできないのかもしれない。 この件については、要調査としてチェックする予定。
他のネット上の解説で、利用されると困るものは削除しておくとなっていた。 このほうが確実だろう。しかし、開発中に参考にしたい場合もあるので、残しておきたい。 Webブラウザで自動修正されても、攻撃者がブラウザを使わない他の通信プログラムを使うこともあるだろう。 サーバ側でtomcatに振り分ける前にnginxが自動修正するのかどうかについても調べておきたい。

proxy_passの設定は複数のサーバを設定することもでき、nginxをロードバランサ―として利用することもできる。 高性能のサーバには2倍働いてもらい、低性能のサーバの負荷を抑える。 要求が溜まっているサーバには割り振らず、暇なサーバを見つけて割り振る。などもできるらしい。

上記設定のhttpプロトコルはデフォルトのままであるが、現在(2021年10月)において、http3が最速のプロトコルらしい。 nginxのデフォルトはhttp1.1となっているため、本Webサーバはhttp2で動作するよう少し修正を加えている。 最速のhttp3の導入は、出たばかりなので、しばらく様子を見てから行う予定。

【参考文献】
  1. 渡辺 高志 著、「nginx実践ガイド」、2017年2月21日、インプレス
  2. 国本 大悟 著、「スッキリわかるサーブレット&JSP入門 第2版」、2019年3月21日、インプレス
  3. WebSocket proxying」、 http://nginx.org/en/docs/http/websocket.html

作成日: 更新日:


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

back    next