HttpServerContext.kt
package com.hexagonkt.http.server.handlers
import com.hexagonkt.handlers.Context
import com.hexagonkt.core.media.TextMedia
import com.hexagonkt.core.assertEnabled
import com.hexagonkt.core.media.TextMedia.PLAIN
import com.hexagonkt.core.toText
import com.hexagonkt.handlers.EventContext
import com.hexagonkt.http.model.*
import com.hexagonkt.http.model.ClientErrorStatus.*
import com.hexagonkt.http.model.ServerErrorStatus.INTERNAL_SERVER_ERROR
import com.hexagonkt.http.model.ServerEvent
import com.hexagonkt.http.model.SuccessStatus.*
import com.hexagonkt.http.model.ws.WsCloseStatus
import com.hexagonkt.http.server.model.*
import com.hexagonkt.http.server.model.ws.WsServerSession
import java.net.URL
import java.security.cert.X509Certificate
import java.util.concurrent.Flow.Publisher
// TODO Add exception parameter to 'send*' methods
data class HttpServerContext(
val context: Context<HttpServerCall>
): Context<HttpServerCall> by context {
val request: HttpServerRequestPort = context.event.request
val response: HttpServerResponse = context.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 = context.predicate as HttpServerPredicate
val pattern = httpHandler.pathPattern
if (assertEnabled)
check(!(pattern.prefix)) { "Loading path parameters not allowed for paths" }
pattern.extractParameters(request.path)
}
constructor(
request: HttpServerRequestPort = HttpServerRequest(),
response: HttpServerResponse = HttpServerResponse(),
predicate: HttpServerPredicate = HttpServerPredicate(),
attributes: Map<*, *> = emptyMap<Any, Any>(),
) : this(EventContext(HttpServerCall(request, response), predicate, attributes = attributes))
override fun next(): HttpServerContext =
HttpServerContext(context.next())
fun success(
status: SuccessStatus,
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
send(status, body, headers, contentType, cookies, attributes)
fun redirect(
status: RedirectionStatus,
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
send(status, body, headers, contentType, cookies, attributes)
fun clientError(
status: ClientErrorStatus,
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
send(status, body, headers, contentType, cookies, attributes)
fun unauthorized(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
send(UNAUTHORIZED, 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<*, *> = context.attributes,
): HttpServerContext =
send(FORBIDDEN, body, headers, contentType, cookies, attributes)
fun serverError(
status: ServerErrorStatus,
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
send(status, 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<*, *> = context.attributes,
): HttpServerContext =
send(INTERNAL_SERVER_ERROR, body, headers, contentType, cookies, attributes)
fun serverError(
status: ServerErrorStatus,
exception: Exception,
headers: Headers = response.headers,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
serverError(
status = status,
body = exception.toText(),
headers = headers,
contentType = ContentType(PLAIN),
attributes = attributes,
)
fun internalServerError(
exception: Exception,
headers: Headers = response.headers,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
serverError(INTERNAL_SERVER_ERROR, exception, headers, attributes)
fun ok(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
success(OK, body, headers, contentType, cookies, attributes)
fun sse(body: Publisher<ServerEvent>): HttpServerContext =
ok(
body = body,
headers = response.headers + Header("cache-control", "no-cache"),
contentType = ContentType(TextMedia.EVENT_STREAM)
)
fun badRequest(
body: Any = response.body,
headers: Headers = response.headers,
contentType: ContentType? = response.contentType,
cookies: List<Cookie> = response.cookies,
attributes: Map<*, *> = context.attributes,
): HttpServerContext =
clientError(BAD_REQUEST, 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<*, *> = context.attributes,
): HttpServerContext =
clientError(NOT_FOUND, 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<*, *> = context.attributes,
): HttpServerContext =
success(CREATED, body, headers, contentType, cookies, attributes)
fun accepted(
onConnect: WsServerSession.() -> Unit = {},
onBinary: WsServerSession.(data: ByteArray) -> Unit = {},
onText: WsServerSession.(text: String) -> Unit = {},
onPing: WsServerSession.(data: ByteArray) -> Unit = {},
onPong: WsServerSession.(data: ByteArray) -> Unit = {},
onClose: WsServerSession.(status: WsCloseStatus, reason: String) -> Unit = { _, _ -> },
): HttpServerContext =
send(
context.event.response.copy(
status = ACCEPTED,
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<*, *> = context.attributes,
): HttpServerContext =
send(
response.copy(
body = body,
headers = headers,
contentType = contentType,
cookies = cookies,
status = status,
),
attributes
)
fun send(
response: HttpServerResponse, attributes: Map<*, *> = context.attributes
): HttpServerContext =
HttpServerContext(
context.with(
event = context.event.copy(response = response),
attributes = attributes
)
)
}