WEB+DB PRESS vol.42
いただきました。ありがとうございます>編集部の方々
年末のうちにいただいていたのですが、出かけていたのでお礼が遅くなってしまってすみません。
特集1「アルゴリズム&データ構造」はとても参考になります。知っている人が読んだら、こんなことも知らずにプログラムを書いているのかと思いそうではありますが、フレームワークの使い方ばかり見ていたり、APIの使い方を見るだけになっているとすっかり忘れてしまうので、復習にはよい記事です。
特集2の「ニコニコ動画」、、、この記事を見て、はじめてそういうものがあることを知りました。こんな面白そうなモノが流行っていたのですね、日本では。(だんだん、日本の流行モノがわからなくなってきています)いろいろ苦労された様子。これからも頑張ってください。
特集3は手堅い感じのRESTの記事ですね。最後のRESTletのサンプルは実際に動かしてみたいと思います。
「エンジニアとして生き抜くための戦略と戦術 記憶力の放棄」はそうだそうだと思いながら読ませていただきました。そもそもこうしてブログを書いているのは"記憶を記録に置き換える"をしているわけで。。。
小さなことですが、とても納得したのは「Alpha Geekに逢いたい」の記事でResig氏が言っている"こういう種類の雑誌は、アメリカには存在しないんです。"の一言。Webサイトしか見ないのかなぁ??アメリカのプログラマは。WEB+DB PRESSは私にとって貴重な雑誌です。
ところで、宮良彩子さんてこういう人だったんですね。すばらしい。
JSR 223 API のJRuby script engineを使うサンプルコード その3
昨年末にJRuby script engineの出力先を変更できないというバグを修正してCVSにコミットしました。CVSにある最新版を使うと、サーブレットの中で実行したRubyスクリプトのputsなどをブラウザに出力できるようになります。例えば、このサーブレットを実行すると
//SimpleServlet.java package arches; import java.io.*; import java.net.*; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.script.SimpleScriptContext; import javax.servlet.*; import javax.servlet.http.*; public class SimpleServlet extends HttpServlet { private ScriptEngine engine; @Override public void init() { ScriptEngineManager manager = new ScriptEngineManager(); engine = manager.getEngineByName("jruby"); } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { ScriptContext context = new SimpleScriptContext(); context.setWriter(out); context.setAttribute("sessionid", request.getSession().getId(), ScriptContext.ENGINE_SCOPE); context.setAttribute("transfered", "できたかな??", ScriptContext.ENGINE_SCOPE); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet SimpleServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h3>Servlet SimpleServlet at " + request.getContextPath() + "</h3>"); out.println("<pre>"); engine.eval("puts 'JRubyから、こんにちは世界'", context); engine.eval("puts $sessionid", context); String path = getServletContext().getRealPath("/") + "WEB-INF/classes/ruby/"; String filename = path + "test.rb"; engine.eval(new FileReader(filename), context); out.println("</pre>"); out.println("</body>"); out.println("</html>"); } catch (ScriptException ex) { throw new ServletException(ex); } finally { out.close(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } public String getServletInfo() { return "Short description"; } }
# test.rb require 'java' include_class 'java.lang.System' message = "おためしJRuby" puts "#{message}, #{$transfered}" System.out.println message + "!!" at_exit { puts "ここでExit" }
ブラウザにこのように表示されます。
(Rubyの中でSystem.out.printlnで出力しているところはブラウザには出力されません。)
このサーブレットは
ScriptContext context = new SimpleScriptContext(); context.setWriter(out); context.setAttribute("sessionid", request.getSession().getId(), ScriptContext.ENGINE_SCOPE); context.setAttribute("transfered", "できたかな??", ScriptContext.ENGINE_SCOPE);
のように、自分でScriptContextのインスタンスを作って、ここに必要なインスタンスをkey,valueペアでセットしておいて、evalメソッドの引数で渡していますが、ここが肝心なところです。
JSR223 APIではengine.put(key, value)のようにしてスクリプト実行時に必要なインスタンスを渡す方法もありますが、これはJRubyScriptEngineクラスの親クラスであるjavax.script.AbstractScriptEngine内のcontextに直接セットされるので、サーブレットのようなマルチスレッド環境下では使えません。誰かのクレジットカード番号が自分のブラウザに表示されてしまうようなトラブルにつながります。サーブレットの場合は必ず、自分でcontextを持ちましょう。
ところが、javax.script.Invocable#invokeFunction(), javax.script.Invocable#invokeMeothd()はcontextを渡せません。これらは親クラスのcontextを参照しているだけです。これはAPIの設計ミスです。サーブレットで使う場合は、値をセットするところからinvokeFunction/Method()を実行して、値をリセットするところまでsynchronizedブロックで囲むしかないでしょう。
さて、JSR223 APIではあらかじめコンパイル(parseのこと。スクリプトをコンパイルして.classにするのではない)しておいて、parse無しのevalだけを後で実行できるようになっています。この機能を利用すると、サーブレットのinit()でparseしてしまって、毎回のリクエスト実行時にはevalだけをすればいいという使い方もありです。開発が終わってしまったときにはこちらの方が明らかに早いです。この方法を使う場合には、たとえばこんなサーブレットを作ります。
// EvalTestServlet.java package arches; import java.io.*; import java.net.*; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import javax.script.SimpleScriptContext; import javax.servlet.*; import javax.servlet.http.*; public class EvalTestServlet extends HttpServlet { private ScriptEngine engine; private CompiledScript script1, script2; @Override public void init() throws ServletException { try { ScriptEngineManager manager = new ScriptEngineManager(); engine = manager.getEngineByName("jruby"); String path = getServletContext().getRealPath("/") + "WEB-INF/classes/ruby/"; String filename = path + "testJava.rb"; script1 = ( (Compilable) engine).compile(new FileReader(filename)); filename = path +"arrayTest.rb"; script2 = ( (Compilable) engine).compile(new FileReader(filename)); } catch (ScriptException ex) { throw new ServletException(ex); } catch (FileNotFoundException ex) { throw new ServletException(ex); } } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { ScriptContext context = new SimpleScriptContext(); context.setWriter(out); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet EvalTestServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h3>Servlet EvalTestServlet at " + request.getContextPath () + "</h3>"); out.println("<pre>"); script1.eval(context); script2.eval(context); out.println("</pre>"); out.println("</body>"); out.println("</html>"); } catch (ScriptException ex) { throw new ServletException(ex); } finally { out.close(); } } (略) }
# testJava.rb # from http://jruby.codehaus.org/The+JRuby+Tutorial+Part+1+-+Getting+Started require 'java' include_class 'java.util.TreeSet' set = TreeSet.new set.add "foo" set.add "Bar" set.add "baz" set.each do |v| puts "value: #{v}" end
# arrayTest.rb # from http://pine.fm/LearnToProgram/?Chapter=07 languages = ['English', 'German', 'Ruby'] languages.each do |lang| puts 'I love ' + lang + '!' puts 'Don\'t you?' end puts 'And let\'s hear it for C++!' puts '...'
init()メソッド内で行っているように、CompileScript#compile()メソッドを実行しておけば、あとはコンパイル(parse)されたスクリプトをscript1.eval(context)やscript2.eval(context)のように実行するだけです。実行すると、このように表示されます。
なお、Rubyのスクリプトは全てサーブレットのソースコードと同じディレクトリにrubyという名前のディレクトリを作って、ここに置きました。