LassoApps

Lasso Server provides a means for bundling source files, HTML, images, and other media types into a single deployable unit called a LassoApp. LassoApps are served over the web using Lasso Server’s FastCGI interface. Lasso Server is required to run LassoApps. A single server can run multiple LassoApps at the same time.

The LassoApp system provides a framework of features to make app development easier and to support a clean and maintainable design. This system also permits data in one app to be accessed and shared by another, allowing multiple apps to work in concert.

LassoApp Concepts

LassoApps consist of regular files, logically structured into a tree of nodes and resources. This node tree is constructed to match the file and directory structure inside the LassoApp bundle. Each node is associated with one or more resources. Resources are generally either Lasso pages, CSS, JavaScript, HTML/XML, XHR, image, or other raw or binary file types.

This node/resource/content representation system permits the logic for producing a particular application object, such as a “user” or a set of database result rows, to be isolated from logic for its display. It also allows application objects to be represented in a variety of manners, and for those representations to be modified, without having to extend the application objects themselves.

Additionally, the system is unobtrusive, permitting the developer to use their own methodologies and frameworks while still taking advantage of the LassoApp system in pieces or as a whole.

Nodes

Nodes represent the object structure behind a live LassoApp. This object structure is hierarchical, like a directory structure. The node tree begins with the “root” node. That root node has a series of subnodes and those subnodes have zero or more subnodes beneath them. In the case of the root node, each of its subnodes represent the currently installed and running LassoApps.

Each node has a name and this name is used when locating a particular node within the tree. Nodes are addressed using standard forward-slash path syntax. The root node is named “lasso9”, so it is accessed using the path /lasso9. The names of subnodes are appended to the path following a “/”.

/lasso9/LassoAppName/resourceName
/lasso9/AddressBook/groups/userX

The default web server configuration for Lasso Server will direct all paths beginning with /lasso9 to Lasso Server. This is the default method for accessing LassoApps over the web, though the configuration can be modified for other situations or server requirements. See the section Server Configuration for more information.

Resources

Nodes not only serve as containers for subnodes, they also represent zero or more resources. These resources represent data files, such as images, CSS, or Lasso source files. Resources are used to produce an object that the LassoApp system must then transform into a format suitable for sending back to the client. Each resource is associated with a content type. This association is done either explicitly using the resource file’s name, or by relying on the default content type, which is text/html.

LassoApp Node Hierarchy

LassoApp node hierarchy

Content Representations

Each resource is associated with a content type which is used when handling, or representing, the object produced by a resource. This handling occurs automatically when a node is requested via a web request and is formatted for output via HTTP. This handling is performed by a variety of content representation objects, each tailored for specific file extension, like “.jpg” or “.js”, and content types such as image/jpeg or application/javascript. New content representation objects can be added and existing representations can be tailored for specific application objects.

If there exists a content representation object for a given node resource and content type, then that resource can be invoked and the resulting object given to the content representation object for transformation or special handling.

To illustrate, consider a resource such as a PNG image that comes from a static, unchanging PNG file within a LassoApp. After the LassoApp is bundled for deployment, that image file may not actually exist on disk; instead it is contained within the LassoApp in a specialized format. Given the resource’s PNG content type, the system chooses the appropriate content representation object. In turn, that object sets an Expires header for that web request, improving application performance by preventing future redundant image requests. The content representation object does not have to modify the object data, and in this case with PNGs, sets an HTTP header but returns the unaltered binary image data.

Another example would be a node resource that produces a “user” object containing a first name, last name, etc. A content representation can be added to handle that particular object type and formats it for display as HTML. Another content representation can be added to format it for sending back as JSON data, while another can be added to convert it to the vCard format.

Constructing a LassoApp

All LassoApps reside as either a file or a directory located within the “LassoApps” directory, which is located within the current Lasso home. (See the section Instance Home Directory Contents in the Lasso Instance Manager chapter for more details.)

LassoApps begin as a directory named according to the application. This directory contains all of the files for the application. Before deployment, this directory can be precompiled into the LassoApp format. However, Lasso Server will happily serve a plain LassoApp directory as long as it is placed in the proper location. This means that an application can be deployed as a regular directory of files and also that a developer needn’t take any special steps transitioning between developing and testing an application.

