Tuesday, September 27, 2011

RedBridge Presentaion Slides

I'll put the links to my presentation slides together here. Since I've written slides using Rails and jQuery instead of PowerPoint or OpenOffice, my slides are not on slideshare.

  • StrangeLoop 2011 - "Embedding Ruby and RubyGems Over RedBridge", http://redbridge-at-strangeloop2011.herokuapp.com/slideshow

  • When you put the cursor on the page, forward/backward arrows will appear on both sides of a main area. If you click the right arrow, you can see next page. Or, you can click bullets on the bottom.
    I used MobilySlider jQuery plugin.


  • RubyConf 2010 - "RubyGems To ALL JVM Languages", http://servletgarden-point.appspot.com/slideshow

  • When you click right and left arrows on the both side of a main area, you can go forward and backward. Also, you can click page numbers.
    I used Sudo Slider jQuery plugin.


  • JRubyKaigi 2010 - "Want To Use Ruby From Java? RedBridge Makes It Pretty Easy!", http://latest.1.servletgarden-point.appspot.com/slideshow

  • Click menus on the left side, then right side of contents will be changed. The right side is an accordion. If you click bottom/head tabs, other pages will show up. Basically, the slide is in English. But, a little amount of Japanese are in.

Sinatra on Scala

I rewrote the Servlet in my former post, Sinatra on RedBridge using Scala. Below is the code:

package chestnut

import java.io.{File, IOException}
import java.util.{ArrayList, List}

import javax.servlet.{ServletConfig, ServletException}
import javax.servlet.annotation.WebServlet
import javax.servlet.http.{HttpServlet, HttpServletRequest => HSReq, HttpServletResponse => HSResp}

import org.jruby.embed.{LocalContextScope, ScriptingContainer => Container}

import scala.collection.JavaConversions._
import scala.collection.mutable.ListBuffer


class HelloSinatra extends HttpServlet() {
private val gemPaths = new ListBuffer[String]
private var config: ServletConfig = _
private val container = new Container(LocalContextScope.THREADSAFE)
private var config_ru_path: String = _

def addGemPaths(gem_path: String) {
val gem_dir = new File(gem_path)
val gems = gem_dir.listFiles
for (gem <- gems) {
val path = gem + "/lib"
gemPaths += path
}
}

override def init(c: ServletConfig) {
config = c
val path = config.getServletContext().getRealPath("/WEB-INF/lib/vendor/jruby/1.8/gems")
addGemPaths(path)
gemPaths += config.getServletContext().getRealPath("/WEB-INF/lib/app")
config_ru_path = config.getServletContext().getRealPath("/WEB-INF/lib/app/config.ru")
}

override def doGet(request: HSReq, response: HSResp) = processHttpRequest(request, response)

override def doPost(request: HSReq, response: HSResp) = processHttpRequest(request, response)

private def processHttpRequest(request: HSReq, response: HSResp) {
container.setLoadPaths(gemPaths)
container.runScriptlet("require 'rubygems'")
container.put("config_ru_path", config_ru_path);
container.put("request", request);
container.put("config", config);
val script =
"""require 'rack'
rack_app, options = Rack::Builder.parse_file config_ru_path
require 'jruby-rack'
require 'rack/handler/servlet'
handler = Rack::Handler::Servlet.new rack_app
request.instance_variable_set(:@context, config.getServletContext)
class << request
def to_io
self.getInputStream.to_io
end
def getScriptName
self.getPathTranslated
end
def context
@context
end
end
handler.call(request)"""
var rack_response = container.runScriptlet(script);
var body : String = container.callMethod(rack_response, "getBody").asInstanceOf[String]
body += "\n\n\nand Hello from Scala Servlet!!"
response.getWriter().print(body);
container.clear();
}

}

Also, you can see the code, https://gist.github.com/1245405.


In the above Scala version, I added a small message to the Scala servlet. When you look at the result on your browser, you'll see two lines from Sinatra and Scala. The first line is from Sinatra, and the second line is from Scala servlet.

Saturday, September 24, 2011

Clojure's PersistentHashMap on JRuby

