Quantcast

RubyServlet

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

RubyServlet

Marcin Mielżyński-2
A short while ago I was dabbling with embedding raw JRuby in a servlet
loosely based
on PyServlet from Jython. The servlet is still very simple but allows to
deploy Ruby classes that mimic RailsControllers in a very primitive way
(name of the class and method is being built from pathInfo)
Now three types of RubyServlets are supported:
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)

Just inherit from one of these Ruby classes to get the required
behaviour, eg:

class SomeRuby < StatefullServlet
   
    def meth
       msg = "hello from RubyServlet"

       %{
          if this method returns some string - it will be displayed <br>
          #{msg}
        }
    end

end

and point to:

http://localhost:8080/RubyServlet/ruby/some_ruby/meth/

when using StatefullServlet and SessionServlet there will be three
methods exposed for RubyServlet: request, session and response to
facilitate access to them.
when using StatelessServlet you will have to receive two arguments in
methods eg:

def some_action request,response
    # ....
end

as in standard J2EE servlets


I found this servlet very useful in testing (no Java/JRuby load
overhead, just refresh the page and the class will be reloaded on the
fly based on file modification, JRuby runtime stays the same)

In the small included application, there is almost fully functional toy
jmx console - I've been running it on jboss (jndi is configured for it).

http://localhost:8080/RubyServlet/ruby/jmx_view/

Give it a try if you want.

lopex



class JmxView < StatefullServlet
    env = java.util.Hashtable.new
    env[javax.naming.Context::PROVIDER_URL] = "jnp://127.0.0.1:1099"
    env[javax.naming.Context::INITIAL_CONTEXT_FACTORY] = "org.jnp.interfaces.NamingContextFactory"
    @@srv = javax.naming.InitialContext.new(env).lookup("jmx/invoker/RMIAdaptor")

    def index
        if domain = request["domain"]
            mbeans = @@srv.query_names(javax.management.ObjectName.new(domain+":*"),nil).sort_by{|m|m.key_property_list_string}
            name = request["name"] || mbeans.to_a.first.key_property_list_string
            info = @@srv.get_mbean_info(mbean_name=javax.management.ObjectName.new("#{domain}:#{name}"))

            if operation = request["operation"]
                sig = java.lang.Object[].new(0),java.lang.String[].new(0)
                @@srv.invoke(mbean_name,operation,*sig)
            end

            if attr_name = request["attr_name"] and attr_value = request["attr_value"]
                attr_info = info.attributes.find{|a|a.name == attr_name}

                cls = JavaUtilities.get_proxy_class(attr_info.get_type)

                # add primitive support and paramater list for mbean update
                unless cls.java_class.primitive?
                    val = cls.new(attr_value) rescue (cls.valueOf(attr_value) rescue '?')
                    attr = javax.management.Attribute.new(attr_name,val)
                    @@srv.set_attribute(mbean_name,attr)
                end
            end
        end
       
        %{<html>
            <head>
                <link rel="stylesheet" type="text/css" href="../../table.css" title="Style">
            </head>
            <body>
            <table>
                <tr>
                    <td>Domain</td>
                    <td>
                        <form name="domain_form">
                            <select name="domain" onchange="domain_form.submit()">
                            #{@@srv.domains.sort.map{|d| "<option #{'selected' if domain==d}>#{d}</option>"}}
                            </select>
                        </form>
                    </td>
                </tr>
                <tr><td>Property key list</td>
                    <td>
                        <form name="property_form">
                            <input type="hidden" name="domain" value="#{domain}">
                            <select name="name" onchange="property_form.submit()">
                            #{mbeans.map{|n|n.key_property_list_string}.map{|n| "<option #{'selected' if name==n}>#{n}</option>"} if mbeans}
                            </select>
                        </form>
                    </td>
                </tr>
            </table>
            <table>
                <tr>
                    <th>Name</th>
                    <th>Arguments
                        <table><tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Value</th>
                            <th>Description</th>
                        </tr></table>
                    </th>
                    <th>Description</th>
                </tr>
            #{onum = 0
             info.operations.map do |o|
                onum+=1
                %{<form action='' name="operation#{onum}">
                    <input type="hidden" name="operation" value=#{o.name}>
                    <input type="hidden" name="domain" value=#{domain}>
                    <input type="hidden" name="name" value=#{name}>
                    <tr>
                        <td><a href="javascript:operation#{onum}.submit()">#{o.return_type} #{o.name}()</a></td>
                        <td>
                        #{unless o.signature.length.zero?
                            %{<table>
                                #{o.signature.map do |arg|
                                    %{<tr>
                                        <td>#{arg.name}</td>
                                        <td>#{arg.type}</td>
                                        <td><input value=''></td>
                                        <td>#{arg.description}</td>
                                    </tr>}
                                end rescue puts o}
                            </table>}
                        else ' ' end}
                        </td>
                        <td>#{o.description}</td>
                    </tr>
                </form>}
            end if info rescue puts info}
            </table>
            #{unless info.nil? or info.attributes.length.zero?
                %{<table>
                    <tr>
                        <th>Name</th>
                        <th>Type</th>
                        <th>Access</th>
                        <th>Value</th>
                        <th>Description</th>
                    </tr>
                    #{anum = 0
                     info.attributes.map do |a|
                        anum+=1
                        value = a.readable? ? (@@srv.get_attribute(mbean_name,a.name) rescue $!): ''
                        %{<tr>
                            #{a.writable? ?
                                %{<form action='' name="attribute#{anum}">
                                    <input type="hidden" name="domain" value=#{domain}>
                                    <input type="hidden" name="name" value=#{name}>
                                    <input type="hidden" name="attr_name" value=#{a.name}>
                                    <td><a href="javascript:attribute#{anum}.submit()">#{a.name}</a></td>} :
                                %{<td>#{a.name}</td>}}
                            <td>#{a.type}</td>
                            <td>#{'R' if a.readable?}#{'W' if a.writable?}</td>
                            <td>#{a.writable? ? %{<input name='attr_value' value='#{value}'>} : value}</td>
                            #{%{</form>} if a.writable?}
                            <td>#{a.description}</td>
                        </tr>}
                    end}
                </table>}
            end}
            </body>
        </html>}
    end
