Webサーバを作りながら学んでいる

初めに

少し素振りにScalaを書いている。

タイトル通りだが、以下の本を読みながら書いている。

Webサーバを作りながら学ぶ 基礎からのWebアプリケーション開発入門

まだまだ序盤だが なかなか丁寧に解説されているのと 実験ベースでゆっくり進んでいく。

最初に書いた2つをgitに上げておいた。

TCPサーバ

簡易的なファイルを送りあう、クライアント・サーバのプログラムをまずはじめに書いた。

ソケットを開くのは普通にJavaのライブラリを使った。 ScalaAPIが分かっていないが、たぶんないだろう。(わざわざラップするほどでもないが辛いと言えば辛い。)

というわけで書いたのが以下のソースだ

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を使う前がこう。 f:id:reteria:20170517050610p:plain

これはひどい。ちなみに画像のソースは動かない。 で、また別の人に助けていただいたのを取り込んで 以下の形に落ち着いた。

// ...省略
  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は奥が深い・・・

本を読み進めていきたい。

リポジトリgithub.com