2013-05-14 2 views
1

Я пытаюсь реализовать пользовательский парсер данных multipartFormData для обработки обратного вызова из API-интерфейса Sendgrid. Обратный вызов - это множественный запрос, в котором данные могут быть закодированы в разных кодировках: например, utf-8 или ISO-8859-1.Пользовательский парсер multipartFormData в play2 scala для обработки нескольких кодировок в dataparts

Sendgrid обеспечивает поле Кодировки, которое просто объект JSON, который объясняет, что кодирующая каждое поле имеет:

{"to":"UTF-8","html":"ISO-8859-1","subject":"UTF-8","from":"UTF-8","text":"ISO-8859-1"} 

Я в настоящее время извлечения кодировок из dataParts так:

val charsets = extract(request.body.dataParts, "charsets", _.as[Charsets]).getOrElse(Charsets(Some(""), Some(""), Some(""), Some(""), Some(""))) 

    def extract[T](env: Map[String, Seq[String]], key: String, conv: JsValue => T): Option[T] = { 
     env.get(key).flatMap(_.headOption).map(Json.parse).map(conv) 
    } 

    case class Charsets(to: Option[String], html: Option[String], subject: Option[String], from: Option[String], text: Option[String]) 

    object Charsets { 
     implicit val charsetReads = Json.format[Charsets] 
    } 

но привычка поскольку все может иметь неправильную кодировку, установленную синтаксическим анализатором.

Оригинальный handleDataPart является жёстко использовать utf-8

def handleDataPart: PartHandler[Part] = { 
    case headers @ Multipart.PartInfoMatcher(partName) if !Multipart.FileInfoMatcher.unapply(headers).isDefined => 
     Traversable.takeUpTo[Array[Byte]](DEFAULT_MAX_TEXT_LENGTH) 
     .transform(Iteratee.consume[Array[Byte]]().map(bytes => DataPart(partName, new String(bytes, "utf-8")))) 
     .flatMap { data => 
     Cont({ 
      case Input.El(_) => Done(MaxDataPartSizeExceeded(partName), Input.Empty) 
      case in => Done(data, in) 
     }) 
     } 
    } 

Так что я хотел бы сделать, это начать извлекать возражают кодировок, а затем использовать его при создании Dataparts или вместо создания строки для каждого поле, создайте Array [Byte], а затем в моем контроллере обработайте создание строк. Может быть, есть и другой способ? Как бы вы решили это? Я чувствую себя застрявшим и нуждаюсь в каком-то руководстве.

ответ

0

Получил ответ от моего приятеля Tor! Работает как шарм!

class Parser @Inject()(mailRequestService: MailRequestService, surveyService: SurveyService) extends Controller { 

    val UTF8 = "UTF-8" 

    def parseMail = Action.async(rawFormData) { request => 
    val charsets = extract(request.body.dataParts, "charsets", _.as[Charsets]).getOrElse(Charsets(Some(""), Some(""), Some(""), Some(""), Some(""))) 
    val envelope = extract(request.body.dataParts, "envelope", _.as[Envelope]).getOrElse(Envelope(Nil, "")) 
    val sendgrid = SendgridMail(
     extractString(request.body.dataParts, "text", charsets), 
     extractString(request.body.dataParts, "html", charsets), 
     extractString(request.body.dataParts, "from", charsets), 
     extractString(request.body.dataParts, "to", charsets), 
     charsets, 
     envelope 
    ) 

    val simple = mailRequestService.createNewMail(sendgrid).map { 
     result => 
     result.fold(
      exception => throw new UnexpectedServiceException("Could not save sendgrid mail: "+sendgrid, exception), 
      mail => Ok("Success") 
     ) 
    } 
    simple 
    } 

    def extractString(data: Map[String, Seq[Array[Byte]]], key: String, charsets: Charsets): Option[String] = { 
    play.Logger.info("data = " + data) 
    data.get(key).flatMap(_.headOption).map { firstValue => 
     (charsets, key) match { 
     case (charset, "text") if charset.text.isDefined => 
      val cset = java.nio.charset.Charset.forName(charset.text.get) 
      Some(new String(firstValue, cset)) 
     case (charset, "html") if charset.html.isDefined => 
      val cset = java.nio.charset.Charset.forName(charset.html.get) 
      Some(new String(firstValue, cset)) 
     case (charset, "from") if charset.from.isDefined => 
      val cset = java.nio.charset.Charset.forName(charset.from.get) 
      Some(new String(firstValue, cset)) 
     case (charset, "to") if charset.to.isDefined => 
      val cset = java.nio.charset.Charset.forName(charset.to.get) 
      Some(new String(firstValue, cset)) 
     case _ => Some("") 
     } 
    }.getOrElse(Some("")) 
    } 

    /** 
    * 1. Retrieve value for key eg. envelope 
    * 2. Use flatmap to flatten the structure so that we do not get Option[Option[_] ] 
    * 3. Call map and use JsonParse on the String to get JsValue 
    * 4. Call map and use provided method eg _.as[Envelope] that results in T, in this case Envelope 
    * 5. RETURN! 
    */ 
    def extract[T](env: Map[String, Seq[Array[Byte]]], key: String, conv: JsValue => T): Option[T] = { 
    env.get(key).flatMap(_.headOption.map(a => new String(a, "UTF-8"))).map(Json.parse).map(conv) 
    } 

    def findSurveyByEmail(email: String): Future[Option[Survey]] = { 
    surveyService.findSurveyByEmail(email) 
    } 

    def handler: parse.Multipart.PartHandler[Part] = { 
    case headers @ PartInfoMatcher(partName) if !FileInfoMatcher.unapply(headers).isDefined => 
     Traversable.takeUpTo[Array[Byte]](1024 * 100) 
     .transform(Iteratee.consume[Array[Byte]]().map(bytes => FilePart(partName, "", None, bytes))) 
     .flatMap { data => 
     Cont({ 
      case Input.El(_) => Done(data, Input.Empty) 
      case in => Done(data, in) 
     }) 
     } 
    case headers => Done(BadPart(headers), Input.Empty) 
    } 

    def rawFormData[A]: BodyParser[RawDataFormData] = BodyParser("multipartFormData") { request => 
    Multipart.multipartParser(handler)(request).map { errorOrParts => 
     errorOrParts.right.map { parts => 
     val data = parts.collect { case FilePart(key, _, _, value: Array[Byte]) => (key, value) }.groupBy(_._1).mapValues(_.map(_._2)) 
     RawDataFormData(data) 
     } 
    } 
    } 
} 

case class RawDataFormData(dataParts: Map[String, Seq[Array[Byte]]]) 

спасибо Tor!