2012年1月29日日曜日

java : HttpServerでimgを出したりpostを読んだり

私のPCの中には色んなデータが入っています。 APNGとかSVGとかに手を出してるんで、まぁ色々です。 全部簡単にチェックできればいいんですが、まず全部表示できるようなヴューアが少ないです。 全部表示して整理できるようなソフトとかって、あるんでしょうかね? 作るわけには行かないしなぁ。

で、ふと気付いたのがブラウザ、これは万能ビューアですよね。 APNGでもSVGでも見れますし、音でも動画でもOK。 しかしローカルのファイルをチェックするには使い勝手が良くありません。 「何とか使えないかなぁ」と適当に検索してみたら、ちょっと気になるネタがありました。 「http 簡易サーバ」で検索するとC#でもjavaでもけっこう簡単にhttpのサーバが作れるようなんですよ。 もちろんインターネットに公開するようなサーバーを作るのは簡単ではないでしょうけど、ヴューアの部分をブラウザに頼った「ローカルファイル整理ツール」を作るハードルはかなり下がりそうです。

C#だと「System.Net.HttpListener」が、javaだと「com.sun.net.httpserver.HttpServer」が使えるようですね。 「データ整理ツールを作るんなら環境依存は少ない方がいいだろう」ってことでjavaの方を試してみました。 とりあえず考えながら作ったら、

  • サーバはjavaアプリ、クライアントはブラウザ。
  • サーバで用意した画像をブラウザで表示。
  • サーバで用意したテキストをブラウザで表示。
  • htmlでformを書く。htmlをブラウザで開いてformの編集情報をpost、サーバで受け取る。

というようなコードになりました。

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.*;
import java.net.*;

public class JunkContentsBox implements HttpHandler
{
    public final static int SERVER_RESPONSE_OK = 200;
    public final static int SERVER_PORT = 80;
    public final static int SERVER_STOP_DELAY = 10;

    private HttpServer _server = null;
    private Thread _serverThread = null;

    private byte[] _testImage;

    public static void main(String[] args)
    {
        JunkContentsBox thisApp = new JunkContentsBox();
        thisApp.run();
    }

    public void run()
    {
        readTestImage();
        startServer();
        WaitEnterKey();
        stopServer();
    }

    public void readTestImage()
    {
        InputStream in = null;
        try
        {
            URL url = this.getClass().getResource("test.png");
            File file = new File(url.getFile());
            in = url.openStream();
            _testImage = new byte[(int)file.length()];
            in.read(_testImage);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
        }
        finally
        {
            if(in != null)
            {
                try
                {
                    in.close();
                }catch(Exception exc){}
            }
        }
    }

    public void WaitEnterKey()
    {
        System.out.print("Push enter to stop.\n> ");
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try
        {
            br.readLine();
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
        }
    }

    public void startServer()
    {
        System.out.println("Confirm a command : server start");
        _serverThread = new Thread()
        {
            @Override
            public void run()
            {
                try
                {
                    _server = HttpServer.create(new InetSocketAddress(SERVER_PORT), 0);
                    _server.createContext("/", JunkContentsBox.this);
                    _server.start();
                }
                catch (IOException exc)
                {
                    exc.printStackTrace();
                }
            }
        };
        _serverThread.start();
        System.out.println("Server is started.");
    }

    public void stopServer()
    {
        System.out.println("Confirm a command : server stop");
        System.out.format("Wait %d seconds...\n", SERVER_STOP_DELAY);
        assert _server != null && _serverThread != null;
        _server.stop(SERVER_STOP_DELAY);
        System.out.println("Server is stopped.");
    }

    @Override
    public void handle(HttpExchange ex)  throws IOException
    {
        OutputStream os = ex.getResponseBody();
        try
        {
            String uri = ex.getRequestURI().toString();
            if(uri.equals("/test.png"))
            {
                ex.getResponseHeaders().add("Content-Type", "image/png");
                ex.sendResponseHeaders(SERVER_RESPONSE_OK, _testImage.length);
                os.write(_testImage);
            }
            else
            {
                ex.getResponseHeaders().add("Content-Type", "text/plain");
                String requestBody = getRequestBodyString(ex);
                String resTxt = uri + "\n" + requestBody;
                byte[] resBuff = resTxt.getBytes();
                ex.sendResponseHeaders(SERVER_RESPONSE_OK, resBuff.length);
                os.write(resBuff);
            }
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
        }
        finally
        {
            try
            {
                os.close();
            }catch(Exception exc){}
        }
    }

    private String getRequestBodyString(HttpExchange ex)
    {
        InputStream is = ex.getRequestBody();
        try
        {
            ByteArrayOutputStream requestBuff = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int len;
            while((len = is.read(buff)) != -1)
            {
                requestBuff.write(buff, 0, len);
            }

            return URLDecoder.decode(requestBuff.toString(), "utf-8");
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            return "";
        }
        finally
        {
            try
            {
                is.close();
            }catch(Exception exc){}
        }
    }
}

クラス名は「最終的にそんなのができたら良いなぁ」という妄想ですな。 とりあえずコンパイルして、クラスと同じフォルダに適当な「test.png」を用意。 次のhtmlも用意です。

<html>
    <head></head>
    <body>
        <form action="http://localhost/testContext" method="post">
            <p>入力1 : <input type="text" name="text1" value="あいうえお" size="20" /></p>
            <p>入力2 : <input type="text" name="text2" value="かきこけこさしすせそ" size="20" /></p>
            <p>入力3 : 
                <input type="radio" name="radio1" value="表示1" checked="checked">表示1</input>
                <input type="radio" name="radio1" value="表示2" checked="checked">表示2</input>
                <input type="radio" name="radio1" value="表示3" checked="checked">表示3</input>
            </p>
            <p><input type="submit" name="submit" value="送信" /></p>
        </form>
        <img src="http://localhost/test.png"/>
    </body>
</html>

htmlファイルのパスは適当でOK。

コマンドプロンプトで実行したら「なんたらかんたら ... Push enter to stop.」と表示されて止まります。 表のスレッドはenterキー待ち、裏のスレッドでサーバを動かしています。 この状態でブラウザで↑のhtmlを開くとformと「test.png」が表示されます。 formはhtmlの内容なのでローカルファイルをそのまま表示、「test.png」は「src="http://localhost/test.png"」なのでサーバが送った画像が表示されています。 formの送信ボタンを押すとパラメータがpostされてサーバに届きます。 その内容はtext/plainで応答。 htmlにしなかったのは手抜きですけど、まぁ多分htmlにしたらブラウザもそう受け取ってくれていたでしょう。 コマンドプロンプトでenterキーを押したらサーバの停止です。

数時間で作ったコードなんでところどころマズいですね。 って言ったらダメなのか、腕のいい人はパッと作ったものが良品といいますしね。 ちょいと反省。 で、こんな私でも数時間で簡易サーバを作れるってのはいい時代です。

ローカル専用と割り切ってセキュリティをぬるめにすれば「formの情報を元にローカルファイルをガリガリ」とかってのは、「GUIもjavaで全部コーディング」ってのと比べて楽になりそうです。 firewallはちゃんとしないとダメでしょうけど。

あれ? なんか文章が整理ツール作る方に行ってる? 無理ですから。