Overview -------- The basic design of Pas has similarities to Java Servlets and Java Server Pages. It uses what looks like an embedded perl in html sytax, psp (pas server pages), and an object model for pages. For a reference to syntax and functionality of PSP see: perldoc Org::Bgw::Pas::Psp::Complier Be sure your PERL5LIB environment variable is set to the proper directory. Differences and similaraties between PSP and JSP ------------------------------------------------ PSPs compile down to page objects. JSPs compile down to servlets. The syntax for PSP was taken almost exactly from the JSP syntax. Some of the Java specific, or Servlet specific features of JSPs were left out or are ignored, these include: - <%@ page buffer = "value" %> All page objects in Pas spool their output to a response object. Under this model, a buffer size doesn't make sense - Perl automaticly allocates the necessary memory. - <%@ page autoFlush = "value" %> Again, since all response data is spooled untill the end of the request (after the page object's execute has been called), flushing has no context under Pas. - <%@ page isThreadSafe = "value" %> Currently, this also has no context under Pas. Running under standard CGI, each request is handled by it's own process (which is inefficient), so there are no threads. Under mod_perl, each request is handled by an Apache child process - and there still is only one thread of execution. In the future, if a threading environment becomes desireable, Pas will be able to accomodate a multi-threaded environment, as it is object oriented, uses no globals, and a page object is instantiated to handle each request. PSP also adds additional tags. Currently the only additional tag is for adding methods to the generated object: <%[ sub foo { my $self = shift; $self->response()->print("this is from inside foo\n"); } %> Differences and similaraties between Pas and Servlets ----------------------------------------------------- As mentioned briefly above, one major difference between the servlet model and Pas is threading. Under the servlet model, when you implement a servlet object, you are overriding either the doGet method, doPost method, or both. These methods will be running in a multi-threaded context, which means that you can use local variables, but no member variables in your servlets. In some ways this goes against data encapsluation and object oriented design. Pas constructs a unique instance of a page object for each request. This allows the page object to use member variables, as well as local variables for it's data. PAS makes no implicit distinction between a GET and a POST request. These are HTTP specific, and since the Request object makes use of CGI.pm, which makes the request data available in the same manner for both types of requests, there is no need to handle the two request types differently. Pas uses a single method: execute(), instead of a pair of methods: doGet() and doPost(). If the developer wishes to discriminate between the two, they can inspect the request object to determine the request type and branch accordingly. Since the bulk of web development is done to support GET and POST requests, those are the two we have focused on. There is no reason that a page object could not also handle request methods like PUT and DELETE. Contrasting PSP with Other Embedded HTML Systems ------------------------------------------------ The only reason I'm singling HTML::Embperl out here is because I have had experience with it. These are my opinions. You may find that not only do you disagree with them, but that I may in fact be wrong. Using a system of embedded code in the presentation layer (HTML in most cases) like JSP, ASP, PHP, and HTML::Embperl is good for large system design, because it allows you to keep the presentation seperate from the logic. This helps prevent coupling between the presentation layer and the code that enforces business rules and fetches data to be displayed. This type of system is also good because it allows either junior developers, or non-developers (HTML authors) to maintain the bulk of the presentation for the site ("Sure, just edit the psp file, but don't touch the stuff inbetween the <% and %> tags") while allowing senior developers to build the back end objects (business logic). Most of the tools that I've used search for the embedded code at run-time (request time) and execute the code on the fly. Many of them cache intermediate parse states as a performance improvement technique, but still execute code at run-time. This can cause issues with syntax errors - since there is no definate compile step, you often have to try out your page and see how far things get before you can track down errors. With many of these tools, the snippets of evaluated (or executed) code that live within the HTML documents often live either in a global namespace, or a pseudo-namespace which is often enough disfunctional. When you breach the subject of included files into the main HTML document, things diverge quickly based on implementation - most leaving much to be desired. These factors often make it difficult to debug the embedded code - with the case of included files, and local variables, it can become a guessing game. By taking almost an opposite approach, JSPs elimiate many of these issues by design. Instead of executing the embedded code as it's parsing the HTML, JSP instead takes the HTML with embedded Java code like this:
The time is <%= (new Date()).toString(); %> and 'compiles' it into a Java Servlet like this: package some.unique.package.name.for.this.jsp.page; import javax.servlet.*; class SomeUniqueNameForThisPackage extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) { reponse.print("\n"); response.print("\n"); response.print("The time is \n"); response.print((new Date()).toString()); response.print("\n"); response.print("\n"); response.print('\n"); } public void doPost(HttpServletRequest request, HttpServletResponse response) { doGet(); } } One good effect of this is that the code is now native Java, and is subject to all the rules and syntax enforcement of the normal compiler. In fact, for JSPs, the page must be compiled to a servelet, and then compiled from the servelet to byte code (a class file). This catches all syntax errors at compile time -- before you ever get to run-time. You also have the benefit of the diagnostics from the compiler, which are typicly informative and easy to track down. To take this kind of approach, and still retain a good design (that is flexible), you almost have to have some kind of object model (whether it's servlets, or page objects). For example, if we had the following embedded perl code: The time is <%= scalar(localtime()); %> We could parse it and generate a stand alone CGI: #!/usr/bin/perl -wT use strict; use CGI; my $query = new CGI(); print "\n"; print "\n"; print "The time is \n"; print scalar(localtime()); print "\n"; print "\n"; print "\n"; Which would be effective, but only to a point. But if we want to use the technique where we buffer all of our output untill the end so we can display a full diagnostic error page instead of half an HTML page followed by an error (which often times won't display on the browser because of unclosed HTML tags). This code has no facility for catching errors or exceptions and producing a well formated diagnostic error page. This method can not provide a way to forward or hand off requests to other CGI's so they can handle the request instead (this is a very useful feature even though it may not sound like it at this point). This flat CGI isn't object oriented, so it doesn't allow you to easily make use of parent object functionality, so it could be seen as discouraging re-use and good design. So we need an object model. Pas uses what I've been calling a page object model. In this model, the base page object creates a request object, and a response object when it is constructed. It provides a method called execute, which derived pages are expected to override. Inside of this exexute method is where the derived page object is expected to produce the content for page. When a request comes in to the web server, the request handler inspects the request and determines which page object should handle the request (each page object can be tied to one and only one uri). The request handler then constructs the page object, invokes its request_init() method, its execute() method, and finally its request_cleanup() method. If these steps are successful the request handler then returns the data that was buffered in the response object back to the client (browser). A basic page object in Pas looks like this: package Some::Namespace::MyPage; use strict; use Org::Bgw::Pas::Page; our @ISA = qw(Org::Bgw::Pas::Page); sub execute { my $self = shift; $self->response()->print("\n"); $self->response()->print("\n"); $self->response()->print("This page was generated by: ",__PACKAGE__); $self->response()->print("\n"); $self->response()->print("\n"); return 1; } 1; This is fairly straightforward. It has it's own private namespace. There is one object per request, so you're safe to store data in member variables. You can make use of the methods from the object's parent. And so on. So, using this model, and our sample psp file: The time is <%= scalar(localtime()); %> We can compile that down to something like: package Org::Bgw::Pas::Psp::_CompiledPages::TestPsp; use strict; use Org::Bgw::Pas::Page; our @ISA = qw(Org::Bgw::Pas::Page); sub execute { my $self = shift; $self->response()->print(q{ The time is }); $self->response()->print(scalar(localtime())); $self->response()->print(q{ }); return 1; } 1; So using a model like this, we can maintain our presentation in the psp (embedded perl in HTML) foramt, which is relativly easy to maintain. If we keep our logic out of the PSP files, and keep it in back end objects - or parent objects that the PSP generated page objects can be dervied from we can keep the logic out of our presentation layer. The goal for the PSP files should be only for taking already computed or fetched information and displaying it. Coding page objects that gather information, place themselves into the request, and then forward the request on to another page object for presentation works well toward keeping logic seperated from presentation. Especially if you're forwarding to a page object generated from a PSP file. Forwarding is a fairly straight forward process. For past projects we have taken the approach of creating a page object to handle some peice of logic for the site, and then forwarding the request to a page object that is generated from a PSP file. The PSP file can then access the page that forwarded the request to it via the prev() method of it's request object: $self->request()->prev() This allows PSP pages to query information from the page object (by simply calling methods on the page object) that forwarded the request to them. The PSP file can then do things like obtain and display information loaded from a database, or show the user an error message. As a simple example, we'll look at how you might go about logging a user in to your website. This example login page has a few basic outcomes. The first outcome is when the user is viewing the login form, which is just a forwarding of the request to an HTML page or PSP file that contains (or generates) the HTML form for logging the user in. The second outcome is if the user is already logged in, where we will just redirect them to the homepage of the site. The third being an error page to be displayed if the user enters incorrect information. The last being a redirect back to the home page of the site so the user can then use the site while logged in. For the sake of berevity, we're going to gloss over many of the implementation details that aren't importiant to this discussion. The execute method for our login page might look something like this: package MySite::Login::LoginPage; ... sub execute { my $self = shift; if($self->user_is_logged_in()) { return $self->response()->set_redirect('/'); } if($self->user_is_logging_in()) { if($self->authenticate_user_and_login()) { return $self->response()->set_redirect('/'); } $self->query()->param('login.error',"Error, invalid username or password."); return $self->forward_request('/login/login_form.psp'); } return $self->forward_request('/login/login_form.psp'); } ... 1; In our pas.conf, we register this page object to service the uri '/login/LoginPage' with the following entry: pas.pages.login.LoginPage=MySite::Login::LoginPage Our login_form.psp page might look like this:Please log in to our site
<% if($self->query()->param('login.error')) { %><%= $self->query()->param('login.error')%>
<% } %> This very well could have just been static HTML. We've found it's often in our best interest to take advantage of CGI.pm's methods for generating form inputs (it has some desireable features), so we're making this a dynamic page (PSP). The first time the user accesses the uri '/login/LoginPage', it just forwards to the login_form.psp page, which generates a page of HTML, puts that HTML into the response object, and returns a true value. The request handler then takes the data from the response object and sends it back to the browser. When the user enters their username and password, then clicks on the submit button, the form data will be sent to the login page object. The login page object can then detect that data is being passed in the request, and attempt to authenticate the user. If all goes well, it then simply redirects them to the main page of the site. If the information the user provided was bad or invalid, the page object puts an error message into the request and forwards the request to the PSP page again. It also could have provided an error message method which the PSP page could have accessed by calling it: <% if($self->request()->prev()->error_message()) { %><%= $self->request()->prev()->error_message(); %>
<% } %> If both the the login page object, and the PSP page had session support, they also would have had the option of storing the message in the session. Though the PSP page would have had to remember to remove it after it displayed it. If the user was already logged in when they requested the LoginPage, then they are simply redirected to the homepage. There is an importiant distinction to keep in mind here. When a redirect is called for, an actual HTTP redirect header is returned to the browser. When a request is forwarded, it never leaves the server. The user sees the same uri (in this case '/login/LoginPage') even though the content that comes back may be from any of several PSP pages, or even other hand coded page objects. This approach, where the hand coded page objects produce no presentation (in thise case HTML), but only forward their requests off to PSP pages, has worked very well for us on past projects. It goes a long way toward keeping the presentation away from the internal logic and business rules of the site.