Clojure is an impressive language. Not just succinct syntax, Clojure has immutable data types for concurrency. Such Clojure's persistent data types might be useful in some cases in other languages. A question is whether we can use Clojure data types from JRuby. The answer is yes. Below is what I tried on irb:

$ rvm jruby-1.6.4
$ irb
jruby-1.6.4 :001 > require 'java'
=> true
jruby-1.6.4 :002 > $CLASSPATH << "/Users/yoko/Tools/clojure-1.3.0"
=> ["file:/Users/yoko/Tools/clojure-1.3.0/"]
jruby-1.6.4 :003 > require 'clojure-1.3.0-slim'
=> true
jruby-1.6.4 :004 > h = {"a" => 100, "b" => 300, "c" => 200}
=> {"a"=>100, "b"=>300, "c"=>200}
jruby-1.6.4 :005 > pmap = Java::clojure.lang.PersistentHashMap.create(h)
=> {"a"=>100, "b"=>300, "c"=>200}
jruby-1.6.4 :006 > pmap.class
=> Java::ClojureLang::PersistentHashMap

OK. I could successfully create Clojure's PersistentHashMap object. PersistentHashMap implements java.util.Map interface, which means the object has all of Ruby's Hash methods.

jruby-1.6.4 :007 > pmap.each {|k, v| puts "#{k} is #{v}"}
a is 100
b is 300
c is 200
=> {"a"=>100, "b"=>300, "c"=>200}
jruby-1.6.4 :008 > pmap.select {|k, v| k > "a"}
=> [["b", 300], ["c", 200]]
jruby-1.6.4 :009 > pmap.has_value?(400)
=> false

PersistenHasMap is immutable. So, when the method tries to change its data, Clojure raises exception:

jruby-1.6.4 :012 > pmap.delete_if {|k, v| k >= "b"}
Java::JavaLang::UnsupportedOperationException:
from clojure.lang.APersistentMap.remove(APersistentMap.java:273)
from org.jruby.java.proxies.MapJavaProxy$RubyHashMap.internalDelete(MapJavaProxy.java:157)
from org.jruby.RubyHash.delete(RubyHash.java:1407)
(snip)

This way, we can use Clojure's immutable data types on JRuby.

Thursday, September 08, 2011

Haml on Clojure Web App

I've wrote a couple of blog posts about making RubyGems work on JVM languages over RedBridge. Clojure is among them. So far, I could successfully make simple examples with DataMapper and UUID RubyGems. This time, I tackled a Clojure web app. The Rubygems to mix in was Haml.


