LoginSignup
4
3

More than 5 years have passed since last update.

scalaでエコサーバーをつくったよ

Last updated at Posted at 2017-02-11

弟子入り中につくったものをはてブに投稿したのでこちらにも書いてみます!

エコーサーバーとは?

ブラウザ(クライアント)から送られてきたhttpリクエストに対して、httpレスポンスでリクエストの内容をそのまま返す!それがエコーサーバーです。

(通常はここでhtmlファイルや画像を返す)

なので、ブラウザに「GET / ~~」(←リクエスト文)みたいな文字列が表示されるのがゴールです。

概要はこんなかんじ

1.使えるjavaのパッケージをインポート(下記のリンクあり)
2.サーバーソケットを生成
3.while文の中にコードを書いていってサーバーソケットはacceptし続けるようにする
4.入力ストリームを作成しhttpリクエストを読み込む
5.出力ストリームを作成しhttpリクエスト文を送り返す
6.ソケットを閉じる

実装コード紹介

httpserver.
 import java.net.ServerSocket
 import java.net.Socket
 import java.io
 import java.io.InputStream
 import java.nio.charset.StandardCharsets

  object HTTPServer {
    def main(args: Array[String]): Unit = {
       println("Hello HTTP Server!Strat----->")
       //サーバーソケットを生成しポート番号をバインドする
       val serverSocket = new ServerSocket(8000)
       println("Serving HTTP on localhost port 8000 ...")
        while (true) {
          //サーバーソケットはacceptし続けて待機
          val socket = serverSocket.accept()

         //input--httpリクエストを読み込む
         val input = socket.getInputStream
         def readUntilEnd(is: InputStream, acc: List[Int] = Nil):String ={
           is.read :: acc match {
             case x if x.take(4) == List(10,13,10,13) => x.reverse.map(_.toChar).mkString //10,13は改行
             case x                                   => readUntilEnd(is, x)
           }
         }

         //output--httpリクエストを送り返す
         val output = socket.getOutputStream
         val CRLF = "\r\n"
         val responseBody = readUntilEnd(input)
         val responseBodySize = responseBody.length
         val responseText = "HTTP/1.1 200 OK" + CRLF +
                            "Content-Length: " + responseBodySize + CRLF +
                            "Content-Type: text/plain" + CRLF +
                            CRLF +
                            responseBody
         output.write(responseText.getBytes(StandardCharsets.UTF_8))

         //ソケットを閉じる
         input.close()
          output.close()
        }
      serverSocket.close()
      }
    }
  }

基本的にやりたいことをしてくれるメソッドをここから探し出せれば完成に近づくって感じでした。そしてこの通信の概念というか、ソケットの存在意義、しくみを理解していればコードも読み解けます。

input 入力ストリームについて

text val input = socket.getInputStream

で入力ストリームを作成すると、 情報(httpリクエスト)がトンネルを通ってどんどんやってきます。

それを一文字ずつreadして、最終的にはString型でひとつの文字列として返すreadUntilEndメソッドをつくりました。

text def readUntilEnd(is: InputStream, acc: List[Int] = Nil):String ={
is.read :: acc match {
case x if x.take(4) == List(10,13,10,13) => x.reverse.map(_.toChar).mkString //10,13は改行
case x => readUntilEnd(is, x)
}
}

ここのパターンマッチではなにをしているかというと、httpリクエスト文の最後で改行がふたつ送られてきたら、再帰をやめるような条件分岐になっています。

一文字ずつByte型でリストに入ってくるので最後にすべてひっくり返して(reverse)キャラにしmkStringでリストからひとつの文字列になるようにがっちゃんこします。

output 出力ストリームについて

同じく出力ストリームを作成し、httpレスポンスの雛形に沿って情報を渡します。ヘッダはここにあるものが全部なくても必要なものだけあれば大丈夫です。

レスポンスボディに必要なものをひとつずつ変数にString型でいれておいて、responseTextでまとめて一気にByte型に変換しソケットにwriteメソッドを適用してあげればhttpレスポンスとして送られます!

最後にソケットを閉じておわり。

かなり力づくな感じがしますがhttpサーバーってこんなことしてるんだなぁ。

すべてのものが魔法みたいに一瞬でパーンと操作されているわけじゃないことを改めて思います。笑

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3