Skip to content

Commit

Permalink
Allow creating a body from Array[Byte] (#2581)
Browse files Browse the repository at this point in the history
* Allow creating a body from `Array[Byte]`

* Add Body.fromArray to HttpGen

* Fix scala 2.12 compiling
  • Loading branch information
kyri-petrou authored Jan 4, 2024
1 parent be67a7d commit f2ce5cf
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,39 @@ import sttp.client3.{HttpURLConnectionBackend, UriContext, basicRequest}
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
class ServerInboundHandlerBenchmark {
private val random = scala.util.Random
random.setSeed(42)
private val largeString = random.alphanumeric.take(100000).mkString

private val baseUrl = "http://localhost:8080"
private val headers = Headers(Header.ContentType(MediaType.text.`plain`).untyped)

private val arrayEndpoint = "array"
private val arrayResponse = ZIO.succeed(
Response(
status = Status.Ok,
headers = headers,
body = Body.fromArray(largeString.getBytes),
),
)
private val arrayRoute = Route.route(Method.GET / arrayEndpoint)(handler(arrayResponse))
private val arrayRequest = basicRequest.get(uri"$baseUrl/$arrayEndpoint")

private val chunkEndpoint = "chunk"
private val chunkResponse = ZIO.succeed(
Response(
status = Status.Ok,
headers = headers,
body = Body.fromChunk(Chunk.fromArray(largeString.getBytes)),
),
)
private val chunkRoute = Route.route(Method.GET / chunkEndpoint)(handler(chunkResponse))
private val chunkRequest = basicRequest.get(uri"$baseUrl/$chunkEndpoint")

private val testResponse = ZIO.succeed(Response.text("Hello World!"))
private val testEndPoint = "test"
private val testRoute = Route.route(Method.GET / testEndPoint)(handler(testResponse))
private val testUrl = s"http://localhost:8080/$testEndPoint"
private val testUrl = s"$baseUrl/$testEndPoint"
private val testRequest = basicRequest.get(uri"$testUrl")

private val shutdownResponse = Response.text("shutting down")
Expand All @@ -27,7 +56,8 @@ class ServerInboundHandlerBenchmark {

private def shutdownRoute(shutdownSignal: Promise[Nothing, Unit]) =
Route.route(Method.GET / shutdownEndpoint)(handler(shutdownSignal.succeed(()).as(shutdownResponse)))
private def http(shutdownSignal: Promise[Nothing, Unit]) = Routes(testRoute, shutdownRoute(shutdownSignal)).toHttpApp
private def http(shutdownSignal: Promise[Nothing, Unit]) =
Routes(testRoute, arrayRoute, chunkRoute, shutdownRoute(shutdownSignal)).toHttpApp

@Setup(Level.Trial)
def setup(): Unit = {
Expand Down Expand Up @@ -58,7 +88,21 @@ class ServerInboundHandlerBenchmark {
}

@Benchmark
def benchmarkApp(): Unit = {
def benchmarkLargeArray(): Unit = {
val statusCode = arrayRequest.send(backend).code
if (!statusCode.isSuccess)
throw new RuntimeException(s"Received unexpected status code ${statusCode.code}")
}

@Benchmark
def benchmarkLargeChunk(): Unit = {
val statusCode = chunkRequest.send(backend).code
if (!statusCode.isSuccess)
throw new RuntimeException(s"Received unexpected status code ${statusCode.code}")
}

@Benchmark
def benchmarkSimple(): Unit = {
val statusCode = testRequest.send(backend).code
if (!statusCode.isSuccess)
throw new RuntimeException(s"Received unexpected status code ${statusCode.code}")
Expand Down
36 changes: 36 additions & 0 deletions zio-http/src/main/scala/zio/http/Body.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ object Body {
*/
def fromChunk(data: Chunk[Byte]): Body = ChunkBody(data)

/**
* Constructs a [[zio.http.Body]] from an array of bytes.
*
* WARNING: The array must not be mutated after creating the body.
*/
def fromArray(data: Array[Byte]): Body = ArrayBody(data)

/**
* Constructs a [[zio.http.Body]] from the contents of a file.
*/
Expand Down Expand Up @@ -293,6 +300,35 @@ object Body {
copy(mediaType = Some(newMediaType), boundary = boundary.orElse(Some(newBoundary)))
}

private[zio] final case class ArrayBody(
data: Array[Byte],
override val mediaType: Option[MediaType] = None,
override val boundary: Option[Boundary] = None,
) extends Body
with UnsafeWriteable
with UnsafeBytes { self =>

override def asArray(implicit trace: Trace): Task[Array[Byte]] = ZIO.succeed(data)

override def isComplete: Boolean = true

override def isEmpty: Boolean = data.isEmpty

override def asChunk(implicit trace: Trace): Task[Chunk[Byte]] = ZIO.succeed(Chunk.fromArray(data))

override def asStream(implicit trace: Trace): ZStream[Any, Throwable, Byte] =
ZStream.unwrap(asChunk.map(ZStream.fromChunk(_)))

override def toString(): String = s"Body.fromArray($data)"

override private[zio] def unsafeAsArray(implicit unsafe: Unsafe): Array[Byte] = data

override def contentType(newMediaType: MediaType): Body = copy(mediaType = Some(newMediaType))

override def contentType(newMediaType: MediaType, newBoundary: Boundary): Body =
copy(mediaType = Some(newMediaType), boundary = boundary.orElse(Some(newBoundary)))
}

private[zio] final case class FileBody(
val file: java.io.File,
chunkSize: Int = 1024 * 4,
Expand Down
3 changes: 3 additions & 0 deletions zio-http/src/main/scala/zio/http/netty/NettyBodyWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ object NettyBodyWriter {
}
},
)
case ArrayBody(data, _, _) =>
ctx.writeAndFlush(Unpooled.wrappedBuffer(data))
None
case ChunkBody(data, _, _) =>
ctx.write(Unpooled.wrappedBuffer(data.toArray))
ctx.flush()
Expand Down
2 changes: 2 additions & 0 deletions zio-http/src/test/scala/zio/http/internal/HttpGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ object HttpGen {
),
Body.fromString(list.mkString("")),
Body.fromChunk(Chunk.fromArray(list.mkString("").getBytes())),
Body.fromArray(list.mkString("").getBytes()),
Body.empty,
),
)
Expand Down Expand Up @@ -139,6 +140,7 @@ object HttpGen {
),
Body.fromString(list.mkString("")),
Body.fromChunk(Chunk.fromArray(list.mkString("").getBytes())),
Body.fromArray(list.mkString("").getBytes()),
),
)
} yield cnt
Expand Down

0 comments on commit f2ce5cf

Please sign in to comment.