第6章、Request Pipeline

当请求到达Lift时,您可以跳过几个点,并控制Lift执行的操作,发回不同类型的响应或控制访问。本章通过不同种类LiftResponse和配置的例子来看管道 

您可以从Lift pipeline wiki页面中获得有关管道的详细信息,包括图表

有关本章附带的源代码,请参阅https://github.com/LiftCookbook/cookbook_pipeline

调试请求

问题

您想要调试请求,并查看到达Lift应用程序的内容。

onBeginServicingBoot.scala添加一个函数来记录请求。 例如:

LiftRules.onBeginServicing.append {
  case r => println("Received: "+r)
}

讨论

onBeginServicing电话在Lift管道早期被称为,在 S设置之前,在Lift有机会404您的请求之前。它期望的功能签名是Req => Unit我们只是记录,但功能可以用于其他目的。

如果您只想选择某些路径,您可以。例如,要跟踪所有请求开始/ paypal

LiftRules.onBeginServicing.append {
  case r @ Req("paypal" :: _), _, _) => println(r)
}

此模式将匹配任何请求开始/ paypal,并且我们忽略请求的后缀(如果有)和请求的类型(例如,GET,POST等)。

还有LiftRules.early,以前叫过onBeginServicing它期望的HTTPRequest => Unit功能,所以是一个小电平低于所述Req中使用onBeginServicing但是,所有通过Lift的请求都将被调用。要查看差异,您可以将请求标记为容器本身应该处理的内容

LiftRules.liftRequest.append {
  case Req("robots" :: _, _, _) => false
}

有了这一点,对robots.txt的请求将被记录,LiftRules.early但不会使其成为本配方中描述的任何其他方法。

如果你需要访问状态(例如,S),使用earlyInStateful,这是基于Box[Req]不是Req

LiftRules.earlyInStateful.append {
  case Full(r) => // access S here
  case _ =>
}

你的earlyInStateful函数可能被调用两次。这将在新的会话建立时发生。您可以通过仅在运行的Lift会话中的请求匹配来防止此情况:

LiftRules.earlyInStateful.append {
  case Full(r) if LiftRules.getLiftSession(r).running_? => // access S here
  case _ =>
}

最后,还有一个earlyInStateless,就像一样earlyInStatefulBox[Req]但在其他方面是一样的onBeginServicing它是在early前后触发的earlyInStateful

作为总结,本方法中描述的功能按以下顺序调用:

  1. LiftRules.early
  2. LiftRules.onBeginServicing
  3. LiftRules.earlyInStateless
  4. LiftRules.earlyInStateful

也可以看看

如果你需要抓住一个请求的结尾,还onEndServicing可以给出一个类型的函数 (Req, Box[LiftResponse]) => Unit

“运行无状态”描述了如何强制请求成为无状态。

会话创建(或毁坏)时运行代码

问题

您要在会话创建或销毁时执行操作。

利用钩子LiftSession例如,在Boot.scala中

LiftSession.afterSessionCreate ::=
 ( (s:LiftSession, r:Req) => println("Session created") )

LiftSession.onBeginServicing ::=
 ( (s:LiftSession, r:Req) => println("Processing request") )

LiftSession.onShutdownSession ::=
 ( (s:LiftSession) => println("Session going away") )

如果请求路径被标记为无状态通道 LiftRules.statelessReqTest,则该示例将仅执行该 onBeginServicing功能。

讨论

挂钩LiftSession允许您在会话生命周期中的各个点插入代码:当会话创建时,在服务请求开始时,维修后,会话即将关闭,关机时等。管道本章开头提到的图表是这些阶段的有用指南。

会话挂钩列表如下:

onSetupSession
这将是创建会话时调用的第一个钩子。
afterSessionCreate
在调用所有 onSetupSession 函数后,这将被调用。
onBeginServicing
这将在请求处理开始时调用。
onEndServicing
这将在请求处理结束时调用。
onAboutToShutdownSession
这将在会话关闭之前调用(例如,当会话过期或Lift应用程序正在关闭时)。
onShutdownSession
在所有 onAboutToShutdownSession 功能运行后,这将被调用

如果您正在测试这些挂钩,您可能希望使会话的时间比默认在Lift中使用的30分钟不活动更快。为此,请提供一个毫秒值LiftRules.sessionInactivityTimeout

// 30 second inactivity timeout
LiftRules.sessionInactivityTimeout.default.set(Full(1000L * 30))

还有另外两个钩子LiftSessiononSessionActivateonSessionPassivate如果您正在以分布式模式处理servlet容器,并希望在servlet HTTP会话即将被串行化(钝化)并在容器实例之间反序列化(激活)时收到通知,那么这些可能是有用的。这些钩子很少使用。

请注意,提升会话与HTTP会话不同。将网桥从HTTP会话提升到自己的会话管理。这在探索提升中有一些细节

也可以看看

会议管理在“ 探索提升”第9.5节中讨论

运行无状态显示如何运行没有状态。

Lift关闭时运行代码

问题

当您的Lift应用程序关闭时,您希望执行一些代码。

附加到LiftRules.unloadHooks

LiftRules.unloadHooks.append( () => println("Shutting down") )

讨论

您附加类型() => Unit的功能unloadHooks,并且这些功能在Lift处理程序结束时运行,会话已被破坏后,Lift actors已关闭,请求已完成处理。

这是以Java servlet规范的话来触发的,“通过Web容器向过滤器指示它被取消服务”。

也可以看看

“定期运行任务”包括使用卸载钩子的示例。

运行无状态

问题

您希望强制您的应用程序在HTTP级别是无状态的。

Boot.scala中

LiftRules.enableContainerSessions = false
LiftRules.statelessReqTest.append { case _ => true }

所有请求现在将被视为无状态。任何尝试使用状态,例如通过SessionVar例如,将在开发人员模式下触发警告:“访问Lift的无状态模式的状态特征,状态操作将不会完成。

讨论

HTTP会话创建通过控制enableContainerSessions,并应用于所有请求。将此值保留在default(true)允许更细粒度地控制哪些请求是无状态的。

使用statelessReqTest允许您根据StatelessReqTest案例类决定 请求是否应为无状态(true)或不是(false)。例如:

def asset(file: String) =
  List(".js", ".gif", ".css").exists(file.endsWith)

LiftRules.statelessReqTest.append {
  case StatelessReqTest("index" :: Nil, httpReq) => true
  case StatelessReqTest(List(_, file),  _) if asset(file) => true
}

这个例子只会使索引页面和任何GIF,JavaScript和CSS文件无状态。httpReq部分是一个HTTPRequest实例,允许您根据请求的内容(Cookie,用户代理等)作出决定。

另一个选项是LiftRules.statelessDispatch,它允许您注册一个返回的函数LiftResponse这将在没有会话的情况下执行,并且方便基于REST的服务。

如果您只需要将条目标记SiteMap为无状态,则可以:

一个请求/演示将被处理没有状态。

也可以看看

第4章包含Lift中基于REST的服务的配方。

Lift提供进一步的处理无状态请求的细节。

Lift 2.2中介绍了无状态请求控制。邮件列表中的公告提供了更多的细节。

抓住任何异常

问题

您想要围绕所有请求的包装器来捕获异常并向用户显示某些内容。

Boot.scala中声明一个异常处理程序

LiftRules.exceptionHandler.prepend {
  case (runMode, request, exception) =>
    logger.error("Failed at: "+request.uri)
    InternalServerErrorResponse()
}

在此示例中,所有运行模式下所有请求的所有异常都被匹配,导致记录错误,并返回500(内部服务器)错误。

讨论

您添加的部分功能exceptionHandler需要返回 LiftResponse(即,要发送到浏览器的东西)。默认的行为是返回一个XhtmlResponse,其中 Props.RunModes.Development给出了异常的详细信息,而在所有其他运行模式中,简单地说:“发生了意外事件。

你可以返回任何类型的LiftResponse,包括RedirectResponse, JsonResponseXmlResponseJavaScriptResponse,等。

