Last updated January 18, 2011 04:56, by qmxme
[[Home|» JRuby Project Wiki Home Page]]<br/>
[[RedBridge|» Embedding JRuby Wiki Page]]
<h1>Embedding JRuby - Servlet Examples</h1>
__TOC__
= Red Bridge =
== Features of Red Bridge ==
See [[RedBridge#Features_of_Red_Bridge|Features of Red Bridge]] section.
== Download ==
See [[RedBridge#Download|Download]] section.
== Getting Started ==
See [[RedBridge#Getting_Started|Getting Started]] section.
== Configurations ==
See [[RedBridge#Configurations|Configurations]] section.
== Code Examples ==
See [[RedBridgeExamples]] page.
== Servlet Examples ==
JRuby Embed API is certainly web application friendly API. However, you should know a couple of programming techniques to make the web applications work well. This section explains how to use API in a servlet.
You can see how these Servlets work at [http://servletgarden-in-red.appspot.com/].
=== Hello World Servlet ===
The first example is a very basic Hello World Servlet. This servlet just evals "puts \"Hello JRuby World!\"" and produces a simple string, "Hello JRuby World!," to a web browser.
<pre name="java">
package gae.jruby.sample;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.ScriptingContainer;
public class HelloWorldServlet extends HttpServlet {
private ScriptingContainer container;
@Override
public void init() {
String classpath = getServletContext().getRealPath("/WEB-INF/classes");
List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator));
container =
new ScriptingContainer(LocalContextScope.SINGLETHREAD);
container.getProvider().setLoadPaths(loadPaths);
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloWorldServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>HelloWorldServlet</h3><p>");
synchronized (container) {
container.setWriter(out);
container.runScriptlet("puts \"Hello JRuby World!\"");
}
out.println("</p></body>");
out.println("</html>");
} finally {
out.close();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Shows \"Hello World\"";
}
}</pre>
Look at the init() method from line 19 to 25 of the servlet above. This servlet uses [[Home#Context_Instance_Type| a singlethread model]] to keep Ruby runtime, variables for sharing and output streams. The singlethread model doesn't care about a possible race condition at all, but it works for a simple servlet like this one.
In the line 24, a classpath is set to the ScriptingContainer. This is really important to use Embed API on a web application, especially, on Google App Engine. If you don't set any classpath, Embed API sees java.class.path System property and set the system classpath to the ScriptingContainer. What will happen? JRuby will try to load every classes and jar archives on the classpath including GAE's SDK, appengine-tools-api.jar. As a result, you'll get an error something like:
<pre>
:1: library `java' could not be loaded: java.security.AccessControlException: access denied (java.io.FilePermission /Users/yoko/Tools/appengine-java-sdk-1.2.2/lib/appengine-tools-api.jar read) (LoadError)</pre>
You can't go forward anymore. So, please don't forget to set a classpath.
Then look at line 38 through 41. In this example, the singlethread model has been chosen. Thus, users are responsible to manage a race condition, which is common to a web application request handling. Object "container" would be the best instance to lock for a synchronization since "container" is an instance variable of this servlet.
=== Servlet with Ruby Method Invocation ===
The second servlet example uses a method invocation. Each method to be invoked from Java is defined in Ruby script, so the script needs to be evaluated before the method invocation. Once the script is evaled, the methods are stored in Ruby runtime and can be invoked as many times as you want without re-evaluation.
<pre name="java">
package gae.jruby.sample;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;
public class GreetingServlet extends HttpServlet {
private final String filename = "ruby/greetings_instancevars.rb";
private ScriptingContainer container;
@Override
public void init() {
String classpath = getServletContext().getRealPath("/WEB-INF/classes");
List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator));
container = new ScriptingContainer();
container.getProvider().setLoadPaths(loadPaths);
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
container.setWriter(out);
Object receiver = container.runScriptlet(PathType.CLASSPATH, filename);
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet GreetingServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>GreetingServlet</h3><p>");
List<String> people = Arrays.asList(request.getParameterValues("people"));
container.put("@people", people);
container.newObjectAdapter().callMethod(receiver, "sayhi", null);
out.println("<br/>");
container.put("@who", request.getParameter("who"));
out.println(container.newObjectAdapter().callMethod(receiver, "greet", String.class));
out.println("</p></body>");
out.println("</html>");
} finally {
out.close();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Greets people.";
}
}</pre>
<pre name="ruby">
# greetings_instancevars.rb
def greet
message = "How are you? #{@who}."
end
def sayhi
$, = ","
$\ = "\n"
print "Hi", @people
$, = ""
$\ = nil
end
def count
@people.size + 1
end</pre>
When you request the URL:
<pre>/GreetingServlet?who=R2-D2&people=C-3PO,Anakin,Obi-Wan</pre>
the servlet above produces:
<pre>GreetingServlet
Hi,[C-3PO,Anakin,Obi-Wan]
How are you? R2-D2.</pre>
In this servlet, [[Home#Context_Instance_Type| a default local context model, threadsafe]], was chosen, so you don't need to worry about synchronization. However, you should be careful when you use a top level method invocation as in the above servlet. Because Ruby runtime that evaluates the script remembers what methods are there, the runtime should be tied to the same thread that invokes Ruby methods later. Since the threadsafe model uses java.lang.ThreadLocal, each thread in a thread pool of a servlet container will have a single Ruby runtime when the thread handles a HTTP request. Therefore, the script should be evaluated in doGet()/doPost() methods so that the evaluation and invocations will be performed on the same thread.
The servlet above uses Ruby's instance variables to share between Java and Ruby. Using methods' arguments would be another measure to give Java objects to Ruby.
=== Servlet with Interface Implementation by Ruby ===
As you know, JRuby permits us to implement a Java interface by Ruby. In the following example, Java interface, Sortable, is implmeneted by two Ruby scripts, bubble_sort.rb and quick_sort.rb, and chooses the implementation using Servlet's initial parameter.
<pre name="java">
package gae.jruby.sample;
import java.util.List;
public interface Sortable {
public List<Long> sort(List<Integer> list);
public String info();
}</pre>
<pre name="ruby">
# bubble_sort.rb
class BubbleSort
include Java::gae.jruby.sample.Sortable
def info
"Bubble Sort"
end
def sort(list)
array = list.to_a
(array.size - 1).times do |i|
inner_sort array.size - 1 -i, array
end
array
end
def inner_sort(lastindex, array)
lastindex.times do |j|
if array[j+1] < array[j]
tmp = array[j]
array[j] = array[j+1]
array[j+1] = tmp
end
end
end
end
BubbleSort.new</pre>
<pre name="ruby">
# quick_sort.rb
class QuickSort
include Java::gae.jruby.sample.Sortable
def info
"Quick Sort"
end
def sort(array)
inner_sort array
end
def inner_sort(array)
if array.size <= 1
return array
end
middle = average array
lows, highs = devide middle, array
inner_sort(lows) + inner_sort(highs)
end
def average(array)
sum = 0
array.size.times do |i|
sum += array[i]
end
sum / array.size
end
def devide (middle, array)
lows = Array.new
highs = Array.new
array.size.times do |i|
if array[i] <= middle
lows << array[i]
else
highs << array[i]
end
end
return lows, highs
end
end
QuickSort.new</pre>
<pre name="java">
package gae.jruby.sample;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;
public class SortableServlet extends HttpServlet {
private ScriptingContainer container;
private String filename;
private Sortable sortable;
@Override
public void init() {
String classpath = getServletContext().getRealPath("/WEB-INF/classes");
List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator));
container = new ScriptingContainer();
container.getProvider().setLoadPaths(loadPaths);
filename = getInitParameter("filename");
Object receiver = container.runScriptlet(PathType.CLASSPATH, filename);
sortable = container.getInstance(receiver, Sortable.class);
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
container.setWriter(out);
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet SortableServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>SortableServlet</h3><p>");
List<Integer> numbers =
getIntList(request.getParameter("numbers"));
out.println(request.getParameter("numbers") + "<br/>");
out.println(sortable.info() + "<br/>");
List<Long> sorted = sortable.sort(numbers);
for (Long l : sorted) {
out.print(l + ", ");
}
out.println("</p></body>");
out.println("</html>");
} finally {
out.close();
}
}
private List<Integer> getIntList(String values) {
List<Integer> numbers = new ArrayList();
StringTokenizer st = new StringTokenizer(values, ",");
while (st.hasMoreElements()) {
numbers.add(Integer.parseInt(st.nextToken().trim()));
}
return numbers;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Sorts numbers.";
}
}</pre>
<pre name="xml">
<servlet>
<servlet-name>SortableServlet</servlet-name>
<servlet-class>gae.jruby.sample.SortableServlet</servlet-class>
<init-param>
<param-name>filename</param-name>
<param-value>ruby/bubble_sort.rb</param-value>
</init-param>
</servlet>
</pre>
When bubble_sort.rb is specified in param-value field, you'll get the outputs below for a request, /SortableServlet?numbers=18,6,9,1,4,15,12,5,20,7,11.
<pre>SortableServlet
18,6,9,1,4,15,12,5,20,7,11
Bubble Sort
1, 4, 5, 6, 7, 9, 11, 12, 15, 18, 20,
</pre>
When quick_sort is set in the param-value field, outputs for the same request will be:
<pre>
SortableServlet
18,6,9,1,4,15,12,5,20,7,11
Quick Sort
1, 4, 5, 6, 7, 9, 11, 12, 15, 18, 20,</pre>
This works as if Ruby code is injected into the Java program. Also, this example well describes the merit of using a Java interface to add flexibility to a single servlet's behavior.
=== Parse Once, Eval Many Times on Servlet ===
Probably, a parse-once-eval-many-times feature is more powerful on Servlet than a standalone application. Since Servlet has a init() method, which is invoked only once when the Servlet is loaded on a web application server. Whereas, the Servlet has a service(doGet()/doPost()) method, which is invoked many times. repeatedly, concurrently to manage HTTP requests. Thus, parsing once in init() method and evaluating many times in service() method will leverage benefit of the feature. Perhaps, users can expect performance improvement since the time for parsing is eliminated in each HTTP request handling.
However, users should be aware that sharing local variables does not work when a script is parsed in init() method and evaluated in service() method with the threadsafe model of Embed API. When the threadsafe model is chosen, Ruby runtime is tied to the thread that is responsible to run each method. In general, Servlet's service() method is executed by a worker thread in a thread pool while init() method is done not by worker thread. To share local variables, exactly the same runtime is needed to both parsing and evaluating. Therefore, sharing local variables is unable under threadsafe model. Instead, Ruby instance and global variables can be shared even though parsing and evaluating are done on different threads. Or, singlethread model of Embed API is avaiable for that purpose.
The following example parses two Ruby scripts in init() method and evals them in doGet()/doPost() methods. One is simple puts, and another one reads yaml file then formats it to output. While parsing yaml file, a builtin, yaml library is used. Two parsed scripts are saved in Servlet's instance variable, message_unit and yaml_unit, which have run() method to evaluate.
To use builtin library in a web application, you need jruby-complete.jar. Builtin library needs jruby.home environment variable to be set correctly, which is done in jruby-complete.jar. However, jruby-complete.jar is too big to deploy on Google App Engine. See [[#How_to_Create_Twin_Jar_Archives_for_GAE|How to Create Twin Jar Archives for GAE]] to create twin jars, jruby-core.jar and jruby-stdlib.jar, by splitting jar-complete.jar.
<pre name="java">
package gae.jruby.sample;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;
import org.jruby.javasupport.JavaEmbedUtils.EvalUnit;
public class ParsenRunServlet extends HttpServlet {
private ScriptingContainer container;
private EvalUnit message_unit;
private EvalUnit yaml_unit;
private String text =
"Trees:\n" +
"- This is a small example to general HTML.\n" +
"- - Quince\n" +
" - flower: Red\n" +
"- - Apple\n" +
" - fruit: Red\n" +
"- - Maple\n" +
" - leaf: Red";
@Override
public void init() {
String classpath = getServletContext().getRealPath("/WEB-INF/classes");
List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator));
container = new ScriptingContainer();
container.getProvider().setLoadPaths(loadPaths);
message_unit = container.parse("puts @message");
String filename = "ruby/yaml_snippet.rb";
yaml_unit = container.parse(PathType.CLASSPATH, filename);
}
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
container.setWriter(out);
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet ParsenRunServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h3>Servlet ParsenRunServlet at " + request.getContextPath() + "</h3><div>");
container.put("@message", "What's up?");
message_unit.run();
container.put("@message", request.getParameter("message"));
message_unit.run();
out.println("</div>");
container.put("@text", text);
yaml_unit.run();
container.put("@text", request.getParameter("text"));
yaml_unit.run();
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Parse-once-eval-many-times Sample";
}
}</pre>
<pre name="ruby">
# yaml_snippet.rb
require 'yaml'
content = YAML::load @text
def format element
case element
when String: print "<div>#{element}</div>"
when Array:
print "<ul>"
element.each do |child|
print "<li>"
format child
print "</li>"
end
puts "</ul>"
when Hash:
element.each do |key, value|
print "<dl><dt>#{key}"
format value
print "</dt></dl>"
end
end
end
content.each do |heading, paragraph|
puts "<h4>#{heading}</h4>"
paragraph.each do |element|
format element
end
end</pre>
=== How to Create Twin Jar Archives for GAE ===
If you want to use JRuby's builtin library, for example yaml, you need jruby-complete.jar included in your war archive. However, jruby-complete.jar is too big to upload to a Google App Engine server, so you need to split jruby-complete.jar up into two jar archives. You can create these twin archives using JRuby's Rakefile of git HEAD codebase.
<pre>
$ git clone git://kenai.com/jruby~main
$ cd jruby~main
$ export JRUBY_HOME=`pwd`
$ PATH=$JRUBY_HOME/bin:$PATH
$ ant
$ gem install rake
$ gem install hoe
$ cd gem
$ rake update
</pre>
Or you can use [http://jruby-embed.kenai.com/tools/build.xml build.xml] and [http://jruby-embed.kenai.com/tools/build.properties build.properties] instead.<br/>
Originally, Ola Bini talked about this in his blog, [http://olabini.com/blog/2009/04/jruby-on-rails-on-google-app-engine/].
Visit [http://servletgarden-in-red.appspot.com/] to see outputs.





