Web Scripting in Pnuts

pnuts.servlet module allows simple and easy servlet programming.

It supports two styles of Web scripting. One is HTML document which scripts are embedded within a special tag, which is similar to JSP and PHP. The other is that some program generates a web contents proactively, as Java servlet and CGI does. In this chapter, we call the former Dynamic Pages, and the latter Servlet Scripts.

1. Preparation

Servlet container

Pnuts servlets require a servlet container which supports Java Servlet API 2.2 or better.

  • http://java.sun.com/products/servlet/industry.html#servers

    Required Files

    Copy the following files to the right place where the servlet container can find. For example, on Tomcat-5.X, copy those files to common/lib/.

    Configuration

    Servlet Class

    Servlet class Description
    pnuts.servlet.PnutsServlet When this class is used, a servlet script is executed for each request.
    pnuts.servlet.DynamicPageServlet When this class is used, a template-based dynamic page is executed for each request.

    Other than those servlet classes, pnuts.servlet.URLRewriteServlet class is used for URL rewriting.

    The Initialization Parameters for Servlet Scripts and Dynamic Pages

    init-param the meaning
    module If one or more comma-separated module names are specified, those modules would be added to the context.
    initialScript If specified, the script is loaded when the servlet is initialized.
    script If specified, the script is executed for each request. Otherwise, the script file that corresponds the URL is used.
    locale The default locale
    • Typically an empty string is specified.
    • Locale information, language, country, and variant, can be specified as a '_' separated string in that order.
    timezone The default timezone
    compile If true (default), scripts are compiled into Java bytecode.
    encoding Character encoding of script files.
    buffering If true (default), contents are buffered and Content-Length is set.
    execute-latest-script If true (default), timestamp of contents are checked and a script is compiled and loaded only if it is updated since last loaded.
    debug If true, the stack trace is appended to the log file when an error occurs.
    isolation If true (default), scripts are executed in an isolated scripting environment.
    workdir The name of the subdirectory in which converted dynamic pages are saved. The same place as .pea file can be specified as ".". If this parameter is not specified, dynamic pages are converted on-memory and not saved to a file.
    error-page If specified, redirect requests to the location when exception is not caught..

    An example of web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
        "http://java.sun.com/dtd/web-app_2_2.dtd">
    <web-app>
        <servlet>
            <servlet-name>pnuts</servlet-name>
            <servlet-class>pnuts.servlet.PnutsServlet</servlet-class>
            <init-param>
                <param-name>execute-latest-script</param-name>
                <param-value>true</param-value>
            </init-param>
            <init-param>
                <param-name>script</param-name>
                <param-value>scripts/index.pnut</param-value>
            </init-param>
            <init-param>
                <param-name>module</param-name>
                <param-value>pnuts.servlet</param-value>
            </init-param>
            <init-param>
                <param-name>compile</param-name>
                <param-value>true</param-value>
            </init-param>
            <init-param>
                <param-name>debug</param-name>
                <param-value>true</param-value>
            </init-param>
            <init-param>
                <param-name>includeLineNo</param-name>
                <param-value>true</param-value>
            </init-param>
            <init-param>
                <param-name>buffering</param-name>
                <param-value>true</param-value>
            </init-param>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>pnuts</servlet-name>
            <url-pattern>*</url-pattern>
        </servlet-mapping>
    </web-app>
    
    

    2. Concepts

    Servlet Scripts

    The programming servlet scripts in Pnuts is basically same as ordinary Pnuts scripts. The only difference is that servlet scripts rely on pnuts.servlet module. Also the rich functionalities of the Java Servlet API are all available to the scripts.

    e.g. test.pnut
    response.setContentType("text/html")
    print("<html><head></head><body>")
    for (i : 1..6) {
        print("Hello, World!!")
    }
    print("</body></html>")
    

    Binary Contents

    The default output stream, to which the messages are printed, is a PrintWriter object. If OutputStream is required, response.getOutputStream() should be used instead of the default output stream.

    e.g. (${TOMCAT}/webapps/test/image.pnut)
    use("pnuts.awt")
    
    response.setContentType("image/png")
    
    import("java.awt.Color")
    im = makeImage(20, 20, function (g) {
       g.setColor(Color::white)
       g.fillRect(0, 0, 20, 20)
       g.setColor(Color::orange)
       g.fillOval(0, 0, 20, 20)
    })
    writeImage(im, "image/png", response.getOutputStream())
    

    Classpath

    The directory where the servlet script is located is added to the application's classpath, i.e. WEB-INF/classes, WEB-INF/lib/*jar.

    Scope Rules

    Scopes for variables in servlet scripting falls into the following scopes.

    Script scope is a name space created for each servlet script specified in a URL. It is shared by requests that designate the same script. So, it is used for storing data which are not changed by requests, and defining functions which are called to construct a response.

    In script scope, the following variable is pre-defined.

    this
    The Servlet object.

    Request scope is a child name space of a script scope, which is created for each request. It is used for storing request-specific data. A servlet script specified in a URL are executed in request scope. Other script files loaded by load() or loadFile() are executed in the parent script scope.

    In request scope, the following variables are defined.

    request
    The ServletRequest object passed to the Servlet.service() method
    response
    The ServletResponse object passed to the Servlet.service() method

    Since request scope is a child packge of the script scope, variables in the script scope are visible from the request scope. But the opposite is not true.

    It is possible to access the request scope from other scope, by using the Package object that requestScope() returns.

    e.g.
    package(requestScope())
    

    It is also possible to access the script scope from the request scope, by using the Package object that rootScope() returns.

    e.g.
    root = rootScope()
    if (root.a == null) root.a = 1 else root.a++
    println(root.a)
    

    Session scope is a name space created by calling getSession() function. It can be shared by not only multiple requests, but also multiple scripts.

    Special Characters

    For HTML or XML templates, care must be taken for special characters such as <, >, and &.

    escape() function is used for sanitizing the input that may include special characters.

    response.setContentType("text/html")
    
    head = "<TABLE BORDER=1>"
    row = template("<TR><TD>%key%</TD><TD>%value%</TD></TR>", "%([A-Za-z]+)%")
    footer = "</TABLE>"
    
    props = System::getProperties()
    m = map()
    
    println(head)
    for (i : props.keys()){
       m.key = escape(i)
       m.value = escape(string(props.get(i)))
       applyTemplate(row, m)()
    }
    println(footer)
    

    Note that URL string embedded in a HTML document should be encoded differently. The following example illustrates how to encode a part of URL with encodeURL().

    templ = template("%link%",  "%([A-Za-z]+)%")
    
    m = map()
    m.link = "http://host/index.pnut?name=" + encodeURL("%&?+")
    
    applyTemplate(templ, m)()
    

    An alternative is to use makeQueryString() function that makes a query string from a Map.

    templ = template("%link%",  "%([A-Za-z]+)%")
    q = map()
    q.name = "%&?+"
    
    m = map()
    m.link = "http://host/index.pnut?" + makeQueryString(q)
    
    applyTemplate(templ, m)()
    

    Dynamic Pages

    Dynamic pages of Pnuts are HTML documents with the special tag , <% and %> . The file name extension is .pea. Any Pnuts expression can be embedded in the special tag.

    Dynamic pages are translated to the equivalent servlet script at the first request. Then the servlet script is executed for each request, unless the original.pea file is updated.

    <%- ... %>Comment
    <%= ... %>Evaluate the expression and embed the result
    <% ... %>Evaluate the expression (does not embed the result)
    <%@ ... %>Directive.
    <%@ include file=".."%> includes a page file.
    <%@ include expr=".."%> includes the result of the script execution (at compile time).
    <%@ escape %> escapes special characters in the following <%=..%> sections.
    <%@ no-escape %> does not escape special characters in the following <%=..%> sections.
    Types of tags
    e.g. test.pea
    <% response.setContentType("text/html") %>
    <% for (i : 1..6) { %>
        <H<%=i%>> <%=i%>: Hello, World!! </H<%=i%>><p>
    <%}%>
    

    Including HTML/PEA file

    The following directive replace itself with the servlet script converted from the file.

    <%@ include file="file" %>
    test.pea
    <% response.setContentType("text/html") %>
    <%@ include file="date.pea" %>
    
    date.pea
    It is <%= date()  %> now.
    

    Embedding Dynamic Contents at Compile-Time

    The following directive replace itself with the result of executing expreesion at compile time.

    <%@ include expr="expression" %>
    test.pea
    This page was compiled at <%@ include expr="Date()" %>.
    

    3. Senario of Use

    3.1 Simple Servlet Scripts

    <servlet-mapping> element in WEB-INF/web.xml specifies which file runs which servlet.

    For example, with the folowing web.xml, .pnut files are mapped to PnutsServlet.

    WEB-INF/web.xml
    <web-app>
        <servlet>
            <servlet-name>pnuts</servlet-name>
            <servlet-class>pnuts.servlet.PnutsServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>pnuts</servlet-name>
            <url-pattern>*.pnut</url-pattern>
        </servlet-mapping>
    </web-app>
    
    When a .pnut file is visited by a web browser, the script is executed and the printed message is displayed.
    index.pnut
    response.setContentType("text/html")
    println("<html><body>")
    println("Hello ", getParameter("name"))
    println("</body></html>")
    

    3.2 Simple Dynamic Pages

    With the folowing web.xml, .pea files are mapped to DynamicPageServlet.

    WEB-INF/web.xml
    <web-app>
        <servlet>
            <servlet-name>dynamicPages</servlet-name>
            <servlet-class>pnuts.servlet.DynamicPageServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>dynamicPages</servlet-name>
            <url-pattern>*.pea</url-pattern>
        </servlet-mapping>
    </web-app>
    
    When a .pea file is visited by a web browser, the template is formatted and displayed.
    hello.pea
    <html>
    <body>
    Hello <%= getParameter("name")%>
    </body>
    </html>
    

    3.3 Servlet Scripts with Separate Actions and Templates

    WEB-INF/web.xml
    <web-app>
        <servlet>
            <servlet-name>pnuts</servlet-name>
            <servlet-class>pnuts.servlet.PnutsServlet</servlet-class>
            <init-param>
                <param-name>initialScript</param-name>
                <param-value>setup.pnut</param-value>
            </init-param>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>pnuts</servlet-name>
            <url-pattern>list/*</url-pattern>
        </servlet-mapping>
    </web-app>
    
    With this web.xml, the following script is executed when the servlet is initialized.
    setup.pnut
    setupActions("actions")
    setupPages("templates")
    

    The setup.pnut compiles the scripts in "actions/" and the templates in "templates/". Once all files are compiled,

    actions/list.pnut
    name = path[1]     // name is defined in the request scope
    render("list")     // format and display 'templates/list.pea'
    
    templates/list.pea
    <html>
      <body>
       Hello <%=escape(name)%>
      </body>
    </html>
    
    index.pnut
    path = requestPath()
    if (size(path) > 1){
       dispatch(path[0])      // executes "actions/something.pnut"
    }
    

    4. URL Rewriting

    Under normal configuration, the URL patterns *.pnut and *.pea are mapped to Servlet Script and Dynamic Page respectively. By rewriting URLs, server-side scripts can be accessed with simpler URL. For example,
    http://localhost/help/encodeURL
    
    http://localhost/help.pnut?function=encodeURL
    
    The former URL can be rewritten to the latter.

    Configuration

    pnuts.servlet.URLRewriteServlet shoule be specified in web.xml. The initial parameters are as follows.
    init-param description
    configuration Configuration File (default: WEB-INF/classes/url_rewrite.conf)
    verbose If true, rewritten URLs are printed to System.err.
    validating If true, configuration file are validated.
    Configuration file should be a XML file that looks like the following. URL that matches the pattern in a rewrite-rule element is replaced by the corresponding replacement string. If no pattern matches or any of the patterns in exclude-pattern elements matches, the URL is used as it is.
    <?xml version="1.0" encoding="UTF-8"?>
    <url-rewrite>
      <rewrite-rule>
        <pattern>Regular Expression</pattern>
        <replacement>Replacement String</replacement>
      </rewrite-rule>
      ...
    
      <exclude-pattern>/scripts/*pnut</exclude-pattern>
      ...
    </url-rewrite>
    

    Here is an example.

    WEB-INF/classes/url_rewrite.conf
    <?xml version="1.0" encoding="UTF-8"?>
    <url-rewrite>
      <rewrite-rule>
        <pattern>[^/]*/help/(\w+)</pattern>
        <replacement>help.pnut?function=$1</replacement>
      </rewrite-rule>
    </url-rewrite>
    
    web.xml
     <web-app>
        ...
        <servlet>
          <servlet-name>url_rewrite</servlet-name>
          <servlet-class>
           pnuts.servlet.URLRewriteServlet
          </servlet-class>
        </servlet>
    
        <servlet-mapping>
            <url-pattern>help*</url-pattern>
            <servlet-name>url_rewrite</servlet-name>
        </servlet-mapping>
        ...