HttpContext.kt

  1. package com.hexagonkt.http.handlers

  2. import com.hexagonkt.handlers.Context
  3. import com.hexagonkt.core.assertEnabled
  4. import com.hexagonkt.core.media.TEXT_EVENT_STREAM
  5. import com.hexagonkt.core.media.TEXT_PLAIN
  6. import com.hexagonkt.core.toText
  7. import com.hexagonkt.handlers.Handler
  8. import com.hexagonkt.http.model.*
  9. import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500
  10. import com.hexagonkt.http.model.ServerEvent
  11. import com.hexagonkt.http.model.ws.WsSession
  12. import java.net.URL
  13. import java.security.cert.X509Certificate
  14. import java.util.concurrent.Flow.Publisher

  15. // TODO Add exception parameter to 'send*' methods
  16. data class HttpContext(
  17.     override val event: HttpCall,
  18.     override val predicate: (Context<HttpCall>) -> Boolean,
  19.     override val nextHandlers: List<Handler<HttpCall>> = emptyList(),
  20.     override val nextHandler: Int = 0,
  21.     override val exception: Exception? = null,
  22.     override val attributes: Map<*, *> = emptyMap<Any, Any>(),
  23.     override val handled: Boolean = false,
  24. ): Context<HttpCall> {
  25.     val request: HttpRequestPort = event.request
  26.     val response: HttpResponsePort = event.response

  27.     val method: HttpMethod by lazy { request.method }
  28.     val protocol: HttpProtocol by lazy { request.protocol }
  29.     val host: String by lazy { request.host }
  30.     val port: Int by lazy { request.port }
  31.     val path: String by lazy { request.path }
  32.     val queryParameters: QueryParameters by lazy { request.queryParameters }
  33.     val parts: List<HttpPart> by lazy { request.parts }
  34.     val formParameters: FormParameters by lazy { request.formParameters }
  35.     val accept: List<ContentType> by lazy { request.accept }
  36.     val authorization: Authorization? by lazy { request.authorization }
  37.     val certificateChain: List<X509Certificate> by lazy { request.certificateChain }

  38.     val partsMap: Map<String, HttpPart> by lazy { request.partsMap() }
  39.     val url: URL by lazy { request.url() }
  40.     val userAgent: String? by lazy { request.userAgent() }
  41.     val referer: String? by lazy { request.referer() }
  42.     val origin: String? by lazy { request.origin() }
  43.     val certificate: X509Certificate? by lazy { request.certificate() }

  44.     val status: HttpStatus = response.status

  45.     val pathParameters: Map<String, String> by lazy {
  46.         val httpHandler = predicate as HttpPredicate
  47.         val pattern = httpHandler.pathPattern

  48.         if (assertEnabled)
  49.             check(!pattern.prefix) { "Loading path parameters not allowed for paths" }

  50.         pattern.extractParameters(request.path)
  51.     }

  52.     constructor(context: Context<HttpCall>) : this(
  53.         event = context.event,
  54.         predicate = context.predicate,
  55.         nextHandlers = context.nextHandlers,
  56.         nextHandler = context.nextHandler,
  57.         exception = context.exception,
  58.         attributes = context.attributes,
  59.     )

  60.     constructor(
  61.         request: HttpRequestPort = HttpRequest(),
  62.         response: HttpResponsePort = HttpResponse(),
  63.         predicate: HttpPredicate = HttpPredicate(),
  64.         attributes: Map<*, *> = emptyMap<Any, Any>(),
  65.     ) : this(HttpCall(request, response), predicate, attributes = attributes)

  66.     override fun next(): HttpContext {
  67.         for (index in nextHandler until nextHandlers.size) {
  68.             val handler = nextHandlers[index]
  69.             val p = handler.predicate
  70.             if (handler is OnHandler) {
  71.                 if ((!handled) && p(this))
  72.                     return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
  73.             }
  74.             else {
  75.                 if (p(this))
  76.                     return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
  77.             }
  78.         }

  79.         return this
  80.     }

  81.     override fun with(
  82.         event: HttpCall,
  83.         predicate: (Context<HttpCall>) -> Boolean,
  84.         nextHandlers: List<Handler<HttpCall>>,
  85.         nextHandler: Int,
  86.         exception: Exception?,
  87.         attributes: Map<*, *>,
  88.         handled: Boolean,
  89.     ): Context<HttpCall> =
  90.         copy(
  91.             event = event,
  92.             predicate = predicate,
  93.             nextHandlers = nextHandlers,
  94.             nextHandler = nextHandler,
  95.             exception = exception,
  96.             attributes = attributes,
  97.             handled = handled,
  98.         )

  99.     fun unauthorized(
  100.         body: Any = response.body,
  101.         headers: Headers = response.headers,
  102.         contentType: ContentType? = response.contentType,
  103.         cookies: List<Cookie> = response.cookies,
  104.         attributes: Map<*, *> = this.attributes,
  105.     ): HttpContext =
  106.         send(UNAUTHORIZED_401, body, headers, contentType, cookies, attributes)

  107.     fun forbidden(
  108.         body: Any = response.body,
  109.         headers: Headers = response.headers,
  110.         contentType: ContentType? = response.contentType,
  111.         cookies: List<Cookie> = response.cookies,
  112.         attributes: Map<*, *> = this.attributes,
  113.     ): HttpContext =
  114.         send(FORBIDDEN_403, body, headers, contentType, cookies, attributes)

  115.     fun internalServerError(
  116.         body: Any = response.body,
  117.         headers: Headers = response.headers,
  118.         contentType: ContentType? = response.contentType,
  119.         cookies: List<Cookie> = response.cookies,
  120.         attributes: Map<*, *> = this.attributes,
  121.     ): HttpContext =
  122.         send(INTERNAL_SERVER_ERROR_500, body, headers, contentType, cookies, attributes)

  123.     fun serverError(
  124.         status: HttpStatus,
  125.         exception: Exception,
  126.         headers: Headers = response.headers,
  127.         attributes: Map<*, *> = this.attributes,
  128.     ): HttpContext =
  129.         send(
  130.             status = status,
  131.             body = exception.toText(),
  132.             headers = headers,
  133.             contentType = ContentType(TEXT_PLAIN),
  134.             attributes = attributes,
  135.         )

  136.     fun internalServerError(
  137.         exception: Exception,
  138.         headers: Headers = response.headers,
  139.         attributes: Map<*, *> = this.attributes,
  140.     ): HttpContext =
  141.         serverError(INTERNAL_SERVER_ERROR_500, exception, headers, attributes)

  142.     fun ok(
  143.         body: Any = response.body,
  144.         headers: Headers = response.headers,
  145.         contentType: ContentType? = response.contentType,
  146.         cookies: List<Cookie> = response.cookies,
  147.         attributes: Map<*, *> = this.attributes,
  148.     ): HttpContext =
  149.         send(OK_200, body, headers, contentType, cookies, attributes)

  150.     fun sse(body: Publisher<ServerEvent>): HttpContext =
  151.         ok(
  152.             body = body,
  153.             headers = response.headers + Header("cache-control", "no-cache"),
  154.             contentType = ContentType(TEXT_EVENT_STREAM)
  155.         )

  156.     fun badRequest(
  157.         body: Any = response.body,
  158.         headers: Headers = response.headers,
  159.         contentType: ContentType? = response.contentType,
  160.         cookies: List<Cookie> = response.cookies,
  161.         attributes: Map<*, *> = this.attributes,
  162.     ): HttpContext =
  163.         send(BAD_REQUEST_400, body, headers, contentType, cookies, attributes)

  164.     fun notFound(
  165.         body: Any = response.body,
  166.         headers: Headers = response.headers,
  167.         contentType: ContentType? = response.contentType,
  168.         cookies: List<Cookie> = response.cookies,
  169.         attributes: Map<*, *> = this.attributes,
  170.     ): HttpContext =
  171.         send(NOT_FOUND_404, body, headers, contentType, cookies, attributes)

  172.     fun created(
  173.         body: Any = response.body,
  174.         headers: Headers = response.headers,
  175.         contentType: ContentType? = response.contentType,
  176.         cookies: List<Cookie> = response.cookies,
  177.         attributes: Map<*, *> = this.attributes,
  178.     ): HttpContext =
  179.         send(CREATED_201, body, headers, contentType, cookies, attributes)

  180.     fun redirect(
  181.         status: HttpStatus,
  182.         location: String,
  183.         headers: Headers = response.headers,
  184.         cookies: List<Cookie> = response.cookies,
  185.         attributes: Map<*, *> = this.attributes,
  186.     ): HttpContext =
  187.         send(
  188.             status,
  189.             headers = headers + Header("location", location),
  190.             cookies = cookies,
  191.             attributes = attributes
  192.         )

  193.     fun found(
  194.         location: String,
  195.         headers: Headers = response.headers,
  196.         cookies: List<Cookie> = response.cookies,
  197.         attributes: Map<*, *> = this.attributes,
  198.     ): HttpContext =
  199.         redirect(FOUND_302, location, headers, cookies, attributes)

  200.     fun accepted(
  201.         onConnect: WsSession.() -> Unit = {},
  202.         onBinary: WsSession.(data: ByteArray) -> Unit = {},
  203.         onText: WsSession.(text: String) -> Unit = {},
  204.         onPing: WsSession.(data: ByteArray) -> Unit = {},
  205.         onPong: WsSession.(data: ByteArray) -> Unit = {},
  206.         onClose: WsSession.(status: Int, reason: String) -> Unit = { _, _ -> },
  207.     ): HttpContext =
  208.         send(
  209.             event.response.with(
  210.                 status = ACCEPTED_202,
  211.                 onConnect = onConnect,
  212.                 onBinary = onBinary,
  213.                 onText = onText,
  214.                 onPing = onPing,
  215.                 onPong = onPong,
  216.                 onClose = onClose,
  217.             )
  218.         )

  219.     fun send(
  220.         status: HttpStatus = response.status,
  221.         body: Any = response.body,
  222.         headers: Headers = response.headers,
  223.         contentType: ContentType? = response.contentType,
  224.         cookies: List<Cookie> = response.cookies,
  225.         attributes: Map<*, *> = this.attributes,
  226.     ): HttpContext =
  227.         send(
  228.             response.with(
  229.                 body = body,
  230.                 headers = headers,
  231.                 contentType = contentType,
  232.                 cookies = cookies,
  233.                 status = status,
  234.             ),
  235.             attributes
  236.         )

  237.     fun send(response: HttpResponsePort, attributes: Map<*, *> = this.attributes): HttpContext =
  238.         copy(
  239.             event = event.copy(response = response),
  240.             attributes = attributes
  241.         )

  242.     fun send(request: HttpRequestPort, attributes: Map<*, *> = this.attributes): HttpContext =
  243.         copy(
  244.             event = event.copy(request = request),
  245.             attributes = attributes
  246.         )

  247.     fun receive(
  248.         body: Any = request.body,
  249.         headers: Headers = request.headers,
  250.         contentType: ContentType? = request.contentType,
  251.         accept: List<ContentType> = request.accept,
  252.         cookies: List<Cookie> = request.cookies,
  253.         attributes: Map<*, *> = this.attributes,
  254.     ): HttpContext =
  255.         send(
  256.             request.with(
  257.                 body = body,
  258.                 headers = headers,
  259.                 contentType = contentType,
  260.                 accept = accept,
  261.                 cookies = cookies,
  262.             ),
  263.             attributes
  264.         )
  265. }