Helma Logo
main list history

Version 2 by zumbrunn on 18. February 2009, 20:26

1<img align="right" height="652" width="225" src="/static/files/3194/helma-request8.jpg">

Version 1 by zumbrunn on 18. February 2009, 20:23

1=== Basics: The Helma Request Cycle
3Web application frameworks usually fall into one of two groups: those that hide the transient nature of the HTTP protocol and those that don't. Helma tries to take a sane middle course. It provides access to HTTP headers and values through request and response objects while judiciously adding infrastructure on top of that for automatic <a href="http://helma.server-side-javascript.org/reference/session.html">session</a> and <a href="http://helma.server-side-javascript.org/reference/app.html">application</a> management to maintain server-side state across individual requests.
5Incoming requests are exposed to Helma application code through the <a href="http://helma.server-side-javascript.org/reference/req.html">request object <i>req</i></a>. The request object allows developers to access information about the incoming HTTP request such as form data, cookies, or authentication information.
7Outgoing responses are built by the application code using the <a href="http://helma.server-side-javascript.org/reference/res.html">response object <i>res</i></a>. The response object provides a buffer to which the response is written, as well as several methods to modify and manipulate the response.
9The request and response objects are the foundation on which the Helma render framework is built.
12=== Separating Presentation from Logic
14The first generation of Web frameworks usually provided ways to include dynamic content into static Web pages through the insertion of scripts or other code. While this seemed like a cool idea back than, by now just about everybody knows the problems that come with this approach. Mixing code and layout leads to unreadable, unmaintainable code very quickly. Many frameworks have kept the original way of mixing code and layout for backwards compatibility, while augmenting them with alternative ways of writing Web applications.
16In Helma, the original concept of "server pages" has been completely dumped and replaced with a mechanism that guarantees total separation of application logic and layout. The three basic constituents of this mechanism - Actions, Skins and Macros - are introduced in the next section.
19=== Actions, Skins and Macros
21Helma divides the responsability of serving a request and generating a response among several components.
23<i>Actions</i> are in charge of handling the request. Every request in Helma is mapped to exactly one Action. If the Action does nothing, nothing happens and an empty response is written back to the client. If no Action can be found to handle a particular request, a HTTP 404 Not Found response is generated. Thus, Actions control the whole processing of each request. In the <a href="http://ootips.org/mvc-pattern.html">Model-View-Controller Pattern</a> (MVC), they are the Controller.
25<i>Skins</i> are parts and pieces of layout. They are rendered by Actions and other application code to generate a response. Skins are parts of static layout markup and can contain special <i>Macro Tags</i> that are replaced with dynamic content when the Skin is rendered. In the MVC pattern, Skins constitute the View.
27Finally, <i>Macros</i> are those pieces of application code that expose application date to Skins. Macros serve as bridges between the Skins and the application code.
30=== Enter the Prototypes
32Almost everything is built around the notion of <i>prototypes</i> in Helma. Prototypes are to JavaScript what classes are to Java: they are used to define and bundle data and functionality into application objects. In Helma, prototypes are defined through the subdirectories of an application's directory. When a Helma application starts, it creates a prototype object for every subdirectory in the application directory. The contents of these directories are then processed: the <a href="http://dev.helma.org/wiki/Object-Relational+Mapping/">type.properties</a> file tells Helma how these objects are mapped to relational or embedded databases, and the code is parsed and compiled. When the actual application objects are than accessed, they are bound to these prototype objects in order to behave the way they are meant to behave.
34Helma has a few prototypes that are present in all applications. These are the root, user, hopobject and global prototypes. <a href="http://helma.server-side-javascript.org/reference/HopObject.html">hopobject</a> prototype. The root prototype is the one that describes the object sitting at the website's root. The user prototype is used for objects representing an application's users. The hopobject prototype is, so to say, the prototype's prototype. It can be used to define functionality for all other prototypes inside an application. Finally, the global prototype is used to define functionality that isn't bound to any object. Stuff defined in global is available globally throughout the application. To these predefined prototypes, applications usually add their own domain-specific prototypes.
36Like application-specific code, actions, skins and macros are part of the application's prototypes. Each action, skin or macro resides in the directory of the prototype it belongs to.
38Actions and Macros are basically just JavaScript functions that follow a special naming convention. Functions with a name ending with an '_action' suffix are handled by Helma as actions, while functions with a name ending with '_macro' are handled as Macro. Thus, adding an Action or a Macro is as simple as writing a function for one of the app's prototypes.
40In addition to the standard '_action' suffix, which handles both GET and POST as well as HEAD requests, you can specify actions that will handle specific request methods, such as '_action_get', '_action_post', '_action_put' and '_action_delete'. See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">RFC 2616 section 9</a> for more information about standard HTTP method definitions.
42  function sayHello_action() {
43      this.renderSkin("hello");
44  }
46  function name_macro(param) {
47      return "Fritz";
48  }
52Note that we place the functions in the root prototype so we are able to access them in the web site's root without the need to build any object structure. The name of the file is arbitrary, but it must end with .js in order for Helma to recognize it as JavaScript.
54For actions with the standard '_action' suffix, there is an alternative way of defining them by creating a file with a '.hac' extension and contains the raw body of the action's function. This method of defining actions is sometimes preferred to the method above because it provides a better overview of a prototype's actions from the directory listing. Thus, the sayHello action above could thus also be written as follows:
56    this.renderSkin("hello");
60Note that in this case, the action name is derived from the file name (action name = file name minus '.hac'), so this is not arbitrary in this case.
62Skins are different from actions and macros in that they are not functions. They are simply chunks of parsed text that Helma internally associates with an app's prototypes. The simplest way to associate a skin to a prototype is to add a file with a '.skin' extension to a prototype's directory. The content of this file is then interpreted as the skin's body, and the file's name (minus the .skin extension) as its name.
64As we'll see later on, it is possible to define skins in other places such as other directories or databases. For now we're satisfied with putting the skin files in the prototype's directory, though.
66    <html><body>
67        Hello <% this.name %>!
68    </body></html>
73=== The render framework in action
75Now that we've seen how actions, skins and macros are defined, let's see how they are actually used.
77When Helma starts processing an HTTP request, the first step is to resolve the request URI's path against the application's object tree. Like URIs for static resources, request paths in Helma applications consist of a number of path elements delimited by the slash character ('/'). In contrast to static ressources, however, the path is not resolved against a directory structure on a file system, but against the application's object model, starting at the predefined root object. The rules for how to get from one object to another are usually derived from the prototype's type.properties files, which describe the child collections of a prototype, how they are accessed and where they are stored. Starting with Helma 1.3.1, it is also possible to implement a function getChildElement() that, if defined, is consulted when resolving request paths.
79Resolving request path elements against the application's object model is performed up to the last element in the path. The last element is interpreted as the name of the action to be invoked on the object referred to by the previous path element. Only when an action with the given name does not exist in that object, the last path element is also resolved against the object tree and "main" is assumed as action name. (This is similar to the index.html convention for static content, allowing us to serve pages at /, which is interpreted as action "main" on the root object.)
81Now let's say our simple application lives at <a href="http://somehost/">http://somehost/</a> and a user somewhere types the URL <a href="http://somehost/sayHello">http://somehost/sayHello</a> into the location field of his browser and hits the return key. Helma receives a HTTP request for /sayHello and starts to resolve it at the app's root object. Since the path consists of only one token, Helma immediately tries to interpret that token as action name, looking for an action called "sayHello" in the root protototype. Accidentally, we have defined such an action in the examples above. Helma sets the intrinsic variables in the scripting engine and calls sayHello_function() on the root object. After the action returns, any response that was created during its execution is written back to the client.
83There are two ways for application code to write to the body of a HTTP response. The first is to use the low level functions in the <a href="http://helma.server-side-javascript.org/reference/res.html">response object</a> such as res.write() directly. The other is to call the renderSkin() function to create output from a skin.
85The renderSkin() function is available in several variants. First, it can be called as <a href="http://helma.server-side-javascript.org/reference/global.html#renderSkin">global function</a> or on <a href="http://helma.server-side-javascript.org/reference/HopObject.html#HopObject.renderSkin">individual hopobjects</a>, depending on whether we want to render a global skin or one that is defined on a specific object prototype. Additionally, there is a <a href="http://helma.server-side-javascript.org/reference/HopObject.html#HopObject.renderSkinAsString">renderSkinAsString()</a> function that will return the result of the skin rendering as a string value instead of directly writing it out to the HTTP response buffer. In all cases, the renderSkin() and renderSkinAsString() functions take either the name of a skin or a parsed skin object as first argument, and an parameter object as optional second argument.
87With that in mind, let's look again at the action we defined in the previous section:
89    this.renderSkin("hello");
93The first thing we notice is that we're calling the renderSkin() function on the <i>this</i> object. Since the action is defined in the root prototype, <i>this</i> refers to the root object. Thus, the skin to be rendered will be searched in the root prototype. If we had just written <i>renderSkin()</i>, omitting the <i>this</i>, the global renderSkin() that searches for skins in the global prototype would have been called.
95Mercifully, we did define a skin called "hello" for the root prototype:
97    <html><body>
98        Hello <% this.name %>!
99    </body></html>
103When rendered for the first time, Helma parses a skin, leaving most of the text as is, but marking the parts within <i>&lt;%</i> and <i>%></i> tags as macro tags. These macro tags are then replaced with dynamic content each time the skin is rendered. Our sample skin only contains one macro tag, which simply consists of the text <i>this.name</i>. Macro tag names are similar to functions in JavaScript in that they may contain an object identifier, telling Helma on which object to evaluate the macro. This is called the <i>macro handler</i>. In our case, the macro handler is <i>this</i>, which just like in JavaScript refers to the object on which the skin is being rendered, which is, again, the root object. If the macro name does not contain a dot ('.') character, the macro is interpreted as global macro and evaluated against the global object.
105So <i>&lt;% this.name %></i> is interpreted as a macro "name" on the root object. Helma now checks if there is a function called "name_macro" defined in the root prototype.
107    function name_macro(param) {
108        return "Fritz";
109    }
113Since this is the case, this function is called on the root object and its return value is added to the rendered skin in place of the macro tag.
115This closes our walk through the invocation of /sayHello. The action returns, and the response buffer is written back to the client. The HTTP body that the browser receives looks like this:
117    <html><body>
118      Hello Fritz!
119    </body></html>
121<i><small>Final response of /sayHello</small></i>
124=== Macro Handlers
126In the last section, we saw how to invoke macro functions from macro tags within a skin. Writing macros as functions is useful when some ammount of processing and/or access checks are to be performed. There are cases, however, where all we need is access some properties in the handler objects. Here, writing a macro function for each property that simply returns the property's value is clearly overkill. This is why, in addition to macro function, Helma allows macros to access object properties directly. The macro syntax is the same as for function macros. Properties have to be defined in the type.properties file for HopObjects or be defined as public fields in generic Java objects.
128As we heard, there are two kinds of macros: global macros and macros that are called on or evaluated against a handler object. In our <i>this.name</i> example, we got acquainted with the <i>this</i> macro handler that always refers to the object that is currently rendering the skin. Outside the special case, macro handlers are usually specified by their prototype name and resolved against the request path. This means that a macro handler "Foo" will be resolved to the object with prototype "Foo" in the request path. If this applies to more than one object in the request path, the last (right most) object is chosen. The place where Helma registers objects in the request path as macro handlers is the res.handlers object. See the next section about ways to manipulate the standard macro handlers.
130In addition to the request path objects, there are a number of built-in handlers that Helma always recognizes:
134<dd>The <b>response</b> macro handler is resolved to properties in the <a href="http://helma.server-side-javascript.org/reference/res.html#res.data">res.data</a> object.</dd>
136<dt><b>request</b> </dt>
137<dd>The <b>request</b> macro handler is resolved to properties in the <a href="http://helma.server-side-javascript.org/reference/req.html#req.data">req.data</a> object.</dd>
139<dt> <b>session</b> </dt>
140<dd>The <b>session</b> macro handler is resolved to properties in the <a href="http://helma.server-side-javascript.org/reference/session.html#session.data">session.data</a> object.</dd>
142<dt> <b>param</b> </dt>
143<dd>The <b>param</b> macro handler is resolved to properties in the object that was passed as optional second argument to the renderSkin() or renderSkinAsString() function that triggered the skin rendering</dd>
147=== Macro Attributes
149In all but the most simple cases, it will probably be desirable to be able to pass arguments to a macro function. Helma provides a simple way to specify arguments in macro tags using a simple attribute syntax that is similar to the one for writing attributes in HTML and XML tags. They consist of an attribute name, a '=' character, and the attribute value wrapped in single or double quotes.
151    <% handler.macro option1="foo" option2="bar" %>
153Strictly speaking, the quotes are necessary only if the attribute values contain whitespace characters. However, it is good practice to always have them.
155The macro attributes are passed to the macro function as the first argument, using a JavaScript object with a string property for every defined attribute.
157To free macro writers from drudging routine work, Helma provides a few standard macro attributes that are handled by the skin rendering code itself instead of passing them to the macro function. These predefined attributes are:
160<dt> <b>prefix</b> </dt>
161<dd>If specified, the value of the <b>prefix</b> attribute is prepended to the output of the macro, if (and only if) the macro did output something.</dd>
163<dt> <b>suffix</b> </dt>
164<dd>The <b>suffix</b> attribute works the same as the <b>prefix</b> attribute, only that its value is added at the end of the macro's output.</dd>
166<dt> <b>encoding</b> </dt>
167<dd>The <b>encoding</b> attribute can be used to specify a conversion that will be applied to the macro's output. The following values are recognized:
169<li>"html" to format text for HTML output like if using the <a href="http://helma.server-side-javascript.org/reference/global.html#format">format()</a> function</li>
170<li>"xml" to format text for XML output like if using the <a href="http://helma.server-side-javascript.org/reference/global.html#encodeXml">encodeXml()</a> function</li></li>
171<li>"form" to format text for HTML form value output like if using the <a href="http://helma.server-side-javascript.org/reference/global.html#encodeForm">encodeForm()</a> function</li></li>
172<li>"url" to URL-encode the text like if using the JavaScript escape() function</li></li>
173<li>"all" to fully escape the text for HTML output like if using the <a href="http://helma.server-side-javascript.org/reference/global.html#encode">encode()</a> function</li></li>
176<dt> <b>default</b> </dt>
177<dd>The <b>default</b> attribute can be used to specify a string to write to the response in case the macro itself didn't produce any output.</dd>
181=== Manipulating Macro Handlers with res.handlers
183In cases where no logic is needed to expose application data to the presentation layer, properties of persistent prototypes may be directly accessed as macros. This can be more convenient than defining a separate macro function. The properties must be defined and mapped in the prototype's <a href="http://dev.helma.org/wiki/Object-Relational+Mapping/">type.properties</a> file in order to be accessible as macro.
185Macro Tags are resolved against the prototypes of objects in the request path. That means you can invoke a macro on some object in the request path using a macro tag prototype. The object on which a macro is invoked is called handler object, its prototype name is the handler name. A macro tag without handler name is interpreted as global macro. A handler name of "this" resolves to the same object the macro tag's skin is being rendered on.
187This mechanism of resolving macro tags against the prototype names of objects in the URL path has proven to be simple and powerful. Yet there are cases in which one needs more flexibility. For example, a developer may wish to add a macro handler to the repertoire that is not in the URL's object path. Another scenario would be to add a macro handler for backwards compatibility to keep old skins working after a prototype has been renamed.
189To add this kind of flexibility to macro handlers, the res.handlers object was introduced. res.handlers is a generic JavaScript object. By default, it is initialized for each request to contain the objects in the resolved URL path by their prototype name. For instance, res.handlers.root will usually be a reference to the app's root object.
191By allowing write access to the res.handlers object, developers can easily define how macro tags will be resolved in their application. New macro handlers can be registered by adding a property, and existing macro handlers can be removed, renamed or aliased by deleting and/or adding existing properties.
193One approach which is often useful is to add a mountpoint of the root object as macro handler even though it is not present in the URL path. The following example defines two new macro handlers:
195    res.handlers.security = root.securityManager;
196    res.handlers.feeds = root.feedReaderModule;
198To access a macro on one the handlers defined above, a skin would have to include something like this:
200    <% security.userLevel %>
201    <% feeds.list categories="foo, bar" %>
204=== Advanced Skins: Skin Paths
206As seen in previous sections, the easiest way to define a skin is to add a .skin file to a prototype directory. This solution is pretty sufficient when an app is coded and packed as is. Often, developers wish to allow others to modify or enhance an application's skins. In most cases, it's not a good idea to let these people, which may be site managers or even ordinary users, access the full application directory.
208For this reason, Helma provides a mechanism that make it possible to override the original application skins with skins that reside outside of the application's directory. One is to include file-based skinset repositories that lie outside the origianal application directory. The other is to define internal, database-mapped objects as skinsets. Both are configured by the same simple mechanism: the skinpath property in the response object.
210The res.skinpath property is set to an Array to let Helma know where it should look for skins, and in which order. Helma will find out by itself whether an item in the res.skinpath array is to be interpreted as external directory or internal object. If the array element is a string, it will be interpreted as file system path and thus as file based skinset location. If it is a HopObject, it will be interpreted as internal skinset collection.
212The following example highlights how the skinset is used to tell Helma to look for skins in an external directory (/home/john/helma/skins) first, then in inside an internal, database-mapped object. Only if a skin is not found in either of these places, the corresponding skin in the application directory will be used.
214    // set the skinpath for the current response
215    res.skinpath = new Array("/home/john/helma/skins", root.skinManager);
217Note that both directories as objects used as items in res.skinpath are expected to use the same prototype structure as the application directory. This means that skinset directories must contain subdirectories for each skinset, and HopObject skinsets must have child HopObjects with the prototype name. These per-prototype HopObjects must in turn contain a HopObject property for each skin they contain. The actual skin source text is expected to reside as a property named "skin" in the skin HopObject.
220=== Appendix: typical skinset setup with relational db mapping
222    _children = collection (Skin)
223    _children.accessname = NAME
224    _children.group = PROTOTYPE
228    # db source as defined in db.properties
229    _db = dbsource
230    # table name
231    _table = SKIN
233    # the primary key
234    _id = ID
236    # the prototype this skin belongs to, used in the group-by
237    # option of the SkinManager
238    proto = PROTOTYPE
240    # the skin's name
241    name = NAME
243    # the skin's source text
244    skin = SOURCE