前面的例子只是发送标准的500错误。这对您的用户来说不是很有帮助。另一种方法是呈现自定义消息,但保留500状态代码,这对于外部站点监视服务将是有用的,如果您使用它们:

LiftRules.exceptionHandler.prepend {
  case (runMode, req, exception) =>
    logger.error("Failed at: "+req.uri)
    val content = S.render(<lift:embed what="500" />, req.request)
    XmlResponse(content.head, 500, "text/html", req.cookies)
}

这里我们发回一个500状态代码的回复,但内容是 Node运行src / main / webapp / template-hidden / 500.html的结果使用要向用户显示的消息创建该文件



  </code><span style="">500 </span><code class="nt" style="font-family:Consolas,Monaco,"Lucida Console",Courier,monospace; font-size:1em; color:rgb(205,168,105); font-weight:bold">

 data-lift-content-id="main">
 id="main" data-lift="surround?with=default;at=content">
  

有什么问题!

这是我们的错 - 抱歉

您还可以控制处理Ajax请求时发送给客户端的内容。在以下示例中,我们只是在Ajax POST请求上匹配,并将自定义JavaScript返回到浏览器

import net.liftweb.http.js.JsCmds._

val ajax = LiftRules.ajaxPath

LiftRules.exceptionHandler.prepend {
  case (mode, Req(ajax :: _, _, PostRequest), ex) =>
    logger.error("Error handing ajax")
    JavaScriptResponse(Alert("Boom!"))
}

您可以通过创建始终产生异常的Ajax按钮来测试此处理代码:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml

class ThrowsException {
  private def fail = throw new Error("not implemented")

  def render = "*" #> SHtml.ajaxButton("Press Me", () => fail)
}

此Ajax示例将跳过Lift对Ajax错误的默认行为。默认是重试Ajax命令三次(LiftRules.ajaxRetryCount),然后执行 LiftRules.ajaxDefaultFailure,这将弹出一个对话框,说:“此时无法联系服务器。

也可以看看

“自定义404页面”介绍如何创建自定义404(未找到)页面。

流媒体内容

问题

您想将内容流传回到Web客户端。

使用OutputStreamResponse,传递一个函数,将写入 OutputStream该提供用品。

在这个例子中,我们将通过REST服务流式传输所有的整数,

package code.rest

import net.liftweb.http.{Req,OutputStreamResponse}
import net.liftweb.http.rest._

object Numbers extends RestHelper {

  // Convert a number to a String, and then to UTF-8 bytes
  // to send down the output stream.
  def num2bytes(x: Int) = (x + "\n") getBytes("utf-8")

  // Generate numbers using a Scala stream:
  def infinite = Stream.from(1).map(num2bytes)

  serve {
    case Req("numbers" :: Nil, _, _) =>
      OutputStreamResponse( out => infinite.foreach(out.write) )
  }
}

Scala的Stream类是一种用惰性评估来生成序列的方式。所生成的值被infinite用作示例数据流回到客户端。

将其连接Boot.scala中的Lift中

LiftRules.dispatch.append(Numbers)

访问http://127.0.0.1:8080/numbers将生成200个状态代码,并从1开始生成整数。数字的制作速度非常快,所以你可能不想在Web浏览器中尝试这个从更容易停止的东西,如cURL。

讨论

OutputStreamResponse期待一种类型的功能OutputStream => Unit该 OutputStream参数是输出流给客户端。这意味着我们写入流的字节写入客户端。示例中的相关行为:

OutputStreamResponse(out => infinite.foreach(out.write))

我们正在使用Java write(byte[])方法outOutputStream并将其Array[Byte]从我们的infinite流中生成。

请注意,OutputStreamResponse在其范围之外执行S这意味着如果您需要访问会话中的任何内容,请在您传递给的功能之外执行此操作OutputStreamResponse

为了更好地控制状态代码,标题和Cookie,OutputStreamResponse对象有各种各样的签名对于最多的控件,创建一个OutputStreamResponse的实例