Important

While the above is generally true, it is currently required to restart Lasso Server when adding or removing files from an in-development LassoApp. We aim to remove this restriction in a future release. (File content can be modified without any such restrictions.)

Directory Organization

By using the concepts of nodes, resources, and content representation, a LassoApp can be organized logically and provide clean, hierarchical, natural language URLs.

For example a simple “Contacts” LassoApp could have a structure similar to the following:

LassoApps/
   mycontacts/
      contacts/
         index.lasso
      css/
         appstyle.css
      index.lasso
      js/
         scripts.js
      other/
         footer.lasso
         header.lasso

This structure would provide the “root” of the LassoApp as http://example.com/lasso9/mycontacts which will serve the “index.lasso” file.

Serving Content

Serving simple content such as images or raw text and HTML is as simple as putting the file into the LassoApp root directory. As long as the file has the appropriate file extension (e.g. “.jpg”, “.txt”, “.html”) then it will be served as expected. Files with a extension other than “.lasso”, “.lasso9” or “.inc” will be served as plain data, meaning they will not be parsed, compiled and executed by Lasso Server.

Serving Processed Content

Processed content is any data produced programmatically by executing Lasso source code files. Such data can be generated wholly by Lasso code, or partially by embedding Lasso code in HTML or other types of templates. This type of content must reside in a file with an extension of “.lasso”, “.lasso9” or “.inc”.

The outgoing content type of processed content is very important. The content type determines any modifications or special handling that the data will receive before it is ultimately converted into a stream of bytes and sent to the client. By default, the content type for a “*.lasso” file is text/html. Lasso Server will automatically set the outgoing content type accordingly. A file will be given the default content type when accessed via a URL with either no extension, a “.html” extension or a “.lasso” extension. For example, requests for the following URLs:

http://localhost/lasso9/AddressBook/users
http://localhost/lasso9/AddressBook/users.html
http://localhost/lasso9/AddressBook/users.lasso

will, assuming the standard Lasso Server web server configuration, be mapped to these files in the LassoApp and served with the content type text/html:

/AddressBook/users.html
/AddressBook/users.html
/AddressBook/users.lasso

Explicit Content Types

The outgoing content type for a source file can be indicated in the file’s name by placing the content type’s file extension between square brackets. These files will be executed and the resulting value will be returned to the client using the indicated content type. The following shows some valid file names:

/AddressBook/users[html].lasso
/AddressBook/users[xml].lasso
/AddressBook/users[rss].lasso
/AddressBook/users[xhr].lasso

The files shown above will expose the following URLs:

http://localhost/lasso9/AddressBook/users.html
http://localhost/lasso9/AddressBook/users.xml
http://localhost/lasso9/AddressBook/users.rss
http://localhost/lasso9/AddressBook/users.xhr

Tip

A filename with an explicit content type overrides a file with a plain extension. For example, a request for http://localhost/lasso9/AddressBook/users or http://localhost/lasso9/AddressBook/users.html will be mapped to /AddressBook/users[html].lasso if it exists, and then fall back on /AddressBook/users.html.

Primary and Secondary Processing

Explicit content types can be used jointly with a similarly named regular “*.lasso” file. In this situation, first the primary file is executed and then its value is made available to the secondary file as it is executed. The primary file is always executed. Only then would the secondary file, which corresponds to the requested content type, be executed.

/AddressBook/users.lasso - primary content
/AddressBook/users[html].lasso - secondary
/AddressBook/users[xml].lasso - secondary
/AddressBook/users[rss].lasso - secondary
/AddressBook/users[xhr].lasso - secondary

Given the files shown above, if the URL http://example.com/lasso9/AddressBook/users.html was accessed, first the file “users.lasso” would be executed, and then the file “users[html].lasso” would be executed. The value produced by the first would be made available to the second. This technique is used to separate the object produced by the primary file from its display, which is handled by the secondary file.

In this scenario, the file “users.lasso” could return an array of all the users in the address book. That list of users might need to be presented to the client in a variety of formats, like HTML, XML, or RSS. The primary file “users.lasso” is concerned only with producing the array of users. The secondary files each handle presenting that array in the desired format.

