【问题标题】:Play 2.0 RESTful request post-processingPlay 2.0 RESTful 请求后处理
【发布时间】:2012-06-19 09:33:12
【问题描述】:

关于this question,我很好奇如何进行请求后 REST 处理(粗略):

def postProcessor[T](content: T) = {
  request match {
    case Accepts.Json() => asJson(content)
    case Accepts.Xml()  => asXml(content)
    case _ => content
  }
}

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

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

目前我到处都在做这种事情:

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

虽然我希望根据接受标头后请求进行 toJson/toXml 转换。

【问题讨论】:

    标签: rest request playframework-2.0 post-processing


    【解决方案1】:

    您希望能够根据请求的Accept 标头值计算包含A 类型对象的表示的Result。您可以使用以下 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类,定义如下:

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

    【讨论】:

    • 好东西。 Repr 结构是我谈到的“包装器”的一个很好的补充
    • 请注意,上面的代码没有考虑接受值的quality factor
    • 希望它很少使用,尤其是在浏览器中。然而,这对于 WS f.i. 之间的内容协商是正确的
    • @JulienRichard-Foy 这令人印象深刻(因此,你得到了复选标记);但是,我喜欢的样板有点太多了。就目前而言,复杂的转换已经在范围内处理;我希望处理简单的情况,例如尚未转换为 json、xml 等的字符串和映射。我需要进行试验,可能会传入状态代码(因此可以返回“Ok”以外的其他内容"),会话映射参数(到 Status(content).withSession(session)),还有谁知道还有什么,尚未探索解决方案 ;-) 感谢您指路...
    【解决方案2】:

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

    应该是这样的:

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

    【讨论】:

    • +1,@andy petralla,我的想法完全正确。我已经为身份验证实现了动作组合,并打算为 REST 后处理做类似的事情。关于您的方法,我在 Play 2.1 上,因此可以匹配 Accepts.Json、Accepts.Xml 等上的请求。此外,可能存在表单绑定错误等情况,您转换为 Json (e.errorsAsJson ) 到位,因此如果内容已经属于给定类型,我们将要检查您的编码器方法。最后,组合动作应该接受通用的 T 内容,以便有一个干净的“def index = Rest {...} implementation”
    • 对于组成物联网做一些身份验证,我写了两个帖子:ska-la.blogspot.be/2012/06/…ska-la.blogspot.be/2012/06/…。你可能会感兴趣。确实,我在 play2.1 中看到了提取器,但仍在 2.0 中。对于T,将acceptEnabledAction 作为通用结果还不够。对于编码器,您可以让 acceptEnabledAction 返回一个 Either(或 Validation),其中左侧部分作为错误形式,右侧作为值:Either[Form[T], T]。所以编码器会将它作为参数。
    • 刚刚回到这里,查看了您的帖子,谢谢,但(还)不是我的那杯茶。 Scalaz、Shapeless 和他们的符号朋友在这个时候对我来说有点难以理解。启用理解倾向于实现与您正在做的类似的事情,但语法更好(IMO),并且更容易(无论如何对我来说)理解;-)我会去Scalaz等人,但是才入 Scala 几个月,建房子需要地基...
    • 嘿没问题,我已经做了一些工作来启用 f° 的东西。我在 scala 工作中只工作了 8 个月,实际上我不得不挖掘一段时间来恢复我的一些 f° 技能^^。但是,如果您只是查看 github 项目(我可能不得不将其转移到一个简单的库),并且您可以重用 Intercept 类,而无需了解海底发生了什么。
    • 我是说这不是必需的 ;-) 动作组合在某种程度上提供了你的“偷窃”方法。我的意思是,你可以返回一个用户实例,对吧?是否需要使用 Sabin 的 Shapeless 库来组合用户 ID + 用户名?我认为不会,dao.user.findById(id) 可以解决问题,并且动作组合抽象出成功/失败响应(即“def update = Authenticated {implicit request => // 看,request.user }”)。不要误会我的意思,你所做的很酷(例如,理解 monad 做得很好),但我还没有在 Scala 中以纯 F 出售
    【解决方案3】:

    另一个选择是使用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 上进行了测试

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-03
      • 2017-09-03
      • 2012-10-12
      • 1970-01-01
      • 2019-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多