HttpContext.kt
package com.hexagontk.http.handlers
import com.hexagontk.handlers.Context
import com.hexagontk.core.assertEnabled
import com.hexagontk.core.media.TEXT_EVENT_STREAM
import com.hexagontk.core.media.TEXT_PLAIN
import com.hexagontk.core.toText
import com.hexagontk.handlers.Handler
import com.hexagontk.http.model.*
import com.hexagontk.http.model.INTERNAL_SERVER_ERROR_500
import com.hexagontk.http.model.ServerEvent
import com.hexagontk.http.model.ws.WsSession
import java.net.URI
import java.security.cert.X509Certificate
import java.util.concurrent.Flow.Publisher
class HttpContext(
override var event: HttpCall,
override var predicate: (Context<HttpCall>) -> Boolean,
override var nextHandlers: Array<Handler<HttpCall>> = emptyArray(),
override var nextHandler: Int = 0,
override var exception: Exception? = null,
override var attributes: Map<*, *> = emptyMap<Any, Any>(),
override var handled: Boolean = false,
): Context<HttpCall> {
val request: HttpRequestPort get() = event.request
val response: HttpResponsePort get() = event.response
val method: HttpMethod get() = request.method
val protocol: HttpProtocol get() = request.protocol
val host: String get() = request.host
val port: Int get() = request.port
val path: String get() = request.path
val queryParameters: Parameters get() = request.queryParameters
val parts: List<HttpPart> get() = request.parts
val formParameters: Parameters get() = request.formParameters
val accept: List<ContentType> get() = request.accept
val authorization: Authorization? get() = request.authorization
val certificateChain: List<X509Certificate> get() = request.certificateChain
val partsMap: Map<String, HttpPart> get() = request.partsMap()
val uri: URI get() = request.uri()
val userAgent: String? get() = request.userAgent()
val referer: String? get() = request.referer()
val origin: String? get() = request.origin()
val certificate: X509Certificate? get() = request.certificate()
val status: Int get() = response.status
val pathParameters: Map<String, String> get() {
val httpHandler = predicate as HttpPredicate
val pattern = httpHandler.pathPattern
if (assertEnabled)
check(!pattern.prefix) { "Loading path parameters not allowed for paths" }
return pattern.extractParameters(request.path)
}
constructor(context: Context<HttpCall>) : this(
event = context.event,
predicate = context.predicate,
nextHandlers = context.nextHandlers,
nextHandler = context.nextHandler,
exception = context.exception,
attributes = context.attributes,
)
constructor(
event: HttpCall,
predicate: (Context<HttpCall>) -> Boolean,
nextHandlers: List<Handler<HttpCall>>,
nextHandler: Int = 0,
exception: Exception? = null,
attributes: Map<*, *> = emptyMap<Any, Any>(),
handled: Boolean = false,
) : this(
event, predicate, nextHandlers.toTypedArray(), nextHandler, exception, attributes, handled
)
constructor(
request: HttpRequestPort = HttpRequest(),
response: HttpResponsePort = HttpResponse(),
predicate: HttpPredicate = HttpPredicate(),
attributes: Map<*, *> = emptyMap<Any, Any>(),
) : this(HttpCall(request, response), predicate, attributes = attributes)
override fun next(): HttpContext {
for (index in nextHandler until nextHandlers.size) {
val handler = nextHandlers[index]
val p = handler.predicate
if (handler is OnHandler) {
if ((!handled) && p(this))
return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
}
else {
if (p(this))
return handler.process(with(predicate = p, nextHandler = index + 1)) as HttpContext
}
}
return this
}
override fun with(
event: HttpCall,
predicate: (Context<HttpCall>) -> Boolean,
nextHandlers: Array<Handler<HttpCall>>,
nextHandler: Int,
exception: Exception?,
attributes: Map<*, *>,
handled: Boolean,
): Context<HttpCall> =
apply {
this.event = event
this.predicate = predicate
this.nextHandlers = nextHandlers
this.nextHandler = nextHandler
this.exception = exception
this.attributes = attributes
this.handled = handled
}
fun unauthorized(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(UNAUTHORIZED_401, body, headers, contentType, cookies, attributes)
fun forbidden(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(FORBIDDEN_403, body, headers, contentType, cookies, attributes)
fun internalServerError(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(INTERNAL_SERVER_ERROR_500, body, headers, contentType, cookies, attributes)
fun serverError(
status: Int,
exception: Exception,
headers: Headers = response.headers,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(
status = status,
body = exception.toText(),
headers = headers,
contentType = ContentType(TEXT_PLAIN),
attributes = attributes,
)
fun internalServerError(
exception: Exception,
headers: Headers = response.headers,
attributes: Map<*, *> = this.attributes,
): HttpContext =
serverError(INTERNAL_SERVER_ERROR_500, exception, headers, attributes)
fun ok(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(OK_200, body, headers, contentType, cookies, attributes)
fun sse(body: Publisher<ServerEvent>): HttpContext =
ok(
body = body,
headers = response.headers + Header("cache-control", "no-cache"),
contentType = ContentType(TEXT_EVENT_STREAM)
)
fun badRequest(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(BAD_REQUEST_400, body, headers, contentType, cookies, attributes)
fun notFound(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(NOT_FOUND_404, body, headers, contentType, cookies, attributes)
fun created(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(CREATED_201, body, headers, contentType, cookies, attributes)
fun redirect(
status: Int,
location: String,
headers: Headers = response.headers,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(
status,
headers = headers + Header("location", location),
cookies = cookies,
attributes = attributes
)
fun found(
location: String,
headers: Headers = response.headers,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
redirect(FOUND_302, location, headers, cookies, attributes)
fun accepted(
onConnect: WsSession.() -> Unit = {},
onBinary: WsSession.(data: ByteArray) -> Unit = {},
onText: WsSession.(text: String) -> Unit = {},
onPing: WsSession.(data: ByteArray) -> Unit = {},
onPong: WsSession.(data: ByteArray) -> Unit = {},
onClose: WsSession.(status: Int, reason: String) -> Unit = { _, _ -> },
): HttpContext =
send(
event.response.with(
status = ACCEPTED_202,
onConnect = onConnect,
onBinary = onBinary,
onText = onText,
onPing = onPing,
onPong = onPong,
onClose = onClose,
)
)
fun send(
status: Int = response.status,
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(
response.with(
body = body,
headers = headers,
contentType = contentType,
cookies = cookies,
status = status,
),
attributes
)
fun send(response: HttpResponsePort, attributes: Map<*, *> = this.attributes): HttpContext =
apply {
this.event.response = response
this.attributes = attributes
}
fun send(request: HttpRequestPort, attributes: Map<*, *> = this.attributes): HttpContext =
apply {
this.event.request = request
this.attributes = attributes
}
fun receive(
body: Any = request.body,
headers: Headers = request.headers,
contentType: ContentType? = request.contentType,
accept: List<ContentType> = request.accept,
cookies: List<Cookie> = request.cookies,
attributes: Map<*, *> = this.attributes,
): HttpContext =
send(
request.with(
body = body,
headers = headers,
contentType = contentType,
accept = accept,
cookies = cookies,
),
attributes
)
}