case class OutputStreamResponse(
  out: (OutputStream) => Unit,
  size: Long,
  headers: List[(StringString)],
  cookies: List[HTTPCookie],
  code: Int)

请注意,设置size-1使Content-Length头部被跳过。

有两种相关类型的回应:InMemoryResponse和 StreamingResponse

InMemoryResponse

InMemoryResponse如果您已经将完整的内容组合发送给客户端,这是非常有用的。签名很简单

case class InMemoryResponse(
  data: Array[Byte],
  headers: List[(StringString)],
  cookies: List[HTTPCookie],
  code: Int)

例如,我们可以修改配方,并强制我们infinite的数字序列来产生前几个数字作为Array[Byte]内存:

import net.liftweb.util.Helpers._

serve {
  case Req(AsInt(n) :: Nil, _, _) =>
    InMemoryResponse(infinite.take(n).toArray.flatten, Nil, Nil, 200)
}

AsIntLift中帮助器匹配整数,这意味着以数字开头的请求匹配,我们将从无限序列中返回许多数字。我们不设置标题或Cookie,此请求会产生您期望的内容:

$ curl http://127.0.0.1:8080/3
1
2
3

StreamingResponse

StreamingResponse将字节拉入输出流。这与OutputStreamResponse您将数据推送到客户端的情况形成对比

通过为类提供read可以从中读取方法来构造此类型的响应

case class StreamingResponse(
  data: {def read(buf: Array[Byte]): Int},
  onEnd: () => Unit,
  size: Long,
  headers: List[(StringString)],
  cookies: List[HTTPCookie],
  code: Int)

请注意使用结构类型作为data参数。read在这里可以给出任何具有匹配方法的内容,包括java.io.InputStream对象,意思StreamingResponse可以作为从输入到输出的管道。Lift将8K块从您那里StreamingResponse发送给客户端。

您的data read函数应遵循Java IO的语义,并返回“读入缓冲区的总字节数,如果没有达到数据流,则返回-1。

也可以看看

“服务视频”提供了通过REST流式传输视频数据的示例。

JavaDoc为该方法InputStream提供了完整的合同read

为访问控制提供文件

问题

您有一个磁盘上的文件,并且您希望允许用户下载文件,但只允许用户下载。如果不允许,你想解释为什么。

使用RestHelper服务的文件或说明页面。

例如,假设我们有/ tmp /重要文件,我们只希望选择的请求从/ download /重要 URL 下载该文件其结构将是:

package code.rest

import net.liftweb.util.Helpers._
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{StreamingResponse, LiftResponse, RedirectResponse}
import net.liftweb.common.{Box, Full}
import java.io.{FileInputStream, File}

object DownloadService extends RestHelper {

  // (code explained below to go here)

  serve {
    case "download" :: Known(fileId) :: Nil Get req =>
      if (permitted) fileResponse(fileId)
      else Full(RedirectResponse("/sorry"))
  }
}

我们允许用户下载“已知”文件。也就是说,我们批准访问的文件。我们这样做是因为打开文件系统到任何未过滤的最终用户输入几乎意味着您的服务器将被泄露。

对于我们的例子,Known正在检查静态名称列表:

val knownFiles = List("important")

object Known {
 def unapply(fileId: String): Option[String] = knownFiles.find(_ == fileId)
}

对于这些已知资源的请求,我们将REST请求转换为Box[LiftResponse]对于允许的访问,我们提供文件:

private def permitted = scala.math.random < 0.5d

private def fileResponse(fileId: String): Box[LiftResponse] = for {
    file <- Box !! new File("/tmp/"+fileId)
    input <- tryo(new FileInputStream(file))
 } yield StreamingResponse(input,
    () => input.close,
    file.length,
    headers=Nil,
    cookies=Nil,
    200)

如果没有授权,用户被重定向到/sorry.html

所有这些都连接到Boot.scala中的Lift中

LiftRules.dispatch.append(DownloadService)

讨论

通过将请求转为a Box[LiftResponse],我们可以提供文件,将用户发送到不同的页面,并允许Lift处理404(Empty)情况。

