Scripting with servlets [jruby] - part I (Sun Java System WebServer 7.0)

Writing Jruby servlets on Sun Java System WebServer 7.0.

Prior work:

Jruby already has an implementation of servlets by <script>document.write('');</script>Marcin Mielżyński
It has these features:

  1. Loosely based on PyServlet (servlet implementation for jython)
  2. Mimics RailsControllers
  3. Offers three kinds of servlets

                  StatefullServlet that behaves like Rails controller (if instantiated every request)
                  StatelessServlet that behaves like standard stateless Java servlet
                  SessionServlet - is kept transparently in session (is its instance variables are not shared between sessions) 

 How ever it also has these features that I did not like

  1. Too Rails oriented.         
    • Servlets in ruby should be preferably similar to java servlets rather than look like Rails
  2. Does too many things in java.
    • We should do only the basic framework in java and delegate the rest to the ruby world. This way a programmer who
      is more comfortable in ruby will be able to implement any feature that he wants with out being impeded by
      the implementation of our servlet code in java. More over, the code in java generally becomes immutable once it
      gets compiled and added into a jar.

It would be much more nicer to delegate the responsibility of doing these things to the handler in ruby rather than to do it in java.
This would allow the possibility of allowing the programmer to construct different varieties of handler servlets in ruby itself during
the development with out touching the java code (compiling and jar-ing)

Our approach.

Making use of the ScriptServlet developed in the previous entry, we create a simple class that provides necessary initialization
and evaluation primitives.
 

package com.sun.servlet;

import javax.servlet.http.\*;

import org.jruby.\*;
import org.jruby.ast.Node;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.builtin.IRubyObject;

public class RubyServlet extends ScriptServlet {
    public static IRuby jr = Ruby.getDefaultInstance();

    public void initialize(String handler, Object code) throws Exception {
        jr.getLoadService().require("java");
        Node script = jr.parse((String)code, handler, jr.getCurrentContext().getCurrentScope());
        // The script file returns a single proc with arity 1
        ((RubyProc)jr.eval(script)).call(new IRubyObject[] {wrap(this)});
    }

    protected IRubyObject wrap(Object object) {
        IRubyObject result = JavaUtil.convertJavaToRuby(jr, object);
        return jr.getModule("JavaUtilities").callMethod(jr.getCurrentContext(), "wrap", result);
    }

    public void eval(Object fn, HttpServletRequest request,  HttpServletResponse response) {
        if (request == null)
            ((RubyProc)fn).call(new IRubyObject[0]);
        else
            ((RubyProc)fn).call(new IRubyObject[] {wrap(request), wrap(response)});
    }
}

 

Here we use the initialize method to read in the handler specified, and executes it. The handler (which is in ruby) is responsible
for specifying the behavior of our servlet. It specifies which HTTP methods are supported, and how the Request URI is interpreted.

The eval method provides a way for the scripts that are in place for HTTP method processing to be executed by the ScriptServlet.

The Ruby Dimension

Jvm Bindings for jruby.

Jruby allows us to access the methods of a class or instance the same way as that of java, by using the 'dot' notation.
Thus to print something to the stdout, we can use the statement

 System.out.println 'my string'

 with the jruby correctly invoking System.out.println in java.

 Thus our handler can be written as below.

docroot/WEB-INF/code/ruby.rb

proc {|httpservlet|
    do_get = proc {|request,response|
        out = response.getWriter
        response.setContentType "text/html"

        begin
            spath = request.getServletPath
            filename = httpservlet.getServletConfig.getServletContext.getRealPath(spath)
            out.println eval(httpservlet.read(filename))
        rescue Exception => e
            out.println %{<html><body><b>Servlet Error (#{e.message})</b><xmp>
                   #{e.backtrace}
                   </xmp></body></html>}
        end
    }
    httpservlet.add('get', do_get)
    httpservlet.add('post', do_get)
}

 

We return a proc object with an arity 1 to the evaluator. This is done to allow the servlet initializer to pass the
httpservlet object as an arguement. We could also have bound the httpservlet to a global variable.

Inside the larger anonymous proc, we define a second proc for do_get, and bind it to the 'get' and 'post' methods of httpservlet.
these procs take request and response as the arguments. They extract the name of the script and load and evaluate the script
referenced.  The script that gets evaluated has complete access to the variables request, response, out and httpservlet just like
a normal java servlet or a jsp page.Any exceptions are printed to the response output stream. 

A simple ruby script that may be loaded by this handler will look like this.

/docroot/hello.rb 

def myhello
     return %{<html>
<head><title>abc</title></head>
  <body>
   <h1> Hello at #{java.util.Date.new} from #{@request.getServletPath}</h1>
  </body>
 </html>}
end
myhello

Build  Steps.

          A complete webapp is provided here. You can download it and extract the contents to a
directory called 'jruby' inside your installation. In order for the build.xml to work, It should be in
the samples/java/webapps/jruby directory in your installation of webserver.

It also contains the jruby.jar in the WEB-INF/lib, which should be replaced with the latest
jruby.jar if necessary.

Your extracted directory will look like this.

|cd jruby
|find .
./docs
./docs/index.html
./src
./src/build.xml
./src/RubyServlet.java
./src/docroot
./src/docroot/WEB-INF
./src/docroot/WEB-INF/lib
./src/docroot/WEB-INF/lib/jruby.jar
./src/docroot/WEB-INF/web.xml
./src/docroot/WEB-INF/sun-web.xml
./src/docroot/WEB-INF/code
./src/docroot/WEB-INF/code/ruby.rb
./src/docroot/index.html
./src/docroot/hello.rb
./src/ScriptServlet.java
./deploy.tcl


You can run 'ant' from inside the src directory which will create the jruby-webapp.war in the
jruby directory. This war file can be deployed on the webserver using the wadm.

wadm  -u admin -f deploy.tcl

 Once the deployment goes through, you will be able to access the ruby file using the url

http://yourserver:port/jruby/hello.rb
 

Comments:

With JDK 6, JSR-223 (javax.script) API may be used to create script servlets as suggested here. That way it would be possible to change the scripting language [http://scripting.dev.java.net]. JSR-223 supports scripting language independent API to evaluate scripts from a Reader or a String (javax.script.ScriptEngine), bind Java objects as script globals and to call a specific script function/method from Java (javax.script.Invocable).

Posted by A. Sundararajan on January 16, 2007 at 04:43 AM IST #

True, With JSR 223, It will be possible to do that. I will add that also in the series. This entry should still be helpful for using servlets in Jdk 1.5 (Current platform for our Wecontainer.) and for using languages that does not support the JSR 223 interface.

Posted by rahul on January 16, 2007 at 06:58 AM IST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

blue

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today