play框架中的路由_Play框架中的高级路由

play框架中的路由

我们经常会遇到有关如何满足Play Framework中各种不同的路由需求的问题。 尽管内置路由器足以满足大多数用户的需求,但有时您可能会遇到用例不足的情况。 或者,也许您想要一种更方便的方法来实现某些路由模式。 无论是什么,Play都可以让您做几乎所有事情。 这篇博客文章将描述一些常见的用例。

迷上Play路由机制

如果由于某种原因您不喜欢Plays路由器,或者如果您想使用修改后的路由器,则Play可以让您轻松地做到这一点。 Global.onRouteRequest是以下方法

调用以进行路由。 默认情况下,它委托给Play路由器,但是您可以覆盖它以执行所需的任何操作。 例如:

override def onRouteRequest(req: RequestHeader): Option[Handler] = {
  (req.method, req.path) match {
    case ("GET", "/") => Some(controllers.Application.index)
    case ("POST", "/submit") => Some(controllers.Application.submit)
    case _ => None
  }
}

如您所见,我实际上在这里实现了自己的小路由DSL。 我还可以通过调用super.onRouteRequest(req)将其委派回默认路由器。 还可以做的一件有趣的事情是根据请求中的内容委派给不同的路由器。 播放路由器会编译为Router.Routes的实例,它将是一个称为Routes本身的对象。 默认情况下, conf目录中扩展名为.routes任何文件都将被编译,并以与文件名相同的名称(减.routes进入.routes 。 因此,如果我有两个路由器foo.routesbar.routes ,则可以实现一种粗略的虚拟主机形式,如下所示:

override def onRouteRequest(req: RequestHeader): Option[Handler] = {
  if (req.host == "foo.example.com") {
    foo.Routes.routes.lift(req)
  } else if (req.host == "bar.example.com") {
    bar.Routes.routes.lift(req)
  } else {
    super.onRouteRequest(req)
  }
}

因此,在以下一些用例中,重写onRouteRequest可能对以下情况有用:

  • 在完成路由之前以某种方式修改请求
  • 插入一个完全不同的路由器(例如,jaxrs)
  • 根据请求的某些方面委派给不同的路由文件

实施自定义路由器

我们在前面的示例中看到了如何使用Plays Router.Routes接口,另一个选择是实现它。 现在,如果您只是直接从onRouteRequest委托给它,则没有真正的理由实现它。 但是,通过实现此接口,您可以使用子路由包含语法将其包含在另一个路由文件中,以防您以前从未遇到过,通常看起来像这样:

->    /foo         foo.Routes

现在,人们经常批评Play的原因是它不支持Rails风格的资源路由,在该协议中使用约定将通常需要的REST端点路由到控制器上的特定方法。 尽管Play并没有提供立即执行此操作的功能,但今天为您的项目实现此功能并不难,但Play 2.1拥有支持它所需的一切,方法是使用包含语法的路由并实现自己的路由器。 我也有一些好消息,我们将很快在Play中引入类似的功能。 但是直到那时,如果您还有自己要实现的自定义约定,则可能会发现这些说明非常有用。
因此,让我们从我们的控制器可以实现的接口开始:

trait ResourceController[T] extends Controller {
  def index: EssentialAction
  def newScreen: EssentialAction
  def create: EssentialAction
  def show(id: T): EssentialAction
  def edit(id: T): EssentialAction
  def update(id: T): EssentialAction
  def destroy(id: T): EssentialAction
}

我可以提供返回未实现的默认实现,但是要实现它,将需要使用override关键字。 我认为这是优先事项。
现在,我要写一个路由器。 路由器接口如下所示:

trait Routes {
  def routes: PartialFunction[RequestHeader, Handler]
  def documentation: Seq[(String, String, String)]
  def setPrefix(prefix: String)
  def prefix: String
}

routes方法很容易说明,它是查找请求处理程序的函数。 documentation用于记录路由器,但不是必需的,但至少一个REST API文档工具使用它来发现可用的路由以及它们的外观。 为了简洁起见,我们不必担心实现它。 Play使用prefixsetPrefix方法来注入路由器的路径。 在上面显示的包含语法的路由中,您可以看到我们已声明路由器位于路径/foo 。 使用此机制可以注入该路径。 因此,我们将编写一个抽象类,该类实现路由接口和ResourceController接口:

abstract class ResourceRouter[T](implicit idBindable: PathBindable[T]) 
    extends Router.Routes with ResourceController[T] {
  private var path: String = ""
  def setPrefix(prefix: String) {
    path = prefix
  }
  def prefix = path
  def documentation = Nil
  def routes = ...
}

我给了它一个PathBindable ,这样我们就可以将id从路径提取的String转换为方法接受的类型。 PathBindable是在普通路由文件中转换类型时使用的相同接口。
现在执行routes 。 首先,我将创建一些正则表达式来匹配不同的路径:

private val MaybeSlash = "/?".r
  private val NewScreen = "/new/?".r
  private val Id = "/([^/]+)/?".r
  private val Edit = "/([^/]+)/edit/?".r

我还将为需要绑定ID的路由创建一个辅助函数:

def withId(id: String, action: T => EssentialAction) = 
  idBindable.bind("id", id).fold(badRequest, action)

badRequest实际上是在一个方法Router.Routes ,是以错误消息,并把它变成一个动作一个返回作为结果。 现在,我准备实现部分功能:

def routes = new AbstractPartialFunction[RequestHeader, Handler] {
  override def applyOrElse[A <: RequestHeader, B >: Handler](rh: A, default: A => B) = {
    if (rh.path.startsWith(path)) {
      (rh.method, rh.path.drop(path.length)) match {
        case ("GET", MaybeSlash()) => index
        case ("GET", NewScreen()) => newScreen
        case ("POST", MaybeSlash()) => create
        case ("GET", Id(id)) => withId(id, show)
        case ("GET", Edit(id)) => withId(id, edit)
        case ("PUT", Id(id)) => withId(id, update)
        case ("DELETE", Id(id)) => withId(id, destroy)
        case _ => default(rh)
      }
    } else {
      default(rh)
    }
  }

  def isDefinedAt(rh: RequestHeader) = ...
}

我已经实现了AbstractPartialFunction ,然后实现的主要方法是applyOrElse 。 match语句看起来与我在第一个代码示例中显示的mini DSL看起来并不相似。 我使用正则表达式作为提取器对象以将id提取出路径。 请注意,我没有显示isDefinedAt的实现。 Play实际上不会调用此方法,但是无论如何都可以实现,这与applyOrElse基本上是相同的实现,不同之处在于它不是调用相应的方法,而是返回true ,或者当没有匹配项时,返回false 。 现在我们完成了。 那么使用它看起来像什么呢? 我的控制器如下所示:

package controllers

object MyResource extends ResourceRouter[Long] {
  def index = Action {...}
  def create(id: Long) = Action {...}
  ...
  def custom(id: Long) = Action {...}
}

在我的路线文件中,我有这个:

->     /myresource              controllers.MyResource
POST   /myresource/:id/custom   controllers.MyResource.custom(id: Long)

您可以看到我还展示了一个向控制器添加自定义动作的示例,显然标准的crud动作还不够,这的好处是您可以添加任意数量的任意路由。
但是,如果我们要拥有一个托管控制器,即实例化由DI框架管理的控制器,该怎么办? 好吧,让我们创建另一个路由器来执行此操作:

class ManagedResourceRouter[T, R >: ResourceController[T]]
    (implicit idBindable: PathBindable[T], ct: ClassTag[R]) 
    extends ResourceRouter[T] {

  private def invoke(action: R => EssentialAction) = {
    Play.maybeApplication.map { app =>
      action(app.global.getControllerInstance(ct.runtimeClass.asInstanceOf[Class[R]]))
    } getOrElse {
      Action(Results.InternalServerError("No application"))
    }
  }

  def index = invoke(_.index)
  def newScreen = invoke(_.newScreen)
  def create = invoke(_.create)
  def show(id: T) = invoke(_.show(id))
  def edit(id: T) = invoke(_.edit(id))
  def update(id: T) = invoke(_.update(id))
  def destroy(id: T) = invoke(_.destroy(id))
}

这使用与常规路由器中的受管控制器相同的Global.getControllerInstance方法。 现在使用它非常简单:

package controllers

class MyResource(dbService: DbService) extends ResourceController[Long] {
  def index = Action {...}
  def create(id: Long) = Action {...}
  ...
  def custom(id: Long) = Action {...}
}
object MyResource extends ManagedResourceRouter[Long, MyResource]

并在路由文件中:

->     /myresource              controllers.MyResource
POST   /myresource/:id/custom   @controllers.MyResource.custom(id: Long)

我们需要考虑的最后一件事是反向路由和Javascript路由器。 同样,这非常简单,但是我在这里不做任何详细说明。 相反,您可以在此处签出具有更多功能的最终产品。

参考: James and Beth Roper的博客博客中来自我们的JCG合作伙伴 James Roper的Play Framework中的高级路由

翻译自: https://www.javacodegeeks.com/2013/05/advanced-routing-in-play-framework.html

play框架中的路由

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值