WebMCP 2.2.0 Documentation

WebMCP is a web development framework based on the Lua programming language (read more about Lua here).

Requirements

WebMCP has been developed on Linux and FreeBSD. Using it with Mac OS X is untested as of yet; Microsoft Windows is not supported. Beside the operating system, the only mandatory dependencies for WebMCP are the programming language Lua version 5.2 or 5.3, the Moonbridge Network Server for Lua Applications version 1.0.1 or higher, PostgreSQL version 8.2 or higher, and a C compiler.

Installation

Please read the following instructions carefully to avoid problems during installation.

  1. Download and compile the Moonbridge Network Server for Lua Applications (follow the README file of Moonbridge).
  2. Optional: Ensure that the moonbridge binary is within the search path for binaries.
  3. Optional: Ensure that the moonbridge_http.lua file is covered by Lua's package path (see Lua's search path documentation on how to format the LUA_PATH environment variable).
  4. Download WebMCP.
  5. Enter WebMCP's unpacked source code directory and execute the command "make".
  6. If you experience problems during step 3, edit the Makefile.options file and repeat step 3.
  7. The framework/ directory (or the framework.precompiled directory if you want to use precompiled code) is WebMCP's framework base directory now. It may be copied to any other directory of your choice (but make sure to follow links, e.g. using cp -L -R, when copying the directory to a different place).
  8. If optional step 3 has been skipped, ensure that Moonbridge's moonbridge_http.lua file is soft-linked or copied to the lib/ directory of the framework's base directory.

Configuration, initializers, and request handling

A WebMCP application may consist of several (sub-)applications. Each application sharing the same base directory also shares the database models but may provide different views and actions. The views and actions of an application are found within the "app/application_name/" directory, relative to the application base path. When starting WebMCP, the application's base path as well as the desired application name must be provided.

In addition to selection of an application, a config file must be chosen when starting the application. This enables to run an application in different contexts, e.g. you may have one configuration file for development purposes and one for productive use. Configuration files are found in the "config/" directory, relative to the application base path.

