HTML Forms and LSP for Beginners

Introduction to Server-Generated Content Using Lua

This tutorial introduces the concept of server-side generated content, aka server-side scripting.

Ref Wikipedia: Server-side scripting
A program running on a web server (server-side scripting) is used to change the content on various web pages or to adjust the sequence or reload of the web pages. Server responses may be determined by conditions such as data in a posted HTML form, parameters in the URL, the type of browser being used, the passage of time, or a database or server state.

In this tutorial, we will look into Lua Server Pages (LSP) basics and how to use HTML forms to send data from the browser to the server. When we convert simple HTML pages into interactive applications, we need to get user input to be processed on the server side. The server-side processing is done by Lua Server Pages (LSP) in the Mako Server, a Barracuda App Server Library derived application server.

How LSP Works

Before we dive into building HTML web forms, let’s quickly review what’s happening behind the scenes with LSP. Understanding how the LSP engine works will make it easier to design server-side applications. LSP can generate any kind of text dynamically, but let’s start with the simplest example: creating a page that just returns the text "Hello World".

If you create an LSP page that contains the words "Hello World", the LSP processor will convert it into Lua code that looks something like this:

 function action() response:write("Hello World") end 
Compile LSP

Recompiling LSP is as simple as refreshing the browser window.

Example 1 illustrates how an LSP "Hello World" page is converted to Lua. Note that this example is for illustration purposes only, and the generated code looks different.

When a browser requests our "Hello World" LSP page, the LSP runtime engine calls the action() function. Inside this function, you can access a predefined environment that includes objects like request and response. The request object lets you easily grab client-provided information, while the response object is how you send data back to the client.

Of course, "Hello World" is not very exciting since it differs little from a static HTML page. To make things interesting, we need to introduce LSP tags and generate truly dynamic content.

Using LSP Tags for Dynamic Content

LSP provides a simple and fast way of creating dynamic content such as web pages. Lua script can be freely intermixed with text such as HTML and XML. LSP tags are XML compliant <?lsp    ?>. The code between these tags is executed as Lua code. Expressions are provided by the <?lsp=    ?> tag. The result of an expression is emitted as HTML/XML.

We can insert Lua code into an LSP page by using LSP code tags and LSP expression tags. The following example dynamically creates a response message from the HTTP method used by the client:

LSP page:

 <?lsp if request:method() == "GET" then ?> You sent a GET request <?lsp else ?> You sent a <?lsp=request:method()?> request <?lsp end ?> 

Example 2 shows how to produce HTML responses dynamically based on user-provided data.

Processed LSP page:

 function action() if request:method() == "GET" then response:write("You sent a GET request") else response:write("You sent a ",request:method()," request") end end 

Example 3 illustrates how the LSP in Example 2 is converted to Lua.

The LSP processing is automatic; you will not see the processed LSP page as shown above. The complete processing and compilation sequence is shown in Example 1, where the LSP processor converts LSP to Lua, and the Lua compiler compiles Lua into byte code that, in turn, is executed by the Lua runtime engine.

HTML form data and query string (URL-encoding)

HTML forms send data as key/value pairs to the server, where the key/value pairs can be sent as data content just after the initial HTTP header or as part of the URL.

key1=value1&key2=value2&key3=value3

Example 4 shows how key/value pairs are sent to the server.

Several characters are reserved and must be escaped. For example, the ampersand character must be escaped since this character is used to separate the key/value pairs. Escaping the characters is known as URL encoding. The encoding/decoding is normally automatic and transparent to you as a developer. The browser automatically encodes the data, and the server automatically decodes it.

The key/value pairs are embedded in the query string when they are sent as part of the URL. The client typically sends a GET request to the server when sending a query string. As an example, clicking the following link sends a GET request with the key 'q' and the value 'URL encoding' as part of the query string to Google.

Having an understanding of the HTTP protocol is not required when using the Mako Server, but if you are curious, you may want to read the online tutorial HTTP Made Really Easy.

https://www.google.com/search?q=URL+encoding

