welcome

Rhino-for-webapps is a simple bridge technology for enabling development of webapps with javascript in proven containers like Jetty, Tomcat, and of course Google AppEngine. It provides as little as possible and is not itself an application framework, only the lowest common denominator.

Rhino is of course Mozilla's wonderful javascript engine for use in the jvm, giving you both the elegance of the javascript language and E4X along with the leverage of being able to script with java libraries for image manipulation, data storage and analytics, network i/o operations etc.

run

appengine

$> /path/to/appengine/bin/dev_appserver.sh /path/to/rhino-for-webapps/

jetty

$> cd /path/to/rhino-for-webapps/
$> ./server.sh (start|stop|run|restart|check|supervise)

tomcat

$> mv /path/to/rhino-for-webapps /path/to/tomcat/webapps/ROOT
$> /path/to/tomcat/bin/startup.sh

Theres not much to it, no coding required to run a server. rhino-for-webapps is bundled with jetty so you can run it out of the box, or just drop it in your Tomcat webapps/ROOT folder. Google AppEngine is built on Jetty as well. The AppEngine development server is available here .

function (request, response )

This is really the the only interface so it's the best place to start to understand what a light weight bridge to javascript rhino-for-webapps offers. The response just requires a body and a header with a couple of properties, Content-Type and status . And its all just javascript from start to finish.

You can add arbitrary properties to the response header and they are sent. Binary content is also supported and we are adding support for chunking and streaming.

response

function(request, response){
                
    response.body =  "Hello World";

    response.headers = {
        status : 200,
        "Content-Type" : "text/html"
    };
    
}

function( request , response )

request

request.pathTranslated null
request.characterEncoding null
request.servletPath /home/
request.contentType null
request.remoteUser null
request.serverPort 80
request.authType null
request.locale en_US
request.pathInfo null
request.protocol HTTP/1.1
request.contextPath
request.requestURI /home/
request.body
request.requestURL http://rhino-for-webapps.appspot.com/home/
request.requestedSessionId null
request.attributes [object Object]
request.remoteHost 54.167.173.250
request.contentLength -1
request.locales [object Object]
request.serverName rhino-for-webapps.appspot.com
request.parameters [object Object]
request.queryString null
request.cookies null
request.remoteAddr 54.167.173.250
request.method GET
request.headers [object Object]

The request object is ready made, we didn't have to invent a specification , it's been around for ten's of years and isnt going anywhere. Best of all it's already here in reality and supported by your local server farm, and most importantly, by AppEngine which provides an immediate, free, highly scalable cloud that you can deploy to with a single command.

The request object is self documenting, here is the request you made for this page. None of these properties are made up, so you can find plenty of documentation about whats available from the servlet containers. But because we show them here we may as well cover them breifly. Among the simple properties on the request object are a few nested objects also shown below.

request.headers
- available for inspection

request.headers

request.headers["X-AppEngine-Country"] US
request.headers["X-AppEngine-City"] ashburn
request.headers["Accept-Language"] en-us,en-gb,en;q=0.7,*;q=0.3
request.headers["X-AppEngine-Region"] va
request.headers["X-AppEngine-CityLatLong"] 39.043757,-77.487442
request.headers.Host rhino-for-webapps.appspot.com
request.headers.Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
request.headers["User-Agent"] CCBot/2.0 (http://commoncrawl.org/faq/)

request.parameters
- try adding some ?this=is&what=you&get

request.parameters

request.parameters {}

request.locales
- specifies prefered locales

request.locales

request.locales.en_US 0
request.locales.en_GB 1
request.locales.en 2
request.locales["*"] 3

request.attributes
- these are javax standard and/or servlet specific.

request.attributes

request.attributes["javax.servlet.forward.context_path"]
request.attributes["javax.servlet.forward.servlet_path"] /
request.attributes["org.mortbay.jetty.newSessionId"] 19whlgngnvhlz
request.attributes["com.google.apphosting.runtime.jetty.APP_VERSION_REQUEST_ATTR"] rhino-for-webapps/1.340902147508211506
request.attributes["javax.servlet.forward.request_uri"] /

dispatch (request, response)

Dispatch handlers are how requests are connected to the container. They are actually like any other request handler but we distinguish them from other handlers because they are the only handlers that are registered with AppEngine, Jetty, or Tomcat.

You could register every handler with the contianer, but we prefer to write more javascript and less xml when possible, so we like to think of the dispatch handlers as having the role of inspecting request properties and passing the requests on to other request handlers. Here is a simple example ->

dispatch

var dispatch = function(request, response){
    print("Dispatching request");
    try{
        switch(request.requestURI){
            case "/home/":
                Site.home(request, response);
                break;
            case "/examples/":
                Site.examples(request, response);
                break;
            default:
                Site._404(request, response);
        }
    }catch(e){
        print(e);
        Site._500(request, response, e);
    }
    print("Finished dispatch");
    return response
};

shell.js

A shell is what loads required javascript files. No need to load them for every request. The shell is loaded at start-up and you can enable file monitoring for development environments where you don't want to have to restart the server each time you make a change.

There are as many was to use a rhino-for-webapps shell as there are ways to use a javascript shell. It's only responsibility to the server is to expose a dispatch request handler.

shell

try{
    print("Loading shell for rhino-for-webapps website");

    load(
        'site.js',
        'templates/base.js',
        'templates/errors.js',
        'templates/pages.js',
        'dispatcher.js'
    );
    
    print("Loaded shell.");
}catch(e){
    print(  
        "/***********************************\n"+
        " ERROR LOADING SHELL!!\n"+
        "    details \n:"+e.toString()+'\n'+
        "************************************/" 
    );
}

<servlet- mapping >

mapping

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/home/</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/examples/</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/example/*</url-pattern>
</servlet-mapping>

Servlet mappings are how the server knows about dispatchers. Not much to it. You can only add a specific path or every path under it (note the last example contains a * at the end of the url-pattern). The lack of flexiblity in the servlet mapping is one reason why we prefer the dispatcher pattern. The less mappings the better.

It's best to think about mapping in terms of site functionality. You should be able to build most sites with just a few mappings, <one mapping per major site feature.

< servlet >

You con have one or many bridge servlets for javascript. The servlet can be used for many mappings. The dipatcher must be made available by a javascript shell, which is just a javascript context that belongs to the servlet, loaded at startup to make any javascript files you need available.

You can still load files on a per-request basis but in general you should load all of your required libraries when you define you shell.js

servlet

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>claypool.server.Servlet</servlet-class>    
    <init-param>
        <param-name>[option-name]</param-name>
        <param-value>[options-value]s</param-value>
    </init-param>   
    <load-on-startup>1</load-on-startup>
</servlet>

The servlet has a couple options available.

  • script-base Where the serlvet shell will load scripts from (which are specified with a relative url)
  • shell The only javascript file that the servlet will load by default. The context created by that javascript file is reused across servlet requests (so you dont have to reload the javascript every time)
  • dispatch-function The globally scoped function exposed as a result of executing the shell function which will be called each time a servlet is invoked by a mapping

http://server:port /

root

web.xml

<welcome-file-list>
   <welcome-file>WEB-INF/root.jsp</welcome-file>
</welcome-file-list>

WEB-INF/root.jsp

<jsp:forward page="/home/" />

In order to avoid mapping every url to a servlet, (dont forget about static resources like /css/, /images/, /scripts/, etc), we can't directly map the root context (/). Instead we use a simple, standard servlet pattern to allow the container to forward requests to / to the dispatcher of your choice.

More examples