[prev] [up] [next]

Creating a Web Application

Smalltalk/X already contains a complete web applicaiton framework. This consists of a webServer, HTML-generation tool classes and a WebService class framework.

In this section, we will learn how to create simple programmed webServices.
In a programmed service, web pages are generated by a program, as opposed to static pages or pages with embedded code.
(services with embedded Smalltalk code are described elsewhere. Have a look at examples, overview and browse the STT classes. For a demonstration, start a webServer in the launchers settings dialog, and enable the STT service there.)

Here, we describe how pure programmed services are created and coded. In these, smalltalk code is fully responsible both for the generation of html pages and to handle submitted forms.

Starting a WebServer

In order for the following to work, we first have to start the ST/X webserver. In the launchers settings-menu, go to the "Communication"-"HTTPServer" section,
[fig: webServer settings1]

and press the "Create new HTTP Server" button.

This will instantiate a new instance of the HTTPServer class (but not yet start it, so HTTP requests are not yet served by this).
If the default Smalltalk server port (8080) was used, a number of default services as appropriate for demonstration purposes are also initially added (see below on how these can be removed or others be added).

Starting an FCGI Server

An alternative to the above setup (where the smalltalk webServer has complete control over the responses to incoming requests on its port), is to start the smalltalk webServer as a FCGI (fast-CGI) client under another webServer, such as apache. For this, press the "Create new FastCGI Server" button.

To be added: more info on how to configure FCGI.

At this point, the server is fully instantiated, but not yet answering request on its port.
To actually start the server, find its item in the settings tree, select it, and press the "start" button (near the bottom of the dialog). You may want to turn on the debug and trace check-boxes, to get some feedback on the servers actions on the transcript.

Starting Services

Next, to add or remove services, find the services section of the new webServer in the tree, [fig: webServer settings2]

pick some service, and add it by pressing the "Add Selected Service" button.

Notice, that every service is registered under a so-called "linkName". This is the first component of the URL-path. For example, if a service has been registered under a linkName of "foo", the URL to get to this service in the webBrowser will be: "http://<yourHostName>:8080/foo".

You may want to play with the webServer for a few minutes, to see what is already provided and how the settings affect the service behavior.
As a recommendation, make sure that at least the following services are up and running:
WebHomePageForSTX will respond under the "/" (i.e. root) linkname with an introduction and links to other running services.
HelloWorldService will respond under the "Hello" linkname with an obvious response.
WebDemoApp1 a demo service showing some simple examples. This service is also able to present its own source.
CommancheSTTService a service which demonstrates embedded smalltalk code (very similar to PHP)
Notice that you can add/remove services at *any* time, and that there is no need to shutdown/restart the webServer for this.

Also, play with the security and authentication settings.

Finally, change the replyRepresenter settings. A replyPresenter is a wrapper, which is able to wrap another services output and add a common decoration to it. This allows you to easily change the look of a services pages (i.e. background, colors, font and icons etc.), without even touching the service itself. For now, only an exept-presenter and a null-presenter are provided. To write your own, take any of them as a starting base.

Now, we let us create our own web application...

Creating a New WebApplication Class

In ST/X, web services are created as subclasses of HTTPService.
To make your life easier, the browser provides an extra menu item, for the creation and initial dfinition of web service classes.

In the browser, select the 'New Web Application' item from the class-list-menu, and accept the resulting class definition template:

    HTTPService subclass:#NewService
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'WebApplications'
Answer 'yes' when asked if the application code shall be generated. After that, go back to the webServer settings dialog, reselect the services page, and ensure that the new service is added to the services list.

The required methods for a webService (which have been already generated for you as templates by the browser) are:
linkName (class protocol) specifies the default linkName. That name is used as default for the registration towards the webServer. The browser-generated code will return the classes name.
process:aRequest (instance protocol) the method invoked to process HTTP-requests. The browser-generated code will generate simple HTML for a "Hello World" message.
Öf course, we will have to change the browser-generated "process:" method later, to fit our needs.

When you send it a request from firefox, internet explorer or any other web browser, you should get the following response:
[fig: webServer response]

The argument to the "process:" message is a request object, which contains all relevant information of the http-request. For example, the requesting host, the URL, any authentication information etc. are found there.
To get familiar with it, place a self halt into your "process:" method, and invoke it by sending a request from your firefox or IE browser.
There, inspect the request object and/or browser its class.

One special item of the request object is the response-object. This is an object, which collects the generated HTML for the response. It understands the write-stream protocol, so you can generate HTML simply as if you would write it to a file.
At the entry of the "process:"-method, the response is empty.

Changing the Response

Let us change the "process:"-method, to return some more information about the request:
process:aRequest
    "This is the web applications main processing method.
     It will be invoked for every incoming webBrowser-request.
     The argument, aRequest contains the parameters (url, fields, parameters etc.)."

    |response|

    response := aRequest response.
    response nextPutLine:'<HTML>'.
    response nextPutLine:'  <HEAD>'.
    response nextPutLine:'  <TITLE>Hello</TITLE>'.
    response nextPutLine:'  </HEAD>'.
    response nextPutLine:'  <BODY>'.
    response nextPutLine:'    <H1>Hello World !</H1>'.
    response nextPutLine:'    The URL was: "', aRequest url ,'"'.
    response nextPutLine:'    <BR>'.
    response nextPutLine:'    The requestor was: "', aRequest peerName ,'"'.
    response nextPutLine:'    <BR>'.
    response nextPutLine:'    The current time is: "', Time now printString ,'"'.
    response nextPutLine:'    <BR>'.
    response nextPutLine:'    The relative URL path is: "', aRequest pathRelativeToService ,'"'.
    response nextPutLine:'  </BODY>'.
    response nextPutLine:'</HTML>'.