Since primary files usually return structured data, it is generally required to return the value using a return statement. However, primary files that simply need to return string data can do so without a return statement by surrounding the code with Lasso delimiters, which will cause the auto-collected value generated by executing that file to be returned. In such cases, the delimiter must be first visible character in the file; otherwise Lasso Server will treat the entire file as executable code.

The following examples show a series of files that produce and format a list of users for both HTML and XML display. The list is generated first by the “users.lasso” file, then that list is processed by the “users[html].lasso” and “users[xml].lasso” files.

users.lasso

// Note: Usually the type definition would be in an _init file
define user => type {
   data
      public firstname::string,
      public middleName::string,
      public lastname::string

   public oncreate(firstname::string, lastname::string) => {
      .firstname = #firstname
      .lastname = #lastname
   }
   public oncreate(firstname::string, middle::string, lastname::string) => {
      .firstname = #firstname
      .middlename = #middle
      .lastname = #lastname
   }
}

// Return an array of users
return array(user('Stephen', 'J', 'Gould'),
         user('Francis', 'Crick'),
         user('Massimo', 'Pigliucci'))

users[html].lasso

<html>
<title>Users List</title>
<body>
<table>
   <tr><th>First Name</th><th>Middle Name</th><th>Last Name</th></tr>
<?lasso
   // The primary value is given to us as the first parameter
   local(users) = #1

   // Start outputting HTML for each user
   with user in #users
   do {^
      '<tr><td>' + #user->firstName + '</td>
         <td>' + #user->middleName + '</td>
         <td>' + #user->lastName + '</td>
      </tr>'
   ^}
?>
</table>
</body>
</html>

users[xml].lasso

<userslist>
<?lasso
   // The primary value is given to us as the first parameter
   local(users) = #1

   // Start outputting XML for each user
   with user in #users
   do {^
      '<user><firstname>' + #user->firstName + '</firstname>
         <middlename>' + #user->middleName + '</middlename>
         <lastname>' + #user->lastName + '</lastname>
      </user>'
   ^}
?>
</userslist>

Pass Multiple Values from Primary to Secondary

To pass multiple values from primary to secondary processors, use a staticarray as a return from the primary:

// Return from primary processor
return (:
   'hello world',
   array(
      user('Stephen', 'J', 'Gould'),
      user('Francis', 'Crick'),
      user('Massimo', 'Pigliucci')
   )
)

The following sets local variables to the returned values from the primary processor, in the order they are specified. The number of local variables being set must match the number of elements in the returned staticarray. (See the section Decompositional Assignment in the Variables chapter.)

local(txt, users) = #1

Special Files

Using the naming conventions below, a LassoApp can be made to install or load files as needed.

Customizing Installation

One or more specially named files can be placed in the root level of a LassoApp directory to be executed the first time a LassoApp is loaded into Lasso Server. These files are named beginning with “_install.” followed by any additional naming characters and ending with a “.lasso” extension. The simplest install file could be named “_install.lasso”. For example, an install file for performing a specific task, such as creating database required by the app, could be named “_install.create_dbs.lasso”.

Lasso Server will record the first time a particular install file is run. That file will not be executed again, even when the instance restarts. Only install files at the root of the LassoApp are executed.

Customizing Initialization

LassoApps can contain a special set of files that are executed every time the LassoApp is loaded. This loading occurs whenever Lasso Server starts up. These files are named beginning with “_init.” followed by any additional naming characters and ending with “.lasso”. The file “_init.lasso” is the simplest valid init file name. Only initialization files at the root of the LassoApp are executed.

Initialization files are used to define types, traits, and methods used within the application. This includes the definition of a thread object that can be used to synchronize aspects of the application, hold globally shared data, or perform periodic tasks.

During the normal operation of an application, definitions should be avoided. Redefining a method can have an impact on performance and memory usage, potentially leading to bottlenecks in your application. However, during application development redefining a method is a common occurrence while source code is frequently modified. In this case, definitions can be placed in non-init files (i.e., a regular file) and included in the _init files using lassoApp_include. This allows the definition be loaded at startup while also letting the developer execute the file “manually” as it is updated during development.

Ignored Files

