CLOVER🍀

That was when it all began.

JDK 6から同梱されている、簡易HTTPサーバを使う

ちょっと試したいことがあって、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)