每一个被Lapis
处理的HTTP
请求在被Nginx
处理后都遵循相同的基本流程。第一步是路由。路由是 url
必须匹配的模式。当你定义一个路由时,你也得包括一个处理函数。这个处理函数是一个常规的Lua/MoonScript
函数,若是相关联的路由匹配,则将调用该函数。html
全部被调用的处理函数都具备一个参数(一个请求对象)。请求对象将存储您但愿在处理函数和视图之间共享的全部数据。此外,请求对象是您向Web服务器
了解如何将结果发送到客户端的接口。nginx
处理函数的返回值用于渲染输出。字符串返回值将直接呈现给浏览器。table
的返回值将用做[渲染选项]()。若是有多个返回值,则全部这些返回值都合并到最终结果中。您能够返回字符串和table
以控制输出。数据库
若是没有匹配请求的路由,则执行默认路由处理程序,在[application callbacks]()了解更多。json
路由模式 使用特殊语法来定义URL
的动态参数 并为其分配一个名字。最简单的路由没有参数:api
local lapis = require("lapis") local app = lapis.Application() app:match("/", function(self) end) app:match("/hello", function(self) end) app:match("/users/all", function(self) end)
这些路由与URL
逐字匹配。 /
路由是必需的。路由必须匹配请求的整个路径。这意味着对 /hello/world
的请求将不匹配 /hello
。浏览器
您能够在:
后面理解跟上一个名称来指定一个命名参数。该参数将匹配除/的全部字符(在通常状况下):服务器
app:match("/page/:page", function(self) print(self.params.page) end) app:match("/post/:post_id/:post_name", function(self) end)
在上面的例子中,咱们调用 print 函数来调试,当在openresty中运行时,print的输出是被发送到nginx的notice级别的日志中去的
捕获的路由参数的值按其名称保存在请求对象的 params
字段中。命名参数必须至少包含1个字符,不然将没法匹配。cookie
splat
是另外一种类型的模式,将尽量匹配,包括任何/
字符。 splat
存储在请求对象的 params
表中的 splat
命名参数中。它只是一个单一 *
session
app:match("/browse/*", function(self) print(self.params.splat) end) app:match("/user/:name/file/*", function(self) print(self.params.name, self.params.splat) end)
若是将任何文本直接放在splat
或命名参数以后,它将不会包含在命名参数中。例如,您能够将以.zip
结尾的网址与/files/:filename.zip
进行匹配(那么.zip
就不会包含在命名参数 filename
中)app
圆括号可用于使路由的一部分可选:
/projects/:username(/:project)
以上将匹配 /projects/leafo
或 /projects/leafo/lapis
。可选组件中不匹配的任何参数在处理函数中的值将为nil。
这些可选组件能够根据须要嵌套和连接:
/settings(/:username(/:page))(.:format)
字符类能够应用于命名参数,以限制能够匹配的字符。语法建模在 Lua
的模式字符类以后。此路由将确保该 user_id
命名参数只包含数字:
/color/:hex[a-fA-F%d]
这个路由只匹配十六进制参数的十六进制字符串。
/color/:hex[a-fA-F%d]
首先按优先顺序搜索路由,而后按它们定义的顺序搜索。从最高到最低的路由优先级为:
精确匹配的路由 /hello/world 变化参数的路由 /hello/:variable 贪婪匹配的路由 /hello/*
为您的路由命名是有用的,因此只要知道网页的名称就能够生成到其余网页的连接,而不是硬编码 URL
的结构。
应用程序上定义新路由的每一个方法都有第二个形式,它将路由的名称做为第一个参数:
local lapis = require("lapis") local app = lapis.Application() app:match("index", "/", function(self) return self: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)
咱们可使用self:url_for()
生成各类操做的路径。第一个参数是要调用的路由的名称,第二个可选参数是用于填充 参数化路由 的值的表。
点击[url_for]() 去查看不一样方式去生成 URL
的方法。
根据请求的 HTTP
动词,进行不一样的处理操做是很常见的。 Lapis
有一些小帮手,让写这些处理操做很简单。 respond_to
接收由 HTTP
动词索引的表,当匹配对应的动词执行相应的函数处理
local lapis = require("lapis") local respond_to = require("lapis.application").respond_to local app = 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 }))
respond_to
也能够采用本身的 before
过滤器,它将在相应的 HTTP
动词操做以前运行。咱们经过指定一个 before
函数来作到这一点。与过滤器相同的语义适用,因此若是你调用 self:write()
,那么其他的动做将不会运行.
local lapis = require("lapis") local respond_to = require("lapis.application").respond_to local app = lapis.Application() app:match("edit_user", "/edit-user/:id", respond_to({ before = function(self) self.user = Users:find(self.params.id) if not self.user then self:write({"Not Found", status = 404}) end end, GET = function(self) return "Edit account " .. self.user.name end, POST = function(self) self.user:update(self.params.user) return { redirect_to = self:url_for("index") } end }))
在任何 POST
请求,不管是否使用 respond_to
,若是 Content-type
头设置为 application/x-www-form-urlencoded
,那么请求的主体将被解析,全部参数将被放入 self.params
。
您可能还看到了 app:get()
和 app:post()
方法在前面的示例中被调用。这些都是封装了 respond_to
方法,可以让您快速为特定 HTTP
动词定义操做。你会发现这些包装器最多见的动词:get
,post
,delete
,put
。对于任何其余动词,你须要使用respond_to
。
app:get("/test", function(self) return "I only render for GET requests" end) app:delete("/delete-account", function(self) -- do something destructive end)
有时你想要一段代码在每一个操做以前运行。一个很好的例子是设置用户会话。咱们能够声明一个 before 过滤器,或者一个在每一个操做以前运行的函数,像这样:
local app = lapis.Application() app:before_filter(function(self) if self.session.user then self.current_user = load_user(self.session.user) end end) app:match("/", function(self) return "current user is: " .. tostring(self.current_user) end)
你能够经过屡次调用 app:before_filter
来随意添加。它们将按照注册的顺序运行。
若是一个 before_filter
调用 self:write()
方法,那么操做将被取消。例如,若是不知足某些条件,咱们能够取消操做并重定向到另外一个页面:
local app = lapis.Application() app:before_filter(function(self) if not user_meets_requirements() then self:write({redirect_to = self:url_for("login")}) end end) app:match("login", "/login", function(self) -- ... end)
self:write()
是处理一个常规动做的返回值,因此一样的事情你能够返回一个动做,能够传递给 self:write()
每一个操做在调用时会请求对象做为其第一个参数传递。因为调用第一个参数 self
的约定,咱们在一个操做的上下文中将请求对象称为 self
。
请求对象具备如下参数:
self.params
一个包含全部GET
,POST
和 URL
参数的表
self.req
原始请求表(从ngx状态生成)
self.res
原始响应表(从ngx状态生成)
self.app
应用程序的实例
self.cookies
cookie
表,能够分配设置新的cookie
。 只支持字符串做为值
self.session
session
表, 能够存储任何可以 被JSON encode
的值。 由Cookie
支持
self.route_name
匹配请求的路由的名称(若是有)
self.options
控制请求如何呈现的选项集,经过write
设置
self.buffer
输出缓冲区,一般你不须要手动设置,经过write
设置
此外,请求对象具备如下方法:
write(options, ...)
指示请求如何呈现结果
url_for(route, params, ...)
根据命名路由或对象来获取 URL
build_url(path, params)
根据 path
和 params
构建一个完整的URL
html(fn)
使用HTML
构建语法生成字符串
原始请求表 self.req
封装了 ngx
提供的一些数据。 如下是可用属性的列表。
self.req.headers
请求头的表
self.req.parsed_url
解析请求的url
,这是一个包含scheme
, path
, host
, port
和 query
属性的表
self.req.params_post
POST
请求的参数表
self.req.params_get
GET
请求的参数表
请求中的 self.cookies
表容许您读取和写入Cookie
。 若是您尝试遍历表以打印 Cookie
,您可能会注意到它是空的:
app:match("/my-cookies", function(self) for k,v in pairs(self.cookies) do print(k, v) end end)
现有的 Cookie
存储在元表的 __index
中。 之这样作,是由于咱们能够知道在操做期间分配了哪些 Cookie
,由于它们将直接在 self.cookies
表中。
所以,要设置一个 cookie
,咱们只须要分配到 self.cookies
表:
app:match("/sets-cookie", function(self) self.cookies.foo = "bar" end)
默认状况下,全部 Cookie
都有额外的属性 Path = /
; HttpOnly
(建立一个session cookie
)。 您能够经过重写 app.cookie_attributes
函数来配置 cookie
的设置。 如下是一个向 cookies
添加过时时间以使其持久化的示例:
local date = require("date") local app = lapis.Application() app.cookie_attributes = function(self) local expires = date(true):adddays(365):fmt("${http}") return "Expires=" .. expires .. "; Path=/; HttpOnly" end
cookie_attributes
方法将请求对象做为第一个参数(self
),而后是要处理的 cookie
的名称和值。
self.session
是一种更先进的方法,经过请求来持久化数据。 会话的内容被序列化为 JSON
并存储在特定名称的 cookie
中。 序列化的 Cookie
使用您的应用程序密钥签名,所以不会被篡改。 由于它是用 JSON
序列化的,你能够存储嵌套表和其余原始值。
session
能够像 Cookie
同样设置和读取:
app.match("/", function(self) if not self.session.current_user then self.session.current_user = "Adam" end end)
默认状况下,session
存储在名为 lapis_session
的 cookie
中。 您可使用配置变量session_name
覆盖 session
的名称。 session
使用您的应用程序密钥(存储在配置的secret
中)进行签名。 强烈建议更改它的默认值。
-- config.lua local config = require("lapis.config").config config("development", { session_name = "my_app_session", secret = "this is my secret string 123456" })
write(things...)
一下列出它的全部参数。 根据每一个参数的类型执行不一样的操做。
string
字符串追加到输出缓冲区
function
(或者是可调用表) 函数被输出缓冲区调用,结果递归传递给write
table
键/值对将会被分配到 self.options
中 ,全部其余值递归传递给write
在大多数状况下,没有必要调用 write
,由于处理函数的返回值会自动传递给 write
。 在before filter
中 ,write
具备写入输出和取消任何进一步操做的双重目的。
url_for(name_or_obj, params, query_params=nil, ...)
依据 路由的name
或一个对象生成 url
url_for 有点用词不当,由于它一般生成到请求的页面的路径。 若是你想获得整个 URL,你能够与build_url函数和一块儿使用。
若是 name_or_obj
是一个字符串,那么使用 params
中的值来查找和填充该名称的路由。 若是路由不存在,则抛出错误。
给定如下路由:
app:match("index", "/", function() -- ... end) app:match("user_data", "/data/:user_id/:data_field", function() -- ... end)
到页面的 URL
能够这样生成:
-- returns: / self:url_for("index") -- returns: /data/123/height self:url_for("user_data", { user_id = 123, data_field = "height"})
若是提供了第三个参数 query_params
,它将被转换为查询参数并附加到生成的 URL
的末尾。 若是路由不接受任何参数,则第二个参数必须被设置为 nil
或 空对象 :
-- returns: /data/123/height?sort=asc self:url_for("user_data", { user_id = 123, data_field = "height"}, { sort = "asc" }) -- returns: /?layout=new self:url_for("index", nil, {layout = "new"})
若是提供了全部封闭的参数,则只包括路由的任何可选组件。 若是 optinal
组件没有任何参数,那么它将永远不会被包括。
给定如下路由:
app:match("user_page", "/user/:username(/:page)(.:format)", function(self) -- ... end)
能够生成如下 URL
:
-- returns: /user/leafo self:url_for("user_page", { username = "leafo" }) -- returns: /user/leafo/projects self:url_for("user_page", { username = "leafo", page = "projects" }) -- returns: /user/leafo.json self:url_for("user_page", { username = "leafo", format = "json" }) -- returns: /user/leafo/code.json self:url_for("user_page", { username = "leafo", page = "code", format = "json" })
若是路由包含了 splat
,则能够经过名为 splat
的参数提供该值:
app:match("browse", "/browse(/*)", function(self) -- ... end)
-- returns: /browse self:url_for("browse") -- returns: /browse/games/recent self:url_for("browse", { splat = "games/recent" })
url_for
若是 name_or_obj
是一个 table
,那么在该 table
上调用 此table
的url_params
方法,并将返回值传递给 url_for
。
url_params
方法接受请求对象做为参数,其次是任何传递给 url_for
的东西。
一般在 model
上实现 url_params
,让他们可以定义它们表明的页面。 例如,为User model
定义了一个 url_params
方法,该方法转到用户的配置文件页面:
local Users = Model:extend("users", { url_params = function(self, req, ...) return "user_profile", { id = self.id }, ... end })
咱们如今能够将User
实例直接传递给 url_for
,并返回 user_profile
路径的l路由:
local user = Users:find(100) self:url_for(user) -- could return: /user-profile/100
你可能会注意到咱们将 ...
传递给 url_params
方法返回值。 这容许第三个 query_params
参数仍然起做用:
local user = Users:find(1) self:url_for(user, { page = "likes" }) -- could return: /user-profile/100?page=likes
url_key
方法若是 params
中参数的值是一个字符串,那么它会被直接插入到生成的路径中。 若是它的值是一个 table
,那么将在此 table
上面调用url_key
方法,并将此方法的返回值插入到路径中。
例如,咱们为 User
模型定义一个咱们的 url_key
方法:
local Users = Model:extend("users", { url_key = function(self, route_name) return self.id end })
若是咱们想生成一个user_profile
文件的路径,咱们一般能够这样写:
local user = Users:find(1) self:url_for("user_profile", {id = user.id})
咱们定义的 url_key
方法让咱们直接传递 User
对象做为 id
参数,它将被转换为 id
:
local user = Users:find(1) self:url_for("user_profile", {id = user})
url_key
方法将路由的名称做为第一个参数,所以咱们能够根据正在处理的路由更改咱们返回的内容。
build_url(path,[options])
依据 path
构建一个绝对 URL
。 当前请求的URI
b被用于构建URL
。
例如,若是咱们在 localhost:8080
上运行咱们的服务器:
self:build_url() --> http://localhost:8080 self:build_url("hello") --> http://localhost:8080/hello
每当写一个表时,键/值对(对因而字符串的键)被复制到 self.options
。 例如,在如下操做中,将复制render
和 status
属性。 在请求处理的生命周期结束时使用options
表来建立适当的响应。
app:match("/", function(self) return { render = "error", status = 404} end)
如下是能够写入的 options
的字段列表
status
设置 http
状态码 (eg. 200,404,500
)
render
致使一个视图被请求渲染。 若是值为 true
,则使用路由的名称做为视图名称。 不然,该值必须是字符串或视图类。
content_type
设置Content-type
头
header
要添加到响应的响应头
json
致使此请求返回 JSON encode
的值。 content-type
被设置为 application / json
。
layout
更改app
默认定义layout
redirect_to
将状态码设置为 302
,并设置Location
头。 支持相对和绝对URL
。 (结合status
执行 301
重定向)
当渲染 JSON
时,确保使用 json
渲染选项。 它将自动设置正确的content-type
并禁用 layout
:
app:match("/hello", function(self) return { json = { hello = "world" } } end)
应用程序回调是一种特殊方法,它能够在须要处理某些类型的请求时调用。能够被应用程序覆盖, 虽然它们是存储在应用程序上的函数,但它们被称为是常规操做,这意味着函数的第一个参数是请求对象的实例。
当请求与您定义的任何路由不匹配时,它将运行默认处理函数。 Lapis
附带了一个默认操做,预约义以下:
app.default_route = function(self) -- strip trailing / if self.req.parsed_url.path:match("./$") then local stripped = self.req.parsed_url:match("^(.+)/+$") return { redirect_to = self:build_url(stripped, { status = 301, query = self.req.parsed_url.query, }) } else self.app.handle_404(self) end end
若是它注意到URL
尾部跟随 一个/
,它将尝试重定向到尾部没有/
的版本。 不然它将调用app
上的handle_404
方法。
这个方法default_route
只是 app
的一个普通方法。 你能够覆盖它来作任何你喜欢的。 例如,添加个日志记录:
app.default_route = function(self) ngx.log(ngx.NOTICE, "User hit unknown path " .. self.req.parsed_url.path) -- call the original implementaiton to preserve the functionality it provides return lapis.Application.default_route(self) end
你会注意到在default_route
的预约义版本中,另外一个方法handle_404
被引用。 这也是预约义的,以下所示:
app.handle_404 = function(self) error("Failed to find route: " .. self.req.cmd_url) end
这将在每一个无效请求上触发 500
错误和 stack trance
。 若是你想作一个 404
页面,这b即是你能实现的地方。
覆盖handle_404
方法而不是default_route
容许咱们建立一个自定义的404
页面,同时仍然保留上面的尾部/
删除代码。
这里有一个简单的404
处理程序,只打印文本Not Found
!
app.handle_404 = function(self) return { status = 404, layout = false, "Not Found!" } end
Lapis
执行的每一个处理函数都被 xpcall
包装。 这确保能够捕获到致命错误,而且能够生成有意义的错误页面,而不是 Nginx
默认错误信息。
错误处理程序应该仅用于捕获致命和意外错误,预期错误在[异常处理指南]()中讨论
Lapis
自带一个预约义的错误处理程序,提取错误信息并渲染模板 lapis.views.error
。 此错误页面包含报错的堆栈和错误消息。
若是你想有本身的错误处理逻辑,你能够重写方法handle_error
:
-- config.custom_error_page is made up for this example app.handle_error = function(self, err, trace) if config.custom_error_page then return { render = "my_custom_error_page" } else return lapis.Application.handle_error(self, err, trace) end end
传递给错误处理程序的请求对象或 self
不是失败了的请求建立的请求对象。 Lapis
提供了一个新的,由于以前的可能已经写入失败了。
您可使用self.original_request
访问原始请求对象
Lapis
的默认错误页面显示整个错误堆栈,所以在生产环境中建议将其替换自定义堆栈跟踪,并在后台记录异常。
lapis-exceptions
模块增长了错误处理程序以在数据库中记录错误。 它也能够当有异常时向您发送电子邮件。