scalaでいわゆるエコーサーバーをつくったよ

エコーサーバーとは?f:id:natsuonatsu:20170207115222j:plain

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

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

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

 

実装の流れは?

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

 

実装コード紹介

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 = {
//サーバーソケットを生成しポート番号をバインドする
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()
}
}
}

 

基本的にやりたいことをしてくれるメソッドをここから探し出せれば完成に近づくって感じでした。 →→ Java Platform SE 8

f:id:natsuonatsu:20170201005336p:plain

そしてこの通信の概念というか、ソケットの存在意義、しくみを理解していればコードも読み解けます。

 

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

val input = socket.getInputStream

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

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

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レスポンスの雛形に沿って情報を渡します。

f:id:natsuonatsu:20170207133350g:plain

ヘッダはここにあるものが全部なくても必要なものだけあれば大丈夫です。

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

 

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

 

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

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