Lua Environments and the .preload Script

Lua keeps all its variables and functions not prefixed by the keyword local in a regular Lua table called the environment. For an introduction to this environment, see the Lua manual, section 2.2.

Lua enables us to create multiple environments and the Mako Server takes advantage of this feature and provides multiple environments for the Lua applications running in the server.

The .preload Script

Keep in mind that deployed (completed) applications should preferably be zipped together and loaded as ZIP files instead of being loaded as directories!

The Mako Server makes it possible to load multiple web applications, which differs from the traditional application server that provides one www directory for one application. With the Mako Server, you can have a www1 and a www2 directory. In fact, you can have any number of directories that can be loaded by the Mako Server. The name of the directory is either provided as an argument or set in the mako.conf file. For example, assuming you have two www directories named app1 and app2, the Mako Server can be started as follows:

mako -l::app1 -l::app2

Create the two directories and run the Mako Server on the command line as shown above. When you run this command, you should see the following being printed in the console:

Info: app1/.preload not found
Info: app2/.preload not found

The .preload script is optional and the above is not an error or warning. The Mako Server simply informs the user that no .preload script could be found in the two loaded applications.

As explained on the Mako Specific Features page, a .preload script is used when creating application generic functions or when designing code that must persist and/or run for the lifetime of the loaded application.

Create the following file in app1/.preload and insert the following Lua code into the .preload script.

varInEnv = 10
print(_ENV == _G)
print(varInEnv == _ENV.varInEnv)
print(varInEnv == _G.varInEnv)

The variable varInEnv is inserted into the application's environment table _ENV since the variable is not prefixed by the keyword local.

You may run the .preload script using the standard Lua distribution as follows:

lua app1/.preload

If you have installed the standard Lua distribution and if you run the above command, the following is printed in the console:

true
true
true

Based on the above printouts, we can conclude that _ENV is the same as the global environment _G. Now start the Mako Server as follows:

mako -l::app1

The following is printed in the console when you run the above command:

false
true
false

Based on the above printouts, we can conclude that the .preload script's environment table _ENV is not the same as _G when run in the Mako Server.

The Mako Server creates a Lua table per loaded application and sets this table as the _ENV (application environment) table. The purpose with this table is to make it easy to separate code in different applications. Without this concept, two applications using the same function or variable name would conflict.

You may change a .preload script's environment variable to use the global scope by inserting the following line at top of the .preload script:

_ENV=_G

Edit the .preload script above, insert the above code line, and re-run the Mako Server command for loading app1. You should see 'true' being printed three times.

Application Environment and Inheritance

Lua provides the concept of inheritance and the Mako Server sets the application environment (_ENV) to inherit from the global environment (_G). The prototype inheritance provided by Lua works as follows: First, the local environment is checked and if no variable/function is found in this table, Lua goes up the prototype chain and looks in the global environment. For example when using the global function print in a .preload script, the function is not found in the _ENV table so Lua looks in the global _G table and finds function print there.

Create the file app2/.preload and insert the following code into this file:

print"Hello World"
print(print) -- output address of function print
local gprint=print
function print(...) gprint("local -> global:", ...) end
print"Hello World"
print(print)

Start the Mako Server as follows:

mako -l::app2

The following is printed in the console when you run the above command:

Hello World
function: 9b12e0
local -> global:        Hello World
local -> global:        function: 4b94298

The first line in the .preload script uses the global print function. Lua cannot find the function in the local scope so it searches and finds the function in the global scope. On line three, we save the address of the global print function in variable gprint. We then create function print in the local application scope, thus locally overriding (shading) the global print function. This function uses the saved global print function to print all arguments. Notice that the printed address for the two print functions differ. The first printout (line 2) prints the global address and the next printout (line 6) prints the address of the local print function.

How the Mako Server Creates an Application

When you instruct the Mako Server to load an application, the server executes the following code:

local dir=ba.create.resrdr(args to resrdr)
local appenv=setmetatable({io=io,dir=dir},{__index=_G})
dir:lspfilter(appenv)
dir:insert()
  1. A Resource Reader is created.
  2. The application environment table (_ENV) is created with pre-configured file IO (io) and reference to the Resource Reader (dir). The table is set to inherit from the global environment _G by setting the prototype inheritance using setmetatable.
  3. The Resource Reader is LSP enabled and the application table is set.
  4. The Resource Reader is inserted into the server's virtual file system.

The above Lua code snippet can be found inside mako.zip in the file .config. You may unzip mako.zip and study the code in .config to find out more on how the Mako Server's integrated Lua code manages applications.

Accessing the Application Environment from Lua Server Pages

The application environment can be accessed from LSP pages by simply prefixing variables and functions with app.

In the app2 directory, create the file env.lsp and insert the following code into the file:

<?lsp
  print"Hello Browser"
  _G.print"Hello Console"
  app.print"Hello Console" -- Access print in app _ENV table
  trace"Hello Trace"
?>

Save the file, use your browser and navigate to http://localhost/env.lsp. You should see Hello Browser in the browser window and the three following lines being printed in the server's console window:

Hello Console
local -> global:        Hello Console
5: Hello Trace

The first code line in our LSP page is using the print function set for the LSP page's ephemeral environment (_ENV), which sends the printed output to the browser. The global environment is accessible using _G and the application environment is accessible using app; thus the next two code lines print using the global print function and the one we defined in the application table in the .preload script for app2.

The LSP page is also inheriting from the global environment and since no trace function is set in the LSP page's environment table, the global trace function is used on line 5.

See the Command (Request/Response) Environment documentation for more information on the scoping rules.

Application Modules

A large code base is usually split up into sections or in Lua terminology, split into modules.

You may create two types of modules, global modules that can be loaded using the Lua function require or local application modules. The benefit in using application modules for an application is that they can use the application environment.

Create the sub directory .lua in the app2 directory and create the file app2/.lua/mymodule.lua

Insert the following code into mymodule.lua and save the file.

app.print"Hello from mymodule"

Open app2/.preload, add the following code line at the end of the file, and save the file:

io:dofile(".lua/mymodule.lua", _ENV)

Restart the Mako Server or start the Mako Server as follows if it is not running:

mako -l::app2

The following is printed in the console when the Mako Server starts:

Hello World
function: 9b12e0
local -> global:        Hello World
local -> global:        function: 4d69220
local -> global:        Hello from mymodule

The last printed line is from the loaded module. Notice that the module is using the print function defined in the .preload script.

Dynamically Loading and Terminating Applications

As mentioned above, applications can be loaded when the Mako Server starts. All applications are gracefully closed when the Mako Server receives a termination command such as pressing Ctrl-C on the command line. Applications can also be created, terminated, and restarted dynamically. An application can be upgraded in a running system without having to restart the server.

When an application terminates, it is sometimes necessary to run code designed to gracefully stop the application. The Mako Server looks for the function onunload in the application's environment table and executes this function, if found, prior to terminating an application.

Open app2/.preload, add the following code line at the end of the file, and save the file.

function onunload()
   print"Closing app2"
end

Restart the Mako Server to load the new .preload script and then press Ctrl-C to terminate the server. You should see the following being printed just before the server exits:

Exiting...
local -> global:        Closing app2

Now that we have a way of knowing when an application terminates by printing a message in the onunload function, go ahead and create the file app2/restart.lsp and insert the following code into the file:

<?lsp
for ix,path in ipairs(mako.getapps()) do
   print("Reloading:",path)
   mako.reloadapp(ix)
end?>

Save the file, use your browser and navigate to http://localhost/restart.lsp. You should see the following being printed in the server's console window:

local -> global:        Closing app2
Hello World
function: 9b12e0
local -> global:        Hello World
local -> global:        function: 50ba108
local -> global:        Hello from mymodule

From the above printouts, we can see that app2 is terminated since the onunload function is run and then started again from the printouts in the .preload and mymodule.lua code. See the Mako Specific Features page to learn more about the functions for dynamically loading and terminating applications.

Posted in Tutorials