All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zhttp.service.server.content.handlers.ServerResponseHandler.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC11
Show newest version
package zhttp.service.server.content.handlers

import io.netty.buffer.ByteBuf
import io.netty.channel.{ChannelHandlerContext, DefaultFileRegion}
import io.netty.handler.codec.http._
import zhttp.http.{HttpData, Response}
import zhttp.service.server.ServerTime
import zhttp.service.{ChannelFuture, HttpRuntime, Server}
import zio.ZIO
import zio.stream.ZStream

import java.io.File

private[zhttp] trait ServerResponseHandler[R] {
  type Ctx = ChannelHandlerContext
  val rt: HttpRuntime[R]
  val config: Server.Config[R, Throwable]

  def serverTime: ServerTime

  def writeResponse(msg: Response, jReq: HttpRequest)(implicit ctx: Ctx): Unit = {
    ctx.write(encodeResponse(msg))
    writeData(msg.data.asInstanceOf[HttpData.Complete], jReq)
    ()
  }

  /**
   * Enables auto-read if possible. Also performs the first read.
   */
  private def attemptAutoRead()(implicit ctx: Ctx): Unit = {
    if (!config.useAggregator && !ctx.channel().config().isAutoRead) {
      ctx.channel().config().setAutoRead(true)
      ctx.read(): Unit
    }
  }

  /**
   * Checks if an encoded version of the response exists, uses it if it does.
   * Otherwise, it will return a fresh response. It will also set the server
   * time if requested by the client.
   */
  private def encodeResponse(res: Response): HttpResponse = {

    val jResponse = res.attribute.encoded match {

      // Check if the encoded response exists and/or was modified.
      case Some((oRes, jResponse)) if oRes eq res =>
        jResponse match {
          // Duplicate the response without allocating much memory
          case response: FullHttpResponse => response.retainedDuplicate()

          case response => response
        }

      case _ => res.unsafeEncode()
    }
    // Identify if the server time should be set and update if required.
    if (res.attribute.serverTime) jResponse.headers().set(HttpHeaderNames.DATE, serverTime.refreshAndGet())
    jResponse
  }

  private def flushReleaseAndRead(jReq: HttpRequest)(implicit ctx: Ctx): Unit = {
    ctx.flush()
    releaseAndRead(jReq)
  }

  private def releaseAndRead(jReq: HttpRequest)(implicit ctx: Ctx): Unit = {
    releaseRequest(jReq)
    attemptAutoRead()
  }

  /**
   * Releases the FullHttpRequest safely.
   */
  private def releaseRequest(jReq: HttpRequest)(implicit ctx: Ctx): Unit = {
    jReq match {
      case jReq: FullHttpRequest if jReq.refCnt() > 0 => jReq.release(jReq.refCnt()): Unit
      case _                                          => ()
    }
  }

  /**
   * Writes file content to the Channel. Does not use Chunked transfer encoding
   */
  private def unsafeWriteFileContent(file: File)(implicit ctx: ChannelHandlerContext): Unit = {
    // Write the content.
    ctx.write(new DefaultFileRegion(file, 0, file.length()))
    // Write the end marker.
    ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT): Unit
  }

  /**
   * Writes data on the channel
   */
  private def writeData(data: HttpData.Complete, jReq: HttpRequest)(implicit ctx: Ctx): Unit = {
    data match {

      case _: HttpData.FromAsciiString => flushReleaseAndRead(jReq)

      case _: HttpData.BinaryChunk => flushReleaseAndRead(jReq)

      case _: HttpData.BinaryByteBuf => flushReleaseAndRead(jReq)

      case HttpData.Empty => flushReleaseAndRead(jReq)

      case HttpData.BinaryStream(stream) =>
        rt.unsafeRun(ctx) {
          writeStreamContent(stream).ensuring(ZIO.succeed(releaseAndRead(jReq)))
        }

      case HttpData.JavaFile(unsafeGet) =>
        unsafeWriteFileContent(unsafeGet())
        releaseAndRead(jReq)
    }
  }

  /**
   * Writes Binary Stream data to the Channel
   */
  private def writeStreamContent[A](
    stream: ZStream[R, Throwable, ByteBuf],
  )(implicit ctx: Ctx): ZIO[R, Throwable, Unit] = {
    for {
      _ <- stream.foreach(c => ZIO.succeed(ctx.writeAndFlush(c)))
      _ <- ChannelFuture.unit(ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT))
    } yield ()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy