Foswiki Stand Alone
Introduction
There are many ways to run web applications, including
CGI,
mod_perl (for Perl apps), and
FastCGI. Each of them has its strengths and weaknesses, and interacts in different ways with the application.
Foswiki Standalone is a code architecture design that makes Foswiki independent of execution technology: It has an abstraction layer to manage retrieving the server request information, sending the response back to the web server, and interacting with the web server, regardless of the specific execution environment mechanism that is being used.
Execution Environments for Web Applications
CGI - Common Gateway Interface
The Common Gateway Interface protocol is simple to use (both when developing applications and installing/configuring them with the web server) as it is independent of programming language, web server architecture and operating system, making CGI apps very portable. It's also very mature and stable, and is broadly available with many web hosting services. However, it has serious performance issues: For each request, the web server forks a new process and loads the application; the application reads its configuration, perform its initialization tasks, answers the request and terminates; and finally, the web server sends the response back to the client. For applications written in interpreted languages, such as Foswiki, written in Perl, there is one more step: read, parse and pre-compile/interpret the code, before the web application begins. All this overhead is resource-intensive, which can lead to poor performance, particular during periods of high access.
Web server API
Some web servers have an API that makes it possible for application developers to extend the web server itself to run web applications within the web server process. This approach leads to much better performance than CGI, since there is no overhead in forking a new process. In addition, the integrated web app can interact with phases of web server request processing other than response generation, which cannot be done with a separate web app process: for example, applications can handle URI translation, authentication, or authorization phases.
Web apps using the web server API approach have limitations that limit their portability. The API is not the same from one web server to another, and it can be different even with different versions of the same web server. Thus a web app using a web server API can be tightly coupled to that web server and its request processing architecture. Also, the app must be written (or interact) with the language bindings provided by the web server API. Another problem is that a single buggy application can compromise the entire web server, since they run within the same memory space. (
mod_perl
is the
Perl interface to the
Apache web server API.)
FastCGI
FastCGI was developed to address some issues when choosing between CGI and a web server API: Applications are separate processes from web server, as with the CGI approach, but they are persistent, rather than being created for each request. There is no overhead in forking a new process for each request, a buggy application doesn't compromise the whole web server, applications are independent of the web server and its internals, and there is no restriction on the programming language used. FastCGI also
provides a way to permit applications to interact with the web server in phases other than response generation:
authentication/authorization and
response filtering. Because of its benefits, FastCGI is broadly available at hosting services, and many web servers support it.
Note that although each of these technologies have different interfaces to web applications, their underlying intent is the same: feed an application with request information, and return a response generated by the app to the client.
Abstraction Classes for Interactions with the Web Server
CGI
makes request info available through
environment variables and
standard input and the application is expected to generate the response and send it through its
standard output.
mod_perl
applications take request info and send response through web server API.
FastCGI
applications receives request info from a
socket — environment variable values followed by request data — and sends the response through the same socket.
With the Foswiki architecture, there are two classes,
Foswiki::Request
and
Foswiki::Response
, that hold request info and the generated response, no matter what underlying technology is used. A
runtime engine populates the information in a
Foswiki::Request
object, and after Foswiki has filled in a
Foswiki::Response
object, sends the information from the response object to the web server. There is a specific runtime engine for each supported execution environment mechanism that deals with the specific protocol or API required for that execution environment.
- The interface of
Foswiki::Request
is compatible with CGI.pm with regards to methods to retrieve request information, for historical reasons. It is a singleton, available from $session->{request}
or Foswiki::Func::getCgiQuery
.
-
Foswiki::Response
is also a singleton, available from $session->{response}
.
- A third class,
Foswiki::LoginManager::Session
, is used in place of CGI::Session to make sessions available under execution technologies other than CGI.
Runtime Engines
Runtime Engines are the glue between the execution technology and Foswiki, so they are the start and end points of
Foswiki execution. Engines are in charge of filling the
Foswiki::Request
object and sending data from
Foswiki::Response
, using the specific way of the corresponding execution technology.
Each engine
must extend the base class called
Foswiki::Engine
, which is composed of many small methods, grouped into two phases:
- The prepare phase incrementally fills the
Foswiki::Request
object.
- The finalize phase sends data from
Foswiki::Response
and performs any needed cleanup.
Prepare phase
The
Prepare phase is composed of:
-
prepareConnection
- should fill
method
, remote_address
, server_port
and secure
fields.
-
prepareQueryParameters
- should fill
param
field with data from query string unless method
is POST
.
-
prepareHeaders
- should fill
headers
and remote_user
fields and maybe update remote_address
(in the case of a X-Forwarded-For
header)
-
prepareCookies
- should fill
cookies
fields. Foswiki::Engine
base class provides a good default. Engines probably won't implement this method.
-
preparePath
- should fill
action
, path_info
and uri
fields.
-
prepareBody
- should perform any initialization tasks related to body processing.
-
prepareBodyParameters
- should fill
param
field with data from HTTP request body (data sent with POST
), except uploads.
-
prepareUploads
- should fill
uploads
field with Foswiki::Request::Upload
objects, tied to uploaded files.
Finalize phase
After the
Prepare phase, all needed information to process the request is available and
Foswiki::UI::handleRequest
is called. It returns a
Foswiki::Response
object used in the
Finalize phase:
-
finalizeUploads
- should delete any temporary files created in preparation phase.
-
finalizeHeaders
- should send to web server the response headers. Engines should call
SUPER::finalizeHeaders
, since the base implementation takes care of some details, like cookies and HEAD
handle.
-
finalizeBody
- should send the
body
field of Foswiki::Response
object to the web server. This method calls write()
as needed, so engines should redefine that method instead of this one.
-
prepareWrite
- this method is called by
finalizeBody
before the first write attempt, so engines have the opportunity to perform any tasks required to begin sending response body. It's all right if no tasks are needed.
-
write
- it receives a buffer as parameter and should send it to the web server. Engines must implement this method.
Currently Supported Environments
Historical Background
Foswiki is
based on a product designed to run as a CGI script. To maintain compatibility with the legacy software base, the
Foswiki::Request
interface is
CGI.pm
compliant with regards to retrieving request information. Various assumptions are made when running as a CGI script:
- There is one process per request.
- No problem in leaking memory, since all memory is returned to the operating system after process is finished.
- No problem holding request-specific data in global variables.
- No problem at all in using global variables.
- No problem in "forgetting" an open file handle.
- The impact of rare crashes is limited.
- CGI object accessible via $session->{cgiQuery} can be used to generate HTML.
The introduction of the Foswiki Standalone Architecture was the first step in improving Foswiki's integration with execution environments other than CGI. Abstracting away the execution environment however breaks the previous assumptions:
-
$session->{cgiQuery}
is no longer intended to be a CGI object and should not be used to generate HTML.
- To reduce the impact on legacy code,
Foswiki::Request
currently subclasses from the CGI module. This should not be relied upon to be true in the future.
- Use of global variables (particularly an issue with non-core extensions) can lead to unexpected behavior with persistent engines.
- Memory leaks or failure to release other resources such as open file handles can lead to DoS and server crashes.
- If there is a crash, all subsequent requests sent to the same persistent process will not be handled, requiring manual intervention.
Developers must keep these new assumptions in mind. In particular, care must be taken that all resources are released when they are no longer needed — for memory, this means eliminating all references to it so it can be garbage collected.
This architecture is not finished yet: though stable, it is still a work in progress.
Note to Extension Developers
Since environments other than CGI are supported, scripts under the
bin/
directory are not necessarily start points. Foswiki uses
$Foswiki::cfg{SwitchBoard}
to determine what method implements each action. Examples of
actions are:
view
,
edit
,
save
, etc.
Foswiki 1.0.5 changed the format of SwitchBoard entries to make it self-documenting and easier to extend. The original format is still supported, but
deprecated (use it only to make your extension usable in Foswiki 1.0.0-1.0.4 and 1.0.5 and newer).
Examples of
old $Foswiki::cfg{SwitchBoard}
entries are:
$Foswiki::cfg{SwitchBoard}{view} = [ 'Foswiki::UI::View', 'view', { view => 1 } ];
$Foswiki::cfg{SwitchBoard}{edit} = [ 'Foswiki::UI::Edit', 'edit', { edit => 1 } ];
$Foswiki::cfg{SwitchBoard}{save} = [ 'Foswiki::UI::Save', 'save', { save => 1 } ];
Each action has:
- A name, that is used as key in
$Foswiki::cfg{SwitchBoard}
hash
- An array reference to three values:
- The module that implements the action.
- The function in the module to be called.
- A hashref to be used as initial context.
Examples of the new format:
$Foswiki::cfg{SwitchBoard}{view} = {
package => 'Foswiki::UI::View',
function => 'view',
context => {view => 1},
};
$Foswiki::cfg{SwitchBoard}{edit} = {
package => 'Foswiki::UI::Edit',
function => 'edit',
context => { edit => 1 },
};
$Foswiki::cfg{SwitchBoard}{save} = {
package => 'Foswiki::UI::Save',
function => 'save',
context => { save => 1 },
allow => { POST => 1 },
};
Each entry is a hashref, instead of an arrayref. Fields are:
-
package
- perl package that contains the method for this request
-
function
- name of the function in package
-
context
- hash of context vars to define (See note below)
-
allow
- hash of HTTP methods to allow (all others are denied)
-
deny
- hash of HTTP methods that are denied (all others are allowed)
- deny is not tested if allow is defined
The method is called with an initialized
$session
object as parameter.
If your extension provides an action, you should add an entry to
$Foswiki::cfg{SwitchBoard}
(see
SpecifyingConfigurationItemsForExtensions). The provided
bin/
script should look like other core scripts and adjust
$ENV{FOSWIKI_ACTION}
accordingly.
Setting Context
Contexts are defined in
IfStatements#Context_identifiers. If your extension replaces or adds an equivalent script to the standard scripts provided with Foswiki, it should set the appropriate context in its switchboard entry. Example: A
compare
script that replaces the
rdiff
script should set the
diff
context so that skins and plugins know that they are running in an equivalent script. A
genpdf
script that provides a equivalent of the
view
script should set the
view
context.