Although one can send form data as a GET request, it is more common to send form data as a POST request and append the URL-encoded data just after the HTTP header. A browser sends POST data as "application/x-www-form-urlencoded" to the server.

In Lua, a table represents key-value pairs. The method request:data() returns a Lua table with all key/value pairs sent to the server. The following example shows how to iterate and print all URL-encoded data sent from the client to the server. The printed data chunks, via function response:write(), are sent as a response to the HTTP client that performed the request.

 <?lsp for key,value in pairs(request:data()) do response:write("key=", key, ", value=", value, "<br>") end ?>

Example 5 shows how to iterate and print out all key/value pairs received from the client.

You can save the above example in an LSP page, run the Mako Server, and enter the key/value pairs in the browser as follows:

http://server-name/page-name.lsp?key1=value1&key2=value2&key3=value3

The returned page should look like the following:

 key=key1, value=value1 key=key2, value=value2 key=key3, value=value3

Example 6: The response from running the example code in Example 5.

HTML Forms

A form may contain many different types of input elements that allow the user to enter and send information to the server. The input element types you can use in a form are: text fields, text area, drop-downs, radio buttons, check boxes, etc.. They are combined together inside <form> and </form> tags. The values of all the input elements are submitted to the server-side when the user clicks a submit button. As an example, the following HTML illustrates how one could design a basic login form.

 <form method="post"> <input type = "text" name="username"> <input type = "submit" value="Login"> </form> 

Example 7: A standard HTML form with no server-side code generation.

You can save the above example as an LSP page and run the example using the Mako Server. Although the example is not a complete HTML page with all required HTML tags, such as the body tag, etc., the example will still run in your browser. You can also press the submit button, and the browser will send this as a POST request to the server. 

HTTP GET and POST

LSP makes it easy to include HTML and process data sent from the browser inside the same LSP page. The first browser request is typically sent as a HTTP GET request and the HTML form data is typically sent as a HTTP POST request when the user clicks the submit button.

The image to the right shows a typical GET – POST sequence.

If you try the above example in the Mako Server, you’ll notice that nothing happens when you press the submit button. The form is sent as POST data from the browser to the server, but the above LSP page includes no server-side logic to manage the submitted data. Let’s go ahead and add some basic Lua code that manages the submitted data.

 <?lsp local username local session = request:session() local data=request:data() if request:method() == "POST" then if data.username then username=data.username request:session(true).username=username elseif session then session:terminate() end else username = session and session.username end ?> <form method="post"> <?lsp if username then ?> <h1>Hello <?lsp=username?></h1> <input type = "submit" value="Logout"> <?lsp else ?> <input type = "text" name="username"> <input type = "submit" value="Login"> <?lsp end ?> </form> 

Example 8: A complete LSP example with server-side HTTP POST processing.

Click the button above to run Example 8 on our online tutorial server. Once you’re there and after running Example 8, select LSP from the left panel to explore additional LSP examples. You can also download the Example 8 source code from GitHub, where you’ll find instructions for running it with the Mako Server.

How Example 8 Works

If you look at the above example, you will notice that we have also changed how the server dynamically creates the form in addition to the new server-side logic at the top. Lines 17 to 25 dynamically create an HTML login form or an HTML logout form. The login form is dynamically created if the variable username is nil. The logout form is dynamically created if the variable username is not nil.

We fetch the session object on line 3. Method request:session() returns nil if we do not have a session, which is the case when we are not logged in.

We fetch the submitted data on line 4. Method request:data() returns a Lua table with all the submitted key/value pairs sent from the browser when the submit button was pressed. The table will be empty if no data was submitted.

We check if this is an HTML form submitted on line 5 -- i.e. if this is an HTTP POST. The browser will not send a POST request without the user pressing the submit button. The first time you load the page or press the refresh button, you will end up on line 13. The code on line 13 retrieves the username if we have a session -- i.e., if the user is logged in.

We check if the submitted data contains "username" on line 6. This is only true if the user submits the data in the login form since only the login form includes an "input" (line 22) whose name is "username". If the login button is pressed, the username is permanently stored in the session object created on line 8.

