基礎からsbtとScalaでServletプログラミング No.2

やりたいこと

jQueryでインタラクティブに結果を変化させたい。
今回は前回作った基礎構成をベースに、HTMLのformに入力した値を即送信して、結果を表示するプログラムを作成する。

  • 数字をフォームに入力
  • 入力する度に内容を送信し、下記結果を受信し計算結果として表示する(入力した数値をNとする)

できたもの

今回からちゃんとバージョン管理して、記事書くごとにtag打ってリンク貼ろうと思う。
https://github.com/tomingtoming/ServletPractice/tree/ServletFromBeginning2

jQueryでServletを呼び出すHTML部分

File:./src/main/webapp/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
$(function() {
  $("#input input").keyup(function() {
    $.get("/fac",{ n : $(this).val() },function(data) {
      $(".factorial").text("factorial:"+data);
    });
    $.get("/fib",{ n : $(this).val() },function(data) {
      $(".fibonacci").text("fibonacci:"+data);
    });
  });
});
</script>
</head>
<body>
  <form id="input">
    input number here to start calculation
    <input type="text" /><span />
  </form>
  <ul>
    <li class="factorial">factorial:</li>
    <li class="fibonacci">fibonacci:</li>
  </ul>
</body>
</html>

これがファイル全体。

jQuery読込

ヘッダの中で下記の様に記述して、jQueryを読み込ませる。

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

これ以降の部分であればjQueryが使用できる。

サーバへの送受信実行部分

フォームへの入力が行われる度に、サーバに内容を送信して結果を表示させる部分

<script>
$(function() {
  $("#input input").keyup(function() {
    $.get("/fac",{ n : $(this).val() },function(data) {
      $(".factorial").text("factorial:"+data);
    });
    $.get("/fib",{ n : $(this).val() },function(data) {
      $(".fibonacci").text("fibonacci:"+data);
    });
  });
});
</script>

ここでは"#input input"によりid="input"のタグの子要素のinputタグを指定して、"keyup"イベントを取得して引数の関数を実行するよう定義している。
実行する関数の中では、"/fac"と"/fib"に各々フォームに入力された値をパラメータとして加えて送信し、結果を処理するコールバック関数に渡している。
コールバック関数の中では、".factorial"と".fibonacci"により、class="factorial"とclass="fibonacci"の属性を持ったタグを指定し、その内部のテキストを名称と結果で書き換えている。

入力フォーム
  <form id="input">
    input number here to start calculation
    <input type="text" /><span />
  </form>

formにidを付与してあり、これを上記スクリプトで指定して入力値を取得。

結果出力先
  <ul>
    <li class="factorial">factorial:</li>
    <li class="fibonacci">fibonacci:</li>
  </ul>

上記の部分で階乗とフィボナッチ用の各表示領域を準備しておき、上記スクリプトによる書き換え対象とする。

Servlet部分

共通trait

階乗を返すServletもフィボナッチ数列を返すServletも数値を受け取って数値を返すので、共通のtraitを作成。
File:./src/main/scala/SimpleNumberApiServlet.scala

import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}

trait SimpleNumberApiServlet extends HttpServlet {
  def response(req:BigInt):Option[BigInt];
  override def doGet(req:HttpServletRequest, res:HttpServletResponse) {
    println(req.getParameter("n"))
    res.setContentType("text/plain")
    res.setCharacterEncoding("UTF-8")
    val writer = res.getWriter()
    try {
      response(req.getParameter("n").toInt) match {
        case Some(n) => writer.write(n.toString)
        case None => "Calculation failed:" + req.getParameter("n")
      }
    } catch {
      case e:NumberFormatException =>
        writer.write("Illigal number format:" + req.getParameter("n"))
      case e =>
        writer.write("Error:" + req.getParameter("n"))
        println("Error:" + e)
    }
  }
}

リクエスト"/hoge?n=NNN"で来たnのパラメータを(数値が大きな場合に備えてBigIntで)数値にして、responseメソッドに渡して結果を文字列で返す。
クライアント側にはエラーが発生した場合にもエラーメッセージを単なる結果として返しているので、あまりよろしくはない…。
今回はjQueryからリクエストを投げ、結果をそのままテキストとして表示したかった為、ContentTypeを"text/plain"としてそのままテキストとして扱えるようにした。

    res.setContentType("text/plain")
    res.setCharacterEncoding("UTF-8")

また、trait継承クラスのresponseメソッドが入力したBigIntに結果を返せない可能性を考え、返り値をOption[BigInt]とした。

  def response(req:BigInt):Option[BigInt];
階乗を返すServlet

File:./src/main/scala/Factorial.scala

class Factorial extends SimpleNumberApiServlet {
  override def response(req:BigInt):Option[BigInt] = try {
    if(req.isValidInt) Option(factorial(req.toInt)) else None
  } catch {
    case _ => None
  }
  object factorial {
    private val stream = 0 #:: Stream.from(1).map(BigInt(_)).scanLeft(BigInt(1))(_*_).tail
    def apply(n:Int):BigInt = stream(n)
  }
}

object factorialは

  • Nの階乗のStream[BigInt]を保持している
  • apply(n:Int)によりn番目の数値を返す

上記の通りとなっている。大きい数字で呼ばれる程にメモリを食いつぶすので実用には耐えない。

フィボナッチ数列上のN番目の数値を返すServlet

File:./src/main/scala/Fibonacci.scala

class Fibonacci extends SimpleNumberApiServlet {
  override def response(req:BigInt):Option[BigInt] = try {
    if(req.isValidInt) Option(fibonacci(req.toInt)) else None
  } catch {
    case _ => None
  }
  object fibonacci {
    private def fib(n:BigInt,m:BigInt):Stream[BigInt] = n #:: fib(m,n+m)
    private val stream = fib(0,1)
    def apply(n:Int) = stream(n)
  }
}

object fibonacciも同様に

となっている。
こちらも大きな数値で呼ばれる程メモリを食いつぶす。

ServletContainer用定義

File:./src/main/webapp/WEB-INF/web.xml

<web-app>
  <servlet>
    <servlet-name>Factorial</servlet-name>
    <servlet-class>Factorial</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Factorial</servlet-name>
    <url-pattern>/fac</url-pattern>
  </servlet-mapping>
  <servlet>
    <servlet-name>Fibonacci</servlet-name>
    <servlet-class>Fibonacci</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Fibonacci</servlet-name>
    <url-pattern>/fib</url-pattern>
  </servlet-mapping>
</web-app>

XML自体の定義とか文字コードとか細かい事は今は気にしない。
HTMLの方でリクエスト先として指定している"/fac"と"/fib"を定義する。

結果

$ sbt "~;container:start; container:reload /"

こう呼び出すと、.scalaや.javaのソース変更を検知して自動的にコンパイル/リロードしてくれる。*1
http://localhost:8080/にアクセス。

フォームに数値を入力すると…?

階乗とフィボナッチ数列のN番目が表示された!
しかし大きな数値を入れると…。

Stream[BigInt]でNまでの階乗とN番目までのフィボナッチ数列を保持してるので、サーバのJVMがキッツキツになる。
限界を超えるとフリーズする。
こういう所でDBを使うべきなんでしょうな。
以上。

*1:Enterキーでsbtを終了できる