◇Javaで簡易Httpサーバを作りLinuxでサービス化する
この記事では次の2つの異なるレベルの問題を扱います。
- Javaで簡易Httpサーバを作る
- Java作成したサーバをLinuxでサービス化する
HTTPS、マルチスレッドの単純サンプルを ◆HTTPS試験サーバJava編に作成しました。
Javaで簡易Httpサーバを作る
Javaで標準で用意されるcom.sun.net.httpserver.HttpHandlerクラスを用いて単純なHTTPサーバを作成します。
「開発環境に簡単なhtmlドキュメントサーバが欲しいのだけど簡単には入れられない」といった場合にこんなおもちゃでも重宝します。
HttpHandlerに関する詳しい説明は他に譲りここでは一応動くプログラムを示すだけにします。
このプログラムは起動時にポートとドキュメントの配置位置、index.htmlを解釈するかどうかを指定するようになっています。
urlでフォルダが指定されると、その中にあるindex.html,index.htmを探し、存在すればそちらにジャンプし、無ければ当該フォルダの内容がリスト表示されるようになっています。
import java.io.File; import java.io.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class MiniHttpServer implements HttpHandler { static final boolean D=true; static final byte[] NOT_FOUND = ("<html><head><title>404 - Not Found</title>" + "</head><body>404 - Not Found" + "(directory must ends with '/')</body></html>") .getBytes(); static final byte[] SERVER_ERROR = ("<html><head><title>500 - Error</title></head>" + "<body>500 - Error</body></html>") .getBytes(); static String base_dir; static boolean index_jump; static PrintStream pr=System.err; static void help(String msg_,int code){ pr.println(msg_); pr.println("Usage: java -jar MiniHttpServ.jar port htdocs_dir [index_jump]"); pr.println(" Ex) java -jar MiniHttpServ.jar 8001 /var/www/htdocs index_jump"); System.exit(code); } public static void main(String[] args) throws Exception { MiniHttpServer miniHttpServer = new MiniHttpServer(); if( args.length<2 ) help("needs port and htdocs_dir",1); base_dir = args[1]; if( args.length>=3 ) index_jump= args[2].equals("index_jump"); int _port = Integer.parseInt(args[0]); HttpServer _server = HttpServer.create(new InetSocketAddress(_port), 0); _server.createContext("/", miniHttpServer); if(D)pr.println("miniHttpSever started, wating at port "+_port); _server.start(); } @Override public void handle(HttpExchange http_) throws IOException { if(D)pr.println(http_.getRequestMethod()+" "+http_.getRequestURI()); OutputStream _out = http_.getResponseBody(); try{ String _pathName=base_dir+http_.getRequestURI(); File _file =new File(_pathName); boolean _dirStyle= _pathName.endsWith("/"); byte[] _buf=null; if(_file.isFile()&&!_dirStyle) { // htmlファイル等読み込み try(InputStream _is = new FileInputStream(_file)){ _buf = new byte[(int)_file.length()]; _is.read(_buf); } catch(Exception _ex){throw _ex;} } else if(_file.isDirectory() && _dirStyle ){ // フォルダの場合ファイルリストを出す StringBuilder _sb = new StringBuilder(); _sb.append("<html><title></title><body><a href=\"../\">../</a><br>\n"); String _indexFile=null; for(File f : _file.listFiles()) { String name = f.isDirectory()?f.getName()+"/" : f.getName(); _sb.append("<a href=\"").append(name).append("\">") .append(name).append("</a><br>\n"); if( name.startsWith("index.htm")) _indexFile=name; } if(index_jump&&_indexFile!=null){ // インデックスファイルを含む場合は強制的に移動 _sb.append("<script>"); _sb.append("window.location.href = '"+_indexFile+"';"); _sb.append("</script>"); } _sb.append("</body></html>"); _buf = _sb.toString().getBytes(); } if( _buf!=null ){ http_.sendResponseHeaders(200, _buf.length); _out.write(_buf); if(D)pr.println("ok/200"); } else { if(D)pr.println("not found/404 "+http_.getRequestURI()); http_.sendResponseHeaders(404, NOT_FOUND.length); _out.write(NOT_FOUND); } } catch (Exception _ex) { if(D)pr.println("exception : "+_ex+"\nserver-error/500"); http_.sendResponseHeaders(500, SERVER_ERROR.length); _out.write(SERVER_ERROR); _ex.printStackTrace(); } finally { _out.close(); http_.close(); } } }
先頭のDをfalseにするとログトレースを出しません。
簡易プログラムであり、残念ながらシングルスレッドで動きます。あくまで臨時のwebドキュメントサーバとして動かすものです。
ビルドし試験起動するバッチファイルを示します。
:: shift-jis (UTF-8では動かない) :: CR-LF javac -encoding utf-8 MiniHttpServer.java if ERRORLEVEL 1 goto ERR jar -cmf MiniHttpServer.mf MiniHttpServer.jar MiniHttpServer.class del *.class java -jar MiniHttpServer.jar 8001 C:/xampp/htdocs index_jump ::MiniHttpServer.jar 8001 C:/xampp/htdocs index_jump :ERR pause
MiniHttpServ.mfには実行可能jarを作るための指定があり、次のようになっています。MiniHttpServerの後ろに改行(CR-LF)が入っていることに留意してください。
Main-Class: MiniHttpServer
実行可能jarはWindowsでは単体で実行出来ます。
Linuxではjava -jarで指定する必要があります。
Windowsでもコマンドウィンドウの終了でうまく終了できないことがありますので、ここではjava -jarを使用しています。
JavaプログラムをLinuxでサーバとして動かす
Javaはシンプルな文法を持ち書きやすいものとなっています。
例えばPythonの複雑極まりない変数スコープやself地獄や、変数名の書き間違い時に別の変数定義となるといった恐ろしい仕様は持ちません。
しかし、折角ビルドされた物を動かすのはとてつもなく大変なのです。
ここでは前項で作成した簡易httpサーバをLinuxでsystemctlで操作するサービスとして定義します。
(ubuntu,centOs7,ASW-ubutuで動作確認しました)
通常のサービスと同様に/etc/systemd/stsytem/に.serviceを置きます。
しかし!
- .serviceで直接javaを起動するのではなく
- 開始用、終了用それぞれシェルスクリプト経由となる
ここでは.jarとシェルスクリプトは/project/miniHttpServer/に置きました。
start.sh
起動用のシェルスクリプト(/project/miniHttpServer/start.sh)を示します。
やっていることはjavaを起動し、後で停止するためのpidをファイルに落とすことです。
プログラム引数であるポート番号やドキュメントフォルダの指定もここで行います。
#!/bin/bash # start.sh # LF (CR-LFでは動かない) cd /project/miniHttpServer/ java -jar MiniHttpServer.jar 8001 /var/www/htdocs index_jump pid=$! touch $pid.pid # PIDファイルを作成する
実行パーミションが必要です。
stop.sh
停止用のシェルスクリプト(/project/miniHttpServer/stop.sh)を示します。
start.shで作成したpidを元に停止処理を行います。
#!/bin/bash # stop.sh # LF (CR-LFでは動かない) cd /project/miniHttpServer/ pidFile=`ls *.pid` pid=${pidFile%.*} # {pid}.pidファイルからpidを取得 echo $pid stop server kill $pid # kill rm -rf $pidFile # pidファイル削除実行パーミションが必要です。
miniHttpServ.service
.service(/etc/systems/system/miniHttpServ.service)を示します。
[Unit] Description = mini HTTP service [Service] StartLimitBurst=0 User = root WorkingDirectory= /project/miniHttpServer/ ExecStart = /project/miniHttpServer/start.sh ExecStop = /project/miniHttpServer/stop.sh Restart = always Type = simple [Install] WantedBy = multi-user.target
サービスの起動にはsystemctlコマンドを使います。
$ sudo systemctl start miniHttpServer # 開始 $ sudo systemctl status miniHttpServer # 状態表示 $ sudo systemctl stop miniHttpServer # 停止 $ sudo systemctl restart miniHttpServer # リスタート $ sudo systemctl enable miniHttpServer # システム起動時に自動開始 $ sudo systemctl disable miniHttpServer # 自動開始解除
サービスにはしないが実行可能ファイルにする
実行可能jarではあってもLinuxではjava -jar無しで実行することはできません。
.jarをシェルスクリプトに含有させることで、実行可能ファイルを作ることができます。
$ echo '#!/usr/bin/java -jar' > miniHttpServer # 基本スクリプト $ cat miniHttpServer.jar >> miniHttpServer # 後ろにファイル追加 $ chmod a+x miniHttpServer # 実行パーミション付加 $ miniHttpServer 8001 /var/www/htdocs index_jump # 実行
残念ながら、この実行可能ファイルを用いて「安定した」サービス化は出来ませんでした。
ダウンロード
次の場所にファイルセットを置きました。右クリックで「対象をファイルに保存」でダウンロードしてください。
minihttpserver.zip内容は次のものです。
A03_build.bat | (win) | MiniHttpServer.jarを作成する |
A05_test.bat | (win) | MiniHttpServer.jarを作成し実行する |
A05_test.sh | (linux) | MiniHttpServer.jarを実行する |
MiniHttpServer.jar | (linux) | 実行形式jar |
MiniHttpServer.java | (win) | javaソース |
MiniHttpServer.mf | (win) | 実行形式jar作成用マニフェスト |
miniHttpServer.service | (linux) | サービス定義 |
start.sh | (linux) | サービス用starスクリプト |
stop.sh | (linux) | サービス用stopスクリプト |
適当なフォルダに展開し、次の修正を行ってください。
- 展開フォルダの名前を
- miniHttpServer.service
- start.sh
- stop.sh
- ポート番号、http保存フォルダ、index.htmlに関するオプションを
- A05_test.bat
- A05_test.sh
- star.sh
miniHttpServer.serviceは/etc/systemd/system/下に移して下さい。
異常時の対応
何等かの障害により一度立ち上げたプログラムがうまく終了しないことがあり, 以降の起動で次のエラーになることがあります。
Exception in thread "main" java.net.BindException: Address already in use: bind
Windowsの場合はウィンドウマネージャでjavaを探しタスクを終了させます。
Linuxの場合はps -ea|grep javaでjavaプロセスを探し、対応すると思われるプロセスをkill -9で殺します。
| 固定リンク