NettyRequestAdapter.kt

  1. package com.hexagontk.http.server.netty

  2. import com.hexagontk.http.model.*
  3. import com.hexagontk.http.parseContentType
  4. import com.hexagontk.http.patterns.PathPattern
  5. import io.netty.buffer.ByteBufHolder
  6. import io.netty.buffer.ByteBufUtil
  7. import io.netty.buffer.Unpooled
  8. import io.netty.channel.Channel
  9. import io.netty.handler.codec.http.HttpHeaderNames.*
  10. import io.netty.handler.codec.http.HttpHeaders
  11. import io.netty.handler.codec.http.QueryStringDecoder
  12. import io.netty.handler.codec.http.cookie.Cookie
  13. import io.netty.handler.codec.http.cookie.ServerCookieDecoder
  14. import io.netty.handler.codec.http.multipart.*
  15. import io.netty.handler.codec.http.HttpRequest
  16. import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite.*
  17. import io.netty.handler.codec.http.cookie.DefaultCookie
  18. import java.net.InetSocketAddress
  19. import java.net.URI
  20. import java.security.cert.X509Certificate
  21. import kotlin.Long.Companion.MIN_VALUE
  22. import io.netty.handler.codec.http.HttpMethod as NettyHttpMethod

  23. class NettyRequestAdapter(
  24.     methodName: NettyHttpMethod,
  25.     req: HttpRequest,
  26.     override val certificateChain: List<X509Certificate>,
  27.     channel: Channel,
  28.     nettyHeaders: HttpHeaders,
  29. ) : HttpRequestPort {

  30.     private val address: InetSocketAddress by lazy { channel.remoteAddress() as InetSocketAddress }

  31.     override val accept: List<ContentType> by lazy {
  32.         nettyHeaders.getAll(ACCEPT).flatMap { it.split(",") }.map { parseContentType(it) }
  33.     }

  34.     override val contentLength: Long by lazy {
  35.         nettyHeaders[CONTENT_LENGTH]?.toLong() ?: 0L
  36.     }

  37.     override val queryParameters: Parameters by lazy {
  38.         val queryStringDecoder = QueryStringDecoder(req.uri())
  39.         Parameters(
  40.             queryStringDecoder.parameters().flatMap { (k, v) -> v.map { Parameter(k, it) } }
  41.         )
  42.     }

  43.     override val parts: List<HttpPart> by lazy {
  44.         HttpPostRequestDecoder(req).bodyHttpDatas.map {
  45.             when (it) {
  46.                 is FileUpload -> HttpPart(
  47.                     name = it.name,
  48.                     body = ByteBufUtil.getBytes(it.content()),
  49.                     submittedFileName = it.filename,
  50.                     contentType = it.contentType?.let { ct -> parseContentType(ct) },
  51.                 )
  52.                 is Attribute -> HttpPart(it.name, it.value)
  53.                 else -> error("Unknown part type: ${it.javaClass}")
  54.             }
  55.         }
  56.     }

  57.     override val formParameters: Parameters by lazy {
  58.         val fields = parts
  59.             .filter { it.submittedFileName == null }
  60.             .map { Parameter(it.name, it.bodyString()) }

  61.         Parameters(fields)
  62.     }

  63.     override val method: HttpMethod by lazy {
  64.         HttpMethod.valueOf(methodName.name())
  65.     }

  66.     override val protocol: HttpProtocol by lazy {
  67.         HttpProtocol.valueOf(req.protocolVersion().protocolName())
  68.     }

  69.     override val host: String by lazy {
  70.         address.hostName
  71.     }

  72.     override val port: Int by lazy {
  73.         address.port
  74.     }

  75.     override val path: String by lazy { URI(req.uri()).path }

  76.     override val cookies: List<com.hexagontk.http.model.Cookie> by lazy {
  77.         val cookieHeader: String = nettyHeaders.get(COOKIE)
  78.             ?: return@lazy emptyList<com.hexagontk.http.model.Cookie>()

  79.         val cookies: Set<Cookie> = ServerCookieDecoder.STRICT.decode(cookieHeader)

  80.         cookies.map {
  81.             Cookie(
  82.                 name = it.name(),
  83.                 value = it.value(),
  84.                 maxAge = if (it.maxAge() == MIN_VALUE) -1 else it.maxAge(),
  85.                 secure = it.isSecure,
  86.                 path = it.path() ?: "/",
  87.                 httpOnly = it.isHttpOnly,
  88.                 sameSite = (it as? DefaultCookie)?.sameSite()?.let { ss ->
  89.                     when (ss) {
  90.                         Strict -> CookieSameSite.STRICT
  91.                         Lax -> CookieSameSite.LAX
  92.                         None -> CookieSameSite.NONE
  93.                     }
  94.                 },
  95.                 domain = it.domain(),
  96.             )
  97.         }
  98.     }

  99.     override val body: Any by lazy {
  100.         val content =
  101.             if (req is ByteBufHolder) req.content()
  102.             else Unpooled.buffer(0)

  103.         if (content.isReadable) ByteBufUtil.getBytes(content)
  104.         else byteArrayOf()
  105.     }

  106.     override val headers: Headers by lazy {
  107.         Headers(
  108.             nettyHeaders.names()
  109.                 .toList()
  110.                 .map { it.lowercase() }
  111.                 .flatMap { h -> nettyHeaders.getAll(h).map { Header(h, it) } }
  112.         )
  113.     }

  114.     override val contentType: ContentType? by lazy {
  115.         nettyHeaders[CONTENT_TYPE]?.let { parseContentType(it) }
  116.     }

  117.     override val authorization: Authorization? by lazy { authorization() }

  118.     override val pathPattern: PathPattern? = null

  119.     override val pathParameters: Map<String, Any> by lazy { throw UnsupportedOperationException() }

  120.     override fun with(
  121.         body: Any,
  122.         headers: Headers,
  123.         contentType: ContentType?,
  124.         method: HttpMethod,
  125.         protocol: HttpProtocol,
  126.         host: String,
  127.         port: Int,
  128.         path: String,
  129.         queryParameters: Parameters,
  130.         parts: List<HttpPart>,
  131.         formParameters: Parameters,
  132.         cookies: List<com.hexagontk.http.model.Cookie>,
  133.         accept: List<ContentType>,
  134.         authorization: Authorization?,
  135.         certificateChain: List<X509Certificate>,
  136.         pathPattern: PathPattern?,
  137.         pathParameters: Map<String, Any>,
  138.     ): HttpRequestPort =
  139.         throw UnsupportedOperationException()
  140. }