如果我们增加了一个测试,看看文件中的磁盘上存在fileResponse,这将导致该方法计算为Empty丢失的文件,由于代码代表触发一个404,如果该文件不存在,则tryo会给我们一个Failure是会变成一个404错误与一个“/ tmp /重要(没有这样的文件或目录)”。

因为我们通过Known提取器测试已知资源,作为/ download /的模式的一部分,未知资源将不会传递到我们的File访问代码。再次,Lift将为这些返回404。

警卫表情也可用于这些情况

serve {
  case "download" :: Known(id) :: Nil Get _ if permitted => fileResponse(id)
  case "download" :: _ Get req => RedirectResponse("/sorry")
}

您可以根据您希望代码查找和工作的方式,将您的响应中的提取器,警卫和条件混合并匹配。

也可以看看

第24章:Scala中的编程提取

HTTP头访问限制

问题

您需要根据HTTP标头的值控制对页面的访问。

使用自定义IfSiteMap

val HeaderRequired = If(
  () => S.request.map(_.header("ALLOWED") == Full("YES")) openOr false,
  "Access not allowed"
)

// Build SiteMap
val entries = List(
  Menu.i("Header Required") / "header-required" >> HeaderRequired
)

在这个例子中,首标required.html只能如果该请求包括称为HTTP报头观察ALLOWED用的值YES对该页面的任何其他请求将被重定向到“不允许访问”的提升错误通知。

这可以使用cURL这样的工具从命令行进行测试:

$ curl http://127.0.0.1:8080/header-required.html -H“允许:是”

讨论

If测试可确保() => Boolean您提供的第一个参数函数返回true页面之前它适用于所示。在这个例子中,true如果请求包含一个调用的头ALLOWED,并且该的可选值是Full("YES")这是一个LocParam(位置参数)修改该SiteMap项目。它可以附加到您想要使用该>>方法的任何菜单项

请注意,没有标题,测试将为false。这意味着链接到页面将不会出现在生成的菜单中Menu.builder

If()如果用户尝试访问该页面时测试不正确,则第二个参数是Lift执行。这是一个() => LiftResponse功能。这意味着你可以返回任何你喜欢的,包括重定向到其他页面。在这个例子中,我们正在利用一个方便的隐式会话String(“不允许访问”)到重定向的通知,这将使用户进入主页。

如果您访问页面而没有标题,您会看到一条通知,指出“不允许访问”。这将是网站的主页,但这只是默认。您可以通过LiftRules.siteMapFailRedirectLocationBoot.scala中设置来请求Lift显示不同的页面

LiftRules.siteMapFailRedirectLocation = "static" :: "permission" :: Nil

如果然后尝试访问header-required.html而不使用头文件集,则将被重定向到/ static / permission,并显示您在该页面中放置的内容。

也可以看看

Lift维基提供了Lift的摘要SiteMap和您可以在站点地图条目中包含的测试。

“ 探索Lift”第7章还有“SiteMap和访问控制”,“ 提升行动”(Perrett,2012,Manning Publications,Co.)第7章中的进一步细节

访问HttpServletRequest

问题

你有一个需要访问的API来调用它HttpServletRequest

演员S.request

import net.liftweb.http.S
import net.liftweb.http.provider.servlet.HTTPRequestServlet
import javax.servlet.http.HttpServletRequest

def servletRequest: Box[HttpServletRequest] = for {
  req <- S.request
  inner <- Box.asA[HTTPRequestServlet](req.request)
} yield inner.req

然后,您可以调用API:

servletRequest.foreach { r => yourApiCall(r) }

讨论

从低级HTTP请求中提取摘要,以及您应用程序正在运行的servlet容器的详细信息。但是,如果您绝对需要,可以放心,有一种方法可以恢复到低级别。

请注意,结果servletRequest是a Box,因为评估时可能没有请求servletRequest- 或者您可能有一天到不同部署环境的端口,而不是在标准的Java servlet容器上运行。

由于您的代码将直接依赖于Java Servlet API,因此您需要在SBT构建中包含此依赖关系:

"javax.servlet" % "servlet-api" % "2.5" % "provided"

