ちょっと試したいことがあって、HTTPサーバを用意したかったのですがApacheとかをインストールするのも面倒だったので、JDK 6から同梱されている簡易HTTPサーバを使ってみることにしました。
が、実際に書いたのはJavaも面倒で、Groovyですが(笑)。
参考にしたのは、このサイトです。
http://www.techscore.com/tech/Java/JavaSE/JavaSE6/8/#mustang8-5
APIドキュメントは、こちら。
http://docs.oracle.com/javase/jp/6/jre/api/net/httpserver/spec/index.html
Jerseyと組み合わせ使うこともできるみたいですよ。
http://news.mynavi.jp/column/jsr/018/index.html
で、書いたコードはこちら。
LightHttpd.groovy
import java.io.IOException import java.net.InetSocketAddress import com.sun.net.httpserver.HttpExchange import com.sun.net.httpserver.HttpHandler import com.sun.net.httpserver.HttpServer class SimpleHttpHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { try { def builder = new StringBuilder() builder << "Accessed URL = ${exchange.requestURI}" << "\r\n" builder << "Accessed Date = ${new Date()}" << "\r\n" def bytes = builder.toString().getBytes("UTF-8") exchange.sendResponseHeaders(200, bytes.length) exchange.responseBody.withStream { it.write(bytes) } } catch (e) { e.printStackTrace() def message = "Server Error = ${e}" def bytes = message.getBytes("UTF-8") exchange.sendResponseHeaders(500, bytes.length) exchange.responseBody.withStream { it.write(bytes) } } } } server = HttpServer.create(new InetSocketAddress(8080), 0) server.createContext("/", new SimpleHttpHandler()) server.start() println("LightHttpd Startup. ${new Date()}")
軽く説明すると…
server = HttpServer.create(new InetSocketAddress(8080), 0)
の部分で、HttpServerのインスタンスを作成します。引数にはInetSocketAddressを指定しますが、バインドせずに作成して、後からHttpServer#bindで設定してもよいのだとか。
続いて、
server.createContext("/", new SimpleHttpHandler())
で、コンテキストパスと対応するHttpHandlerのインスタンスを渡してHttpContextを生成します。第1引数のコンテキストパスは、サーブレットのコンテキストパスみたいなもののようですが、「/foo/bar/」みたいな指定もできるそうです。
あとは、HttpServerを開始します。
server.start()
このコードだと、HttpServer#startメソッド呼び出し時に作成されるスレッドで動作するのだとか。マルチスレッドで動作するようにさせたければ、HttpServer#setExecutorを使用してjava.util.concurrent.Executorのインスタンスを指定すればよいらしいです。
あとは、HttpHandlerインターフェースを実装したクラスを作成して、handleメソッドをオーバーライドします。リクエストとレスポンスを扱う時には、HttpExchangeクラスを使用するそうな。
今回は、何を受け取ってもアクセスパスと時刻を返すようにしました。
class SimpleHttpHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { try { def builder = new StringBuilder() builder << "Accessed URL = ${exchange.requestURI}" << "\r\n" builder << "Accessed Date = ${new Date()}" << "\r\n" def bytes = builder.toString().getBytes("UTF-8") exchange.sendResponseHeaders(200, bytes.length) exchange.responseBody.withStream { it.write(bytes) } } catch (e) { e.printStackTrace() def message = "Server Error = ${e}" def bytes = message.getBytes("UTF-8") exchange.sendResponseHeaders(500, bytes.length) exchange.responseBody.withStream { it.write(bytes) } } } }
このクラスのインスタンスを、HttpServer#createContextに渡しています。
では、動作確認。まずは起動して
$ groovy LightHttpd.groovy LightHttpd Startup. Sat Oct 13 01:07:36 JST 2012
アクセスしてみます。
$ telnet localhost 8080 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /index.html HTTP/1.0 HTTP/1.1 200 OK Content-length: 74 Connection: close Date: Fri, 12 Oct 2012 16:11:44 GMT Accessed URL = /index.html Accessed Date = Sat Oct 13 01:11:44 JST 2012 Connection closed by foreign host.
大丈夫そうですね。
ちなみに、HTTPのバージョン指定をしなかった場合、Bad Requestとして扱われてしまいます…。
簡単なツールやスタブなどで、JavaでHTTPサーバを実装したい場合には便利かも?
なんとなく、Scala版も書いてみました…。scalaコマンドでスクリプト実行することを想定した書き方になってます。
LightHttpd.scala
import java.io.{Closeable, IOException} import java.net.InetSocketAddress import java.util.Date import com.sun.net.httpserver.{HttpExchange, HttpHandler, HttpServer} def using[A <: Closeable, B](resource: A)(body: A => B): B = try { body(resource) } finally { if (resource != null) { resource.close() } } class SimpleHttpHandler extends HttpHandler { @throws(classOf[IOException]) def handle(exchange: HttpExchange): Unit = { try { val builder = new StringBuilder builder ++= "Accessed URL = " builder ++= exchange.getRequestURI.toString builder ++= "\r\n" builder ++= "Accessed Date =" builder ++= new Date().toString builder ++= "\r\n" val bytes = builder.toString.getBytes("UTF-8") exchange.sendResponseHeaders(200, bytes.size) using(exchange.getResponseBody) { output => output.write(bytes) } } catch { case e => e.printStackTrace() val message = "Server Error = %s".format(e.toString) val bytes = message.getBytes("UTF-8") exchange.sendResponseHeaders(500, bytes.size) using(exchange.getResponseBody) { output => output.write(bytes) } } } } val server = HttpServer.create(new InetSocketAddress(8080), 0) server.createContext("/", new SimpleHttpHandler) server.start() printf("LightHttpd Startup. %s%n", new Date)