Of course, you can generate whatever response you like here - It is even possible to send an HTTP-request to another webServer and postprocess its response (that is what a meta-search engine does).

Adding Sub-Page Responses

One particular interresting piece of information in the request is pathRelativeToService. This is the URL-path relative the the services linkName and can be used to dispatch into sub-pages of our service.
For example, to have two sub-pages named "foo" and "bar", let us first create two corresponding methods:
foo:aRequest
    |response|

    response := aRequest response.
    response nextPutLine:'<HTML>'.
    response nextPutLine:'  <HEAD>'.
    response nextPutLine:'  <TITLE>Foo</TITLE>'.
    response nextPutLine:'  </HEAD>'.
    response nextPutLine:'  <BODY>'.
    response nextPutLine:'    <H1>Hello Foo !</H1>'.
    response nextPutLine:'  </BODY>'.
    response nextPutLine:'</HTML>'.
and
bar:aRequest
    |response|

    response := aRequest response.
    response nextPutLine:'<HTML>'.
    response nextPutLine:'  <HEAD>'.
    response nextPutLine:'  <TITLE>Bar</TITLE>'.
    response nextPutLine:'  </HEAD>'.
    response nextPutLine:'  <BODY>'.
    response nextPutLine:'    <H1>Hello Bar !</H1>'.
    response nextPutLine:'  </BODY>'.
    response nextPutLine:'</HTML>'.
and then change the main-process method into a dispatcher:
process:aRequest
    "This is the web applications main processing method.
     It will be invoked for every incoming webBrowser-request.
     The argument, aRequest contains the parameters (url, fields, parameters etc.)."

    |response nameOfSubPage|

    response := aRequest response.

    nameOfSubPage := aRequest pathRelativeToService.
    ( #('foo' 'bar' ) includes:nameOfSubPage) ifTrue:[
	self
	    perform:(nameOfSubPage , ':') asSymbol
	    with:aRequest.
	^ self.
    ].

    response nextPutLine:'Bad Request.'.
In the above code, please have a look at two specialities:
  1. the use of a perform:-message, which takes the pages name as a message-selector to dispatch into the "foo:" and "bar:" methods.
  2. the check for the name being in a "list of allowed pages" (the includes:-line). Without that, an bad quy could send you any message and possibly do harm to your service. This is needed because the use of the above perform:-message can possibly open security holes.
Now, try the URLs:
     http://localhost:8080/NewService/foo
and
     http://localhost:8080/NewService/bar
Notice the default behavior for unknown pages.
A better response would be to send a proper error message, which is done by a "reportBadRequest:-message to the request, as in:
process:aRequest
    "This is the web applications main processing method.
     It will be invoked for every incoming webBrowser-request.
     The argument, aRequest contains the parameters (url, fields, parameters etc.)."

    |response nameOfSubPage|

    response := aRequest response.

    nameOfSubPage := aRequest pathRelativeToService.
    ( #('foo' 'bar' ) includes:nameOfSubPage) ifTrue:[
	self
	    perform:(nameOfSubPage , ':') asSymbol
	    with:aRequest.
	^ self.
    ].

    aRequest reportNotFound:'This is not a valid foo-bar request !'.
Have a look into the HTTPRequest classes' "error-reporting" category for more possible answers.

Easier HTML Generation

In the above examples, HTML was generated as plain line oriented text. Although this is ok to begin with, you will sooner or later encounter difficulties in generating proper html.
Every programmer makes mistakes, and so will you - especially, when the generated HTML becomes complex and tag-structures become deeply nested. typicall bugs are missing end-tags, unallowed tags (such as TR outside of a table) etc.
To help in those situations, a set of classes is provided, which is able to represent the html document as a tree of objects. These deal with nesting and also know which element is allowed to be contained in which other.

The classes are found under the "Net-Documents-ModelTree" category; the classes to look at are "HTML::AbstractElement" for the entities and "HTML::TreeBuilder" for building.

The treebuilder understands HTML-tag-like messages, and constructs a dom-like tree of HTMLElement objects on the fly. Eventually, the tree is asled for its ascii representation, which is returned as the requests response.

The advantage of using a tree is that there is no longer a need to construct it sequentially from top to bottom, as would be the case with sequential, linewise html-generation. Thus, you can define and use a much more modular aproach, in which components and parts of the document can be easily generated from building blocks.

Using this library, the code for the foo-handler would be:

foo:aRequest
    |response builder|

    response := aRequest response.
    builder := HTML::TreeBuilder new.
    builder beginWith:(HTML::Document new).

    builder
	head;
	  title:'Foo';
	headEnd;
	body;
	  h1:'Hello Foo!';
	bodyEnd.

    response nextPutAll:(builder rootElement htmlString).

Text to be continued ...

Session Control

Cookies

Temporary Links

Higher Level Frameworks

Existing Services


Continue in "Creating a WebApplicaiton".


[stx-logo]
Copyright © Claus Gittinger Development & Consulting
Copyright © eXept Software AG

<cg@exept.de>

Doc $Revision: 1.7 $ $Date: 2006/12/15 12:04:48 $