![]() |
![]() |
||||||
|
|||||||
Development & IT > Introduction to Web Application Programming posted on 5:01 PM, July 27, 2007
An ExSite Web Application has the usual features associated with web applications, most importantly 2-way interactivity with the web site user, in which data is taken in, and dynamic web pages generated in response. ExSite Web Applications also have some additional features made possible by the ExSite Website Operating System framework:
There are four common types of web applications:
Dynamic Content DriversA Dynamic Content Driver (or DCD) is a way of coding a web application so that it can behave as a dynamic content module and take advantage of ExSite's website operating system framework. They are called dynamic content drivers because:
In end-user documentation, DCDs are often referred to as "plug-ins", "modules", or "web applications". DCDs are written in Perl, the native language of ExSite. There are methods for interfacing DCDs to applications written in other languages, if your existing code is not written in Perl. DCDs are invoked on the public side using content management tags that are inserted into web pages. A single web page can contain DCD tags for multiple web apps. Many DCDs are like "applets", and accomplish small but useful tasks. Many different DCDs may have to work together to build a full page, by dynamically creating page elements such as:
ExSite Dynamic Content Drivers follow the model of Unix device drivers, which is to say that they support three calls:
These three calls into the DCD can be invoked using tags embedded into the raw (pre-published) HTML of the page, as described later on in DCD Calling Conventions. ExSite interacts with the web application only through these three entry points. In particular, information is returned to the viewer only by the write() function. Access control to the application can be concentrated at these points, simplifying your security.
BaseDCD classExSite provides a BaseDCD class that can serve both as a template for writing DCDs, and also as a class that simple DCDs can inherit from to save on redundant code common to many DCDs. In particular, the BaseDCD class includes a generic constructor, new(), and a generic read() method. The generic read() is particularly useful, because it automatically prefetches all QUERY_STRING and POST input, parses it using URL-encoding conventions, and stores the combined results in a hash reference under the {input} key of the DCD object. Furthermore it does this in a neighbourly way so that POST input can be shared with any other DCD that may need to use it. The generic new() method automatically calls read() so that this input is available to the write() method without any additional effort. These generic methods are useful to many basic DCDs, meaning the developer only needs to code a write() method, telling the DCD how to generate content, and optionally an ioctl() method, if the DCD does anything extra or has an adminstrator interface. The method for inheriting from the BaseDCD class is simply to include the following lines near the top of your DCD module: # inherit from BaseDCD class In addition to generic versions of these standard methods, the BaseDCD class also has a few useful utility functions that may come in handy when programming the DCD:
Recursive PagesDynamically-generated web pages can be visited recursively with different results; that is you can return to the same page using a different set of inputs, and end up with a different set of content being displayed. This is no different in principle than a CGI program generating different output with different inputs. In fact, the page generator is a CGI program (page.cgi), so this is exactly what is happening. The important difference is that page.cgi is a clearing house for all of your web applications, so all input and output go through the same program, instead of having separate CGI programs for every function on your site. The default version of a page is viewed using the URL to the published page, if it exists, or by using the page.cgi program to dynamically render it, ie: http://mydomain.com/cgi-bin/page.cgi?_id=42 (This tells ExSite to display page number 42 in its database.) This page can call itself recursively, adding additional data to the URL as needed. For instance: http://mydomain.com/cgi-bin/page.cgi?_id=42&action=view&product_id=295 Using these additional inputs, the DCD embedded in the page may make completely different decisions about what output to return to the page. This process can go on indefinitely, depending on how many variables and values your DCD can respond to. The QUERY_STRING accepted by page.cgi is URL-encoded by convention, and the data parameters and values specified in it are visible to the internals of page.cgi and to all the DCDs that are embedded in that page. For this reason, it helps to have some conventions to help sort out what data belongs to whom. By convention, data intended for page.cgi itself uses parameter names with a preceeding underscore (such as _id in the above URL examples). Data intended for DCDs is named with no preceeding underscore. Creating Recursive LinksInside a DCD it is not obvious which page you are generating content for. (Indeed it is not obvious that you are generating content for an actual page at all.) This can make it tricky to figure out what URL you need to point to when creating recursive links back to the DCD. The relink() and link() functions are invaluable for generating URLs to link back to yourself. &relink(param=>"value"); The relink() function (in the ExSite::Misc library) returns a URL to the current page, but with the given arguments added or modified. For example, if the current URL is /cgi/page.cgi?_id=25¶m=oldvalue, then the above call will return /cgi/page.cgi?_id=25¶m=value. If you want to clear a parameter (ie. remove it from the QUERY_STRING entirely, set its value to undef. Note that the relink() function works for any URL-encoded QUERY_STRING on any CGI program, not just page.cgi. $this->link(param=>"value"); The link() function is similar, but has additional features. For starters, it is a class method inherited from ExSite::BaseDCD. Rather than linking back to the same page, it generates a link back to the DCD. In simple cases, this links back to the same page (since the DCD is embedded in the page), which gives the same behaviour as relink(). However, if the DCD has been configured to be served from a specific page on the site (see interpage services), then the link could jump to another page entirely. Furthermore, link() is sensitive to AJAX/DHTML methods, and will, in certain cases, generate Javascript to dynamically update certain elements of the page, instead of reloading the entire page. Web Application CollisionsSince any number of DCDs can be embedded into a single page, it is normal for DCDs to see input that was not intended for them. DCDs should be programmed to gracefully ignore input that makes no sense, since that input might have been targeted at another web application on the same page. It is also conceivable that two DCDs will see input that makes sense to both of them. It may be the case that the user is only trying to communicate with one of the DCDs, but the consequence is that both will respond, possibly with unexpected results. This is called a DCD collision. To avoid this situation:
Control PanelsThe control panel of an application is the administrator back-end, for configuring and managing the application. Not all applications have or need a control panel; they inform ExSite of the existence of a control panel by responding to the "ControlPanel" ioctl request. If the return value is a string scalar, that string is taken by ExSite to be a URL that will bring up the control panel. If the return value is a code reference, ExSite assumes that is a directly callable routine that will return the control panel HTML in a string. For example: sub ioctl {
If your application's control panel is also a form of content (that is, it can be accessed from one of your site's web pages in addition to the ExSite administrator interface), then you can simply include a hook in the write() method to call the control panel method. (This only works if your control panel method is encoded in the DCD itself; if it is an external CGI program, it cannot be used as content.) Example: # control panel accessible from both admin interface and the site itself Porting Existing CGI Web ApplicationsA quick-and-dirty method for bringing CGI programs into ExSite is to simply run the CGI program from within the DCD, capture the content, and pass it back to ExSite for further processing. Example: # CGI wrapper DCD If your web application consists of Perl CGI programs, then it is relatively easy to completely absorb them into ExSite. In the simple case, you can take each Perl CGI program and convert it to its own DCD. Your original CGI program can be reformatted as a subroutine in this DCD, and the DCD write method set up to call this particular subroutine as required. Your code should be modified to only output the relevant content, since ExSite takes care of the templates and wrappers on its own. If you have many CGI programs that act in concert to provide all of the functions of your web application, it may be more sensible in the long run to combine them into a single DCD. Here is one method to do this:
If you formally invoked your web application using a series of URLs such as: http://mydomain.com/cgi-bin/task1.cgi Those tasks/URLs are now invoked as: http://mydomain.com/cgi-bin/page.cgi?_id=XXX&action=task1 where the web application has been embedded into page XXX. Additional parameters to the original CGI programs can still be used with the new URLs. Forms and Shared InputWell-behaved DCDs share their input with their fellow DCDs, by using the ExSite::Input class. This is especially important when dealing with POST data, since once it is read from stdin, it is no longer available for other DCDs to read. Normally a form's input will be directed at a single DCD, so it may not seem like a big deal to share it. However, if you have several DCDs in a page that can potentially respond to form input, then the first one will grab the input to see if it makes sense to it. Without shared input, subsequent DCDs will not see the input stream, even if the first DCD did nothing with it. See the example DCD in the appendices for an example of this. DCD Calling ConventionsDCDs are "called" using tags embedded in the raw HTML of the ExSite Content Management System. The HTML is only "raw" before the page is fully constructed. The content management tags are replaced during the page construction process. Once a page is written to disk as a .html or .php file, it has been fully processed. No more content management tags exist in it, and no more replacements will occur. (PHP and other server-side substitutions will still occur, however, allowing a whole second level of content management to take over...) If you have a web applet or application called "App", then it is called from your raw HTML using a tag like this: <!--&App.method(options)--> where "method" is one of read, write, or ioctl. For instance: <!--&Debug.write(POST)--> This would call our Debug applet (from the appendices, below), invoking the write() method, with argument "POST". More formally, it results in the equivalent of these Perl statements being executed: require Modules::Debug; Note that the only "methods" you can use in these tags are the three standard methods. All others (eg. showhash(), from the Debug example) will be ignored. It is useful to note that if no method is given in the tag, then the write() method is assumed, so the above can be written more concisely as: <!--&Debug(POST)--> The default output of the DCD can be invoked with no arguments, eg: <!--&Debug()--> AJAX/DHTML content-handling methods can be used automatically with minor changes to this notation, as described here. Alternative read logic can be invoked in some pages but not others by calling read() as needed, for instance (using an imaginary online shopping application): <!--&Shop.read(currency-cookie)--> (Note that the middle line here is a normal HTML comment, which will pass through ExSite unchanged.) In principle, you can call ioctl() using CMS tags as well. Since ioctls generate no visible output, this is only useful for executing certain tasks behind the scenes. For example, to receive an email whenever a certain page is viewed, you might use an ioctl like this: <!--&Shop.ioctl(send_to=morgan@foo.com)--> Calling Other ApplicationsIt is easy for one web application to call another. It simply has to include the tags to make the call as part of the output generated from its own write() methods. For example, if package A wants to invoke package B, it can do something like this: # in package A... This feature also makes it simple to maintain dynamic content libraries, consisting of re-useable utility functions, such as clocks, calendars, and so on, which can be re-used by any other applications. For example: >!-- display current date and time, using the ExLib DCD --< Page generated on <!--&ExLib(datetime)-->. A more direct form of calling can be performed by simply using or requiring the DCD module, and then invoking its methods directly. To ensure portability and compatibility, it is best to restrict your calls to the "public" driver methods read(), write(), and ioctl(), but in fact you are not prevented from making a call to any "private" DCD method using this trick. # direct call to another DCD A similar effect can be achieved by inheriting another DCD, and simply overloading its methods in the cases where the new DCD differs. ServicesMost DCDs will generate new content that is substituted for the same tag on the calling page. This effectively keeps the DCD confined to the space allocated for that tag. If the DCD is placed in a page body, that's no big deal, since the body is usually designed to expand or contract as much as necessary to hold its content. In other cases, however, the DCD may be embedded in a sidebar, header, footer, or other confined space. Rather than expanding the DCD there, you would prefer that the DCD request be served by a different DCD tag that has more room. In some cases, you want the request to be served by an entirely different page. Such DCDs are called "services" because there is a particular tag or page that services the requests that come from other tags. In-page servicesIf you want the DCD request to be served by a different tag on the same page, that can be accomplished by selective processing of the DCD input. For example, say the following tag generates a form to search: <!--&MyDCD(searchform)--> And the next tag displays search results: <!--&MyDCD(dosearch)--> Then, the form can be embedded in some part of the page such as a
header, sidebar, or right-align If you want the DCD request to be served by a different page, that
is a little more involved. The DCD must be configured to run as an
inter-page service, as follows: * To bind a service to a page, you create a record in the Service
table in the ExSite database (this can be done using the WebDB tool, or
the Security plug-in).
There are two fields that must be set: To restrict a DCD so that it can only be used by selected
websites, it must be configured similarly to an inter-page service: * To bind a service to a site, you create a record in the Service
table in the ExSite database (this can be done using the WebDB tool, or
the Security plug-in).
There are two fields that must be set: |
AnnouncementsKeyword Tags |