• PLAY2.6-SCALA(四) 请求体解析器


    一个http请求是一个请求头后面跟着一个请求体,头部信息比较短,可以安全的缓存在内存中,在Play中头部信息使用RequestHeader类进行建模。请求体的内容可能较大,使用流stream的形式进行建模。

    然而,有许多请求体是比较小的,因此Play提供了一个BodyParser抽象用于将流中的信息转换为内存对象。由于Play是一个异步框架,对流的处理不使用传统的InputStream,因为该方法在读取时会阻塞

    整个线程直到数据读取完毕。Play中使用异步的Akka Stream进行处理,Akka Stream是Reactive Streams的一个实现。

    一、使用play内部的解析器

    1.在不指定解析器的情况下,play会使用内部的解析器根据请求头的Content-Type字段来判断解析后的类型,application/json会被解析为JsValue,application/x-www-form-urlencoded会被解析为Map[String, Seq[String]]

    def save = Action { request: Request[AnyContent] =>
      val body: AnyContent = request.body
      val jsonBody: Option[JsValue] = body.asJson
    
      // Expecting json body
      jsonBody.map { json =>
        Ok("Got: " + (json  "name").as[String])
      }.getOrElse {
        BadRequest("Expecting application/json request body")
      }
    }
    • text/plainString, accessible via asText.
    • application/jsonJsValue, accessible via asJson.
    • application/xmltext/xml or application/XXX+xmlscala.xml.NodeSeq, accessible via asXml.
    • application/x-www-form-urlencodedMap[String, Seq[String]], accessible via asFormUrlEncoded.
    • multipart/form-dataMultipartFormData, accessible via asMultipartFormData.
    • Any other content type: RawBuffer, accessible via asRaw.

    2.指定解析器类型

    def save = Action(parse.json) { request: Request[JsValue]  =>Ok("Got: " + (request.body  "name").as[String])}
    

    忽略content-type,尽力把请求体解析为json 类型

    def save = Action(parse.tolerantJson) { request: Request[JsValue]  =>Ok("Got: " + (request.body  "name").as[String])}
    

    将请求体存为一个文件

    def save = Action(parse.file(to = new File("/tmp/upload"))) { request: Request[File]  =>
      Ok("Saved the request content to " + request.body)
    }
    

    之前是所有用户都存在一个文件里,现在每个用户都存一个文件

    val storeInUserFile = parse.using { request =>
      request.session.get("username").map { user =>
        parse.file(to = new File("/tmp/" + user + ".upload"))
      }.getOrElse {
        sys.error("You don't have the right to upload here")
      }
    }
    
    def save = Action(storeInUserFile) { request =>
      Ok("Saved the request content to " + request.body)
    }
    

    默认内存中最大缓存为100KB,但是可以通过application.conf进行设置: play.http.parser.maxMemoryBuffer=128K

    而磁盘最大缓存默认为10MB, play.http.parser.maxDiskBuffer 

    也可以在函数中设置:

    // 设置最大为10KB数据
    def save = Action(parse.text(maxLength = 1024 * 10)) { request: Request[String]  =>
      Ok("Got: " + text)
    }
    def save = Action(parse.maxLength(1024 * 10, storeInUserFile)) { request =>
      Ok("Saved the request content to " + request.body)
    }
    

    二、自定义一个解析器

    1.当你不想解析时,可以把请求体转到别的地方

    import javax.inject._
    import play.api.mvc._
    import play.api.libs.streams._
    import play.api.libs.ws._
    import scala.concurrent.ExecutionContext
    import akka.util.ByteString
    
    class MyController @Inject() (ws: WSClient, val controllerComponents: ControllerComponents)
        (implicit ec: ExecutionContext) extends BaseController {
    
      def forward(request: WSRequest): BodyParser[WSResponse] = BodyParser { req =>
        Accumulator.source[ByteString].mapFuture { source =>
          request
            .withBody(source)
            .execute()
            .map(Right.apply)
        }
      }
    
      def myAction = Action(forward(ws.url("https://example.com"))) { req =>
        Ok("Uploaded")
      }
    } 

    2.通过akka Streams定制解析器

    对akka Stream的讨论超出本文的内容,最好去参考akka的相关文档,https://doc.akka.io/docs/akka/2.5/stream/index.html?language=scala

    以下例子是一个CSV文件的解析器,该实例基于 
    https://doc.akka.io/docs/akka/2.5/stream/stream-cookbook.html?language=scala#parsing-lines-from-a-stream-of-bytestrings

    import play.api.mvc.BodyParser
    import play.api.libs.streams._
    import akka.util.ByteString
    import akka.stream.scaladsl._
    
    val csv: BodyParser[Seq[Seq[String]]] = BodyParser { req =>
    
      // A flow that splits the stream into CSV lines
      val sink: Sink[ByteString, Future[Seq[Seq[String]]]] = Flow[ByteString]
        // We split by the new line character, allowing a maximum of 1000 characters per line
        .via(Framing.delimiter(ByteString("
    "), 1000, allowTruncation = true))
        // Turn each line to a String and split it by commas
        .map(_.utf8String.trim.split(",").toSeq)
        // Now we fold it into a list
        .toMat(Sink.fold(Seq.empty[Seq[String]])(_ :+ _))(Keep.right)
    
      // Convert the body to a Right either
      Accumulator(sink).map(Right.apply)
    }
  • 相关阅读:
    《数据安全实践指南》 数据销毁安全实践数据销毁处理
    《数据安全实践指南》 数据销毁安全实践介质销毁处理
    软考信息安全网络攻击原理与常用方法
    《数据安全实践指南》 通用安全实践数据安全策略规划
    软考信息安全密码学基本理论
    Discourse 清理存储空间的方法
    Java “constant string too long” 编译错误
    Java 是否应该使用通配符导入( wildcard imports)
    Java 多行字符串
    Edge 提供了标签分组功能
  • 原文地址:https://www.cnblogs.com/feiyumo/p/9134644.html
Copyright © 2020-2023  润新知