ServletでrenderSnakeを利用する

前回は、Javaプログラムから単純メソッド呼び出しだけで直感的にHTMLコードを生成することができるライブラリ「renderSnake」を紹介した。今回はこれを、サーバサイドでServletと連携させて利用する方法を解説する。

とは言っても、単純にServletのプログラム内でrenderSnakeを使う方法は簡単だ。HttpCanvasクラスからのHTMLコードの出力先を、HttpRequestから取得したWriterオブジェクトに指定すればいいだけだからだ。HTMLの出力部分をRenderableインタフェースを利用してコンポーネント化すれば、Servlet部分のコードはシンプルに記述することができる。

例えば、RenderableをimplementsしたMyComponentというクラスを用意したとする。これを、ServletプログラムのdoGet()やdoPost()メソッドにおけるHTMLの出力に利用する。次のような具合だ。

@WebServlet("/SampleServlet")
public class SampleServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter output = response.getWriter();
    try {
      // renderSnakeを利用してHTMLを生成
      HtmlCanvas html = new HtmlCanvas(output);
      html.render(new MyComponent());
    } finally {
      output.close();
    }
  }
}

RenderSnakeServletを利用してGETリクエストを処理する

上の例はrenderSnakeによるHTML出力をServletプログラムに付け足しただけだが、renderSnakeにはそれ以外にも、より緊密にServletと連携させるための仕組みが用意されている。それがRenderSnakeServletクラスである。このクラスを利用することで、Renderableを呼び出すためのServletプログラムを自前で書かなくても、クライアントに生成したHTMLコードを返すことができるようになる。ただし、このクラスでは日本語を正しく処理することができず文字化けしてしまうので、今後の改善を期待したい。

リクエストの処理の仕組みはGETとPOSTで異なるので、詳細はDeveloper's Guideに掲載されている流れ図を参照していただきたい。

このRenderSnakeServletを利用して、まずはGETリクエストを処理してみよう。最初に、リクエストをRenderSnakeServletで処理するためにweb.xmlには次のように記述する。

<?xml version="1.0" encoding="UTF-8"?>


<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
              http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <servlet>
    <servlet-name>renderSnake</servlet-name>
    <servlet-class>org.rendersnake.servlet.RenderSnakeServlet</servlet-class>
    <init-param>
      <param-name>resolver</param-name>
      <param-value>org.rendersnake.servlet.URIMappingResolver</param-value>
    </init-param>
  </servlet>


  <servlet-mapping>
    <servlet-name>renderSnake</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>


</web-app>

Renderableをimplementsしたクラスとしては、今回は以下のようにフォームからの入力を処理するMessageGetFormクラスを用意する。

package jp.co.mycom.toolde.rendersnake;


import java.io.*;
import javax.servlet.http.HttpServletRequest;
import org.rendersnake.*;
import org.rendersnake.servlet.ServletUtils;


public class MessageGetForm implements Renderable {
  @Override
  public void renderOn(HtmlCanvas html) throws IOException {
    // HttpRequestからパラメータを取得
    HttpServletRequest request = ServletUtils.requestFor(html);
    String username = (String)request.getParameter("username");
    String message = (String)request.getParameter("message");
    if (username == null) {
      username = "";
    }
    if (message == null) {
      message = "";
    }

    // ヘッダを出力
    html.html()
      .head()
        .meta(HtmlAttributesFactory.http_equiv("Content-Type")
                               .content("text/html; charset=UTF-8"))
        .title().write("renderSnakeのサンプル")._title()
      ._head();
    html.h1().write("GETリクエストの利用")._h1();

    // フォーム用のコードを出力
    html.form(HtmlAttributesFactory.id("form")
                  .action(this.getClass().getName()).method("get"))
      .label(HtmlAttributesFactory.for_("username"))
        .write("名前:")
      ._label()
      .input(HtmlAttributesFactory.type("text")
                  .id("username").name("username").value(username))
      .br()
      .label(HtmlAttributesFactory.for_("message"))
        .write("メッセージ:")
      ._label()
      .input(HtmlAttributesFactory.type("text")
                  .id("message").name("message").value(message))
      .br()
      .input(HtmlAttributesFactory.type("submit").value("Send"))
    ._form();

    // メッセージを出力
    html.h2().write(username + " 『" + message + "』")._h2();

    // HTMLタグを閉じる
    html._html();
  }

}

このプログラムは、名前とメッセージを入力する2つのテキストフィールドを持ったフォームを表示し、名前・メッセージが入力された場合にはそれを下部に表示するというものである。リクエストを自分自身で処理するので、フォームの出力部分では、formタグのaction属性に自身のクラス名が指定されるようにしてある。

ポイントはHttpRequestからパラメータを取得する部分だ。renderOn()メソッドに渡されるHtmlCanvasオブジェクトだけでは、リクエストのパラメータを取得することはできない。そこで、ServletUtilsクラスのrequestFor()メソッドを利用してHtmlCanvasオブジェクトから、リクエスト元のHttpServletRequestオブジェクトを取得している。ServletUtilsには、同様にHttpServletResponseを取得するメソッドも用意されている。

さて、このままではまだRenderSnakeServletとMessageGetFormの関連付けが行われていないので、そのための設定ファイルとして「rendersnake-uri-mappings.properties」というファイルを用意する。この設定ファイルは、Servletのソースコードのルートディレクトリに配置する。内容は次のようにする。

