=htc #16395 print decoded text for non-binary strict entities

* truncate strict entity before decoding on toString
* print raw byte string if strict entity decoding fails
This commit is contained in:
Catalin Ursachi 2015-12-07 14:54:27 +00:00 committed by Roland Kuhn
parent 0f74a0e02b
commit 026e4f0078
2 changed files with 66 additions and 2 deletions

View file

@ -16,10 +16,13 @@ import akka.stream.stage._
import akka.stream._
import akka.{ japi, stream }
import akka.http.javadsl.model.HttpEntityStrict
import akka.http.scaladsl.model.ContentType.{ NonBinary, Binary }
import akka.http.scaladsl.util.FastFuture
import akka.http.javadsl.{ model jm }
import akka.http.impl.util.JavaMapping.Implicits._
import scala.util.control.NonFatal
/**
* Models the entity (aka "body" or "content) of an HTTP message.
*/
@ -239,6 +242,27 @@ object HttpEntity {
else Default(contentType, data.length, limitableByteSource(Source.single(data))) withSizeLimit maxBytes
override def productPrefix = "HttpEntity.Strict"
override def toString = {
val dataAsString = contentType match {
case _: Binary
data.toString()
case nb: NonBinary
try {
val maxBytes = 4096
if (data.length > maxBytes) {
val truncatedString = data.take(maxBytes).decodeString(nb.charset.value).dropRight(1)
s"$truncatedString ... (${data.length} bytes total)"
} else
data.decodeString(nb.charset.value)
} catch {
case NonFatal(e)
data.toString()
}
}
s"$productPrefix($contentType,$dataAsString)"
}
}
/**

View file

@ -9,7 +9,7 @@ import com.typesafe.config.{ ConfigFactory, Config }
import scala.concurrent.{ Promise, Await }
import scala.concurrent.duration._
import org.scalatest.{ BeforeAndAfterAll, MustMatchers, FreeSpec }
import org.scalatest.matchers.Matcher
import org.scalatest.matchers.{ MatchResult, Matcher }
import akka.util.ByteString
import akka.actor.ActorSystem
import akka.stream.scaladsl._
@ -17,6 +17,8 @@ import akka.stream.ActorMaterializer
import akka.http.scaladsl.model.HttpEntity._
import akka.http.impl.util.StreamUtils
import scala.util.Random
class HttpEntitySpec extends FreeSpec with MustMatchers with BeforeAndAfterAll {
val tpe: ContentType = ContentTypes.`application/octet-stream`
val abc = ByteString("abc")
@ -28,7 +30,6 @@ class HttpEntitySpec extends FreeSpec with MustMatchers with BeforeAndAfterAll {
akka.event-handlers = ["akka.testkit.TestEventListener"]
akka.loglevel = WARNING""")
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
import system.dispatcher
implicit val materializer = ActorMaterializer()
override def afterAll() = system.shutdown()
@ -117,6 +118,36 @@ class HttpEntitySpec extends FreeSpec with MustMatchers with BeforeAndAfterAll {
transformTo(Strict(tpe, doubleChars("abcfghijk") ++ trailer))
}
}
"support toString" - {
"Strict with binary MediaType" in {
val binaryType = ContentTypes.`application/octet-stream`
val entity = Strict(binaryType, abc)
entity must renderStrictDataAs(entity.data.toString())
}
"Strict with non-binary MediaType and less than 4096 bytes" in {
val nonBinaryType = ContentTypes.`application/json`
val entity = Strict(nonBinaryType, abc)
entity must renderStrictDataAs(entity.data.decodeString(nonBinaryType.charset.value))
}
"Strict with non-binary MediaType and over 4096 bytes" in {
val utf8Type = ContentTypes.`text/plain(UTF-8)`
val longString = Random.alphanumeric.take(10000).mkString
val entity = Strict(utf8Type, ByteString.apply(longString, utf8Type.charset.value))
entity must renderStrictDataAs(s"${longString.take(4095)} ... (10000 bytes total)")
}
"Default" in {
val entity = Default(tpe, 11, source(abc, de, fgh, ijk))
entity.toString must include(entity.productPrefix)
}
"CloseDelimited" in {
val entity = CloseDelimited(tpe, source(abc, de, fgh, ijk))
entity.toString must include(entity.productPrefix)
}
"Chunked" in {
val entity = Chunked(tpe, source(Chunk(abc)))
entity.toString must include(entity.productPrefix)
}
}
}
def source[T](elems: T*) = Source(elems.toList)
@ -136,6 +167,15 @@ class HttpEntitySpec extends FreeSpec with MustMatchers with BeforeAndAfterAll {
Await.result(transformed.toStrict(250.millis), 250.millis)
}
def renderStrictDataAs(dataRendering: String): Matcher[Strict] =
Matcher { strict: Strict
val expectedRendering = s"${strict.productPrefix}(${strict.contentType},$dataRendering)"
MatchResult(
strict.toString == expectedRendering,
strict.toString + " != " + expectedRendering,
strict.toString + " == " + expectedRendering)
}
def duplicateBytesTransformer(): Flow[ByteString, ByteString, Unit] =
Flow[ByteString].transform(() StreamUtils.byteStringTransformer(doubleChars, () trailer))