end

package org.jruby.servlet;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jruby.IRuby;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyObject;
import org.jruby.RubySymbol;
import org.jruby.ast.Node;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.ValueAccessor;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callback.Callback;

public class RubyServlet extends HttpServlet {

        private static final long serialVersionUID = -6931009055136162945L;

        enum SuperClass {
                StatefullServlet, StatelessServlet, SessionServlet
        }

        static class CacheEntry {
                public long modified = 0;

                // statefull
                public RubyClass servletClass;

                // stateless/session
                public IRubyObject servletObject;

                public SuperClass superClass;

                public String scriptFile;

        }

        private final Hashtable<String, CacheEntry> cache = new Hashtable<String, CacheEntry>();

        private final IRuby runtime = Ruby.getDefaultInstance();

        private boolean proxyClassesSet = false;

        private String rootPath;

        // need to support this
        private boolean reloadable = false;

        public void init(ServletConfig config) throws ServletException {
                super.init(config);

                reloadable = Boolean.valueOf(config.getInitParameter("reloadable"));

                rootPath = getServletContext().getRealPath("/");
                if (!rootPath.endsWith(File.separator))
                        rootPath += File.separator;

                runtime.getLoadService().require("java");
                setupRubyClasses();
                // na for later usage...
                List libPaths = new LinkedList();
                runtime.getLoadService().init(libPaths);

                exposeVariable("$servlet", this);
        }

