1

Sendgrid API からのコールバックを処理するカスタム multipartFormData データ パーサーを実装しようとしています。コールバックは、dataparts が異なるエンコーディングでエンコードされる可能性のあるマルチパート リクエストutf-8ですISO-8859-1

Sendgrid は、各フィールドのエンコーディングを説明する単純な json オブジェクトである charsets フィールドを提供します。

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

したがって、私がやりたいことは、charsets オブジェクトの抽出を開始し、それを Dataparts の作成時に使用するか、各フィールドの文字列を作成する代わりに、Array[Byte] を作成してから、コントローラーで文字列の作成を処理することです。多分他の方法がありますか?これをどのように解決しますか?行き詰まりを感じており、ガイダンスが必要です。

4

1 に答える 1

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

トルさんありがとう!

于 2014-10-23T11:51:04.293 に答える