get-form.html=jp.co.mycom.toolde.rendersnake.MessageGetForm

この設定(とweb.xmlの設定)によって、get-form.htmlに対するリクエストが、RenderSnakeServletを経由してjp.co.mycom.toolde.rendersnake.MessageGetFormクラスで処理されるようになる。実際にWebサーバにデプロイしてget-form.htmlにアクセスしてみよう。、のように、フォームに名前とメッセージを入力して「Send」ボタンを押すと、それが下部に表示されるはずだ。

名前とメッセージを入力

MessageGetFormで処理されて出力される

RenderSnakeServletを利用してPOSTリクエストを処理する

POSTリクエストを処理したい場合にはFormHandlerインタフェースを利用する。FormHandlerにはhandle()メソッドが定義されており、これがPOSTリクエストが送付された際のハンドラとして動作するようになっている。引数としてHttpServletRequestとHttpServletResponseが渡されるので、これを利用してパラメータの処理も行える。handle()では、最後にHTMLを出力するためのRenderableの実装クラス名を返す。この戻り値のクラスが、クライアントに対するレスポンスを生成する。

以下に、POSTリクエストを処理するクラスの例を示す。renderOn()でクライアントへ送付するHTMLコードの生成を、handle()でフォームからの入力を処理する形式になっている。

package jp.co.mycom.toolde.rendersnake;


import java.io.*;
import javax.servlet.http.*;
import org.rendersnake.*;
import org.rendersnake.servlet.ServletUtils;


public class MessagePostForm implements FormHandler, Renderable {
  public String username = "";
  public String message = "";

  @Override
  public Class<? extends Renderable> handle(HttpServletRequest request, HttpServletResponse response) {
    // HttpSessionを取得(生成)して属性を設定
    HttpSession session = request.getSession();
    session.setAttribute("sUsername", username);
    session.setAttribute("sMessage", message);

    return MessagePostForm.class;
  }


  @Override
  public void renderOn(HtmlCanvas html) throws IOException {
    String aUsername = "";
    String aMessage = "";

    // HttpRequestを取得
    HttpServletRequest request = ServletUtils.requestFor(html);
    // HttpSessionからを取得して属性を取得
    HttpSession session = request.getSession(false);
    if (session != null) {
      aUsername = (String)session.getAttribute("sUsername");
      aMessage = (String)session.getAttribute("sMessage");
    }

    // ヘッダを出力
    html.html()
      .head()
        .meta(HtmlAttributesFactory.http_equiv("Content-Type")
                              .content("text/html; charset=UTF-8"))
        .title().write("renderSnakeのサンプル")._title()
      ._head();
    html.h1().write("POSTリクエストの利用")._h1();

    // フォーム用のコードを出力
    html.form(HtmlAttributesFactory.id("form")
                    .action(this.getClass().getName()).method("post"))
      .label(HtmlAttributesFactory.for_("username"))
        .write("名前:")
      ._label()
      .input(HtmlAttributesFactory.type("text")
                    .id("username").variable("username", aUsername))
      .br()
      .label(HtmlAttributesFactory.for_("message"))
        .write("メッセージ:")
      ._label()
      .input(HtmlAttributesFactory.type("text")
                    .id("message").variable("message", aMessage))
      .br()
      .input(HtmlAttributesFactory.type("submit").value("Send"))
    ._form();

    // メッセージを出力
    html.h2().write(aUsername + " 『" + aMessage + "』")._h2();

    // HTMLタグを閉じる
    html._html();
  }

}

MessagePostFormはフォームから入力された値を保持する2つのフィールドを持っている。input()メソッドの属性指定の部分で、このプログラムではvalue()メソッドの代わりにvariable()というメソッドを使っている。このメソッドは、フォームへの入力値とフィールドの値を関連付けるためのものである。例えば「variable("usetname", "hogehoge")」とすれば、自動的にname属性の値が"username"、value属性の値が"hogehoge"になり、それに加えてhandle()での処理の前にusernameフィールドにvalue属性の値が自動で代入されるようになる。

handle()メソッド側でgetParameter()を使わなくてもパラメータの取得が完了しているのはそのためである。しかし、handle()メソッドでは最後にMessagePostFormにリダイレクトしてしまうため、取得した入力値をページに反映されることができない。そこでセッションを利用してrenderOn()に値が渡るようにしている。

rendersnake-uri-mappings.propertiesには次の設定を追加する。これによってpost-form.htmlへのリクエストがMessagePostFormクラスで処理されるようになる。

post-form.html=jp.co.mycom.toolde.rendersnake.MessagePostForm

完成したら、Webサーバにデプロイして、Webブラウザからpost-form.htmlにアクセスしてみよう。GETの場合と同様に、フォームから入力した名前とメッセージを下部に表示されるはずだ(、)。

名前とメッセージを入力

MessagePostFormのhandle()メソッドで処理され、renderOn()メソッドによってHTMLが生成されて表示される

RenderSnakeServletを使った処理は日本語に対応していないなどの問題があるが、それを差し引いて単にHTMLを生成する部分だけとっても、renderSnakeはシンプルで使い勝手のいいライブラリだ。アプリケーション内のちょっとしたHTMLの生成時に利用すると便利だろう。