        @Override
        protected void service(final HttpServletRequest req, final HttpServletResponse res)
                        throws ServletException, IOException {

                String pathInfo = req.getPathInfo();

                if (pathInfo.endsWith("/")) {
                        pathInfo = pathInfo.substring(0, pathInfo.lastIndexOf("/"));
                }

                if (pathInfo.startsWith("/")) {
                        pathInfo = pathInfo.substring(1);
                }

                int first = pathInfo.indexOf("/");
                int last = pathInfo.lastIndexOf("/");

                String clazzName;
                String methodName;
                String fileName;

                if (first == -1) {
                        fileName = clazzName = convertToCamel(pathInfo);
                        methodName = "index";
                } else if (first == last) {
                        fileName = clazzName = convertToCamel(pathInfo.substring(0, first));
                        methodName = pathInfo.substring(first + 1);
                } else {
                        String pathAndName = pathInfo.substring(0, last);
                        clazzName = convertToCamel(pathAndName.substring(pathAndName.lastIndexOf("/") + 1));
                        String path = pathAndName.substring(0, pathAndName.lastIndexOf("/"));
                        fileName = path + "/" + clazzName;
                        // no implicit index method allowed for now
                        methodName = pathInfo.substring(last + 1);
                }

                String sriptPath = rootPath + "/" + fileName + ".rb";

                File scriptFile = new File(sriptPath);

                if (!scriptFile.isFile()) {
                        throw new FileNotFoundException(sriptPath);
                }

                CacheEntry entry = cache.get(fileName);

                if (entry == null) {
                        entry = new CacheEntry();
                }

                if (scriptFile.lastModified() > entry.modified) {
                        synchronized (this) {

                                RubyClass oldClazz = runtime.getClass(clazzName);
                                if (oldClazz != null) {
                                        runtime.getObject().remove_const(
                                                        RubySymbol.newSymbol(runtime, oldClazz.getName()));
                                }

                                FileInputStream fis = new FileInputStream(scriptFile);
                                byte buff[] = new byte[fis.available()];
                                fis.read(buff);
                                fis.close();

                                String src = new String(buff);
                                Node script = runtime.parse(src, fileName, runtime.getCurrentContext()
                                                .getCurrentScope());
                                runtime.eval(script);

                                RubyClass clazz = runtime.getClass(clazzName);

                                if (clazz == null) {
                                        res.getWriter().printf("Servlet class name must match the file name (%s)",
                                                        clazzName);
                                        return;
                                }

                                try {
                                        entry.superClass = SuperClass.valueOf(clazz.getSuperClass().getName());
                                } catch (IllegalArgumentException iae) {
                                        res.getWriter().print("Servlet class must inherit from one of these: ");
                                        StringBuilder builder = new StringBuilder();
                                        for (SuperClass sc : SuperClass.values()) {
                                                builder.append(sc.toString() + " ");
                                        }
                                        res.getWriter().println(builder.toString());
                                        return;
                                }

                                switch (entry.superClass) {
                                case StatelessServlet:
                                        entry.servletObject = clazz.newInstance(IRubyObject.NULL_ARRAY);
                                        break;
                                case SessionServlet:
                                        req.getSession(true).removeAttribute(entry.servletClass.getName());
                                case StatefullServlet:
                                        entry.servletClass = clazz;
                                }

                                cache.put(fileName, entry);
                                entry.modified = scriptFile.lastModified();
                        }
                }

                IRubyObject requestProxy = wrapToProxy(req);
                IRubyObject responseProxy = wrapToProxy(res);

                if (!proxyClassesSet) {
                        RubyClass requestProxyClass = requestProxy.getMetaClass();
                        RubyClass responseProxyClass = responseProxy.getMetaClass();
                        setupProxyClasses(requestProxyClass, responseProxyClass);
                        proxyClassesSet = true;
                }

                IRubyObject result;

                try {
                        switch (entry.superClass) {
                        case StatelessServlet:
                                result = entry.servletObject.callMethod(runtime.getCurrentContext(), methodName,
                                                new IRubyObject[] { requestProxy, responseProxy });
                                break;
                        case StatefullServlet:
                                IRubyObject servletObject = result = entry.servletClass
                                                .newInstance(IRubyObject.NULL_ARRAY);
                                servletObject.setInstanceVariable("@request", requestProxy);
                                servletObject.setInstanceVariable("@response", responseProxy);
                                result = servletObject.callMethod(runtime.getCurrentContext(), methodName);
                                break;
                        case SessionServlet:
                                HttpSession session = req.getSession(true);
                                String servletName = entry.servletClass.getName();
                                servletObject = (IRubyObject) session.getAttribute(servletName);
                                if (servletObject == null) {
                                        servletObject = entry.servletClass.newInstance(IRubyObject.NULL_ARRAY);
                                        session.setAttribute(servletName, servletObject);
                                }
                                servletObject.setInstanceVariable("@request", requestProxy);
                                servletObject.setInstanceVariable("@response", responseProxy);
                                result = servletObject.callMethod(runtime.getCurrentContext(), methodName);
                                break;
                        default:
                                res.getWriter().print("This shouldn't have happened");
                                return;
                        }

                        if (result.isKindOf(runtime.getString())) {
                                res.getWriter().print(result.toString());
                        }
                } catch (RaiseException e) {
                        RubyException re = e.getException();
                        PrintWriter out = res.getWriter();
                        out.println(re.inspect());

                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        re.printBacktrace(new PrintStream(baos));
                        out.println(baos);
                }
        }

