资讯专栏INFORMATION COLUMN

lapis请求处理

Olivia / 3203人阅读

摘要:处理函数的返回值用于渲染输出。例如,如果不满足某些条件,我们可以取消操作并重定向到另一个页面是处理一个常规动作的返回值,所以同样的事情你可以返回一个动作,可以传递给请求对象每个操作在调用时会请求对象作为其第一个参数传递。

lapis请求处理

每个被Lapis处理的HTTP请求在被Nginx处理后都遵循相同的基本流程。第一步是路由。路由是 url 必须匹配的模式。当你定义一个路由时,你也得包括一个处理函数。这个处理函数是一个常规的Lua/MoonScript函数,如果相关联的路由匹配,则将调用该函数。

所有被调用的处理函数都具有一个参数(一个请求对象)。请求对象将存储您希望在处理函数和视图之间共享的所有数据。此外,请求对象是您向Web服务器了解如何将结果发送到客户端的接口。

处理函数的返回值用于渲染输出。字符串返回值将直接呈现给浏览器table 的返回值将用作[渲染选项]()。如果有多个返回值,则所有这些返回值都合并到最终结果中。您可以返回字符串和table以控制输出。

如果没有匹配请求的路由,则执行默认路由处理程序,在[application callbacks]()了解更多。

Routes 和 URL 模式

路由模式 使用特殊语法来定义URL的动态参数 并为其分配一个名字。最简单的路由没有参数:

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个字符,否则将无法匹配。

splat是另一种类型的模式,将尽可能匹配,包括任何/字符。 splat存储在请求对象的 params 表中的 splat 命名参数中。它只是一个单一 *

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

可选路由组件

圆括号可用于使路由的一部分可选:

/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动词

根据请求的 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 动词定义操作。你会发现这些包装器最常见的动词:getpostdeleteput。对于任何其他动词,你需要使用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 Filters

有时你想要一段代码在每个操作之前运行。一个很好的例子是设置用户会话。我们可以声明一个 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 一个包含所有GETPOSTURL 参数的表

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) 根据 pathparams 构建一个完整的URL

html(fn) 使用HTML构建语法生成字符串

@req

原始请求表 self.req 封装了 ngx 提供的一些数据。 以下是可用属性的列表。

self.req.headers 请求头的表

self.req.parsed_url 解析请求的url,这是一个包含scheme, path, host, portquery 属性的表

self.req.params_post POST请求的参数表

self.req.params_get GET请求的参数表

Cookies

请求中的 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):affffdays(365):fmt("${http}")
  return "Expires=" .. expires .. "; Path=/; HttpOnly"
end

cookie_attributes 方法将请求对象作为第一个参数(self),然后是要处理的 cookie 的名称和值。

Session

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_sessioncookie 中。 您可以使用配置变量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 上调用 此tableurl_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 。 当前请求的URIb被用于构建URL

例如,如果我们在 localhost:8080 上运行我们的服务器:

self:build_url() --> http://localhost:8080
self:build_url("hello") --> http://localhost:8080/hello
渲染选项

每当写一个表时,键/值对(对于是字符串的键)被复制到 self.options。 例如,在以下操作中,将复制renderstatus 属性。 在请求处理的生命周期结束时使用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 模块增加了错误处理程序以在数据库中记录错误。 它也可以当有异常时向您发送电子邮件。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39495.html

相关文章

  • lapis入门

    摘要:入门是为和编写的框架。使用来安装创建一个应用命令行工具附带了一个命令行工具,可帮助您创建新项目和启动服务器。在生产环境中,应当启用缓存以获得最佳性能。指令指定一个代码块,它将处理与其他不匹配的任何请求。将忽略常规的二进制文件。 lapis入门 Lapis 是为 Lua 和 MoonScript 编写的 Web 框架。 Lapis 很有趣,因为它建立在Nginx 发行的 OpenRest...

    endless_road 评论0 收藏0
  • lapis的配置及环境

    摘要:配置及环境被设计于依据不同环境载入不同的配置来运行服务器。环境名称仅影响加载的配置。例如,这里有一个的配置块编译时,首先检查环境变量。默认日志记录位置设置为,在默认的配置中指定。 配置及环境 Lapis 被设计于依据不同环境载入不同的配置来运行服务器。例如,可能您开发环境的配置设置为本地数据库的URL,禁用代码缓存和单个worker。然后,您生产环境的配置可能设定为远程数据库的 URL...

    sarva 评论0 收藏0
  • 创建Lapis应用程序

    摘要:使用创建应用程序生成一个新项目如果您尚未阅读,请阅读入门指南,了解有关创建新项目骨架的信息以及,配置和命令的详细信息。是包含应用程序的常规模块。 使用Lua创建Lapis应用程序 生成一个新项目 如果您尚未阅读,请阅读入门指南,了解有关创建新项目骨架的信息以及OpenResty,Nginx配置和lapis命令的详细信息。 您可以在当前目录中通过运行以下命令启动一个新的Lua项目: la...

    jzzlee 评论0 收藏0
  • lapis的异常处理

    摘要:的异常处理错误的种类区分两种错误可恢复和不可恢复错误。捕获可恢复的错误帮助程序用于包装一个操作,以便它可以捕获错误并运行错误处理程序。相反,使用协同程序创建一个异常处理系统。 lapis的异常处理 错误的种类 Lapis 区分两种错误:可恢复和不可恢复错误。 Lua 的运行时在执行期间抛出的错误或调用错误被认为是不可恢复的。 (这也包括 Lua 内置函数 assert ) 因为不可恢复...

    cucumber 评论0 收藏0
  • lapis配置之 lua语法

    摘要:配置语法配置示例的配置模块提供了对递归合并的支持。例如,我们可以定义一个基本配置,然后覆盖更多具体的配置声明中的一些值这将产生以下两个配置结果默认值省略您可以在相同的配置名称上调用函数多次,每次将传入的表合并到配置中。 Lua 配置语法 配置示例 Lapis 的配置模块提供了对递归合并 table 的支持。 例如,我们可以定义一个基本配置,然后覆盖更多具体的配置声明中的一些值: --...

    BaronZhang 评论0 收藏0

发表评论

0条评论

Olivia

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<