Webサーバを作りながら学んでいる
初めに
少し素振りにScalaを書いている。
タイトル通りだが、以下の本を読みながら書いている。
Webサーバを作りながら学ぶ 基礎からのWebアプリケーション開発入門
まだまだ序盤だが なかなか丁寧に解説されているのと 実験ベースでゆっくり進んでいく。
最初に書いた2つをgitに上げておいた。
TCPサーバ
簡易的なファイルを送りあう、クライアント・サーバのプログラムをまずはじめに書いた。
ソケットを開くのは普通にJavaのライブラリを使った。 ScalaのAPIが分かっていないが、たぶんないだろう。(わざわざラップするほどでもないが辛いと言えば辛い。)
というわけで書いたのが以下のソースだ
class TcpServer(port: Int) { def open() { withClosable(new ServerSocket(port)) { server => withClosable(server.accept()) { socket => val src = socket.getInputStream withClosable(Files.newOutputStream(Paths.get("server_recv.txt"))) { dest => Source.fromInputStream(src).takeWhile(_ != 0).foreach(dest.write(_)) } val dest = socket.getOutputStream withClosable(Files.newInputStream(Paths.get("server_send.txt"))) { src => Source.fromInputStream(src).takeWhile(_ != -1).foreach(dest.write(_)) } } } } } object TcpServer extends App { new TcpServer(8001).open() }
めっちゃネスト深い!!めっちゃネスト深い!! まぁこんなもんだろうと思い、諦めた感ある。 元々のソースはJavaなので・・・。 そして溢れ出る、Java臭。
上記ソース中に出てくるwithClosable(Closeableではない)は以下のソースである。 コップ本に出てくるローンパターンをそれっぽく名前を付けて定義しているだけである。
object Resources { def withClosable[T <: AutoCloseable](resource: T)(r: T => Unit): Unit = { try { r(resource) } finally { resource.close() } } }
いい方法が思い浮かばなかった。
簡易的なWebサーバ
とりあえず、適当なヘッダ付けてhtmlを返却してみよう。みたいな課題。
で、さっきのwithClosableをN個定義しようと思ったけど 助け船が。scala-arm、というライブラリがあるそう。
foreach生えた。すごい。 まだ辛い。
// ...省略 def start() { for ( server <- managed(new ServerSocket(port)); socket <- managed(server.accept)) { val startLine = Source.fromInputStream(socket.getInputStream).getLines().take(1) startLine.next().split(" ") match { case Array("GET", path, "HTTP/1.1") => val output = socket.getOutputStream writeHeader.foreach(output.write(_)) for (src <- managed(Source.fromInputStream(Files.newInputStream(Paths.get(".", path))))) { src.foreach(output.write(_)) } case _ => sys.error("invalid") } } }
scala-armを使う前がこう。
これはひどい。ちなみに画像のソースは動かない。 で、また別の人に助けていただいたのを取り込んで 以下の形に落ち着いた。
// ...省略 def start() { for ( server <- managed(new ServerSocket(port)); socket <- managed(server.accept)) { val startLine = Source.fromInputStream(socket.getInputStream).getLines().take(1).toList.headOption.map(_.split(" ")) startLine flatMap { case Array("GET", path, "HTTP/1.1") => Some(path) case _ => None } foreach { path => val output = socket.getOutputStream writeHeader.foreach(output.write(_)) for (src <- managed(Source.fromInputStream(Files.newInputStream(Paths.get(".", path))))) { src.foreach(output.write(_)) } } } }
flatmapっょぃ
最後のforeach内部の処理、もう少し綺麗に書けそうな気もするけど そこまで気にしなくてもいいかなぁとおもいつつ。
まとめ
今回はここで終わり。 Scalaは奥が深い・・・
本を読み進めていきたい。