Skip to main content

skip to main content

developerWorks  >  Web development  >

Deploy an application with Cerise Web server

Use Ruby as your programming language to create a simple application

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Koen Vervloesem (koen.vervloesem@myrealbox.com), Journalist, Freelance

01 Feb 2005

This article shows you how to create a guestbook Web application with the Cerise Web server and the Ruby programming language. You'll use RSS 1.0 as the file format for the guestbook entries and XSLT for transforming files to HTML.

Introduction

With a World Wide Web dominated by Java 2 Platform, Enterprise Edition (J2EE) application servers, choosing another Web framework can seem strange or unprofessional. However, there are many viable alternatives on the market. As with operating systems, you don't have to use a specific application server just because everyone else is using it. You have a choice, and choosing the application server that fits the problem is where you should start. In this article, I present a rather unconventional and little-known application server: Cerise. This operating system-independent server offers a flexible, lightweight, and developer-friendly environment for building both simple and complex Web applications.

Cerise is written in the Ruby programming language, just like the applications written on top of it. This proves very useful in that Ruby is a powerful object-oriented language with a clean and concise syntax, perfectly suited for rapid application development.

To show the effectiveness of combining Ruby and Cerise for Web application development, I'll use the example of a guestbook on a Web site. This example represents the basics of most small- and medium-scale Web applications. It shows the processes of form handling, error handling, and reading and writing of XML files.



Back to top


How Cerise applications work

First, let me give an overview of Cerise's possibilities. Cerise ships with its own Web server (not WEBRick, Ruby's standard Web server). Download the latest release and extract it. Then, start Cerise by using the code shown in Listing 1.