If the logout button is pressed, line 6 evaluates to false, and we end up on line 10.

The following two images show our basic login page in action.

image image

LSP: A Deeper Dive

In Example 3, we show how the text from Example 2 is translated into Lua code. However, that example is not an exact representation of how the parsing works so let's take a closer look at a more direct example. Below is Example 9 that uses ba.parselsp() to parse the text from Example 2. As the LSP documentation explains, the LSP preprocessor looks for two specific tags: <?lsp ?> and <?lsp= ?>.

 <?lsp local luaCode = ba.parselsp[[ <?lsp if request:method() == "GET" then ?> You sent a GET request <?lsp else ?> You sent a <?lsp=request:method()?> request <?lsp end ?> ]] response:setcontenttype"text/plain" print("BEGIN parsed LSP page\n",luaCode,"\nEND parsed LSP page\n\n") local action=load(luaCode) -- Compile Lua source code action(_ENV,pathname,page,app) -- Execute compiled source code ?> 

Example 9: How LSP parsing works.

By clicking the above run button, you'll see how the LSP code is parsed, compiled, and then run using the action() function. Keep in mind that the action() function does not exist until the code is compiled; load() turns the returned Lua source code chunk into a callable function.

Here's the step-by-step breakdown of how Example 9 works:

  1. Parsing the LSP Code: lines 3 - 9
    We pass the content from Example 2 into ba.parselsp() using a Lua multiline string -- [[ Example 2 ]]. The LSP tags and any Lua expressions inside them are detected by ba.parselsp() , which transforms the LSP file into pure Lua source code. When run, this Lua code will generate the desired dynamic content.

  2. Generating Lua Code: line 3
    After parsing, the result is stored in the variable luaCode . This variable contains all the logic and output instructions as valid Lua source code.

  3. Displaying the Generated Code: line 12
    Before execution, the Lua source code is printed using print() to illustrate what the final Lua source looks like.

  4. Compiling the Generated Code: line 14
    Using Lua's load() function, the generated Lua source code is turned into a callable function. This step essentially "activates" the source code, preparing it for execution.

  5. Running the Compiled Code: line 15
    Once compiled, the resulting function action is called with the required parameters:

    • The environment table ( _ENV )
    • The relative path ( pathname )
    • The page table ( page )
    • The parent application table ( app )

    These arguments are covered in detail in the documentation under The Command (Request/Response) Environment.

  6. Producing Dynamic Output
    With everything in place, the action() function executes the logic defined in the original LSP content. This allows the server to respond dynamically based on factors such as the request method or other client-provided data.

LSP Examples

