HttpContext.kt

  1. package com.hexagontk.http.handlers

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

  15. class HttpContext(
  16.     override var event: HttpCall,
  17.     override var predicate: (Context<HttpCall>) -> Boolean,
  18.     override var nextHandlers: Array<Handler<HttpCall>> = emptyArray(),
  19.     override var nextHandler: Int = 0,
  20.     override var exception: Exception? = null,
  21.     override var attributes: Map<*, *> = emptyMap<Any, Any>(),
  22.     override var handled: Boolean = false,
  23. ): Context<HttpCall> {
  24.     val request: HttpRequestPort get() = event.request
  25.     val response: HttpResponsePort get() = event.response

  26.     val method: HttpMethod get() = request.method
  27.     val protocol: HttpProtocol get() = request.protocol
  28.     val host: String get() = request.host
  29.     val port: Int get() = request.port
  30.     val path: String get() = request.path
  31.     val queryParameters: Parameters get() = request.queryParameters
  32.     val parts: List<HttpPart> get() = request.parts
  33.     val formParameters: Parameters get() = request.formParameters
  34.     val accept: List<ContentType> get() = request.accept
  35.     val authorization: Authorization? get() = request.authorization
  36.     val certificateChain: List<X509Certificate> get() = request.certificateChain

  37.     val partsMap: Map<String, HttpPart> get() = request.partsMap()
  38.     val uri: URI get() = request.uri()
  39.     val userAgent: String? get() = request.userAgent()
  40.     val referer: String? get() = request.referer()
  41.     val origin: String? get() = request.origin()
  42.     val certificate: X509Certificate? get() = request.certificate()

  43.     val status: Int get() = response.status

  44.     val pathParameters: Map<String, String> get() {
  45.         val httpHandler = predicate as HttpPredicate
  46.         val pattern = httpHandler.pathPattern

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

  49.         return pattern.extractParameters(request.path)
  50.     }

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

  59.     constructor(
  60.         event: HttpCall,
  61.         predicate: (Context<HttpCall>) -> Boolean,
  62.         nextHandlers: List<Handler<HttpCall>>,
  63.         nextHandler: Int = 0,
  64.         exception: Exception? = null,
  65.         attributes: Map<*, *> = emptyMap<Any, Any>(),
  66.         handled: Boolean = false,
  67.     ) : this(
  68.         event, predicate, nextHandlers.toTypedArray(), nextHandler, exception, attributes, handled
  69.     )

  70.     constructor(
  71.         request: HttpRequestPort = HttpRequest(),
  72.         response: HttpResponsePort = HttpResponse(),
  73.         predicate: HttpPredicate = HttpPredicate(),
  74.         attributes: Map<*, *> = emptyMap<Any, Any>(),
  75.     ) : this(HttpCall(request, response), predicate, attributes = attributes)

  76.     override fun next(): HttpContext {
  77.         for (index in nextHandler until nextHandlers.size) {
  78.             val handler = nextHandlers[index]
  79.             val p = handler.predicate
  80.             if (handler is OnHandler) {
  81.                 if ((!handled) && p(this))
  82.                     return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
  83.             }
  84.             else {
  85.                 if (p(this))
  86.                     return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
  87.             }
  88.         }

  89.         return this
  90.     }

  91.     override fun with(
  92.         event: HttpCall,
  93.         predicate: (Context<HttpCall>) -> Boolean,
  94.         nextHandlers: Array<Handler<HttpCall>>,
  95.         nextHandler: Int,
  96.         exception: Exception?,
  97.         attributes: Map<*, *>,
  98.         handled: Boolean,
  99.     ): Context<HttpCall> =
  100.         apply {
  101.             this.event = event
  102.             this.predicate = predicate
  103.             this.nextHandlers = nextHandlers
  104.             this.nextHandler = nextHandler
  105.             this.exception = exception
  106.             this.attributes = attributes
  107.             this.handled = handled
  108.         }

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

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

  125.     fun internalServerError(
  126.         body: Any = response.body,
  127.         headers: Headers = response.headers,
  128.         contentType: ContentType? = response.contentType,
  129.         cookies: List<Cookie> = response.cookies,
  130.         attributes: Map<*, *> = this.attributes,
  131.     ): HttpContext =
  132.         send(INTERNAL_SERVER_ERROR_500, body, headers, contentType, cookies, attributes)

  133.     fun serverError(
  134.         status: Int,
  135.         exception: Exception,
  136.         headers: Headers = response.headers,
  137.         attributes: Map<*, *> = this.attributes,
  138.     ): HttpContext =
  139.         send(
  140.             status = status,
  141.             body = exception.toText(),
  142.             headers = headers,
  143.             contentType = ContentType(TEXT_PLAIN),
  144.             attributes = attributes,
  145.         )

  146.     fun internalServerError(
  147.         exception: Exception,
  148.         headers: Headers = response.headers,
  149.         attributes: Map<*, *> = this.attributes,
  150.     ): HttpContext =
  151.         serverError(INTERNAL_SERVER_ERROR_500, exception, headers, attributes)

  152.     fun ok(
  153.         body: Any = response.body,
  154.         headers: Headers = response.headers,
  155.         contentType: ContentType? = response.contentType,
  156.         cookies: List<Cookie> = response.cookies,
  157.         attributes: Map<*, *> = this.attributes,
  158.     ): HttpContext =
  159.         send(OK_200, body, headers, contentType, cookies, attributes)

  160.     fun sse(body: Publisher<ServerEvent>): HttpContext =
  161.         ok(
  162.             body = body,
  163.             headers = response.headers + Header("cache-control", "no-cache"),
  164.             contentType = ContentType(TEXT_EVENT_STREAM)
  165.         )

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

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

  182.     fun created(
  183.         body: Any = response.body,
  184.         headers: Headers = response.headers,
  185.         contentType: ContentType? = response.contentType,
  186.         cookies: List<Cookie> = response.cookies,
  187.         attributes: Map<*, *> = this.attributes,
  188.     ): HttpContext =
  189.         send(CREATED_201, body, headers, contentType, cookies, attributes)

  190.     fun redirect(
  191.         status: Int,
  192.         location: String,
  193.         headers: Headers = response.headers,
  194.         cookies: List<Cookie> = response.cookies,
  195.         attributes: Map<*, *> = this.attributes,
  196.     ): HttpContext =
  197.         send(
  198.             status,
  199.             headers = headers + Header("location", location),
  200.             cookies = cookies,
  201.             attributes = attributes
  202.         )

  203.     fun found(
  204.         location: String,
  205.         headers: Headers = response.headers,
  206.         cookies: List<Cookie> = response.cookies,
  207.         attributes: Map<*, *> = this.attributes,
  208.     ): HttpContext =
  209.         redirect(FOUND_302, location, headers, cookies, attributes)

  210.     fun accepted(
  211.         onConnect: WsSession.() -> Unit = {},
  212.         onBinary: WsSession.(data: ByteArray) -> Unit = {},
  213.         onText: WsSession.(text: String) -> Unit = {},
  214.         onPing: WsSession.(data: ByteArray) -> Unit = {},
  215.         onPong: WsSession.(data: ByteArray) -> Unit = {},
  216.         onClose: WsSession.(status: Int, reason: String) -> Unit = { _, _ -> },
  217.     ): HttpContext =
  218.         send(
  219.             event.response.with(
  220.                 status = ACCEPTED_202,
  221.                 onConnect = onConnect,
  222.                 onBinary = onBinary,
  223.                 onText = onText,
  224.                 onPing = onPing,
  225.                 onPong = onPong,
  226.                 onClose = onClose,
  227.             )
  228.         )

  229.     fun send(
  230.         status: Int = response.status,
  231.         body: Any = response.body,
  232.         headers: Headers = response.headers,
  233.         contentType: ContentType? = response.contentType,
  234.         cookies: List<Cookie> = response.cookies,
  235.         attributes: Map<*, *> = this.attributes,
  236.     ): HttpContext =
  237.         send(
  238.             response.with(
  239.                 body = body,
  240.                 headers = headers,
  241.                 contentType = contentType,
  242.                 cookies = cookies,
  243.                 status = status,
  244.             ),
  245.             attributes
  246.         )

  247.     fun send(response: HttpResponsePort, attributes: Map<*, *> = this.attributes): HttpContext =
  248.         apply {
  249.             this.event.response = response
  250.             this.attributes = attributes
  251.         }

  252.     fun send(request: HttpRequestPort, attributes: Map<*, *> = this.attributes): HttpContext =
  253.         apply {
  254.             this.event.request = request
  255.             this.attributes = attributes
  256.         }

  257.     fun receive(
  258.         body: Any = request.body,
  259.         headers: Headers = request.headers,
  260.         contentType: ContentType? = request.contentType,
  261.         accept: List<ContentType> = request.accept,
  262.         cookies: List<Cookie> = request.cookies,
  263.         attributes: Map<*, *> = this.attributes,
  264.     ): HttpContext =
  265.         send(
  266.             request.with(
  267.                 body = body,
  268.                 headers = headers,
  269.                 contentType = contentType,
  270.                 accept = accept,
  271.                 cookies = cookies,
  272.             ),
  273.             attributes
  274.         )
  275. }