Listing 1. Start the Cerise application server
$ ruby server.rb
[2004-11-05 12:01:00 #112]  INFO cerise/0.9 started
[2004-11-05 12:01:00 #112]  INFO ruby 1.8.1/2003-12-25 (powerpc-darwin)

By default, the server listens to HTTP requests on port 8000. Point your browser to http://localhost:8000/examples/ to check out the examples.

Each Web site you deploy on Cerise is an application, which resides in its own subdirectory of cerise/apps. For instance, the examples application has the following directory structure:

  • cerise/apps/examples/
  • cerise/apps/examples/app.cfg (application configuration)
  • cerise/apps/examples/content/ (application content)

The example application demonstrates Cerise's possibilities with a nice presentation of HTML templates, form validating, access control, sessions, and so on.



Back to top


What makes Cerise Cerise

Some important features of Cerise include:

Request handlers. When an HTTP request comes in, the URL path is compared to the base path of each Cerise application (which is defined in the application's app.cfg file). If the requested path is a part of the application's base path (for example, http://localhost:8000/guestbook/guestbook.ahtml), the request and response object are dispatched to the application (Guestbook). The application looks inside its handler map, which maps regular expressions of paths to request handlers. If the request path matches a regular expression, the request and response are forwarded to the handler and the handle(request, response) method is called. In this method you define what happens; typically, you inspect the request url and headers, and then write the appropriate response in the response headers and response body. You can also forward the request to another handler or redirect it to another path. When no handler matches the request, Cerise interprets the request path as a file and searches for a file in the application's content directory. If the file is found, it is served by a FileHandler with the correct MIME type (based on the file extension) in the response header.

Separation of content and code. This is one of Cerise's most important features. Traditional CGI programs contain statements that output pieces of HTML. Therefore, content and code are intermingled like spaghetti. ASP and JSP use the opposite technique in that they generate an HTML document with pieces of code or custom tags. Cerise goes even further to achieve complete separation of content and code; pages served by its TemplateHandler have a template and a custom request handler. The template is a standard HTML page without code or special tags, but some HTML elements are placeholders for the dynamic content. The code resides in the template handler. The handle method instantiates the variables, which are substituted in the HTML placeholders.

Dynamic reconfiguration. When you change an application configuration file, Cerise reloads it. So, you can change the mapping of request handlers, parameters of the template handler, and so forth, all without having to restart the server.

Easy form handling. With most Web applications, processing form input is a very tedious task. You have to write code to check the validity of each user input, display error messages and the given field inputs when the field contains invalid data, convert fields to the proper type, and so on. Cerise does all these things for you. For the error messages, you must include a list with the messages in the HTML template; Cerise doesn't generate the messages in HTML code when errors don't exist. When there are errors, the form is redisplayed automatically with appropriate error messages and the user's input. When the input validates, it is processed and the next page is shown. This all happens with minimal code.

User- and role-based access control. Cerise offers an API for user authentication and access control. The server supports multiple user managers, which can be linked to a specific application. You can transparently define different user managers; for example, one that uses a MySQL database for its user information, or another that uses plain text files. To limit access to specific paths, you can define secure paths of an application. When an HTTP request comes in for a secure path, Cerise automatically checks if the user is logged in. If the user is not logged in, a login page is displayed. Cerise supports two authentication methods: HTTP basic or form-based. To prevent the user name and password from being sent in clear over the network, Cerise should be configured to use SSL. With form-based authentication, you can use a custom login page.

Session management. Cerise offers an API for session managers and has a standard session manager that stores sessions in memory. You can write your own session manager that uses, for example, a database or a flat file, and you can link each application to a specific session manager. When a session is requested, Cerise passes the session ID as a cookie to the client. Each request that comes in is checked for this cookie, and if present, the request is associated with the right session.



Back to top


Constructing the guestbook

Now you are ready to create the guestbook. First, I'll discuss its structure, which will have two Web pages: the guestbook itself and a form where visitors can add an entry to the guestbook. You have to store the guestbook items in a file, so I'll store it in an RSS 1.0 file. Then, you define an XSL stylesheet to transform the RSS file into an HTML fragment. Because you separate the content and code of each Web page, you have the following six files in the application's content directory:

  • guestbook.rb: The code to open the RSS file and transform it into HTML
  • guestbook.ahtml: The guestbook's HTML template, in which you will insert the RSS file's HTML fragment
  • guestbookform.rb: The form handling code for adding a guestbook item
  • guestbookform.ahtml: The HTML template of the form used to add a guestbook item
  • guestbook.rss: The RSS file with the guestbook items
  • guestbook2html.xsl: The XSLT stylesheet transforming the RSS file into an HTML fragment

We don't have a CSS file because we just want to show how to program the guestbook application's functionality. The advantage of storing the guestbook in RSS is that the guestbook's Webmaster (and other visitors) can watch the addition of guestbook items in his RSS feed reader. So he will always be warned when someone adds something to his guestbook.

First, make a directory for the application, named "guestbook," under the cerise/apps directory. The app.cfg file, which contains the configuration of the application and the content directory with the six files from above, will reside in this directory. The configuration file will look like Listing 2.


Listing 2. app.cfg

application {
  @name           = "Guestbook"
  @base_path      = "/guestbook"

  @session_manager   = "session/SessionManager"
  @user_manager      = "auth/UserManager"

  @handler_map    = {
    /\.ahtml/ => Cerise::TemplateHandler
  }

  @default_page  = "guestbook.ahtml"

  @parameters[TemplateHandler] = {
    :use_module => Guestbook,
    :id_attribute => "cid",
    :delete_id => true,
  }
}

You define the application's base path as "/guestbook," so that when you point your browser to http://localhost:8000/guestbook, the request is forwarded to this application. The handler map says that Cerise's TemplateHandler will process all .ahtml files. The default page is guestbook.ahtml. Then, in the parameters of TemplateHandler, you specify that you will use the attribute cid for placeholders and delete the id afterwards. An example of this is shown later in the article.

Let's look at the code in detail.

The RSS file

The RSS file with the guestbook's content will look something like Listing 3.


Listing 3. guestbook.rss
<?xml version='1.0' encoding='ISO-8859-15'?>
<rdf:RDF xmlns:blog='http://purl.org/net/rssmodules/blogcomments/' 
         xmlns:dc='http://purl.org/dc/elements/1.1/' 
         xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' 
         xmlns='http://purl.org/rss/1.0/'>
  <channel rdf:about='http://localhost:8000/guestbook/guestbook.rss'>
    <title>Guestbook</title>
    <description>Write here your comment in our guestbook.</description>
    <link>http://localhost:8000/guestbook/guestbook.ahtml</link>
    <language>en</language>
    <dc:date>2004-08-23</dc:date>
    <dc:creator>Koen Vervloesem</dc:creator>
    <dc:rights>Copyright © 2004 Koen Vervloesem</dc:rights>
    <items>
      <rdf:Seq>
        <rdf:li rdf:resource='http://localhost:8000/guestbook/guestbook..ahtml#comment2'/>
        <rdf:li rdf:resource='http://localhost:8000/guestbook/guestbook..ahtml#comment1'/>
      </rdf:Seq>
    </items>
  </channel>
  <item rdf:about='http://localhost:8000/guestbook/guestbook.ahtml#comment2'>
    <title>Welcome</title>
    <link>http://localhost:8000/guestbook/guestbook.ahtml#comment2</link>
    <blog:comments>
      <blog:comment>
        <blog:name>Koen Vervloesem</blog:name>
        <blog:email rdf:resource='mailto:koen.vervloesem@myrealbox.com'/>
        <blog:url rdf:resource='http://koan.fab4.be'/>
        <blog:date>Thu Dec 16 23:58:55 CET 2004</blog:date>
        <blog:body>Welcome at my guestbook.</blog:body>
      </blog:comment>
    </blog:comments>
  </item>
  <item rdf:about='http://localhost:8000/guestbook/guestbook.ahtml#comment1'>
    <title>Test</title>
    <link>http://localhost:8000/guestbook/guestbook.ahtml#comment1</link>
    <blog:comments>
      <blog:comment>
        <blog:name>koan</blog:name>
        <blog:email rdf:resource='mailto:koen.vervloesem@myrealbox.com'/>
        <blog:url/>
        <blog:date>Sat Oct 02 18:12:44 CEST 2004</blog:date>
        <blog:body>test</blog:body>
      </blog:comment>
    </blog:comments>
  </item>
</rdf:RDF>

Each guestbook entry is an item in the RSS file. I use the blog RSS module, which defines all the information I need: the name, the visitor's e-mail address and url, the date, and the body text. Every time someone submits a guestbook entry, this information is added as an item element to the RSS file.



Back to top


Defining the XSLT stylesheet

Now you define an XSLT stylesheet to convert the RSS file into an HTML fragment, as shown in Listing 4.


Listing 4. guestbook2html.xsl
<?xml version='1.0' encoding='ISO-8859-15'?>
<xsl:stylesheet
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xsl:version="1.0"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:rss="http://purl.org/rss/1.0/"
     xmlns:blog="http://purl.org/net/rssmodules/blogcomments/"
     exclude-result-prefixes="rdf rss blog">

  <xsl:output method="html"/>

  <xsl:template match="/">
    <xsl:apply-templates select="rdf:RDF/rss:item"/>
  </xsl:template>

  <xsl:template match="rss:item">
    <xsl:variable name="guestbookemail" 
       select="blog:comments/blog:comment/blog:email/@rdf:resource"/>
    <xsl:variable name="guestbookurl" 
       select="blog:comments/blog:comment/blog:url/@rdf:resource"/>
    <div class="guestbookitem">
      <a name="#{substring-after(rss:link, '#')}"/>
      <div class="guestbooktitle">
      <xsl:value-of select="rss:title"/>
      </div>
      <div class="guestbooktext">
      <xsl:value-of select="blog:comments/blog:comment/blog:body"/>
      </div>
       <div class="guestbookdate">
       <xsl:value-of select="blog:comments/blog:comment/blog:date"/>
       </div>
       <div class="guestbookname">
       <xsl:value-of select="blog:comments/blog:comment/blog:name"/>
       </div>
       <div class="guestbookemail">
       <a href="{$guestbookemail}">
       <xsl:value-of select="substring-after($guestbookemail, 'mailto:')"/></a>
       </div>
       <div class="guestbookurl"><a href="{$guestbookurl}">
       <xsl:value-of select="$guestbookurl"/></a></div>
       </div>
  </xsl:template>

</xsl:stylesheet>

This stylesheet transforms each item element of the RSS file. The element is wrapped in an HTML div element of class guestbookitem. All guestbook entry elements are wrapped in div elements of specific classes: guestbooktitle, guestbooktext, and so forth. You can use this in a CSS file to specify the HTML layout. The e-mail and url are wrapped in an anchor (a) element; in the case of an e-mail address, mailto: precedes the href attribute. For my example RSS file, this stylesheet gives the output shown in Listing 5.


Listing 5. Example HTML fragment
<div class="guestbookitem">
<a name="#comment2"></a><div class="guestbooktitle">Welcome</div>
<div class="guestbooktext">Welcome at my guestbook</div>
<div class="guestbookdate">Thu Dec 16 23:58:55 CET 2004</div>
<div class="guestbookname">Koen Vervloesem</div>
<div class="guestbookemail">
<a href="mailto:koen.vervloesem@myrealbox.com">koen.vervloesem@myrealbox.com</a>
</div>
<div class="guestbookurl"><a href="http://koan.fab4.be">http://koan.fab4.be</a></div>
</div><div class="guestbookitem">
<a name="#comment1"></a><div class="guestbooktitle">Test</div>
<div class="guestbooktext">test</div>
<div class="guestbookdate">Sat Oct 02 18:12:44 CEST 2004</div>
<div class="guestbookname">koan</div>
<div class="guestbookemail">
<a href="mailto:koen.vervloesem@myrealbox.com">koen.vervloesem@myrealbox.com</a>
</div>
<div class="guestbookurl"><a href=""></a></div>
</div>



Back to top


Defining the guestbook HTML template

Now, I define a simple HTML template for the guestbook page, as shown in Listing 6.


Listing 6. guestbook.ahtml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Guestbook</title> 
  </head>
  <body>
    <h1>Guestbook</h1>
    <p>Welcome to our guestbook. You can <a href="guestbookform.ahtml">add</a> 
a comment in our guestbook if you wish.</p>

    <div cid="guestbooklist"/>

  </body>
</html>

Of course, this is a simple page. In a real situation, the page won't be as simple and will have, for example, a CSS file associated with it. But the important case here is the line <div cid="guestbooklist"/>. I specify an empty div element with an attribute cid, which has the value guestbooklist. You'll understand how to use this when you look at the code associated with this template. You see that this divider acts as a placeholder for the guestbook data.



Back to top


Writing the Ruby code for the guestbook

You can now write the code associated with the guestbook HTML template, as shown in Listing 7.


Listing 7. guestbook.rb
require 'xml/libxml'
require 'xml/libxslt'

module Guestbook

  class Guestbook < Cerise::RequestHandler

    def handle(request, response)
      xslt = XML::XSLT.file(application.get_file('guestbook2html.xsl'))
      xp = XML::Parser.new
      xp.string = File.new(application.get_file('guestbook.rss')).readlines.join
      xslt.doc = xp.parse
      output = xslt.parse
      output.apply
      { :guestbooklist => output.to_s}
    end
  end

end

The require statements say libxml and libxslt are used to process the RSS file. Then, you define a class, Guestbook, in the module Guestbook. All HTML pages of my application have a class associated with them. The class method handle executes when Cerise receives a request for the page. The class's name is the same as the name of the HTML page, but is capitalized. So, when you point your browser to guestbook.ahtml, the request is forwarded to the handle method of the Guestbook class.

What happens in the handle method? I load the XSLT stylesheet, then the RSS file, and then I transform the RSS file with the stylesheet. The handle method returns the stylesheet's output (the HTML fragment) in the guestbooklist variable.

Now look back at the line <div cid="guestbooklist"/> in the HTML template file. The value of the cid attribute of this div element is guestbooklist, exactly the name of the variable where the XSL stylesheet's output has been saved. So the div element gets the value of the variable guestbooklist as its content. The attribute cid is removed to get valid HTML (I specified this in app.cfg). The result looks like Listing 8.


Listing 8. The guestbook HTML file with the HTML fragment inserted
<?xml version='1.0' encoding='ISO-8859-1'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Guestbook</title> 
  </head>
  <body>
    <h1>Guestbook</h1>
    <p>Welcome to our guestbook. You can <a 
    href="guestbookform.ahtml">add</a> a comment in our guestbook if you wish.</p>


    <div><div class="guestbookitem">
<a name="#comment2"></a><div class="guestbooktitle">Welcome</div>
<div class="guestbooktext">Welcome at my guestbook</div>
<div class="guestbookdate">Thu Dec 16 23:58:55 CET 2004</div>
<div class="guestbookname">Koen Vervloesem</div>
<div class="guestbookemail">
<a href="mailto:koen.vervloesem@myrealbox.com">koen.vervloesem@myrealbox.com</a>
</div>
<div class="guestbookurl"><a href="http://koan.fab4.be">http://koan.fab4.be</a></div>
</div><div class="guestbookitem">
<a name="#comment1"></a><div class="guestbooktitle">Test</div>

<div class="guestbooktext">test</div>
<div class="guestbookdate">Sat Oct 02 18:12:44 CEST 2004</div>
<div class="guestbookname">koan</div>
<div class="guestbookemail">
<a href="mailto:koen.vervloesem@myrealbox.com">koen.vervloesem@myrealbox.com</a>
</div>
<div class="guestbookurl"><a href=""></a></div>
</div>
</div>

  </body>
</html>

At this point, one part of the guestbook works: When you have guestbook entries in an RSS file, you can convert the file to an HTML page. Now I move on to the other (more elaborate) part: writing new guestbook entries.



Back to top


Creating the guestbook form template

The template file for the guestbook form is shown in Listing 9.


Listing 9. guestbookform.ahtml
<?xml version="1.0" encoding="ISO-8859-15"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Guestbook Form</title>
  </head>
  <body>
    <h1>Write a comment in our guestbook</h1>

          <ul class="errors">
            <li cid="errurl">Url has to begin with http://</li>
            <li cid="guestbookformtitle_missing">You have to enter a title.</li>
            <li cid="guestbookformbody_missing">You have to enter a text.</li>
            <li cid="guestbookformname_missing">You have to enter a name.</li>
          </ul>

          <p>Write here your comment in our guestbook.</p>

          <form method="post" action="guestbookform.ahtml">
            <div class="formrow">
              <label>Title:</label>
              <input type="text" name="guestbookformtitle" value="@guestbookformtitle"/>
            </div>
            <div class="formrow">
              <label>Text:</label>
              <textarea cols="40" rows="8" name="guestbookformbody" 
              value="@guestbookformbody"/>
            </div>
            <div class="formrow">
              <label>Name:</label>
              <input type="text" name="guestbookformname" value="@guestbookformname"/>
            </div>
            <div class="formrow">
              <label>E-mail:</label>
              <input type="text" name="guestbookformemail" value="@guestbookformemail"/>
            </div>
            <div class="formrow">
              <label>Url:</label>
              <input type="text" name="guestbookformurl" value="@guestbookformurl"/>
            </div>
            <input type="submit" name="submit" value="Voeg toe"/>
          </form>

  </body>
</html>

Again, a simple HTML page is created. Note first the list with class errors: It contains list items that give each a description of a possible error. Each list item has a cid attribute that has the error's name as its value. The code that handles this template validates the form and sets the variables of the errors that occur. These error descriptions are shown in HTML, while the others disappear because the variables don't get a value.

Also notice the form element; the action of the form is the page itself: guestbookform.ahtml. When the user enters the information and submits the form, the data is posted and guestbookform.ahtml is requested. This triggers the Guestbookform class and processes the data.

In the form element, you can see that each input has a name and a value. The handler code uses the name, for example, guestbookformtitle. The value attribute contains something like @guestbookformtitle. The entered value replaces this when the page is reloaded (that is, when the input doesn't validate).



Back to top


Writing the Ruby code of the guestbook

The request of the form page is handled by the Ruby code in Listing 10.


Listing 10. Request form code
require 'rexml/document'

module Guestbook

  class Guestbookform < Cerise::FormHandler

    attr_accessor :guestbookformtitle, :guestbookformname, :guestbookformemail, 
    :guestbookformurl, :guestbookformbody

    def form_fields
      @@form_fields
    end

    @@form_fields = {
      "guestbookformtitle" => {

        :type => :String,
        :optional => false
      },
      "guestbookformname" => {
        :type => :String,
        :optional => false
      },
      "guestbookformemail" => {
        :type => :String,
        :optional => true
      },
      "guestbookformurl" => {
        :type => :String,
        :optional => true,
        :rules => [['=~ /^http:\/\//', :errurl]]
      },
      "guestbookformbody" => {
        :type => :String,
        :optional => false
      }
    }

    def submit(request, response)
      rssfile = File.new(application.get_file('guestbook.rss'))
      doc = REXML::Document.new(rssfile)
      rssfile.close
      root = doc.root
      channel = root.elements["channel"]
      items = channel.elements["items/rdf:Seq"]
      numitems = items.elements.size

      link = "http://localhost:8000/guestbook/guestbook.ahtml#comment#{numitems + 1}"
      itemli = REXML::Element.new('rdf:li')
      itemli.add_attribute('rdf:resource', link)
      if numitems == 0
        items.add_element(itemli)
      else
        items.insert_before(items[0], itemli)
      end

      item = REXML::Element.new('item')
      item.add_attribute('rdf:about', link)
      if root.elements.size == 1 #only channel element
        root.add_element(item)
      else
        root.insert_before('/rdf:RDF/item[1]', item)
      end

      item.add_element('title').text = @guestbookformtitle
      item.add_element('link').text = link
      comment = item.add_element('blog:comments').add_element('blog:comment')
      comment.add_element('blog:name').text = @guestbookformname
      comment.add_element('blog:email', {"rdf:resource" => "mailto:#{@guestbookformemail}"})
      comment.add_element('blog:url', {"rdf:resource" => @guestbookformurl})
      comment.add_element('blog:date').text = Time.now
      comment.add_element('blog:body').text = @guestbookformbody
      rssfile = File.new(application.get_file('guestbook.rss'), 'w')
      doc.write(rssfile)
      rssfile.close

      "guestbook.ahtml"
    end
  end

end

This is the heavy part of the guestbook application. First, define accessors for all form inputs: guestbookformtitle, guestbookformname, and so on. These initialize when the user submits the form. Then, define constraints on the form fields; for example, guestbookformtitle is a string and must be completed (optional => false). The e-mail field is optional. The url field is also optional, but when present, it should begin with "http://". All these constraints are checked when the form is submitted. If a constraint is violated, the HTML template shows the corresponding error message.

If the input is validated successfully, the method submit gets called. The instance variables for which you have defined accessors are now instantiated with the entered form inputs. In the submit method, open the RSS file and parse it with the REXML library. Now, insert a new item element with the entered inputs as content. In the element blog:date, set the current time (Time.now). Write the modified RSS file to disk and close it. Finally, load the guestbook HTML page so you see your new entry in the guestbook after you submit a guestbook entry .



Back to top


In conclusion

In this article, you learned how easy it is to deploy a guestbook Web application with the Ruby programming language and the Cerise Web server. The form handling is especially a selling point for Cerise. When you're looking for an alternative to well-known application servers, Cerise is a powerful choice.




Back to top


Download

NameSizeDownload method
wa-cerisesource.zip5 KBHTTP
Information about download methods


Resources



About the author

Author photo of Koen Vervloesem

Koen Vervloesem graduated with a degree in computer science in 2004 and has been writing about IT since 2000. His main interests in IT are XML, Ruby, Web site development, open source, and security. You can contact him at koen.vervloesem@myrealbox.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top