request:write(things...)request:url_for(name_or_obj, params, query_params=nil, ...)request:build_url(path, [options])request:flow(module_name)request:html(fn)request:get_request()application.layoutapplication.error_pageapplication.views_prefixapplication.actions_prefixapplication.flows_prefixapplication.Requestapplication:match([route_name], route_patch, action_fn)application:get(...)application:post(...)application:delete(...)application:put(...)application:enable(feature)application:before_filter(fn)application:include(other_app, opts={})application:find_action(name, resolve=true)Application:extend([name], fields={}, [init_fn])Every HTTP request that is handled by Lapis follows the same basic flow afterbeing handed off from processing server. The first step is routing. The path ofan incoming request is matched against the routes defined in your applicationto look up the correspondingaction function. An action is a regular Luafunction that will be called if the associated route matches.
Anaction function is invoked with one argument, arequestobject, when processing a request. This object containsproperties and methods that allow you to access data bout the request, likeform paramters and URL parameters.
The request object is mutable, it’s a place where you can store any data youwant to share between your actions and views. Additionally, the request objectis your interface to the webserver on how the result is sent to the client.
The return value of the action is used to control how the output is rendered. Astring return value will be rendered to the browser directly. A table returnvalue will be used as therender options. If there is morethan one return value, all of them are merged into the final result. You canreturn both strings and tables to control the output.
If there is no route that matches the request then the default route handler isexecuted, read more inapplicationcallbacks.
To define an action, you minimally need two components: a URL path pattern andan action function. Optionally, you can assign a name to the action, which canbe referenced when generating URLs in other sections of your app.
locallapis=require("lapis")localapp=lapis.Application()-- an unnamed actionapp:match("/",function(self)return"hello"end)-- an action with a nameapp:match("logout","/logout",function(self)return{status=404}end)-- a named action with a path parameter that loads the action function by-- module nameapp:match("profile","/profile/:username","user_profile")lapis=require"lapis"classAppextendslapis.Application-- an unnamed action"/":=>"hello"-- an action with a name[logout:"/logout"]:=>status:404-- a named action with a path parameter that loads the action function by-- module name[profile:"/profile/:username"]:"user_profile"Instead of directly providing a function value to the action definition, youcan supply a string. The action will then be lazily loaded by the module nameupon its first execution. The
actions_prefixis prepended to the beginningof the module name, with the default being"actions.".
Route patterns use a special syntax to define dynamic parameters of the URL andassign a name to them. The simplest routes have no parameters though:
locallapis=require("lapis")localapp=lapis.Application()app:match("/",function(self)end)app:match("/hello",function(self)end)app:match("/users/all",function(self)end)lapis=require"lapis"classAppextendslapis.Application"/":=>"/hello":=>"/users/all":=>The simplest route patterns match the path verbatim with no variables orwildcards. For any route pattern, the leading/ is mandatory. A route patternalways matches the entire path, from start to finish. A request to/hello/world will not match/hello, and will instead fail with a not founderror.
You can specify a named parameter by using a: immediately followed by thename. This parameter will match as many characters as possible excluding the/ character:
app:match("/page/:page",function(self)print(self.params.page)end)app:match("/post/:post_id/:post_name",function(self)-- ...end)lapis=require"lapis"classAppextendslapis.Application"/page/:page":=>print@params.page"/post/:post_id/:post_name":=>In the example above we called
The captured values of the route parameters are saved in theparams field ofthe request object by their name. A named parameter must contain at least 1character, and will fail to match otherwise.
A splat,*, is a special pattern that matches as much as it can, includingany/ characters. This splat is stored as thesplat named parameter in theparams table of the request object. If the splat is the last character in theroute, it will match until the end of the provided path. However, if there isany text following the splat in the pattern, it will match as much as possibleup until any text that matches the remaining part of the pattern.
app:match("/browse/*",function(self)print(self.params.splat)end)app:match("/user/:name/file/*/download",function(self)print(self.params.name,self.params.splat)end)lapis=require"lapis"classAppextendslapis.Application"/browse/*":=>print@params.splat"/user/:name/file/*/download":=>print@params.name,@params.splatIf you put any text directly after the splat or the named parameter it will notbe included in the named parameter. For example you can match URLs that end in.zip with/files/:filename.zip
Parentheses can be used to make a section of the route optional:
/projects/:username(/:project)The above would match either/projects/leafo or/projects/leafo/lapis. Anyparameters within optional components that don’t match will have a value ofnil from within the action.
These optional components can be nested and chained as much as you like:
/settings(/:username(/:page))(.:format)A character class can be applied to a named parameter to restrict whatcharacters can match. The syntax modeled after Lua’s pattern character classes.This route will make sure the thatuser_id named parameter only containsdigits:
/user/:user_id[%d]/postsAnd this route would only match hexadecimal strings for thehex parameter.
/color/:hex[a-fA-F%d]Routes are searched first by precedence, then by the order they were defined.Route precedence from highest to lowest is:
/hello/world/hello/:variable:variable will decrease the precedence of the route/hello/*/hello/*spat and/hello/*splat/world/*rest, the second one will be checked before the first.It’s useful to give names to your routes so links to other pages can begenerated just by knowing the name of the page instead of hard-coding thestructure of the URL.
If the route of the action is a table with a singlepair, then the key of that table is the name and the value is the pattern.MoonScript gives us convenient syntax for representing this:
locallapis=require("lapis")localapp=lapis.Application()app:match("index","/",function(self)returnself:url_for("user_profile",{name="leaf"})end)app:match("user_profile","/user/:name",function(self)return"Hello "..self.params.name..", go home: "..self:url_for("index")end)lapis=require"lapis"classextendslapis.Application[index:"/"]:=>@url_for"user_profile",name:"leaf"[user_profile:"/user/:name"]:=>"Hello #{@params.name}, go home: #{@url_for "index"}"We can generate the paths to various actions using@url_forself:url_for(). The first argument is the name of theroute, and the second optional argument is a table of values to fill aparameterized route with.
Read more abouturl_for to see thedifferent ways to generate URLs to pages.
It’s common to have a single action do different things depending on the HTTPverb. Lapis comes with some helpers to make writing these actions simple.respond_to takes a table indexed by HTTP verb with a value of the function toperform when the action receives that verb.
locallapis=require("lapis")localrespond_to=require("lapis.application").respond_tolocalapp=lapis.Application()app:match("create_account","/create-account",respond_to({GET=function(self)return{render=true}end,POST=function(self)do_something(self.params)return{redirect_to=self:url_for("index")}end}))lapis=require"lapis"importrespond_tofromrequire"lapis.application"classAppextendslapis.Application[create_account:"/create-account"]:respond_to{GET:=>render:truePOST:=>do_something@paramsredirect_to:@url_for"index"}respond_to can also take a before filter of its own that will run before thecorresponding HTTP verb action. We do this by specifying abefore function.The same semantics ofbefore filters apply, so if you call@writeself:write() then the rest of the action will not getrun.
locallapis=require("lapis")localrespond_to=require("lapis.application").respond_tolocalapp=lapis.Application()app:match("edit_user","/edit-user/:id",respond_to({before=function(self)self.user=Users:find(self.params.id)ifnotself.userthenself:write({"Not Found",status=404})endend,GET=function(self)return"Edit account "..self.user.nameend,POST=function(self)self.user:update(self.params.user)return{redirect_to=self:url_for("index")}end}))lapis=require"lapis"importrespond_tofromrequire"lapis.application"classAppextendslapis.Application"/edit_user/:id":respond_to{before:=>@user=Users\find@params.id@writestatus:404,"Not Found"unless@userGET:=>"Edit account #{@user.name}..."POST:=>@user\update@params.userredirect_to:@url_for"index"}On anyPOST request, regardless of whetherrespond_to is used or not, iftheContent-type header is set toapplication/x-www-form-urlencoded thenthe body of the request will be parsed and all the parameters will be placedinto@paramsself.params.
You may have also seen theapp:get() andapp:post()methods being called in previous examples. These are wrappers aroundrespond_to that let you quickly define an action for a particular HTTP verb.You'll find these wrappers for the most common verbs:get,post,delete,put. For any others you'll need to userespond_to.
app:get("/test",function(self)return"I only render for GET requests"end)app:delete("/delete-account",function(self)-- do something destructiveend)Sometimes you want a piece of code to run before every action. A good exampleof this is setting up the user session. We can declare a before filter, or afunction that runs before every action, like so:
localapp=lapis.Application()app:before_filter(function(self)ifself.session.userthenself.current_user=load_user(self.session.user)endend)app:match("/",function(self)return"current user is: "..tostring(self.current_user)end)lapis=require"lapis"classAppextendslapis.Application@before_filter=>if@session.user@current_user=load_user@session.user"/":=>"current user is: #{@current_user}"You are free to add as many as you like by calling@before_filterapp:before_filter multiple times. They will be run inthe order they are registered.
If a before filter calls the@writeself:write() method then the action will be cancelled.For example we can cancel the action and redirect to another page if somecondition is not met:
localapp=lapis.Application()app:before_filter(function(self)ifnotuser_meets_requirements()thenself:write({redirect_to=self:url_for("login")})endend)app:match("login","/login",function(self)-- ...end)lapis=require"lapis"classAppextendslapis.Application@before_filter=>unlessuser_meets_requirements!@writeredirect_to:@url_for"login"[login:"/login"]:=>...
@writeself:write()is what processes the return value of aregular action, so the same things you can return in an action can be passedto@writeself:write()
When a request is processed, the action function is passed arequest objectas its first argument. Because of Lua’s convention to call the first argumentself, we refer to the request object asself when in the context of anaction.
The request object contains the following fields:
| Name | Description |
@route_nameself.route_name | The name of the route that was matched during routing, if available Show Example |
@paramsself.params | A table containing all request parameters merged together, including query parameters and form-encoded parameters from the body of the request. SeeRequest Parameters for more details. |
@GETself.GET | A table containing only the query parameters included in the URL (eg. |
@POSTself.POST | A table containing only the form encoded parameters included in the body of the request. Note that this field is included for any request with form data in the body, regardless of the HTTP verb. |
@reqself.req | An object containing the internal request information generated by the underlying server processing the request. The full structure of this object is intentionally undocumented. Only resort to referencing it if you need server specific data not available elsewhere. |
@resself.res | An object used to used to generate the response for the client at the end of the request. The structure of this object is specific to the underlying server processing the request, and is intentionally undocumented. |
@appself.app | The instance of the |
@cookiesself.cookies | A proxy table that can be used to read any cookies that have been included with the request. New cookies can be stored for the response by setting them on this table. Only strings are supported as field names and values. SeeCookies for more information. Show Example |
@sessionself.session | A proxy table for reading and writing the dynamically created session object. A session object is a signed, json-encoded object that is transferred via cookies. Because it is signed, it’s safe to include data in it that you know the end user can not tamper with. SeeSession for more information. |
@optionsself.options | A table of options that will controls how the request is rendered. It is populated by calls to |
@bufferself.buffer | The output buffer containing the fragments of text that will be written to the client after all processing is complete. Typically you'll not need to touch this manually. It is populated via the |
request.reqThe raw request table@reqself.req contains data from the request providedby the underlying server. The format of this data may be server specific, butgenerally will contain the following common fields:
| Name | Description |
@req.headersself.req.headers | Request headers table |
@req.parsed_urlself.req.parsed_url | A table generated containing all the components of the requesting URL. Contains fields like |
@req.params_getself.req.params_get | Unprocessed table of parameters from the query string of the requesting URL |
@req.params_postself.req.params_post | Unprocessed table of parameters from the body of the request |
The@cookiesself.cookies table in the request lets you read and write cookies.
The cookies object,@cookiesself.cookies, is a proxy object. It supportsreading existing cookies by indexing the object by name, and writing newcookies by writing them to the table. When iterating, the cookies object willalways appear as an empty table. The initial cookies are stored in the__index of the metatable.
app:match("/reads-cookie",function(self)print(self.cookies.foo)end)app:match("/sets-cookie",function(self)self.cookies.foo="bar"end)classAppextendslapis.Application"/reads-cookie":=>print@cookies.foo"/sets-cookie":=>@cookies.foo="bar"All new cookies created are given the default attributesPath=/; HttpOnly(know as asessioncookie). You canconfigure a cookie’s settings by overriding the thecookie_attributes methodon your application. Here’s an example that adds an expiration date to cookiesto make them persist:
localdate=require("date")localapp=lapis.Application()app.cookie_attributes=function(self)localexpires=date(true):adddays(365):fmt("${http}")return"Expires="..expires.."; Path=/; HttpOnly"enddate=require"date"classAppextendslapis.Applicationcookie_attributes:(name,value)=>expires=date(true)\adddays(365)\fmt"${http}""Expires=#{expires}; Path=/; HttpOnly"Thecookie_attributes method takes the request object as the first argument(self) and then the name and value of the cookie being processed.
The@sessionself.session is a more advanced way to persist data over requests.The content of the session is serialized to JSON and stored in a speciallynamed cookie. The serialized cookie is also signed with your application secretso it can’t be tampered with. Because it’s serialized with JSON you can storenested tables and other primitive values.
The session object,@sessionself.session, is a proxy object. It supportsreading values by indexing the object, and writing new session fields bywriting to the table. When iterating, the session object will always appear asan empty table.
The session object can be manipulated the same way as the cookies object:
app.match("/",function(self)ifnotself.session.current_userthenself.session.current_user="Adam"endend)"/":=>unless@session.current_user@session.current_user="Adam"By default the session is stored in a cookie calledlapis_session. You canoverwrite the name of the session using thesession_nameconfigurationvariable. Sessions are signed with yourapplication secret, which is stored in the configuration valuesecret. It ishighly recommended to change this from the default.
-- config.lualocalconfig=require("lapis.config").configconfig("development",{session_name="my_app_session",secret="this is my secret string 123456"})-- config.moonimportconfigfromrequire"lapis.config"config"development",->session_name"my_app_session"secret"this is my secret string 123456"The request object contains several fields that facilitate access touser-supplied parameters sent with the request. These parameters areautomatically loaded from the following sources:
/users/:id will create a parameter namedid.POST andPUT, the body will be automatically parsed if the content type isapplication/x-www-form-urlencoded ormultipart/form-data.?. For example,/users?filter=blue will create a parameter calledfilter with the value"blue".The@paramsself.params object is a combination of all the default loadedparameters listed above. URL parameters have the highest precedence, followedby body parameters, and then query parameters. This means that an:id URLparameter will not be overwritten by an?id= query parameter.
Headers and cookies can also be accessed on the request object, but they arenot included in the parameters object.
The body of the request is only parsed if the content type isapplication/x-www-form-urlencoded ormultipart/form-data. For requests thatuse another content type, such asjson, you can use thejson_params helperfunction to parse the body.
SeeHow can I read JSON HTTP body?.
A query parameter without a value is treated as a boolean parameter and willhave the valuetrue.
/hello?hide_description&color=blue →{ hide_description = true, color = "blue"}
It is common to use the[] syntax within a parameter name to represent nesteddata within parameters. Lapis supports expanding this syntax for simplekey, value objects:
/hello?upload[1][name]=test.txt&upload[2][name]=file.png →{ upload = { ["1"] = { name = "test.txt" }, -- note that strings are not converted to numbers! ["2"] = { name = "file.png"} }}Lapis does not support the empty
[]syntax that you may have seen in otherframeworks for creating arrays. Only simple object expansion is supported.Generally we encourage the application developer to do the parsing sinceadvanced parameter can unknowingly introduce bugs.
The value of a parameter can either be a string,true, or a simple table. Nocomplex parsing or validation is performed on parameters; it is theresponsibility of the application creator to verify and sanitize anyparameters. For instance, if you're expecting a number, you will need toconvert the value to a number using something like the Lua built-intonumber.
Lapis provides avalidation module toassist with verifying that user-supplied data matches a set of constraints thatyou provide.
Duplicate parameter names are overwritten by subsequent values. Due to hashtable ordering, the final value may not be consistent, so we recommend againstsetting the same parameters multiple times.
When using Nginx, a default limit of 100 parameters is parsed by default fromthe body and query. This is to prevent malicious users from overloading yourserver with a large amount of data.
Are you storing or processing user input as a string? We highly recommendadding limits on the maximum length of the string and trimming whitespacefrom the sides. Additionally, verifying that the data is a valid Unicodestring can help prevent any processing errors by your database.
request:write(things...)Appends all of the arguments to the output buffer or options table. The actionperformed varies depending on the type of each argument:
string — The string is appended to the output buffer.function (or callable table) — The function is called with the output buffer, and the result is recursively passed towrite.table — Key/value pairs are assigned into the@optionsself.options, while all other values are recursively passed towrite.Under most circumstances, it is unnecessary to callwrite because the returnvalue of an action is automatically passed to it. In before filters,writeserves a dual purpose: it writes to the output and cancels any further actionsfrom running.
request:url_for(name_or_obj, params, query_params=nil, ...)Generates a URL forname_or_obj.
The function
url_foris somewhat misleadingly named as it typically generates a path to the requested page. To obtain the complete URL, you can combine this function withbuild_url.
Ifname_or_obj is a string, the route of that name is looked up and populatedusing the values inparams. If no named route exists that matches, then anerror is thrown.
Given the following routes:
app:match("index","/",function()-- ...end)app:match("user_data","/data/:user_id/:data_field",function()-- ...end)classAppextendslapis.Application[index:"/"]:=>-- ..[user_data:"/data/:user_id/:data_field"]:=>-- ...URLs to the pages can be generated like this:
-- returns: /self:url_for("index")-- returns: /data/123/heightself:url_for("user_data",{user_id=123,data_field="height"})-- returns: /@url_for"index"-- returns: /data/123/height@url_for"user_data",user_id:123,data_field:"height"If the third argument,query_params, is supplied, it will be converted intoquery parameters and appended to the end of the generated URL. If the routedoesn’t take any parameters in the URL thennil, or empty object, must bepassed as the second argument:
-- returns: /data/123/height?sort=ascself:url_for("user_data",{user_id=123,data_field="height"},{sort="asc"})-- returns: /?layout=newself:url_for("index",nil,{layout="new"})-- returns: /data/123/height?sort=asc@url_for"user_data",{user_id:123,data_field:"height"},sort:"asc"-- returns: /?layout=new@url_for"index",nil,layout:"new"Any optional components of the route will only be included if all of theenclosed parameters are provided. If the optional component does not have anyparameters then it will never be included.
Given the following route:
app:match("user_page","/user/:username(/:page)(.:format)",function(self)-- ...end)classAppextendslapis.Application[user_page:"/user/:username(/:page)(.:format)"]:=>-- ...The following URLs can be generated:
-- returns: /user/leafoself:url_for("user_page",{username="leafo"})-- returns: /user/leafo/projectsself:url_for("user_page",{username="leafo",page="projects"})-- returns: /user/leafo.jsonself:url_for("user_page",{username="leafo",format="json"})-- returns: /user/leafo/code.jsonself:url_for("user_page",{username="leafo",page="code",format="json"})-- returns: /user/leafo@url_for"user_page",username:"leafo"-- returns: /user/leafo/projects@url_for"user_page",username:"leafo",page:"projects"-- returns: /user/leafo.json@url_for"user_page",username:"leafo",format:"json"-- returns: /user/leafo/code.json@url_for"user_page",username:"leafo",page:"code",format:"json"If a route contains a splat, the value can be provided via the parameter namedsplat:
app:match("browse","/browse(/*)",function(self)-- ...end)classAppextendslapis.Application[browse:"/browse(/*)"]:=>-- ...-- returns: /browseself:url_for("browse")-- returns: /browse/games/recentself:url_for("browse",{splat="games/recent"})-- returns: /browse@url_for"browse"-- returns: /browse/games/recent@url_for"browse",splat:"games/recent"url_forIfname_or_obj is a table, then theurl_params method is called on thattable, and the return values are passed tourl_for.
Theurl_params method takes as arguments therequest object, followed byanything else passed tourl_for originally.
It’s common to implementurl_params on models, giving them the ability todefine what page they represent. For example, consider aUsers model thatdefines aurl_params method, which goes to the profile page of the user:
localUsers=Model:extend("users",{url_params=function(self,req,...)return"user_profile",{id=self.id},...end})classUsersextendsModelurl_params:(req,...)=>"user_profile",{id:@id},...We can now just pass an instance ofUsers directly tourl_for and the pathfor theuser_profile route is returned:
localuser=Users:find(100)self:url_for(user)-- could return: /user-profile/100user=Users\find100@url_foruser-- could return: /user-profile/100You might notice we passed... through theurl_params method to the returnvalue. This allows the thirdquery_params argument to still function:
localuser=Users:find(1)self:url_for(user,{page="likes"})-- could return: /user-profile/100?page=likesuser=Users\find1@url_foruser,page:"likes"-- could return: /user-profile/100?page=likesurl_key methodThe value of any parameter inparams is a string then it is inserted into thegenerated path as is. If the value is a table, then theurl_key method iscalled on it, and the return value is inserted into the path.
For example, consider aUsers model which we've generated aurl_key methodfor:
localUsers=Model:extend("users",{url_key=function(self,route_name)returnself.idend})classUsersextendsModelurl_key:(route_name)=>@idIf we wanted to generate a path to the user profile we might normally writesomething like this:
localuser=Users:find(1)self:url_for("user_profile",{id=user.id})user=Users\find1@url_for"user_profile",id:user.idTheurl_key method we've defined lets us pass theUser object directly astheid parameter and it will be converted to the id:
localuser=Users:find(1)self:url_for("user_profile",{id=user})user=Users\find1@url_for"user_profile",id:userThe
url_keymethod takes the name of the path as the first argument, so wecould change what we return based on which route is being handled.
request:build_url(path, [options])Builds an absolute URL for the path. The current request’s URI is used to buildthe URL.
For example, if we are running our server onlocalhost:8080:
self:build_url()--> http://localhost:8080self:build_url("hello")--> http://localhost:8080/helloself:build_url("world",{host="leafo.net",port=2000})--> http://leafo.net:2000/world@build_url!--> http://localhost:8080@build_url"hello"--> http://localhost:8080/hello@build_url"world",host:"leafo.net",port:2000--> http://leafo.net:2000/worldThe following options are supported:
| Name | Description | Default |
scheme | eg. | Current scheme |
host | Current host | |
port | If port matches the default for the scheme (eg. 80 for http) then it will be left off | Current port |
fragment | Part of the URL following the | nil |
query | Part of the URL following the | nil |
request:flow(module_name)Loads a flow bymodule_name with theflows_prefix on the current requestobject. If the flow with that name has been previously loaded, the existingflow instance is returned.
request:html(fn)Returns a new function that implements the buffer writer interface forrendering the contents offn as an HTML scoped function. This is suitable forreturning from an action.
request:get_request()Returnsself. This method is useful in scenarios where the request object isbeing proxied, and you need direct access to the instance of the request objectfor mutation. Examples include accessing the request object in a flow or in awidget where the request object is embedded into thehelper chain.
Render options are set by explicit calls towrite or by the return value ofthe action function. They are accumulated in the@optionsself.options field ofthe request object. Typically, an action function does not generate theresponse directly but sets the options to be used by Lapis during the renderingphase of the request, which happens immediately after executing the action.
For example, in the following action, therender andstatus fields are usedto set the HTTP status response code and specify a view by name to be used togenerate the response body.
app:match("/",function(self)return{render="error",status=404}end)classextendslapis.Application"/":=>render:"error",status:404Here are the options that can used to control the how the response is generated:
| Name | Description | Default |
status | Sets HTTP status code of the response (eg. 200, 404, 500, …) Show Example |
|
render | Renders a view to the output buffer during the rendering phase of the request. If the value is Show Example | nil |
content_type | Sets the Show Example | nil |
headers | A table of headers to add to the response | nil |
json | Renders the the JSON encoded value of the option. The content type is set to Show Example | nil |
layout | Overrides the layout from the application default. Set to Show Example | nil |
redirect_to | Sets status to 302 and uses the value of this option for the Show Example | nil |
skip_render | Set to Show Example | nil |
When rendering JSON make sure to use thejson render option. It willautomatically set the correct content type and disable the layout:
app:match("/hello",function(self)return{json={hello="world"}}end)classAppextendslapis.Application"/hello":=>json:{hello:"world!"}The following fields on the application object are designed to be overwrittenby the application creator to configure how the application processes arequest. These fields can either be overwritten on the instance or by settingthe instance fields when creating a new Application class.
application.layoutThis specifies a default view that will be used to wrap the content of theresults response. A layout is always rendered around the result of the action’srender unlesslayout is set tofalse, or a renderer with a separate contenttype is used (e.g.,json).
It can either be an instance of a view or a string. When a string is provided,the layout is loaded as a module via therequire using the module name{views_prefix}.{layout_name}.
Defaultrequire "lapis.views.layout"
application.error_pageThis is the view used to render an unrecoverable error in the defaulthandle_error callback. The value of this field is passed directly to RenderOptionrender, enabling the use of specifying the page by view name ordirectly by a widget or template.
Defaultrequire "lapis.views.error"
application.views_prefixThis is a prefix appended to the view name (joined by.) whenever a view isspecified by string to determine the full module name to require.
Default"views"
application.actions_prefixThis is a prefix appended to the action name (joined by.) whenever an actionis specified by string to determine the full module name to require.
Default"actions"
application.flows_prefixThis is a prefix appended to the flow name (joined by.) whenever a flow isspecified by string to determine the full module name to require.
Default"flows"
application.RequestThis is the class that will be used to instantiate new request objects whendispatching a request.
Defaultrequire "lapis.request"
Application callbacks are special methods that can be overridden to handlespecial cases and provide additional configuration.
Although they are functions stored on the application, they are called likelike actions, meaning the first argument to the function is an instance of arequest object.
application:default_route()When a request does not match any of the routes you've defined, thedefault_route method will be called to create a response.
A default implementation is provided:
functionapp:default_route()-- strip trailing /ifself.req.parsed_url.path:match("./$")thenlocalstripped=self.req.parsed_url:match("^(.+)/+$")return{redirect_to=self:build_url(stripped,{status=301,query=self.req.parsed_url.query,})}elseself.app.handle_404(self)endenddefault_route:=>-- strip trailing /if@req.parsed_url.path\match"./$"stripped=@req.parsed_url.path\match"^(.+)/+$"redirect_to:@build_url(stripped,query:@req.parsed_url.query),status:301else@app.handle_404@The default implementation will check for excess trailing/ on the end of theURL it will attempt to redirect to a version without the trailing slash.Otherwise it will call thehandle_404 method on the application.
This method,default_route, is a normal method of your application. You canoverride it to do whatever you like. For example, this adds logging:
functionapp:default_route()ngx.log(ngx.NOTICE,"User hit unknown path "..self.req.parsed_url.path)-- call the original implementaiton to preserve the functionality it providesreturnlapis.Application.default_route(self)endclassAppextendslapis.Applicationdefault_route:=>ngx.logngx.NOTICE,"User hit unknown path #{@req.parsed_url.path}"super!application:handle_404()In the defaultdefault_route, the methodhandle_404 is called when the pathof the request did not match any routes.
A default implementation is provided:
functionapp:handle_404()error("Failed to find route: "..self.req.request_uri)endclassextendslapis.Applicationhandle_404:=>error"Failed to find route: #{@req.request_uri}"This handler will cause a 500 error and a stack trace for every invalidrequest. If you wish to create a suitable 404 page, this is where you would doit.
By overriding thehandle_404 method instead of thedefault_route, we cancreate a custom 404 page while maintaining the code for removing the trailingslash.
Here’s a straightforward 404 handler that merely displays the text"NotFound!":
functionapp:handle_404()return{status=404,layout=false,"Not Found!"}endclassextendslapis.Applicationhandle_404:=>status:404,layout:false,"Not Found!"application:handle_error(err, trace)Every action executed by Lapis is called throughxpcall. This ensuresthat fatal errors can be captured and a meaningful error page can be producedinstead of the server’s default error page.
The error handler should only be utilized to capture fatal and unexpectederrors. Expected errors are discussed in theException Handlingguide.
Lapis comes with a pre-defined error handler that extracts information aboutthe error and renders it into the template specified byapplication.error_page. This error page includes a stack trace and the errormessage.
If you wish to implement your own error handling logic, you can override thehandle_error method.
-- config.custom_error_page is made up for this examplefunctionapp:handle_error(err,trace)ifconfig.custom_error_pagethenreturn{render="my_custom_error_page"}elsereturnlapis.Application.handle_error(self,err,trace)endend-- config.custom_error_page is made up for this exampleclassAppextendslapis.Applicationhandle_error:(err,trace)=>ifconfig.custom_error_page{render:"my_custom_error_page"}elsesupererr,traceThe request object, orself, that is passed to the error handler is not theone that was created for the request that failed, as it may have been taintedby the failed request via a partial write. Lapis generates an empty requestobject for rendering the error page.
If you need to inspect the original request object to extract information aboutwhy the error occurred, you can access it through@original_requestself.original_request.
Lapis' default error page displays a full stack trace. Therefore, it isrecommended to replace it with a custom one in your production environments andlog the exception in the background to prevent leaking file pathes and functionnames related to your application.
Thelapis-exceptions module augments the error handler to records errorsin a database. It can also email you when there’s an exception.
A Lapis Application can be built either by subclassing it (via MoonScript orextend), or by creating an instance of it and calling the appropriate methodsor overriding the appropriate fields.
application:match([route_name], route_patch, action_fn)Adds a new route to the route group contained by the application. See above formore information on registering actions. Note that routes are inheritance bythe inheritance change of the application object.
You can overwrite a route by re-using the same route name, or path, and thatroute will take precedence over one defined further up in the inheritancechange.
Class approach:
localapp=lapis.Application:extend()app:match("index","/index",function(self)return"Hello world!"end)app:match("/about",function(self)return"My site is cool"end)classextendslapis.Application@match"index","/index",=>"Hello world!"@match"/about",=>"My site is cool"Instance approach:
localapp=lapis.Application()app:match("index","/index",function(self)return"Hello world!"end)app:match("/about",function(self)return"My site is cool"end)app=lapis.Application!app\match"index","/index",=>"Hello world!"app\match"/about",=>"My site is cool"application:get(...)Shortcut method for adding route for a specific HTTP verb by utilizing therespond_to viamatch. Same arguments asmatch.
application:post(...)Shortcut method for adding route for a specific HTTP verb by utilizing therespond_to viamatch. Same arguments asmatch.
application:delete(...)Shortcut method for adding route for a specific HTTP verb by utilizing therespond_to viamatch. Same arguments asmatch.
application:put(...)Shortcut method for adding route for a specific HTTP verb by utilizing therespond_to viamatch. Same arguments asmatch.
application:enable(feature)Loads a module namedfeature usingrequire. If the result of that module iscallable, then it will be called with one argument,application.
application:before_filter(fn)Appends a before filter to the chain of filters for the application. Beforefilters are applied in the order they are added. They receive one argument, therequest object.
A before filter is a function that will run before the action’s function. If awrite takes place in a before filter then the request is ended after thebefore filter finishes executing. Any remaining before filters and the actionfunction are not called.
SeeBefore Filters for more information.
application:include(other_app, opts={})Copies all the routes fromother_app into the current app.other_app can beeither an application class or an instance. If there are any before filters inother_app, every action ofother_app will be be wrapped in a new functionthat calls those before filters before calling the original function.
Options can either be provided in the arugmentopts, or will be pulled fromother_app, with precedence going to the value provided inopts if provided.
Note that application instance configuration likelayout andviews_prefixare not kept from the included application.
| Name | Description | Default |
path | If provided, every path copied over will be prefixed with the value of this option. It should start with a | nil |
name | If provided, every route name will be prefixed with the value of the this option. Provide a trailing | nil |
application:find_action(name, resolve=true)Searches the inheritance chain for the first action specified by the routename,name.
Returns theaction value and the route path object if an action could befound. Ifresolve is true the action value will be loaded if it’s a deferredaction liketrue or a module name
Returnsnil if no action could be found.
Application:extend([name], fields={}, [init_fn])Creates a subclass of the Application class. This method is only available onthe class object, not the instance. Instance fields can be provided as via thefields arugment or by mutating the returned metatable object.
This method returns the newly created class object, and the metatable for anyinstances of the class.
localMyApp,MyApp_mt=lapis.Application:extend("MyApp",{layout="custom_layout",views_prefix="widgets"})functionMyApp_mt:handle_error(err)error("oh no!")end-- note that `match` is a class method, so MyApp_mt is not used hereMyApp:match("home","/",function(self)return"Hello world!"end)