每一個被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
模塊增長了錯誤處理程序以在數據庫中記錄錯誤。 它也能夠當有異常時向您發送電子郵件。