To create a Clojure web app, I used Leiningen (https://github.com/technomancy/leiningen) and Ring (https://github.com/mmcgrana/ring) like I did when I wrote the blog post, JRuby on Heroku via Clojure.


I installed Leiningen, then followed the instruction:
$ lein new helloworld
$ cd helloworld
$ vi project.clj


My project.clj became below in the end:
(defproject hello-world "0.0.1"
:dependencies
[[org.clojure/clojure "1.2.1"]
[org.clojure/clojure-contrib "1.2.0"]
[org.jruby/jruby-complete "1.6.4"]
[ring/ring-jetty-adapter "0.3.9"]])

There were the choices such that [ring "0.3.9"] or others, but this worked well enough. Then, I ran:
$ lein deps

This downloaded all jar archives with dependencies and put them in lib directory. Leiningen deletes all entries in lib directory and downloaded jar archives when project.clj is updated. So, I kept in mind to stay away from lib directory.


Next, I wrote the Clojure web app. I needed to think about was how to load Haml gem. There were two choices, setting a load path and putting a gem to some directory that was listed in a classpath. The former is easier but needs to be careful not to see L/Ruby GEM_HOME that might be already there. So, I chose the latter. For just in case, I also wrote the former way commented out.


I used clojure.contrib.clsaspath to know what directories are in the classpath. As far as I checked, I could use classes and src directories. I chose src, but perhaps, there's no difference between them. I copied haml-3.1.2 and whole stuff under that direcrory from I installed the gem in Ruby way. The very important thing is the name, "haml-3.1.2/lib" should be renamed to something else. Leiningen or some other tool deletes all files under "lib" directory while deploying the app on Heroku. (This never happened on my local env) So, I changed the name from "haml-3.1.2/lib" to "haml-3.1.2/gem" . I really struggled to figure out this fact.


Usually, we write "require 'rubygems'; require 'haml'" , however, I wrote "require 'rubygems'; require 'haml-3.1.2/gem/haml'" in this case. JRuby loads Ruby code from classpath. On the classpath list, I have "src", and 'haml-3.1.2/gem/haml' is a relative path to "src" . The code is below (https://gist.github.com/1205198):

1 (ns demo.gemstoclojure
2 (:use ring.adapter.jetty)
3 (:use clojure.contrib.io)
4 (:use clojure.contrib.classpath))
5
6 (import '(org.jruby.embed ScriptingContainer LocalContextScope))
7 (def c (ScriptingContainer. LocalContextScope/THREADSAFE))
8
9 (println (classpath))
10 (println (pwd))
11
12 ;; Using $LOAD_PATH to load haml gem
13 ;(def gempath [(str (pwd) "/src/haml-3.1.2/gem")])
14 ;(. c setLoadPaths gempath)
15 ;(. c runScriptlet "require 'rubygems'; require 'haml'")
16
17 ;; Using classpath to load haml gem
18 (. c runScriptlet "require 'rubygems'; require 'haml-3.1.2/gem/haml'")
19
20 (def engineclass (. c runScriptlet "Haml::Engine"))
21 (def template
22 "%html
23 %head
24 %title
25 Hello Clojure!
26 %body
27 %h2
28 Hello Clojure from Haml!")
29 (def engine (. c callMethod engineclass "new" template Object))
30
31 (defn app [req]
32 {:status 200
33 :headers {"Content-Type" "text/html"}
34 :body (. c callMethod engine "render" String)})
35
36 (defn -main []
37 (let [port (Integer/parseInt (System/getenv "PORT"))]
38 (run-jetty app {:port port})))

The code above:

  • instantiates ScriptingContainer (line 6-7) (RedBridge!)

  • loads haml gem (line 18)

  • gets Haml::Engine class (line 20)

  • writes haml template (line 21-28)

  • instantiates Haml::Engine with template (line 29)

  • renders haml template (line 34)




Then, I wrote Procfile:

web: lein run -m demo.gemstoclojure



At last, I deployed this app on Heroku, and saw the result:

$ curl http://freezing-autumn-54.herokuapp.com
<html>
<head>
<title>
Hello Clojure!
</title>
</head>
<body>
<h2>
Hello Clojure from Haml!
</h2>
</body>
</html>



Yikes! RubyGems worked on the Clojure web app.

Tuesday, September 06, 2011

Sinatra on RedBridge

Trinidad (http://thinkincode.net/trinidad/), Kirk (https://github.com/strobecorp/kirk), TorqueBox (http://torquebox.org/), mizuno (https://github.com/matadon/mizuno ) and perhaps some more are out there. As you know, those hook up Rails and/or Sinatra app on Java web servers. They are all easy to use tools even for people don't have Java background. On the other hand, there are people who want to write Java Servlet and control Rails/Sinatra apps on a Servlet, like me. :)


In the past, to make Rails apps controllable on Servlet, I've attempted Rails on RedBridge a couple of times. For example, The second step to Rails on RedBridge and Rails on RedBridge, Scaffolded App to Work are. In those attempts, I tried to invoke rack middleware's method, call, directly using RedBrdige. It was possible because Rails' controllers have a "call(env)" method, the one of a rack middleware. The idea was simple; however, it revealed that creating "env" argument was not so simple. In my past attempts, I somehow created "env" argument on Servlet and succeeded to make just a simple app to work. But, for more complicated database models or complicated HTTP requests, that was not enough. Thus, in this attempt, I used JRuby-Rack to create "env", of course, on the Servlet. A web framework is now Sinatra. Since Sinatra is much simpler than Rails, it is easy to try my idea out.


My attempt has done in three steps. The first step is really simple and just checks whether it works. I created a Java Web Application project on Eclipse whose name is Walnut. Java web server is Tomcat 7.0.4. Nothing is special to create the project so far. After I created the project, I put jruby-complete.jar in WEB-INF/lib under the web app directory tree. On Eclipse, the directory is located in WebContent/WEB-INF/lib, which varies on IDEs. Then, set the build path on Eclipse so that jruby-complete.jar is used in both compiling and executing. Before writing a Servlet, I installed gems in the Web application directory tree.

cd WebContent/WEB-INF/lib
mkdir -p jruby/1.8
export GEM_HOME=`pwd`/jruby/1.8
java -jar jruby-complete.jar -S gem install bundler --no-ri --no-rdoc -i jruby/1.8
java -jar jruby-complete.jar -S jruby/1.8/bin/bundle init
vi Gemfile
java -jar jruby-complete.jar -S jruby/1.8/bin/bundle install --path=.

Gemfile is below:

# A sample Gemfile
source "http://rubygems.org"

gem "sinatra"
gem "jruby-rack"


The first Servlet I wrote was below, which can be seen at HelloJRuby.java:

1 package walnut;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.concurrent.ConcurrentHashMap;
9
10 import javax.servlet.ServletConfig;
11 import javax.servlet.ServletException;
12 import javax.servlet.annotation.WebServlet;
13 import javax.servlet.http.HttpServlet;
14 import javax.servlet.http.HttpServletRequest;
15 import javax.servlet.http.HttpServletResponse;
16
17 import org.jruby.embed.LocalContextScope;
18 import org.jruby.embed.ScriptingContainer;
19
20 /**
21 * Servlet implementation class HelloJRuby
22 */
23 @WebServlet("/HelloJRuby")
24 public class HelloJRuby extends HttpServlet {
25 private static final long serialVersionUID = 1L;
26 private List<String> gemPaths;
27 private ServletConfig config;
28 private ScriptingContainer container;
29
30 /**
31 * @see HttpServlet#HttpServlet()
32 */
33 public HelloJRuby() {
34 super();
35 gemPaths = new ArrayList<String>();
36 container = new ScriptingContainer(LocalContextScope.THREADSAFE);
37 }
38
39 private void addGemPaths(String gem_path) {
40 File gem_dir = new File(gem_path);
41 File[] gems = gem_dir.listFiles();
42 for (File gem : gems) {
43 String path = gem + "/lib";
44 gemPaths.add(path);
45 }
46 }
47
48 public void init(ServletConfig config) {
49 this.config = config;
50 String path = config.getServletContext().getRealPath("/WEB-INF/lib/jruby/1.8/gems");
51 addGemPaths(path);
52 }
53
54 /**
55 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
56 */
57 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
58 processHttpRequest(request, response);
59 }
60
61 /**
62 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
63 */
64 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
65 processHttpRequest(request, response);
66 }
67
68 private void processHttpRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
69 container.setLoadPaths(gemPaths);
70 String class_def =
71 "require 'rubygems'\n" +
72 "require 'sinatra/base'\n" +
73 "class MyApp < Sinatra::Base\n" +
74 " get '/' do\n" +
75 " 'Hello from Sinatra'\n" +
76 " end\n" +
77 "end\n" +
78 "MyApp.new";
79 Object myApp = container.runScriptlet(class_def);
80 Map<String, String> minimal_env = new ConcurrentHashMap<String, String>();
81 minimal_env.put("PATH_INFO", "/");
82 minimal_env.put("REQUEST_METHOD", "GET");
83 minimal_env.put("rack.input", "");
84 List rack_response = (List)container.callMethod(myApp, "call", minimal_env, List.class);
85 response.getWriter().print(rack_response.get(2));
86 container.clear();
87 }
88 }

The Sinatra app in HelloJRuby Servlet would be the simplest one with a minimal env argument. What I wanted to test in this Servlet was whether gems were correctly loaded or not. Since a web application must be portable, every path must be relative to a Servlet context. The line 50 gets the path to gems, which is relative to the context, then the paths are saved in a List, "gemPaths" . This gemPaths is set to the ScriptingContainer in line 69. I ran this Servlet on Eclipse and got the string "Hello from Sinatra" on a browser.


The second Servlet uses JRuby-Rack instead of creating rack request directly. This needs a little trick to make JRuby-Rack work. I didn't want to use whole lot of JRuby-Rack but just a part of it to create a rack request on a Servlet. The second Servlet is below, which is also HelloRack.java on Github:

1 package walnut;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import javax.servlet.ServletConfig;
9 import javax.servlet.ServletException;
10 import javax.servlet.annotation.WebServlet;
11 import javax.servlet.http.HttpServlet;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14
15 import org.jruby.embed.LocalContextScope;
16 import org.jruby.embed.ScriptingContainer;
17
18 /**
19 * Servlet implementation class HelloRack
20 */
21 @WebServlet("/HelloRack")
22 public class HelloRack extends HttpServlet {
23 private static final long serialVersionUID = 1L;
24 private List<String> gemPaths;
25 private ServletConfig config;
26 private ScriptingContainer container;
27
28 /**
29 * @see HttpServlet#HttpServlet()
30 */
31 public HelloRack() {
32 super();
33 gemPaths = new ArrayList<String>();
34 container = new ScriptingContainer(LocalContextScope.THREADSAFE);
35 }
36
37 private void addGemPaths(String gem_path) {
38 File gem_dir = new File(gem_path);
39 File[] gems = gem_dir.listFiles();
40 for (File gem : gems) {
41 String path = gem + "/lib";
42 gemPaths.add(path);
43 }
44 }
45
46 public void init(ServletConfig config) {
47 this.config = config;
48 String path = config.getServletContext().getRealPath("/WEB-INF/lib/jruby/1.8/gems");
49 addGemPaths(path);
50 }
51
52 /**
53 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
54 */
55 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
56 processHttpRequest(request, response);
57 }
58
59 /**
60 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
61 */
62 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
63 processHttpRequest(request, response);
64 }
65
66 private void processHttpRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
67 container.setLoadPaths(gemPaths);
68 String class_def =
69 "require 'rubygems'\n" +
70 "require 'sinatra/base'\n" +
71 "class MyApp < Sinatra::Base\n" +
72 " get '/' do\n" +
73 " 'Hello from Sinatra over JRuby-Rack'\n" +
74 " end\n" +
75 "end\n" +
76 "MyApp.new";
77 Object myApp = container.runScriptlet(class_def);
78 container.put("rack_app", myApp);
79 String creates_handler =
80 "require 'jruby-rack'\n" +
81 "require 'rack/handler/servlet'\n" +
82 "Rack::Handler::Servlet.new rack_app";
83 Object handler = container.runScriptlet(creates_handler);
84 container.put("handler", handler);
85 container.put("request", request);
86 container.put("config", config);
87 String calls_app =
88 "request.instance_variable_set(:@context, config.getServletContext)\n" +
89 "class << request\n" +
90 " def to_io\n" +
91 " self.getInputStream.to_io\n" +
92 " end\n" +
93 " def getScriptName\n"+
94 " self.getPathTranslated\n" +
95 " end\n" +
96 " def context\n" +
97 " @context\n" +
98 " end\n" +
99 "end\n" +
100 "handler.call(request)";
101 Object rack_response = container.runScriptlet(calls_app);
102 String body = (String)container.callMethod(rack_response, "getBody", String.class);
103 response.getWriter().print(body);
104 container.clear();
105 }
106 }

