月曜日 5 20, 2013

Nashorn で HTTP サーバを実装する

はじめに

Nashorn で HTTP サーバを実装するサンプルが紹介されていました。ご参照下さい。

おことわり

以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文は https://blogs.oracle.com/nashorn/entry/http_server_written_in_nashorn でご覧頂けます。翻訳文の URL は https://blogs.oracle.com/nashorn_ja/entry/http_server_written_in_nashorn です。

訳文

今日は少しひねりを加えたサンプルプログラムをご紹介します。

Node.js はホットで、沢山の可能性を秘めています。Node.js を触っていて、Oracle に加わる前に働いていたサーバ会社で使っていたツールの事を思い出しました。その時はネットワーク越しにサーバに接続して開発をしていました。私はキーボード作業が好きじゃなかったのですが、そこでは SSH かコンソール接続しかサーバにアクセスする手段がありませんでした。そこで簡単な HTTP サーバを作り、それをプロキシーにして、リポジトリの作業やプログラムのビルド、ファイルの編集などをブラウザ経由で実行出来るようにすることにしました。この仕組みは簡単に作成できて、なおかつとても柔軟でした。

世の中には既に HTTP サーバは沢山あります(その多くが Java で掛かれています)。それらを自分用に手直しすることも出来ますが、Nashorn で一から実装出来ることを証明してみたくなりました。ささっと作ってみたのがこちらです。

#!/usr/bin/jjs -scripting
#

var Thread            = java.lang.Thread;
var ServerSocket      = java.net.ServerSocket;
var PrintWriter       = java.io.PrintWriter;
var InputStreamReader = java.io.InputStreamReader;
var BufferedReader    = java.io.BufferedReader;
var FileInputStream   = java.io.FileInputStream;
var ByteArray         = Java.type("byte[]");

var PORT = 8080;
var CRLF = "\r\n";
var FOUROHFOUR = <<<EOD;
<HTML>
    <HEAD>
        <TITLE>404 Not Found</TITLE>
    </HEAD>
    <BODY>
        <P>404 Not Found</P>
    </BODY>
</HTML>
EOD

var serverSocket = new ServerSocket(PORT);

while (true) {
    var socket = serverSocket.accept();

    try {
        var thread = new Thread(function() { httpRequestHandler(socket); });
        thread.start();
        Thread.sleep(100);
    } catch (e) {
        print(e);
    }
}

function httpRequestHandler(socket) {
    var out       = socket.getOutputStream();
    var output    = new PrintWriter(out);
    var inReader  = new InputStreamReader(socket.getInputStream(), 'utf-8');
    var bufReader = new BufferedReader(inReader);

    var lines = readLines(bufReader);

    if (lines.length > 0) {
        var header = lines[0].split(/\b\s+/);

        if (header[0] == "GET") {
            var URI = header[1].split(/\?/);
            var path = String("./serverpages" + URI[0]);

            try {
                if (path.endsWith(".jjsp")) {
                    var body = load(path);
                    if (!body) throw "JJSP failed";
                    respond(output, "HTTP/1.0 200 OK", "text/html", body);
                } else {
                    sendFile(output, out, path);
                }
            } catch (e) {
                respond(output, "HTTP/1.0 404 Not Found", "text/html", FOUROHFOUR);
            }
        }
    }

    output.flush();
    bufReader.close();
    socket.close();
}

function respond(output, status, type, body) {
    sendBytes(output, status + CRLF);
    sendBytes(output, "Server: Simple Nashorn HTTP Server" + CRLF);
    sendBytes(output, "Content-type: ${type}" + CRLF);
    sendBytes(output, "Content-Length: ${body.length}" + CRLF);
    sendBytes(output, CRLF);
    sendBytes(output, body);
}