When serving a LassoApp, Lasso Server will ignore certain file paths based on their names. Although they can be included in a LassoApp, Lasso will not serve or process files or directories whose names begin with a period (.), hyphen (-), or two underscores (__). All other file names are permitted without restriction.

Packaging and Deploying LassoApps

A LassoApp can be packaged in one of three ways: as a directory of files, as a zipped directory, and as a compiled platform-specific binary. Each method has its own benefits. Developers can choose the packaging mechanism most suitable to their needs.

As a Directory

The first method is as a directory containing the application’s files. This is the simplest method, requiring no extra work by the developer. The same directory used during development of the LassoApp can be moved to another machine running Lasso Server and run as-is. Of course, when using this method, the user has access to all the source code for the application. Generally, this packaging method would be used by an in-house application where source code availability is not a concern and the LassoApp is installed manually on a server by copying the LassoApp directory.

As a Zip File

The second method is to zip the LassoApp directory. This produces a single zip file that can be installed on a Lasso Server instance. Lasso Server will handle unzipping the file in-memory and serving its contents. LassoApps zipped in this manner provide easy downloading and distribution while still making the source code for the application accessible. Zipped LassoApps must have a “.zip” file extension.

Developers should verify that a LassoApp directory is zipped properly. Specifically, Lasso requires that all of the files and folders inside the LassoApp directory be zipped and not the LassoApp directory itself. On UNIX-based platforms (OS X or Linux) the zip command-line tool can be used to create zipped LassoApps. To accomplish this, a developer would cd into the LassoApp directory and issue the zip command. Assuming a LassoApp name of “AddressBook”, the following command would be used.

$> zip -qr ../AddressBook.zip *

The above would zip the files and folders within the AddressBook directory and create a file named “AddressBook.zip” at the same level as the “AddressBook” directory. The “r” option indicates to zip that it should recursively zip all subdirectories, while the “q” option simply indicates that zip should do its job quietly (by default, zip outputs verbose information on its activities).

As a Compiled Binary

Using the lassoc tool, included with Lasso Server, a developer can compile a LassoApp directory into a single distributable file. LassoApps packaged in this manner will have the file extension “.lassoapp”. Packaging in this manner provides the greatest security for one’s source code because the source code is not included in the package and is not recoverable by the end user.

Compiled binary LassoApps are platform-specific. Because these LassoApps are compiled to native OS-specific executable code, a binary compiled for OS X, for example, will not run on Linux.

Both lassoc and the freely available gcc compiler tools are required to compile a binary LassoApp. Several steps are involved in this task. However, you can use a “makefile” which simplifies this process on Linux and OS X. To use this example makefile, copy the file into the same location as the LassoApp directory. Then, on the command line, type:

$> make DirectoryName.lassoapp

Replace “DirectoryName” with the name of the LassoApp directory in the above command. The resulting file will have a “.lassoapp” extension and can be placed in the “LassoApps” directory. Lasso Server will load the LassoApp once it is restarted.

For information on compiling without using a makefile or on Windows, see the section Compiling Lasso Code in the Command-Line Tools chapter.

Installing the GCC compiler

On OS X, either:

  • Install and open Xcode, then go to Preferences ‣ Downloads ‣ Components ‣ Command Line Tools, and click Install.
  • Or, install the Command Line Tools package directly from https://developer.apple.com/download/more/ (Apple ID required).

On CentOS:

  • run sudo yum install make on the command line. This will install all required dependencies including gcc.

On Ubuntu:

  • run sudo apt-get install make on the command line. As with CentOS this will install all required dependencies.

Platform-Specific Considerations

It is important to note that the target for each compiled LassoApp is specific to that which it is compiled on. If your development platform is OS X and you wish to deploy your compiled LassoApp on 64-bit CentOS, you must compile the LassoApp on a 64-bit CentOS machine. The same issue exists for 32-bit vs. 64-bit architectures on the same distribution. A LassoApp compiled for 32-bit Ubuntu will not run on 64-bit Ubuntu.

Server Configuration