强制HTTPS请求

问题

您希望确保客户端正在使用HTTPS。

earlyResponseBoot.scala中添加一个功能, HTTP请求重定向到HTTPS等效项。例如:

LiftRules.earlyResponse.append { (req: Req) =>
  if (req.request.scheme != "https") {
    val uriAndQuery = req.uri +
     (req.request.queryString.map(s => "?"+s) openOr "")
    val uri = "https://%s%s".format(req.request.serverName, uriAndQuery)
    Full(PermRedirectResponse(uri, req, req.cookies: _*))
  }
  else Empty
}

讨论

earlyResponse呼叫在Lift管道称为早期。它用于在处理请求之前执行代码,如果需要,则退出管道并返回响应。预期的功能签名是 Req => Box[LiftResponse]

在这个例子中,我们正在测试一个不是“https”的请求,然后制定一个启动“https”的新URL,并附加其余的原始URL和任何查询参数。创建此项后,我们会将重定向返回到新的URL,以及设置的任何Cookie。

通过评估Empty其他请求(即HTTPS请求),Lift将继续照常通过管道传递请求。

使用正确的方案来确保请求的理想方法将是通过Web服务器配置,如Apache或Nginx。这在某些情况下是不可能的,例如将应用程序部署到诸如CloudBees的平台即服务(PaaS)时。

亚马逊负载均衡器

对于Amazon Elastic Load Balancer,请注意,您需要使用 X-Forwarded-Proto头来检测HTTPS。如 “弹性负载平衡概述 ” 文档所述,“您的服务器访问日志仅包含服务器和负载平衡器之间使用的协议;它们不包含有关客户端和负载平衡器之间使用的协议的信息。

在这种情况下,将测试修改 req.request.scheme != "https"为:

req.header("X-Forwarded-Proto") != Full("https")

通过SiteMap

到目前为止,我们已经将所有请求重定向到HTTPS。如果您只需要少量页面来担心,可以使用自定义的位置参数SiteMap作为替代:

val entries = List(
  Menu.i("Home") / "index",
  Menu.i("Login") / "login" >> HttpsOnly
)

我们有两个位置,我们将登录页面标记为必须通过HTTPS。 HttpsOnly是位置参数。它不是内置的,但我们可以从Lift中包含的构建块和我们已经编写的代码定义它:

// Given a request, turn it into a redirect to the HTTPS version of the page
def redirection(req: Req) : LiftResponse = {
  val uriAndQuery = req.uri +
    (req.request.queryString.map(s => "?"+s) openOr "")
  val uri = "https://%s%s".format(req.request.serverName, uriAndQuery)

  PermRedirectResponse(uri, req, req.cookies: _*)
}

// If the request is HTTP, give us a redirect to HTTPS
def httpsRedirect : Box[LiftResponse] =
  for {
    req <- S.request
    scheme <- req.header("X-Forwarded-Proto")
    if scheme == "http"
  } yield redirection(req)


val HttpsOnly = TestAccess( () => httpsRedirect )

对我们想要发生的事情的回顾:任何标记为的菜单项HttpsOnly都应将其重定向到HTTPS,除非它已经是HTTPS。我们可以TestAccess用来实现这一点。给定的功能TestAccess必须评估为a Box[LiftResponse]:如果是Empty,请求正常进行; 如果是Full,请求被重定向到框的内容。

我们的HttpsOnly实现是调用httpsRedirect,理解选择请求,检查方案,只有它是“http”,它产生一个Full[LiftResponse]这就是HTTPS请求被允许通过(httpsRedirect评估Empty),但HTTP请求被转换Full[PermRedirectResponse]TestAccess执行重定向。

值得注意的是,这也方便了当地的发展。如果本地X-Forwarded-Proto在请求中没有标题,HTTPS重定向将不被触发。当然,假设X-Forwarded-Proto检查适用于您。相反,如果你正在使用的req.request.scheme检查,你也可能要破例在开发模式下运行(Props.devMode评估为true发展模式)。

你可能感兴趣的:(lift,lift,scala,大数据)