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

zio.http.netty.model.Conversions.scala Maven / Gradle / Ivy

/*
 * Copyright 2021 - 2023 Sporta Technologies PVT LTD & the ZIO HTTP contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.http.netty.model

import scala.collection.AbstractIterator

import zio.http.Server.Config.CompressionOptions
import zio.http._

import com.aayushatharva.brotli4j.encoder.Encoder
import io.netty.handler.codec.compression.StandardCompressionOptions
import io.netty.handler.codec.http._
import io.netty.handler.codec.http.websocketx.WebSocketScheme

private[netty] object Conversions {
  def methodFromNetty(method: HttpMethod): Method =
    method match {
      case HttpMethod.OPTIONS => Method.OPTIONS
      case HttpMethod.GET     => Method.GET
      case HttpMethod.HEAD    => Method.HEAD
      case HttpMethod.POST    => Method.POST
      case HttpMethod.PUT     => Method.PUT
      case HttpMethod.PATCH   => Method.PATCH
      case HttpMethod.DELETE  => Method.DELETE
      case HttpMethod.TRACE   => Method.TRACE
      case HttpMethod.CONNECT => Method.CONNECT
      case method             => Method.CUSTOM(method.name())
    }

  def methodToNetty(method: Method): HttpMethod =
    method match {
      case Method.OPTIONS      => HttpMethod.OPTIONS
      case Method.GET          => HttpMethod.GET
      case Method.HEAD         => HttpMethod.HEAD
      case Method.POST         => HttpMethod.POST
      case Method.PUT          => HttpMethod.PUT
      case Method.PATCH        => HttpMethod.PATCH
      case Method.DELETE       => HttpMethod.DELETE
      case Method.TRACE        => HttpMethod.TRACE
      case Method.CONNECT      => HttpMethod.CONNECT
      case Method.ANY          => HttpMethod.GET
      case Method.CUSTOM(name) => new HttpMethod(name)
    }

  def headersToNetty(headers: Headers): HttpHeaders =
    headers match {
      case Headers.FromIterable(_)        => encodeHeaderListToNetty(headers)
      case Headers.Native(value, _, _, _) => value.asInstanceOf[HttpHeaders]
      case Headers.Concat(_, _)           => encodeHeaderListToNetty(headers)
      case Headers.Empty                  => new DefaultHttpHeaders()
    }

  def urlToNetty(url: URL): String = {
    // As per the spec, the path should contain only the relative path.
    // Host and port information should be in the headers.
    val url0 = if (url.path.isEmpty) url.addLeadingSlash else url
    url0.relative.addLeadingSlash.encode
  }

  private def nettyHeadersIterator(headers: HttpHeaders): Iterator[Header] =
    new AbstractIterator[Header] {
      private val nettyIterator = headers.iteratorCharSequence()

      override def hasNext: Boolean = nettyIterator.hasNext

      override def next(): Header = {
        val entry = nettyIterator.next()
        Header.Custom(entry.getKey, entry.getValue)
      }
    }

  def headersFromNetty(headers: HttpHeaders): Headers =
    Headers.Native(
      headers,
      (headers: HttpHeaders) => nettyHeadersIterator(headers),
      // NOTE: Netty's headers.get is case-insensitive
      (headers: HttpHeaders, key: CharSequence) => headers.get(key),
      (headers: HttpHeaders, key: CharSequence) => headers.contains(key),
    )

  private def encodeHeaderListToNetty(headers: Iterable[Header]): HttpHeaders = {
    val nettyHeaders  = new DefaultHttpHeaders()
    val setCookieName = Header.SetCookie.name
    val iter          = headers.iterator
    while (iter.hasNext) {
      val header = iter.next()
      val name   = header.headerName
      if (name == setCookieName) {
        nettyHeaders.add(name, header.renderedValueAsCharSequence)
      } else {
        nettyHeaders.set(name, header.renderedValueAsCharSequence)
      }
    }
    nettyHeaders
  }

  def statusToNetty(status: Status): HttpResponseStatus =
    HttpResponseStatus.valueOf(status.code, status.reasonPhrase)

  def statusFromNetty(status: HttpResponseStatus): Status =
    Status.fromInt(status.code) match {
      case Status.Custom(code, _) => Status.Custom(code, status.reasonPhrase)
      case status                 => status
    }

  def schemeToNetty(scheme: Scheme): Option[HttpScheme] = scheme match {
    case Scheme.HTTP  => Option(HttpScheme.HTTP)
    case Scheme.HTTPS => Option(HttpScheme.HTTPS)
    case _            => None
  }

  def schemeToNettyWebSocketScheme(scheme: Scheme): Option[WebSocketScheme] = scheme match {
    case Scheme.WS  => Option(WebSocketScheme.WS)
    case Scheme.WSS => Option(WebSocketScheme.WSS)
    case _          => None
  }

  def schemeFromNetty(scheme: HttpScheme): Option[Scheme] = scheme match {
    case HttpScheme.HTTPS => Option(Scheme.HTTPS)
    case HttpScheme.HTTP  => Option(Scheme.HTTP)
    case _                => None
  }

  def schemeFromNetty(scheme: WebSocketScheme): Option[Scheme] = scheme match {
    case WebSocketScheme.WSS => Option(Scheme.WSS)
    case WebSocketScheme.WS  => Option(Scheme.WS)
    case _                   => None
  }

  def compressionOptionsToNetty(
    compressionOptions: CompressionOptions,
  ): io.netty.handler.codec.compression.CompressionOptions =
    compressionOptions match {
      case CompressionOptions.GZip(cfg)    =>
        StandardCompressionOptions.gzip(cfg.level, cfg.bits, cfg.mem)
      case CompressionOptions.Deflate(cfg) =>
        StandardCompressionOptions.deflate(cfg.level, cfg.bits, cfg.mem)
      case CompressionOptions.Brotli(cfg)  =>
        StandardCompressionOptions.brotli(
          new Encoder.Parameters().setQuality(cfg.quality).setWindow(cfg.lgwin).setMode(brotliModeToJava(cfg.mode)),
        )
    }

  def brotliModeToJava(brotli: CompressionOptions.Mode): Encoder.Mode = brotli match {
    case CompressionOptions.Mode.Font    => Encoder.Mode.FONT
    case CompressionOptions.Mode.Text    => Encoder.Mode.TEXT
    case CompressionOptions.Mode.Generic => Encoder.Mode.GENERIC
  }

  def versionToNetty(version: Version): HttpVersion = version match {
    case Version.Http_1_0 => HttpVersion.HTTP_1_0
    case Version.Http_1_1 => HttpVersion.HTTP_1_1
    case Version.Default  => HttpVersion.HTTP_1_1
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy