tana_ash's diary

プログラミングや電子工作など。やってみたこと、わかったことをまとめておく場所。

小さなHTTPサーバーNanoHTTPDを使う&改造する

今年の4月ごろからAndroid開発を始めたのです。
だからJavaもやってるのです。
そして、突然新しいアプリのネタを思いついてしまったのです。
というわけで、今日は、Javaソースコード1ファイルだけの小さなHTTPサーバー、NanoHTTPDというものを見つけたので動かして、それを改造するのですよー!
NanoHTTPDは http://elonen.iki.fi/code/nanohttpd/ ここからダウンロードできるのですよ。

改造後のNanoHTTPDと自分で書いたコードはgistにアップロードしてあります。
https://gist.github.com/3436665

そのまま動かしてみる

まずNanoHTTPDのソースコードをダウンロードし、それをjavacでコンパイルします。
普通に

javac NanoHTTPD.java

でコンパイルして

java NanoHTTPD -p 44444

と起動します。-pの後に数字を入れてポートを指定するとサーバーを起動でき、起動したディレクトリにあるファイルにアクセスできます。

組み込んでみる

ここは後付けで書いたため短く薄いです。
NanoHTTPDのサンプルコードにHelloServer.javaというのがあったのでそれ参考です。
ただし、superの引数が違っていたと思います。バージョンの違いでしょうか。
NanoHTTPDを継承したクラスを作り、コンストラクタでは適当に初期化します。
superの引数はポート番号とFileですが、ファイルを使わないのならnullで大丈夫です。
実際の処理はserveメソッドです。ここでは、最初の引数がファイルパスなのでこれで分岐して処理します。

改造しちゃう

ここで思いっきり改造します。
まず、コンパイルする時に警告が出ないようにしました。
修正箇所は、HashtableやVectorVectorのように型を指定せずに使っている場所に、型を書いたり、「Java 1.4ではこっちを使って」と書かれているところを使うようにしたようなところです。
また、「リアルタイムでクライアントにデータを通知したい!」と思ったのでchunkedなレスポンスを返せるようにしました。
chunkedについては http://d.hatena.ne.jp/kiririmode/20100606/p1 ここを参考にしています。
ただし、ここで大きなミスにはまってしまいました。
NanoHTTPDではデータを読み込んでクライアントに送るところでInputStreamを使っているのですが、ほとんどそのままやってみた場合、
InputStreamをreadしているところで、InputStreamにデータを送り込む側のPipedOutputStreamをcloseしても止まりませんでした。無限ループです。
実は大きな落とし穴がありました。
InputStreamのreadメソッドは、3つの引数を与えた場合は読み込んだバイト数を返しますが、
ストリームが終わっている場合は-1を返します。
しかし3つめの引数(たぶん読み込むバイト数か終わりの位置)が0の場合、ストリームが終わっていても-1ではなく0を返すようです。
それが、一旦残りが無くなっても本当に終わる、つまり-1を返すまで終了しないという改造部分に引っかかり無限ループしていたのでした。
これで、serveの戻り値でResponseを作るところでPipedInputStreamを渡せば、少しずつデータが送られてくるプログラムが作れます。

まとめ

ここまでHTTPサーバーが小さければ、Androidや家電やアレなアレにサーバーを組み込んだり夢が広がりますね!
(ちなみに、最後に自慢。HSPでHTTPサーバーを書いた頃の自分は何かに取り憑かれていた。HSPの精霊とか。)