8/25 (日)
CentOS 7 で yum のパッケージをダウングレードする方法
CentOS 7 の OpenJDK 1.8 を最新に上げたら javac コンパイルエラーが出るようになった。 ひとつ前のバージョンに戻す方法を調べたので、それをメモしておく。
# yum --showduplicate list java-1.8.0-openjdk 読み込んだプラグイン:fastestmirror, langpacks, priorities Loading mirror speeds from cached hostfile * base: ftp.riken.jp * epel: nrt.edge.kernel.org * extras: ftp.riken.jp * updates: ftp.riken.jp インストール済みパッケージ java-1.8.0-openjdk.x86_64 1:1.8.0.212.b04-0.el7_6 @updates 利用可能なパッケージ java-1.8.0-openjdk.i686 1:1.8.0.181-7.b13.el7 base java-1.8.0-openjdk.x86_64 1:1.8.0.181-7.b13.el7 base java-1.8.0-openjdk.i686 1:1.8.0.191.b12-0.el7_5 updates java-1.8.0-openjdk.x86_64 1:1.8.0.191.b12-0.el7_5 updates java-1.8.0-openjdk.i686 1:1.8.0.191.b12-1.el7_6 updates java-1.8.0-openjdk.x86_64 1:1.8.0.191.b12-1.el7_6 updates java-1.8.0-openjdk.x86_64 1:1.8.0.201.b09-2.el7_6 updates java-1.8.0-openjdk.i686 1:1.8.0.212.b04-0.el7_6 updates java-1.8.0-openjdk.x86_64 1:1.8.0.212.b04-0.el7_6 updates java-1.8.0-openjdk.i686 1:1.8.0.222.b10-0.el7_6 updates java-1.8.0-openjdk.x86_64 1:1.8.0.222.b10-0.el7_6 updates
# yum downgrade java-1.8.0-openjdk-1.8.0.212.b04-0.el7_6 java-1.8.0-openjdk-devel-1.8.0.212.b04-0.el7_6 java-1.8.0-openjdk-headless-1.8.0.212.b04-0.el7_6
javac のコンパイルエラー自体は、いったんバージョンを戻した後に再度上げると直ったので、インストールし損ないのようだ。
8/5 (月)
[Java] Java Servlet で WebSocket のリバースプロキシを作る
Java Servlet を使って WebSocket が通る HTTP リバースプロキシを調査した。 https://github.com/nminoru/misc/tree/master/java/reverse_proxy_servletにサンプルを上げる。
WebSocket
WebSocket は HTTP リクエストの GET メソッドの中で以下の赤字のヘッダーを追加することでサーバーとのハンドシェイクを開始する。
GET /endpoint HTTP/1.1 Origin: http://example.com Host: websocket.example.com Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Protocol: foo
そしてそのレスポンスは
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: foo ← この空白行(\r\n)を受け取った次から 送信側と受信側で双方のバイナリ転送となる。
クライアント側の処理
WebSocket のライブラリとして JSR 356 (javax.websocket) とその参照実装である Project Tyrus があるが、いろいろやってこれは WebSocket のリバースプロキシとしてはつけないと看破した。 JSR 356 は WebSocket をかなり抽象化しており、リバースプロキシを実装するのに必要な低位の情報に手が届かなくなっている。 例えば WebSocket はバイナリデータ送受信とテキストデータ送受信の2つのモードがあるが、JSR 356 は両方のモードに対応したサーバーやクライアントを記述することができないので、リーバスプロキシに必要な任意モードでの待ち受けができない。
Java 11 の Java HTTP Client を使えば書けそうだが、Java 8 縛りがあるので reverse_proxy_servlet では java.net.Socket を使って WebSocket サーバーへの直接 TCP コネクションを張る。 java.net.Socket のコネクションに対して最初は 1回だけは HTTP リクエストを投げて HTTP レスポンスを受信するが、その後は WebSocket にアップグレードした TCP コネクションとみなすことにする。
サーバー側の処理
Java Servlet で WebSocket を使う場合、HttpServletRequest にある upgrade メソッドを使って HTTP コネクションのアップグレードに対応させる必要がある。
HttpServletRequest#service の中では (1) のように通常の HTTP レスポンスを作成する必要がある。 この HTTP ステータスとヘッダーを正しく返さない場合には、HTTP コネクションのアップグレードは成功しない。
HttpServletRequest#service に HttpServletRequest#upgrade を呼ぶと、引数に渡された HttpUpgradeHandler インターフェイスを実装した CustomHttpUpgradeHandler.class のデフォルトコンストラクタ―によって作成されたインスタンスが返る。 この HttpServletRequest#upgrade は HttpUpgradeHandler の登録だけを行うようで、実際に CustomHttpUpgradeHandler#init が呼び出されるのは HttpServletRequest#service メソッドの呼び出しが終わった直後になる。
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { // リクエストを処理し WebSocket サーバーにアクセスする処理をここに書く // (1) 通常の HTTP のレスポンスをここに書く servletResponse.setStatus(101); servletResponse.addHeader("Upgrade", "websocket"); servletResponse.addHeader("Connection", "Upgrade"); servletResponse.addHeader("Sec-WebSocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Connection"); servletResponse.addHeader("Sec-WebSocket-Protocol", "foo"); // (2) HttpUpgradeHandler の仕込み CustomHttpUpgradeHandler handler = servletRequest.upgrade(CustomHttpUpgradeHandler.class); }