Movatterモバイル変換


[0]ホーム

URL:


Lapis

v1.16.0
LuaMoonScript

Requests and Actions

All Guides

Requests and Actions

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.

Defining an action

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. Theactions_prefix is prepended to the beginningof the module name, with the default being"actions.".

Routes & URL Patterns

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 calledprint to debug. When running insideOpenResty, the output ofprint is sent to the Nginx notice log.

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.splat

If 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

Optional route components

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)

Parameter character classes

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]/posts

And this route would only match hexadecimal strings for thehex parameter.

/color/:hex[a-fA-F%d]

Route precedence

Routes are searched first by precedence, then by the order they were defined.Route precedence from highest to lowest is:

  • Literal routes/hello/world
  • Variable routes/hello/:variable
    • Each additional:variable will decrease the precedence of the route
  • Splat routes routes/hello/*
    • Each additional splat willincrease the precedence of the route. Given the routes/hello/*spat and/hello/*splat/world/*rest, the second one will be checked before the first.

Named Routes

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:Every method on the application that defines a new route has asecond form that takes the name of the route as the first argument:

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.

Handling HTTP verbs

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)

Before Filters

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()

Request Object

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:

NameDescription
@route_nameself.route_name

The name of the route that was matched during routing, if available

Show Example
app:match("my_page","/my-page",function(self)returnassert(self.route_name=="my_page")end)
app\match"my_page","/my-page",=>assert@route_name=="my_page"
@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.?hello=world). Note that this field is included for any request with URL query parameters, regardless of the HTTP verb used for the request.

@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 thelapis.Application that is responding to requests. Note that a single instance is shared across many requests, but there may be multiple instances if there are multiple worker processes handling requests.

@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
app:match("/",function(self)print(self.cookies.last_seen)self.cookies.current_date=tostring(os.time)end)
app\match"/",=>print@cookies.last_seen@cookies.current_date=tostringos.time
@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 towrite, and also set by the return value of your action. SeeRender Options for more information.

@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 thewrite method.

request.req

The 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:

NameDescription
@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 likescheme,path,host,port, andquery

@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

Cookies

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"end
date=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.

Session

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"

Request Parameters

The request object contains several fields that facilitate access touser-supplied parameters sent with the request. These parameters areautomatically loaded from the following sources:

  • URL parameters – These are created when using a route that has a named variable. For instance, a route/users/:id will create a parameter namedid.
  • Body parameters – For request methods that support a body, such asPOST andPUT, the body will be automatically parsed if the content type isapplication/x-www-form-urlencoded ormultipart/form-data.
  • Query parameters – These are parameters included at the end of a request URL following the?. 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?.

Boolean parameters

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"}

Nested Parameters

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.

Parameters Types & Limits

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 Object Methods

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 functionurl_for is 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"

Passing an object tourl_for

Ifname_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/100
user=Users\find100@url_foruser-- could return: /user-profile/100

You 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=likes
user=Users\find1@url_foruser,page:"likes"-- could return: /user-profile/100?page=likes

Using theurl_key method

The 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)=>@id

If 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.id

Theurl_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:user

Theurl_key method 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/world

The following options are supported:

NameDescriptionDefault
scheme

eg.http,https

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#. Must be string

nil
query

Part of the URL following the?. Must be string

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

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:404

Here are the options that can used to control the how the response is generated:

NameDescriptionDefault
status

Sets HTTP status code of the response (eg. 200, 404, 500, …)

Show Example
app:match("/",function(self)return{status=201}end)
app\match"/",=>status:201

200

render

Renders a view to the output buffer during the rendering phase of the request. If the value istrue then the name of the route is used as the view name. Otherwise the value must be a string or a view class. When a string is provided as the view name, it will be loaded as a module withrequire using the full module name{app.views_prefix}.{view_name}

Show Example
app:match("index","/",function(self)return{render=true},"This loads views.index"end)app:match("/page1",function(self)return{render="my_view"}end)app:match("/page2",function(self)return{render=require("helpers.my_view")}end)
app\match"index","/",=>render:true,"This loads views.index"app\match"/page1",=>render:"my_view"app\match"/page2",=>render:require"helpers.my_view"
nil
content_type

Sets theContent-type header

Show Example
app:match("/plain",function(self)return"Plain text",{layout=false,content_type="text/plain"}end)
app\match"/plain",=>"Plain text",layout:false,content_type:"text/plain"
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 toapplication/json and the layout is disabled.

Show Example
app:match("/plain",function(self)return{json={name="Hello world!",ids={1,2,3}}}end)
app\match"/plain",=>json:{name:"Hello world!",ids:{1,2,3}}
nil
layout

Overrides the layout from the application default. Set tofalse to entirely disable the layout. Can either be a renderable object (eg. a Widget or etlua template), or a string. When a string is provided it is used as the view name, it will be loaded as a module withrequire using the full module name{app.views_prefix}.{view_name}

Show Example
app:match("/none",function(self)return"No layout here!",{layout=false,content_type="text/plain"}end)app:match("/mobile",function(self)return"Custom layout",{layout="mobile_layout"}end)
app\match"/none",=>"No layout here!",layout:false,content_type:"text/plain"app\match"/mobile",=>"Custom layout",layout:"mobile_layout"
nil
redirect_to

Sets status to 302 and uses the value of this option for theLocation header. Both relative and absolute URLs can be used. (Combine withstatus to perform 301 redirect)

Show Example
app:match("/old",function(self)return{redirect_to=self:url_for("new")}end)app:match("new","/new",function(self)return"You made it!"end)
app\match"/old",=>redirect_to:@url_for("new")app\match"new","/new",=>"You made it!"
nil
skip_render

Set totrue to cause Lapis to skip it’s entire rendering phase (including content, status, headers, cookies, sessions, etc.). Use this if you manually write the request response in the action method (using low levelngx.print,ngx.header or equivalent). This can be used to implement streaming output, as opposed to Lapis' default buffered output.

Show Example
app:match("/stream",function(self)ngx.print("this will...")ngx.sleep(1)ngx.print("stream to you")ngx.sleep(1)ngx.print("slowly")return{skip_render=true}end)
app\match"/stream",=>ngx.print"this will..."ngx.sleep1ngx.print"stream to you"ngx.sleep1ngx.print"slowly"skip_render:true
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!"}

Application Configuration

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.layout

This 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_page

This 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_prefix

This 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_prefix

This 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_prefix

This 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.Request

This is the class that will be used to instantiate new request objects whendispatching a request.

Defaultrequire "lapis.request"

Callbacks

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)endend
default_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)end
classAppextendslapis.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)end
classextendslapis.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!"}end
classextendslapis.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,trace

The 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.

Application Methods

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.

NameDescriptionDefault
path

If provided, every path copied over will be prefixed with the value of this option. It should start with a/ and a trailing slash should be inlcuded if desired.

nil
name

If provided, every route name will be prefixed with the value of the this option. Provide a trailing. if desired.

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)

[8]ページ先頭

©2009-2025 Movatter.jp