        protected IRubyObject wrapToProxy(Object object) {
                IRubyObject result = JavaUtil.convertJavaToRuby(runtime, object);
                return runtime.getModule("JavaUtilities").callMethod(runtime.getCurrentContext(), "wrap",
                                result);
                // return JavaEmbedUtils.javaToRuby(runtime, object);
        }

        protected void exposeVariable(String name, Object object) {
                runtime.getGlobalVariables().define(name, new ValueAccessor(wrapToProxy(object)));
        }

        protected void setupProxyClasses(RubyClass requestProxyClass, RubyClass responseProxyClass) {
                requestProxyClass.defineAlias("[]", "getParameter");
        }

        protected void setupRubyClasses() {

                RubyClass baseClass = runtime.defineClass("RubyServlet", runtime.getObject());

                RubyClass statelessClass = runtime.defineClass(SuperClass.StatelessServlet.toString(),
                                baseClass);

                RubyClass statefullClass = runtime.defineClass(SuperClass.StatefullServlet.toString(),
                                baseClass);

                statefullClass.attr_reader(new RubyObject[] { RubySymbol.newSymbol(runtime, "request") });
                statefullClass.attr_reader(new RubyObject[] { RubySymbol.newSymbol(runtime, "response") });

                statefullClass.defineMethod("session", new Callback() {
                        boolean sessionProxyClassSet = false;

                        public Arity getArity() {
                                return Arity.noArguments();
                        }

                        public IRubyObject execute(IRubyObject self, IRubyObject[] args) {
                                IRubyObject session = self.getInstanceVariable("@session");
                                if (session == null) {
                                        IRubyObject request = self.getInstanceVariable("@request");
                                        // JavaObject javaObject = (JavaObject)
                                        // request.callMethod("java_object");
                                        // HttpServletRequest req = (HttpServletRequest)
                                        // javaObject.getValue();
                                        session = request.callMethod(runtime.getCurrentContext(), "getSession",
                                                        new IRubyObject[] { runtime.getTrue() });
                                        if (!sessionProxyClassSet) {
                                                RubyClass sessionProxyClass = session.getMetaClass();
                                                sessionProxyClass.defineAlias("[]", "getAttribute");
                                                sessionProxyClass.defineAlias("[]=", "setAttribute");
                                                sessionProxyClassSet = true;
                                        }
                                }
                                return session;
                        }
                });

                RubyClass sessionClass = runtime.defineClass(SuperClass.SessionServlet.toString(),
                                statefullClass);
        }

        protected static String convertToCamel(String s) {
                if (s.length() == 0) {
                        return s;
                }

                StringBuilder builder = new StringBuilder();

                builder.append(Character.toUpperCase(s.charAt(0)));

                for (int i = 1; i < s.length(); i++) {
                        char c = s.charAt(i);

                        if (c == '_') {
                                if (i < s.length() - 1) {
                                        builder.append(Character.toUpperCase(s.charAt(++i)));
                                }
                        } else {
                                builder.append(c);
                        }
                }
                return builder.toString();
        }
}

---------------------------------------------------------------------
To unsubscribe from this list please visit:

    http://xircles.codehaus.org/manage_email

RubyServlet.zip (11K) Download Attachment
Loading...