HttpContext.kt
package com.hexagonkt.http.handlers
import com.hexagonkt.handlers.Context
import com.hexagonkt.core.assertEnabled
import com.hexagonkt.core.media.TEXT_EVENT_STREAM
import com.hexagonkt.core.media.TEXT_PLAIN
import com.hexagonkt.core.toText
import com.hexagonkt.handlers.Handler
import com.hexagonkt.http.model.*
import com.hexagonkt.http.model.INTERNAL_SERVER_ERROR_500
import com.hexagonkt.http.model.ServerEvent
import com.hexagonkt.http.model.ws.WsSession
import java.net.URL
import java.security.cert.X509Certificate
import java.util.concurrent.Flow.Publisher
// TODO Add exception parameter to 'send*' methods
data class HttpContext(
override val event: HttpCall,
override val predicate: (Context<HttpCall>) -> Boolean,
override val nextHandlers: List<Handler<HttpCall>> = emptyList(),
override val nextHandler: Int = 0,
override val exception: Exception? = null,
override val attributes: Map<*, *> = emptyMap<Any, Any>(),
override val handled: Boolean = false,
): Context<HttpCall> {
val request: HttpRequestPort = event.request
val response: HttpResponsePort = event.response
val method: HttpMethod by lazy { request.method }
val protocol: HttpProtocol by lazy { request.protocol }
val host: String by lazy { request.host }
val port: Int by lazy { request.port }
val path: String by lazy { request.path }
val queryParameters: QueryParameters by lazy { request.queryParameters }
val parts: List<HttpPart> by lazy { request.parts }
val formParameters: FormParameters by lazy { request.formParameters }
val accept: List<ContentType> by lazy { request.accept }
val authorization: Authorization? by lazy { request.authorization }
val certificateChain: List<X509Certificate> by lazy { request.certificateChain }
val partsMap: Map<String, HttpPart> by lazy { request.partsMap() }
val url: URL by lazy { request.url() }
val userAgent: String? by lazy { request.userAgent() }
val referer: String? by lazy { request.referer() }
val origin: String? by lazy { request.origin() }
val certificate: X509Certificate? by lazy { request.certificate() }
val status: HttpStatus = response.status
val pathParameters: Map<String, String> by lazy {
val httpHandler = predicate as HttpPredicate
val pattern = httpHandler.pathPattern
if (assertEnabled)
check(!pattern.prefix) { "Loading path parameters not allowed for paths" }
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(
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: List<Handler<HttpCall>>,
nextHandler: Int,
exception: Exception?,
attributes: Map<*, *>,
handled: Boolean,
): Context<HttpCall> =
copy(
event = event,
predicate = predicate,
nextHandlers = nextHandlers,
nextHandler = nextHandler,
exception = exception,
attributes = attributes,
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: HttpStatus,
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: HttpStatus,
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: HttpStatus = 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 =
copy(
event = event.copy(response = response),
attributes = attributes
)
fun send(request: HttpRequestPort, attributes: Map<*, *> = this.attributes): HttpContext =
copy(
event = event.copy(request = request),
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
)
}