Since LSP pages are translated directly into Lua code, you can use any standard Lua syntax within them. The following example shows how to declare and call several functions within an LSP page.

 Joke of the Day: <?lsp local function pony() ?> What do you call a pony with a cough? A little horse. <?lsp end local function hat() ?> What did one hat say to the other? You wait here. I'll go on a head. <?lsp end local function dog() ?> What do you call a magic dog? A labracadabrador. <?lsp end local function shark() ?> What did the shark say when he ate the clownfish? This tastes a little funny. <?lsp end local function carrot() ?> What's orange and sounds like a carrot? A parrot. <?lsp end response:setcontenttype"text/plain" local jokes={pony,hat,dog,shark,carrot} -- Call one of the jokes functions jokes[ba.rnd(1,#jokes)]() ?> 

Example 10: Inner functions within an LSP page.

Example 10 defines several functions, each printing a different joke. It then selects one of these functions at random and executes it, resulting in a single, randomly chosen “Joke of the Day” displayed as plain text when the page is loaded.

Instead of defining multiple functions in a single LSP page, you can store your jokes in separate text (.txt) files and let the server handle which one to display. In this example, we randomly select one of five text files and then use server-side routing to delegate the response directly to that file.

 <?lsp local jokes = { "pony.txt", "hat.txt", "dog.txt", "shark.txt", "carrot.txt" } -- Select one of the five joke files at random local joke = jokes[ba.rnd(1, #jokes)] response:setcontenttype"text/plain" -- Forward the response to the selected file, letting the server handle the rest response:forward(joke) ?> 

Example 11: server side delegation (routing).

Validating Form Submissions with Encrypted Tokens

When creating public-facing HTML forms, it's critical to guard against bots and malicious automated submissions. While CAPTCHAs are the traditional go-to, they're often overkill-or simply annoying-for simple forms like email sign-ups.

This example shows a lightweight alternative using AES encryption and JavaScript to verify that a form submission was initiated by a real browser and not some automated script.

The technique works by embedding a server-generated secret in the form that gets validated on submission. Here's why it's effective and worth using:

  • No third-party service required (unlike CAPTCHA).
  • Hard to spoof without access to your encryption key.
  • Stops basic bots that can't process JavaScript or manipulate encrypted tokens.

Let's look at how this is implemented using Lua Server Pages (LSP) and Barracuda App Server's crypto APIs.

This code implements a basic anti-bot mechanism using AES encryption to verify that the form was generated and submitted by a real browser:

 <?lsp local rnd,secret local key=app.key if not key then -- first time this page executes key=ba.aeskey(32) app.key=key -- Persistently store key in page table end if "POST" == request:method() then local data=request:data() local calc=data.calc or "0" secret=ba.aesdecode(key, data.secret or "") print(secret == calc and "Valid POST" or "Invalid POST") end rnd=ba.rnd(1,100000) secret=ba.aesencode(key,tostring(rnd+1)) ?> <form method="post"> <label for="email">Email</label> <input type="email" name="email" value="" placeholder="Enter your email address"> <input type="hidden" id="calc" name="calc" value="<?lsp=rnd?>" > <input type="hidden" name="secret" value="<?lsp=secret?>" > <button type="submit">Sign Up</button> </form> <script> const calcField = document.querySelector('input[name="calc"]'); if (calcField) { const currentValue = parseInt(calcField.value, 10); calcField.value = currentValue + 1; } </script> 

Example 12: form submission validation.

  • On page load, it generates a random number rnd, encrypts rnd + 1, and embeds both rnd and the encrypted value (secret) in hidden form fields.
  • The client-side script increments rnd by 1 before the form is submitted, so calc = rnd + 1.
  • On POST, the server decrypts the secret and checks if it equals calc.
  • If they match, the request is considered valid; otherwise, it's invalid.

This ensures that only a client capable of running JavaScript (not a basic bot) can produce a valid POST.

LSP and Server-Side Routing

Server-side routing is a fundamental technique in web development that determines how a server responds to client requests—whether loading a webpage, submitting a form, or accessing a particular resource. In other words, it is how the server decides which code handles a given request based on factors like the requested URL path and the HTTP method (GET, POST, PUT, DELETE, etc.).

The Barracuda App Server manages server-side routing using its Virtual File System. When working with derivatives like the Mako Server or Xedge, you typically don’t need to worry about setting up routing for basic applications. That’s because these servers automatically insert an LSP-enabled Resource Reader into the Virtual File System for each created application. Within the Resource Reader directory, you can place any number of .lsp files (Lua Server Pages).

For more advanced application designs, however, you may want to look under the hood at how functions like ba.parselsp() are used within a content management system (CMS) to build complete, dynamic applications. The tutorial How to Build an Interactive Dashboard App offers two ready-to-use CMS-powered dashboard examples that rely on ba.parselsp() as part of their internal CMS engine. This tutorial can serve as a roadmap for implementing more sophisticated routing and content management in your projects.

Modern HTML Form Alternatives: The Fetch API and AJAX

Now that you've learned how to submit HTML forms and generate dynamic responses on the server, it's time to explore some modern alternatives. One of the most widely used tools today is the Fetch API, a cleaner, more flexible way to send and receive data asynchronously. To get started, check out the AJAX for Beginners Tutorial, where we walk through how to use the Fetch API to update parts of a page without a full reload.

Posted in Tutorials