Play 2.0 RESTful请求后处理

时间:2012-06-16 17:13:38

标签: rest request playframework-2.0 post-processing

关于this question我很好奇如何做一个la(粗略)后请求REST处理:

def postProcessor[T](content: T) = {
  request match {
    case Accepts.Json() => asJson(content)
    case Accepts.Xml()  => asXml(content)
    case _ => content
  }
}
全局配置中的

overriding onRouteRequest似乎不提供对响应正文的访问,因此Action composition似乎是拦截响应并执行后处理任务的方法(s )。

问题:这是一个好主意,还是直接在已知类型转换的控制器(或其他类)方法中进行内容类型转换更好?

目前我到处都是这样做的事情:

toJson( i18n("account not found") )
toJson( Map('orderNum-> orderNum) )

虽然我希望toJson / toXml转换基于接受标头后请求而发生。

3 个答案:

答案 0 :(得分:4)

您希望能够根据请求的Result标头值计算包含A类型对象表示的Accept。您可以使用以下 type trait 编码此功能:

trait Repr[-A] {
  def render(a: A, request: RequestHeader): Result
}

然后,您可以使用以下帮助程序特征从控制器渲染任何资源:

trait ReprSupport {
  def repr[A](a: A)(implicit request: RequestHeader, repr: Repr[A]) =
    repr.render(a, request)
}

object MyApp extends Controller with ReprSupport {
  def index = Action { implicit request =>
    repr(Foo("bar"))
  }
}

其中Foo是一个简单的案例类,定义如下:

case class Foo(bar: String)

为了能够编译上面的代码,您需要在隐式作用域中具有类型Repr[Foo]的值。第一个实现可以写成如下:

object Foo extends AcceptExtractors {
  implicit val fooRepr = new Repr[Foo] {
    def render(foo: Foo, request: RequestHeader): Result = request match {
      case Accepts.Html() => Ok(views.html.foo(foo)) // Assumes there is a foo.scala.html template taking just one parameter of type Foo
      case Accepts.Json() => Ok(Json.obj("bar" -> foo.bar))
      case _ => NotAcceptable
    }
  }
}

但对于您要为其编写Repr个实例的每种数据类型,render方法将遵循相同的模式:

implicit val somethingRepr = new Repr[Something] {
  def render(value: Something, request: RequestHeader): Result = request match {
    // <Some interesting code> (e.g. case Accepts.Html() => Ok(views.html.something(value)))
    case _ => NotAcceptable
  }
}

您可能希望通过抽象此模式来减少样板并避免用户忘记最后一个“case”语句。例如,您可以编写以下帮助方法来构建Repr[Something]

object Repr {
  def apply[A](f: PartialFunction[RequestHeader, A => Result]): Repr[A] = new Repr[A] {
    def render(a: A, request: RequestHeader): Result =
      if (f.isDefinedAt(request)) f(request)(a)
      else NotAcceptable
  }
}

因此,您只需编写以下内容即可获得Repr[Foo]

implicit val fooRepr = Repr[Foo] {
  case Accepts.Html() => foo => Ok(views.html.foo(foo))
  case Accepts.Json() => foo => Ok(Json.obj("bar" -> foo.bar))
}

答案 1 :(得分:1)

一种选择可能是为此创建一种包装。

它应该类似于以下内容:

  //THE WRAPPER that takes the action computation and the formatter
  def acceptEnabledAction[A]: (RequestHeader => A, (RequestHeader, A) => Result) => Action[AnyContent] =
     (a, f) => 
        Action { request => 
            f(request, a(request))
        }

  //a sample formatter
  val encoder = (r, r) => r.accept /*or smthg*/ match {
        case x if x == "text/json" || x == "application/json" => Ok(toJson(r)) /*dummy to json*/
        case x if x == "text/xml" || x == "application/xml"   => Ok(toXml(r)) /*dummy to xml*/
        case _ => BadRequest("not accepted")
      }


  //an action using it    
  def index = acceptEnabledAction[Map[String, Boolean]](
      rh => /*the real action content is here*/Map("a" -> true), 
      encoder
    )

答案 2 :(得分:0)

另一个选择是使用mimerender模块(披露:我写了它)。您可以定义映射一次:

val m = mapping(
  "text/html" -> { s: String => views.html.index(s) },
  "application/xml" -> { s: String => <message>{s}</message> },
  "application/json" -> { s: String => toJson(Map("message" -> toJson(s))) },
  "text/plain" -> identity[String]_
)

只需在所有控制器中重复使用它:

object Application extends Controller {
  def index = Action { implicit request =>
    m.status(200)("Hello, world!")
  }
}

注意:这是一个非常早期的版本,仅在Play 2.0.4上进行了测试