Line 79-83 creates Rack Servlet handler. This is a part of JRuby-Rack and nothing special. Following part of line 84-101 is a little hack. Basically, JRuby-Rack uses an instance of HttpServletRequest as an argument of "call" method. However, some methods are lacked. The snippet here adds those methods in Ruby way, dynamically to the instance. The return value at line 101 is an instance of JRuby::Rack::Response type (response.rb). So, I called "getBody" method of the returned object. We can call other methods of JRuby::Rack::Response in the same way. When I ran this Servlet on Eclipse, I got the expected result. JRuby-Rack worked.


The third step is the one leads to a real Sinatra app. In HelloRack Servlet, I wrote whole Sinatra app in the Servlet as a string. Nobody does this to create a real app. So, the third Servlet, HelloSinatraApp, uses config.ru file, below:

require 'my_app'
run MyApp

"my_app.rb" is:

require 'sinatra/base'

class MyApp < Sinatra::Base
get '/' do
'Hello from Sinatra App over JRuby-Rack!'
end
end

I put both config.ru and my_app.rb files in WEB-INF/lib/app directory. This means I need to add WEB-INF/lib/app to a load path. The HelloSinatraApp Servlet is below, or HelloSinatraApp.java on Github:

1 package walnut;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import javax.servlet.ServletConfig;
9 import javax.servlet.ServletException;
10 import javax.servlet.annotation.WebServlet;
11 import javax.servlet.http.HttpServlet;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14
15 import org.jruby.embed.LocalContextScope;
16 import org.jruby.embed.ScriptingContainer;
17
18 /**
19 * Servlet implementation class HelloSinatraApp
20 */
21 @WebServlet("/HelloSinatraApp")
22 public class HelloSinatraApp extends HttpServlet {
23 private static final long serialVersionUID = 1L;
24 private List<String> gemPaths;
25 private ServletConfig config;
26 private ScriptingContainer container;
27 private String config_ru_path;
28
29 /**
30 * @see HttpServlet#HttpServlet()
31 */
32 public HelloSinatraApp() {
33 super();
34 gemPaths = new ArrayList<String>();
35 container = new ScriptingContainer(LocalContextScope.THREADSAFE);
36 }
37
38 private void addGemPaths(String gem_path) {
39 File gem_dir = new File(gem_path);
40 File[] gems = gem_dir.listFiles();
41 for (File gem : gems) {
42 String path = gem + "/lib";
43 gemPaths.add(path);
44 }
45 }
46
47 public void init(ServletConfig config) {
48 this.config = config;
49 String path = config.getServletContext().getRealPath("/WEB-INF/lib/jruby/1.8/gems");
50 addGemPaths(path);
51 gemPaths.add(config.getServletContext().getRealPath("/WEB-INF/lib/app"));
52 config_ru_path = config.getServletContext().getRealPath("/WEB-INF/lib/app/config.ru");
53 }
54
55 /**
56 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
57 */
58 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
59 processHttpRequest(request, response);
60 }
61
62 /**
63 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
64 */
65 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
66 processHttpRequest(request, response);
67 }
68
69 private void processHttpRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
70 container.setLoadPaths(gemPaths);
71 container.runScriptlet("require 'rubygems'");
72 container.put("config_ru_path", config_ru_path);
73 String creates_handler =
74 "require 'rack'\n" +
75 "rack_app, options = Rack::Builder.parse_file config_ru_path\n" +
76 "require 'jruby-rack'\n" +
77 "require 'rack/handler/servlet'\n" +
78 "Rack::Handler::Servlet.new rack_app";
79 Object handler = container.runScriptlet(creates_handler);
80 container.put("handler", handler);
81 container.put("request", request);
82 container.put("config", config);
83 String calls_app =
84 "request.instance_variable_set(:@context, config.getServletContext)\n" +
85 "class << request\n" +
86 " def to_io\n" +
87 " self.getInputStream.to_io\n" +
88 " end\n" +
89 " def getScriptName\n"+
90 " self.getPathTranslated\n" +
91 " end\n" +
92 " def context\n" +
93 " @context\n" +
94 " end\n" +
95 "end\n" +
96 "handler.call(request)";
97 Object rack_response = container.runScriptlet(calls_app);
98 String body = (String)container.callMethod(rack_response, "getBody", String.class);
99 response.getWriter().print(body);
100 container.clear();
101 }
102
103 }

Let's see. The path to WEB-INF/lib/app is added in line 51. The path to config.ru is set in line 52. Line 73-79 creates Sinatra app instance and Rack Servlet handler. Other part is the same as the second Servlet, HelloRack. Again, I got the result I expected from this Servlet. I may write paths to gems and apps in web.xml to make this Servlet configurable.


Like in the above, Sinatra on RadBridge worked! This needs a knowledge of Servlet and Java web application. But, the good side is the app won't choose web servers. The app is an ordinary Java web application and works on any Servlet based web application. If you want to write a Servlet for some reasons, like me, this would be the choice.