WebMCP uses the Moonbridge Network Server to handle HTTP requests. The Moonbridge Network Server listens to a TCP port and passes control to WebMCP (by invoking bin/mcp.lua in the framework's base directory), eventually resulting in a call of request.handler(...) for each request. However, before any request is processed, WebMCP will initialize the environment. This initialization includes tasks such as

For each request, it is also possible to execute filters. Filters can be used to

Filters and initializers are created by adding files in the application's directory structure. The filename determines the execution order (lexicographical order). It is a common idiom to start the filename of a filter or initializer with a two digit number to be easily able to change the execution order when desired. Filters and initializers are wrapping requests, i.e. part of them is executed before and part after the remaining request handling.

When an initializer or filter calls execute.inner(), execution of the initializer or filter is suspended and the remaining initializers and/or filters or the requested view or action are executed. Afterwards, the interrupted filters and initializers are resumed in reverse order (from where they called execute.inner()). Most often, execute.inner() is the last line in an initializer or filter, resulting in all code to be executed prior to request handling (and nothing to be executed afterwards).

The Moonbridge server creates forks (i.e. clones) of the application server process (i.e. the whole Lua engine including all libraries and variables) in order to handle concurrent requests. Certain initializations may be performed before forking, other initializations must be performed after forking. For this purpose, WebMCP allows an application to provide so-called "pre-fork" and "post-fork" initializers. The application's configuration files as well as its pre-fork initializers are executed before forking. The application's post-fork initializers are executed after forking. In particular, any libraries that open file or network handles during initialization must not be loaded before the server process is forked. Opening database connections must be performed after forking as well. WebMCP follows the following execution order (directory structure is explained further down):

  1. Initialization code of mcp.lua in the bin/ directory of the framework, including:
  2. Executing the selected configuration file: config/configuration_name.lua;
  3. Executing all pre-fork initializers (both those in the app/_prefork/ and those in the app/application_name/_prefork/ directory) until call of execute.inner() within each initializer;
  4. The Moonbridge Network Server forks the process (i.e. cloning the whole Lua machine);
    Note: no file handles or network connections must be opened prior to this point!
  5. Loading WebMCP's "multirand" library;
  6. Executing all post-fork initializers (both those in the app/_postfork/ and those in the app/application_name/_postfork/ directory) until call of execute.inner() within each initializer;
  7. For each request:
  8. Resuming execution of all post-fork initializers in reverse order from that position where execute.inner() had been called;
  9. Resuming execution of all pre-fork initializers in reverse order from that position where execute.inner() had been called.

As a minimum configuration, the used configuration file or pre-fork initializer should at least contain a listen{...} call, e.g.:

listen{
  { proto = "tcp", host = "::", port = 8080 },
  { proto = "tcp", host = "0.0.0.0", port = 8080 }
}
execute.inner()  -- only use this line if done in pre-fork initializer

Using the atom library

Lua itself has only very few built-in data types. The atom library gives support for extra data types. Currently the following extra data types are provided:

In addition the following pseudo-types are existent, corresponding to Lua's base types:

Both atom.integer and atom.number refer to Lua's base type “number”.

New values of atom data types are created by either calling atom.type:load(string_representation) or by calling atom.type{...}, e.g. atom.date{year=1970, month=1, day=1}. Note that atom.date{...} is a shortcut for atom.date:new{...}. You can dump any atom value as a string by calling atom.dump(value) and later reload it with atom.type:load(string).

Using the Object-Relational Mapper “mondelefant”

The library “mondelefant” shipping with WebMCP can be used to access PostgreSQL databases. It also serves as an Object-Relational Mapper (ORM). The database connection is usually configured in the config file (e.g. in config/devel.lua):

config.db = { engine="postgresql", dbname="webmcp_demo" }

In addition to configuring the database, it must be opened within a post-fork initializer (e.g. in app/_postfork/01_database.lua):

_G.db = assert(mondelefant.connect(config.db))
function mondelefant.class_prototype:get_db_conn() return db end
execute.inner()

The parameters for mondelefant.connect are directly passed to PostgreSQL's client library libpq. See PostgreSQL's documentation for information about supported parameters.

To define a model to be used within a WebMCP application, create a file named with the name of the model and .lua as extension in the model/ directory of your application. The most basic definition of a model (named “movie” in this example) is:

Movie = mondelefant.new_class()
Movie.table = 'movie'

Note: Model classes are always written CamelCase, while the name of the file in model/ is written lower_case.

To select objects from the database, the mondelefant library provides a selector framework:

local s = Movie:new_selector()
s:add_where{ 'id = ?', param.get_id() }
s:single_object_mode()  -- return single object instead of list
local movie = s:exec()

A short form of the above query would be:

local movie = Movie:new_selector():add_where{ 'id = ?', param.get_id() }:single_object_mode():exec()

For more examples about how to use the model system, please take a look at the demo application.

The Model-View-Action (MVA) concept

As opposed to other web application frameworks, WebMCP does not use a Model-View-Controller (MVC) concept, but a Model-View-Action (MVA) concept.

Models

The models in MVA are like the models in MVC; they are used to access data, which is stored in a relational database (PostgreSQL), in an object oriented way. Methods provided by the corresponding classes can be used to alter stored objects or execute any other associated program code. Models are usually defined in a file with a lowercase filename ending with ".lua" in the models/ directory of the application. The corresponding model name (i.e. class name) must be written in CamelCase, e.g. "models/my_model.lua" should define a model class named "MyModel". The simplest model is created by calling mondelefant.new_class() and subsequently setting the table attribute of the returned class.

-- filename: model/customer_receipt.lua
CustomerReceipt = mondelefant.new_class()
CustomerReceipt.table = "custreceipt"

Methods such as :add_reference(...) can be used to further modify or extend the class.

Views

The views in the MVA concept are different from the views in the MVC concept. As WebMCP has no controllers, the views are responsible for processing the GET/POST parameters from the webbrowser, fetching the data to be displayed, and creating the output by directly writing HTML to slots of a layout (see slot.select(...), slot.put(...), and slot.put_into(...) or by calling helper functions for the user interface (those functions beginning with "ui."). Views are stored in files with the file path "app/application_name/module_name/view_name.lua". When their corresponding URL, e.g. "http://hostname:port/module_name/view_name.html", is requested, the code in that file gets executed (after calling appropriate filters). After the execution of the view has finished (and after all filters have finished their execution too), the slot data will be inserted into placeholder sections in the selected layout file. The layout file defaults to "app/application_name/_layout/default.html" but may be changed using the slot.set_layout(...) function.

Actions

Actions are similar to views, but supposed to change data in the database, hence only callable by HTTP POST requests. They are also responsible for processing the POST parameters from the webbrowser. They can modify the database, but instead of rendering a page to be displayed, they just return a status code string (via Lua's return statement, where true can also be used instead of "ok", and false instead of "error"). Depending on the status string there will be an internal forward or an HTTP 303 redirect to a view. When calling an action via a POST request, additional POST parameters, which are usually added by hidden form fields, determine the view to be displayed for each status string returned by the action. See the routing parameter to the ui.form{...} function for further details.

Layouts

Templates for HTML documents to be returned by views are stored at the path "app/application_name/_layout/layout_name.html", relative to the application base path. The default layout name is "default". For system errors, the layout name "system_error" is used. A sample layout is given as follows:

<!DOCTYPE HTML>
<html>
  <head>
    <title><!-- WEBMCP SLOTNODIV title --></title>
    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/style.css"/>
  </head>
  <body>
    <!-- WEBMCP SLOT content -->
  </body>
</html>

The following elements in a layout file get replaced automatically:

Internationalization/Localization

To translate certain strings in your application, simply use the underscore function. A language can be selected with locale.set{lang = "code"} where code is a language code. The translations for strings are expected to be contained in files "locale/translations.language_code.lua". These files should return a table that maps strings to their corresponding translation:

return{
["Are you sure?"] = "Bist Du sicher?";
["User '#{name}' created"] = "Benutzer '#{name}' created";
}

Such translation files can be automatically created with the langtool.lua program, found in the framework's bin/ directory.

Global variables and the environment

To avoid accidental programming errors, WebMCP forbids setting of global variables by default. This is overridden by using the prefix "_G." (see reference) when setting the variable, e.g. _G.myvar = 7, or by setting the variable in a file with the same name of the global varaible (but suffixed with ".lua") in the env/ directory of the framework or application. Note, however, that the lifetime of global variables is not predictable as it depends on process recycling of the Moonbridge webserver (one fork will handle more than one request) and because there may be multiple forks of the Lua machine which all have their own global variable space (there is usually more than one fork).

If an application needs to store request related data, the global table app should be used (e.g. app.myvar = true instead of _G.myvar = true). The app table gets automatically initialized (i.e. emptied) for each request.

Global variables are still useful when providing access to libraries, for example. WebMCP automatically loads certain libraries and functions though an autoloader mechanism. On read-accessing any unknown variable, WebMCP will search the framework's and application's env/ directories for an appropriate file (e.g. "my_func.lua if you invoke "my_func()") or a matching directory (e.g. "my_module/ if you access "my_module.my_func()). In case of an existing directory in env/, an empty table with autoloading capabilities is automatically created as global variable with the name of the directory. The autoloading mechanism allows directories to contain further files which can be used to initialize variables within that table when accessed. Directories can also contain a special file called "__init.lua" that always gets executed when the table is accessed for the first time. The env/ root directory can also contain a file ("env/__init__.lua") which gets executed before any configuration is loaded.

A good place to store utility functions is a global table called util. This table will be automatically accessible if you create a env/util/ directory in your WebMCP application. To provide a function util.myfunc(...) simply create a file env/util/myfunc.lua, with the following function definition:

-- filename: env/util/myfunc.lua
function util.myfunc()
  slot.put_into("hello", "Hello World!")
end

Directory structure of a WebMCP application

Summarizing information from the previous section, we get the following directory structure for a WebMCP application:

Starting your application

If the moonbridge executable is within your system's search path for binaries (i.e. if optional step 2 of the installation instructions has been carried out), you can directly call the mcp.lua script (found in framework/bin/mcp.lua). Pass the following arguments to mcp.lua:

  1. Path of the WebMCP framework directory, e.g. ./framework
  2. Path of your application's directory, e.g. ./demo-app
  3. Name of your application (usually main)
  4. Name of configuration (e.g. devel to use config/devel.lua)

Alternatively, the moonbridge binary may be called manually (e.g. by invoking ../moonbridge/moonbridge). In this case, the following five arguments are required:

  1. Path to mcp.lua, e.g. ./framework/bin/mcp.lua
  2. Path of the WebMCP framework directory, e.g. ./framework
  3. Path of your application's directory, e.g. ./demo-app
  4. Name of your application (usually main)
  5. Name of configuration (e.g. devel to use config/devel.lua)

Note that the demo application will require a database to be set up prior to starting. Execute the following shell commands first:

createdb webmcp_demo
psql -v ON_ERROR_STOP=1 -f demo-app/db/schema.sql webmcp_demo

Automatically generated reference for the WebMCP environment


Copyright (c) 2009-2019 Public Software Group e. V., Berlin