function contentType(path) {
    if (path.endsWith(".htm") ||
        path.endsWith(".html")) {
      return "text/html";
    } else if (path.endsWith(".txt")) {
      return "text/text";
    } else if (path.endsWith(".jpg") ||
               path.endsWith(".jpeg")) {
      return "image/jpeg";
    } else if (path.endsWith(".gif")) {
      return "image/gif";
    } else {
      return "application/octet-stream";
    }
}

function readLines(bufReader) {
    var lines = [];

    try {
        var line;
        while (line = bufReader.readLine()) {
            lines.push(line);
        }
    } catch (e) {
    }

    return lines;
}

function sendBytes(output, line) {
    output.write(String(line));
}

function sendFile(output, out, path) {
    var file = new FileInputStream(path);

    var type = contentType(path);
    sendBytes(output, "HTTP/1.0 200 OK" + CRLF);
    sendBytes(output, "Server: Simple Nashorn HTTP Server" + CRLF);
    sendBytes(output, "Content-type: ${contentType(path)}" + CRLF);
    sendBytes(output, "Content-Length: ${file.available()}" + CRLF);
    sendBytes(output, CRLF);
    output.flush();

    var buffer = new ByteArray(1024);
    var bytes = 0;

    while ((bytes = file.read(buffer)) != -1) {
        out.write(buffer, 0, bytes);
    }
}

たった 84 行の JavaScript コードで実装出来ました。HTTP の仕様をもっと沢山実装する事も出来ますが、実証テストとしてはこれで十分でしょう。

この HTTP サーバを単に HTML や JPEG ファイルのダウンロードの為に使用することも出来ますが、プログラムの真ん中当たりにある以下のコードにご注目下さい。

                if (path.endsWith(".jjsp")) {
                    var body = load(path);
                    if (!body) throw "JJSP failed";
                    respond(output, "HTTP/1.0 200 OK", "text/html", body);

これは、リクエストされたファイルのファイル名が .jjsp で終わっていたら、その中身を JavaScript のプログラムとして評価し、その実行結果を HTTP の応答として返すという処理です。

次のサンプル .jjsp ファイルは私がテストの為に用意したものです。

#!/usr/bin/jjs -scripting
#

var colours = {
    java: "BLUE",
    js: "RED", 
    css: "GREEN",
    html: "ORANGE"
};

function colorize(file) {
    var suffix = file.substr(file.lastIndexOf(".") + 1);
    var colour = colours[suffix];
    if (colour) {
        return "<FONT COLOR='${colour}'>${file}</FONT>";
    }
    return file;
}

var files = `ls`.trim().split("\n");
files = files.map(colorize);
files = files.map(function(file) "${file}<BR />");
files = files.join("\n");

var HTML = <<<EOD;
<HTML>
    <HEAD>
        <TITLE>Simple HTML</TITLE>
    </HEAD>
    <BODY>
        <img width="256" height="138" src="rrr256x138.jpg" alt="rrr256x138" />
        <BR />
        <FONT FACE="Courier New" SIZE="2">
        ${files}
        </FONT>
    </BODY>
</HTML>
EOD

HTML;

この様に JavaScript のコードも HTML のタグも高い可読性を保ったままプログラムにまとめる事が出来ます。このスクリプトは ls コマンドでディレクトリの一覧を取得し、ファイルの拡張子に応じて色付けをしています。私の手元では以下のような実行結果になりました。

FX3D.js
HTML.js
HelloWorld.java
Server
Server.zip
SimpleHTTPServer.js
Test.class
Test.java
Text.js
canvas.js
colourfulcircles.js
hellofxml.js
helloworld1.js
helloworld2.js
http.js
introspect.js
jogl
languages.css
languages1.js
languages2.js
script.js
scripting.fxml
serverpages
slow1.js
suspect.js
test.js
threading.js
trends.css
trends.js
twitter4j-core-3.0.1.jar
twitter4j.properties

このスクリプトは様々な応用が出来そうです。

About

JavaVM 用 JavaScript エンジンの Nashorn について情報発信しているブログです。Nashorn の読み方はナズホーンです。

Search

Archives
« 4月 2014
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today