Although LassoApps are available through the path /lasso9/AppName/, it is often desirable to dedicate a site to serving a single LassoApp. This can be accomplished by having the web server set an environment variable for Lasso to indicate which LassoApp the website is serving. The environment variable is named LASSOSERVER_APP_PREFIX. Its value should be the path to the root of the LassoApp. For example, if a site were dedicated to serving the Lasso Server Admin app, the value for the LASSOSERVER_APP_PREFIX variable would be “/lasso9/admin”. Having the variable set in this manner would cause all lassoApp_link paths to be prefixed with “/lasso9/admin”.

The LASSOSERVER_APP_PREFIX variable is used along with other web server configuration directives to provide transparent serving of a LassoApp. The following example for the Apache 2 web server illustrates how the Lasso Server Admin app would be served out of a virtual host named “admin.local”.

<VirtualHost *:80>
    ServerName admin.local
    ScriptAliasMatch ^(.*)$ /lasso9/admin$1
    RewriteEngine on
    RewriteRule ^ - [E=LASSOSERVER_APP_PREFIX:/lasso9/admin]
</VirtualHost>

Consult your web server’s documentation for further information.

LassoApp Tips

Loading Required Types/Traits/Methods at Initialization

It is a good habit to load all types and methods required by the LassoApp at the time Lasso Server loads it. This can be achieved by using “_init.lasso”:

_init.lasso

// Load traits
lassoApp_include('core/traits/mytrait.lasso')
lassoApp_include('core/traits/anothertrait.lasso')

// Load types
local(coretypes) = array('my_usertype', 'my_addresstype', 'my_companytype')
with i in #coretypes
do lassoApp_include('core/types-methods/' + #i + '.lasso')

This will load the specified traits and types at the time the LassoApp is loaded. All documents in the LassoApp can then assume these types exist. Note that these types can later be redefined individually by accessing the URL directly; in this case, at http://example.com/lasso9/myLassoApp/core/types-methods/my_usertype.lasso.

Creating Required SQLite Database on Installation

It is often desirable to keep configuration data for your LassoApp in a database rather than a local config file. One method of storing this is to leverage Lasso Server’s embedded SQLite data source.

The following code demonstrates automatically creating a SQLite database whenever the LassoApp is installed on a new instance:

_install.lasso

define myLassoApp_sqlite_dbname  => 'myLassoApp_db'
define myLassoApp_sqlite_db      => sys_databasesPath + myLassoApp_sqlite_dbname
define myLassoApp_config_table   => 'config'

local(sql) = sqlite_db(myLassoApp_sqlite_db)

#sql->doWithClose => {
   #sql->executeNow(
      "CREATE TABLE IF NOT EXISTS " + myLassoApp_config_table +
      " (host PRIMARY KEY, dbname, username, pwd, status INTEGER, registerkey);"
   )
}

The code within “_install.lasso” will only ever be executed when this LassoApp is first placed in the “LassoApps” directory of an instance and the instance is restarted.

Serving JSON and XHR Files

Content Representation can be leveraged to provide a range of data formats. One of these is XHR, also known as AJAX, which in most cases will use a GET request to send data to the server, e.g. http://example.com/lasso9/myLassoapp/userdata.xhr?id=123.

While discussions directly regarding AJAX, jQuery, XHR, REST, XML, and JSON are outside the scope of this chapter, XHR response data can be in various forms, including JSON, which we will use for this example.

Consider the following JavaScript (using jQuery):

var dataObj       = new Object;
dataObj.id        = $('#userid').val();
$.ajax({
      url:        '/lasso9/myLassoapp/userdata.xhr',
      data:       dataObj,
      async:      true,
      type:       'post',
      cache:      false,
      dataType:   'json',
      success:    function(xhr) {
          alert('User name: ' + xhr.firstname + ' ' + xhr.lastname);
      }
});

The XHR request is for “userdata.xhr”, which Lasso Server will interpret as a request for “userdata[xhr].lasso” and serve as an XHR file with the correct MIME type.

userdata[xhr].lasso

local(id)     = integer(web_request->param('id')->asString)
local(mydata) = map
inline(
   -database='example',
   -sql="SELECT firstname, lastname FROM mytable WHERE id = " + #id + " LIMIT 1;"
) => {
   records => {
      #mydata->insert('firstname' = field('firstname')->asString)
      #mydata->insert('lastname'  = field('lastname')->asString)
   }
}
local(xout) = json_serialize(#mydata)
#xout