io.javalin.Context.kt Maven / Gradle / Ivy
The newest version!
/*
* Javalin - https://javalin.io
* Copyright 2017 David Åse
* Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
*/
package io.javalin
import io.javalin.cookie.CookieStore
import io.javalin.core.HandlerType
import io.javalin.core.util.ContextUtil
import io.javalin.core.util.Header
import io.javalin.core.util.MultipartUtil
import io.javalin.json.JavalinJson
import io.javalin.rendering.JavalinRenderer
import io.javalin.validation.TypedValidator
import io.javalin.validation.Validator
import java.io.InputStream
import java.nio.charset.Charset
import java.util.*
import java.util.concurrent.CompletableFuture
import javax.servlet.http.Cookie
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* Provides access to functions for handling the request and response
*
* @see Context in docs
*/
open class Context(private val servletRequest: HttpServletRequest, private val servletResponse: HttpServletResponse, private val javalin: Javalin) {
// @formatter:off
@get:JvmSynthetic @set:JvmSynthetic internal var inExceptionHandler = false
@get:JvmSynthetic @set:JvmSynthetic internal var matchedPath = ""
@get:JvmSynthetic @set:JvmSynthetic internal var endpointHandlerPath = ""
@get:JvmSynthetic @set:JvmSynthetic internal var pathParamMap = mapOf()
@get:JvmSynthetic @set:JvmSynthetic internal var splatList = listOf()
@get:JvmSynthetic @set:JvmSynthetic internal var handlerType = HandlerType.BEFORE
@JvmField val req = servletRequest
@JvmField val res = servletResponse
// @formatter:on
private val cookieStore by lazy { CookieStore(cookie(CookieStore.COOKIE_NAME)) }
private var resultStream: InputStream? = null
private var resultFuture: CompletableFuture<*>? = null
/** Gets an attribute from the Javalin instance serving the request */
fun appAttribute(clazz: Class): T = javalin.attribute(clazz) as T
/**
* Gets cookie store value for specified key.
* @see Cookie store in docs
*/
fun cookieStore(key: String): T = cookieStore[key]
/**
* Sets cookie store value for specified key.
* Values are made available for other handlers, requests, and servers.
* @see Cookie store in docs
*/
fun cookieStore(key: String, value: Any) {
cookieStore[key] = value
cookie(cookieStore.serializeToCookie())
}
/**
* Clears cookie store in the context and from the response.
* @see Cookie store in docs
*/
fun clearCookieStore() {
cookieStore.clear()
removeCookie(CookieStore.COOKIE_NAME)
}
///////////////////////////////////////////////////////////////
// Request-ish methods
///////////////////////////////////////////////////////////////
/** Gets the request body as a [String]. */
fun body(): String = bodyAsBytes().toString(Charset.forName(servletRequest.characterEncoding ?: "UTF-8"))
/**
* Maps a JSON body to a Java/Kotlin class using JavalinJson.
* JavalinJson can be configured to use any mapping library.
* @return The mapped object
*/
inline fun body(): T = bodyAsClass(T::class.java)
/** Gets the request body as a [ByteArray]. */
fun bodyAsBytes(): ByteArray = servletRequest.inputStream.readBytes()
/**
* Maps a JSON body to a Java/Kotlin class using JavalinJson.
* JavalinJson can be configured to use any mapping library.
* @return The mapped object
*/
fun bodyAsClass(clazz: Class): T = JavalinJson.fromJson(body(), clazz)
/**
* Creates a [TypedValidator] for the body() value, with the prefix "Request body as $clazz"
* Throws [BadRequestResponse] if validation fails.
*/
fun bodyValidator(clazz: Class) = try {
TypedValidator(JavalinJson.fromJson(body(), clazz), "Request body as ${clazz.simpleName}")
} catch (e: Exception) {
throw BadRequestResponse("Couldn't deserialize body to ${clazz.simpleName}")
}
/** Reified version of [bodyValidator] */
inline fun bodyValidator() = bodyValidator(T::class.java)
/** Gets first [UploadedFile] for the specified name, or null. */
fun uploadedFile(fileName: String): UploadedFile? = uploadedFiles(fileName).firstOrNull()
/** Gets a list of [UploadedFile]s for the specified name, or empty list. */
fun uploadedFiles(fileName: String): List {
return if (isMultipartFormData()) MultipartUtil.getUploadedFiles(servletRequest, fileName) else listOf()
}
/**
* Gets a form param if it exists, else a default value (null if not specified explicitly).
* Including a default value is mainly useful when calling from Java,
* use elvis (formParam(key) ?: default) instead in Kotlin.
*/
@JvmOverloads
fun formParam(key: String, default: String? = null): String? = formParams(key).firstOrNull() ?: default
/**
* Creates a [TypedValidator] for the formParam() value, with the prefix "Form parameter '$key' with value '$value'"
* Throws [BadRequestResponse] if validation fails.
*/
@JvmOverloads
fun formParam(key: String, clazz: Class, default: String? = null) = Validator(formParam(key, default), "Form parameter '$key' with value '${formParam(key, default)}'").asClass(clazz)
/** Reified version of [formParam] (Kotlin only) */
inline fun formParam(key: String, default: String? = null) = formParam(key, T::class.java, default)
/** Gets a list of form params for the specified key, or empty list. */
fun formParams(key: String): List = formParamMap()[key] ?: emptyList()
/** Gets a map with all the form param keys and values. */
fun formParamMap(): Map> =
if (isMultipartFormData()) MultipartUtil.getFieldMap(servletRequest)
else ContextUtil.splitKeyValueStringAndGroupByKey(body())
/**
* Maps form params to values, or returns null if any of the params are null.
* Ex: val (username, email) = ctx.mapFormParams("username", "email") ?: throw MissingFormParamException()
* This method is mainly useful when calling from Kotlin.
*/
fun mapFormParams(vararg keys: String): List? = ContextUtil.mapKeysOrReturnNullIfAnyNulls(keys) { formParam(it) }
/**
* Returns true if any of the specified form params are null.
* Mainly useful when calling from Java as a replacement for [mapFormParams].
*/
fun anyFormParamNull(vararg keys: String): Boolean = keys.any { formParam(it) == null }
/**
* Gets a path param by name (ex: pathParam("param").
*
* Ex: If the handler path is /users/:user-id,
* and a browser GETs /users/123,
* pathParam("user-id") will return "123"
*/
fun pathParam(key: String): String = ContextUtil.pathParamOrThrow(pathParamMap, key, matchedPath)
/**
* Creates a [TypedValidator] for the pathParam() value, with the prefix "Path parameter '$key' with value '$value'"
* Throws [BadRequestResponse] if validation fails.
*/
fun pathParam(key: String, clazz: Class) = Validator(pathParam(key), "Path parameter '$key' with value '${pathParam(key)}'").asClass(clazz)
/** Reified version of [pathParam] (Kotlin only) */
inline fun pathParam(key: String) = pathParam(key, T::class.java)
/** Gets a map of all the [pathParam] keys and values. */
fun pathParamMap(): Map = Collections.unmodifiableMap(pathParamMap)
//
// Gets a splat by its index.
// Ex: If the handler path is /users/*
// and a browser GETs /users/123,
// splat(0) will return "123"
//
fun splat(splatNr: Int): String? = splatList[splatNr]
/** Gets a list of all the [splat] values. */
fun splats(): List = Collections.unmodifiableList(splatList)
/**
* Gets basic-auth credentials from the request.
*
* Returns a wrapper object [BasicAuthCredentials] which contains the
* Base64 decoded username and password from the Authorization header.
*/
fun basicAuthCredentials(): BasicAuthCredentials? = ContextUtil.getBasicAuthCredentials(header(Header.AUTHORIZATION))
/**
* Registers an extension to the Context, which can be used later in the request-lifecycle.
* This method is mainly useful when calling from Java, as Kotlin has native extension methods.
*
* Ex: ctx.register(MyExt.class, myExtInstance())
*/
fun register(clazz: Class<*>, value: Any) = servletRequest.setAttribute("ctx-ext-${clazz.canonicalName}", value)
/**
* Use an extension stored in the Context.
* This method is mainly useful when calling from Java as Kotlin has native extension methods.
*
* Ex: ctx.use(MyExt.class).myMethod()
*/
@Suppress("UNCHECKED_CAST")
fun use(clazz: Class): T = servletRequest.getAttribute("ctx-ext-${clazz.canonicalName}") as T
/** Sets an attribute on the request. Attributes are available to other handlers in the request lifecycle */
fun attribute(attribute: String, value: Any?) = servletRequest.setAttribute(attribute, value)
/** Gets the specified attribute from the request. */
@Suppress("UNCHECKED_CAST")
fun attribute(attribute: String): T? = servletRequest.getAttribute(attribute) as? T
/** Gets a map with all the attribute keys and values on the request. */
fun attributeMap(): Map = servletRequest.attributeNames.asSequence().associate { it to attribute(it) }
/** Gets the request content length. */
fun contentLength(): Int = servletRequest.contentLength
/** Gets the request content type, or null. */
fun contentType(): String? = servletRequest.contentType
/** Gets a request cookie by name, or null. */
fun cookie(name: String): String? = servletRequest.cookies?.find { name == it.name }?.value
/** Gets a map with all the cookie keys and values on the request. */
fun cookieMap(): Map = servletRequest.cookies?.associate { it.name to it.value } ?: emptyMap()
/** Gets a request header by name, or null. */
fun header(header: String): String? = servletRequest.getHeader(header)
/** Gets a map with all the header keys and values on the request. */
fun headerMap(): Map = servletRequest.headerNames.asSequence().associate { it to header(it)!! }
/** Gets the request host, or null. */
fun host(): String? = servletRequest.getHeader(Header.HOST)
/** Gets the request ip. */
fun ip(): String = servletRequest.remoteAddr
/** Returns true if request is multipart. */
fun isMultipart(): Boolean = header(Header.CONTENT_TYPE)?.toLowerCase()?.contains("multipart/") == true
/** Returns true if request is multipart/form-data. */
fun isMultipartFormData(): Boolean = header(Header.CONTENT_TYPE)?.toLowerCase()?.contains("multipart/form-data") == true
/**
* Gets the path that Javalin used to match the request.
*
* Ex: If the handler path is /users/:user-id,
* and a browser GETs /users/123,
* matchedPath() will return /users/:user-id
*/
fun matchedPath() = matchedPath
/**
* Gets the path that Javalin used to match this request (excluding any AFTER handlers)
*/
fun endpointHandlerPath() = if (handlerType != HandlerType.BEFORE) {
endpointHandlerPath
} else {
throw IllegalStateException("Cannot access the endpoint handler path in a 'BEFORE' handler")
}
/** Gets the request method. */
fun method(): String = servletRequest.method
/** Gets the request path. */
fun path(): String = servletRequest.requestURI
/** Gets the request port. */
fun port(): Int = servletRequest.serverPort
/** Gets the request protocol. */
fun protocol(): String = servletRequest.protocol
/**
* Gets a query param if it exists, else a default value (null if not specified explicitly).
* Including a default value is mainly useful when calling from Java,
* use elvis (queryParam(key) ?: default) instead in Kotlin.
*/
@JvmOverloads
fun queryParam(key: String, default: String? = null): String? = queryParams(key).firstOrNull() ?: default
/**
* Creates a [TypedValidator] for the queryParam() value, with the prefix "Query parameter '$key' with value '$value'"
* Throws [BadRequestResponse] if validation fails.
*/
@JvmOverloads
fun queryParam(key: String, clazz: Class, default: String? = null) = Validator(queryParam(key, default), "Query parameter '$key' with value '${queryParam(key, default)}'").asClass(clazz)
/** Reified version of [queryParam] (Kotlin only) */
inline fun queryParam(key: String, default: String? = null) = queryParam(key, T::class.java, default)
/** Gets a list of query params for the specified key, or empty list. */
fun queryParams(key: String): List = queryParamMap()[key] ?: emptyList()
/** Gets a map with all the query param keys and values. */
fun queryParamMap(): Map> = ContextUtil.splitKeyValueStringAndGroupByKey(queryString() ?: "")
/**
* Maps query params to values, or returns null if any of the params are null.
* Ex: val (username, email) = ctx.mapQueryParams("username", "email") ?: throw MissingQueryParamException()
* This method is mainly useful when calling from Kotlin.
*/
fun mapQueryParams(vararg keys: String): List? = ContextUtil.mapKeysOrReturnNullIfAnyNulls(keys) { queryParam(it) }
/**
* Returns true if any of the specified query params are null.
* Mainly useful when calling from Java as a replacement for [mapQueryParams]
*/
fun anyQueryParamNull(vararg keys: String): Boolean = keys.any { queryParam(it) == null }
/** Gets the request query string, or null. */
fun queryString(): String? = servletRequest.queryString
/** Gets the request scheme. */
fun scheme(): String = servletRequest.scheme
/** Sets an attribute for the user session. */
fun sessionAttribute(attribute: String, value: Any?) = servletRequest.session.setAttribute(attribute, value)
/** Gets specified attribute from the user session, or null. */
@Suppress("UNCHECKED_CAST")
fun sessionAttribute(attribute: String): T? = servletRequest.session.getAttribute(attribute) as? T
/** Gets a map of all the attributes in the user session. */
fun sessionAttributeMap(): Map = servletRequest.session.attributeNames.asSequence().associate { it to sessionAttribute(it) }
/** Gets the request url. */
fun url(): String = servletRequest.requestURL.toString()
/** Gets the request context path. */
fun contextPath(): String = servletRequest.contextPath
/** Gets the request user agent, or null. */
fun userAgent(): String? = servletRequest.getHeader(Header.USER_AGENT)
///////////////////////////////////////////////////////////////
// Response-ish methods
///////////////////////////////////////////////////////////////
/** Gets the current response [Charset]. */
private fun responseCharset() = try {
Charset.forName(servletResponse.characterEncoding)
} catch (e: Exception) {
Charset.defaultCharset()
}
/**
* Sets context result to the specified [String].
* Will overwrite the current result if there is one.
*/
fun result(resultString: String) = result(resultString.byteInputStream(responseCharset()))
/** Gets the current context result as a [String] (if set). */
fun resultString() = resultStream?.apply { reset() }?.readBytes()?.toString(responseCharset())
/**
* Sets context result to the specified [InputStream].
* Will overwrite the current result if there is one.
*/
fun result(resultStream: InputStream): Context {
this.resultFuture = null
this.resultStream = resultStream
return this
}
/** Gets the current context result as an [InputStream] (if set). */
fun resultStream(): InputStream? = resultStream
/**
* Sets context result to the specified CompletableFuture
* or CompletableFuture.
* Will overwrite the current result if there is one.
* Can only be called inside endpoint handlers (ones representing HTTP verbs).
*/
fun result(future: CompletableFuture<*>): Context {
resultStream = null
if (handlerType.isHttpMethod() && !inExceptionHandler) {
this.resultFuture = future
return this
}
throw IllegalStateException("You can only set CompletableFuture results in endpoint handlers.")
}
/** Gets the current context result as a [CompletableFuture] (if set). */
fun resultFuture(): CompletableFuture<*>? = resultFuture
/** Sets response content type to specified [String] value. */
fun contentType(contentType: String): Context {
servletResponse.contentType = contentType
return this
}
/** Sets response header by name and value. */
fun header(headerName: String, headerValue: String): Context {
servletResponse.setHeader(headerName, headerValue)
return this
}
/** Sets the response status code and redirects to the specified location. */
@JvmOverloads
fun redirect(location: String, httpStatusCode: Int = HttpServletResponse.SC_MOVED_TEMPORARILY) {
servletResponse.setHeader(Header.LOCATION, location)
status(httpStatusCode)
if (handlerType == HandlerType.BEFORE) {
throw RedirectResponse(httpStatusCode)
}
}
/** Sets the response status. */
fun status(statusCode: Int): Context {
servletResponse.status = statusCode
return this
}
/** Gets the response status. */
fun status(): Int = servletResponse.status
/** Sets a cookie with name, value, and (overloaded) max-age. */
@JvmOverloads
fun cookie(name: String, value: String, maxAge: Int = -1): Context = cookie(Cookie(name, value).apply { setMaxAge(maxAge) })
/** Sets a Cookie. */
fun cookie(cookie: Cookie): Context {
cookie.path = cookie.path ?: "/"
servletResponse.addCookie(cookie)
return this
}
/** Removes cookie specified by name and path (optional). */
@JvmOverloads
fun removeCookie(name: String, path: String? = null): Context {
servletResponse.addCookie(Cookie(name, "").apply {
this.path = path
this.maxAge = 0
})
return this
}
/** Sets context result to specified html string and sets content-type to text/html. */
fun html(html: String): Context = contentType("text/html").result(html)
/**
* Serializes object to a JSON-string using JavalinJson and sets it as the context result.
* Sets content type to application/json.
*
* JavalinJson can be configured to use any mapping library.
*/
fun json(obj: Any): Context {
return contentType("application/json").result(JavalinJson.toJson(obj))
}
/**
* Serializes the object resulting from the completion of the given future
* to a JSON-string using JavalinJson and sets it as the context result.
* Sets content type to application/json.
*
* JavalinJson can be configured to use any mapping library.
*/
fun json(future: CompletableFuture<*>): Context {
val mappingFuture = future.thenApply { JavalinJson.toJson(it) }
return contentType("application/json").result(mappingFuture)
}
/**
* Renders a file with specified values and sets it as the context result.
* Also sets content-type to text/html.
* Determines the correct rendering-function based on the file extension.
*/
@JvmOverloads
fun render(filePath: String, model: Map = emptyMap()): Context {
return html(JavalinRenderer.renderBasedOnExtension(filePath, model))
}
// Deprecated validation, will be removed in 3.0
@Deprecated("Use bodyValidator(class) instead")
fun validatedBodyAsClass(clazz: Class) = bodyValidator(clazz)
@Deprecated("Use bodyValidator() instead")
inline fun validatedBody() = bodyValidator(T::class.java)
@Deprecated("Use formParam(key, class) instead.")
@JvmOverloads
fun validatedFormParam(key: String, default: String? = null) = Validator(formParam(key, default), "Form parameter '$key' with value '${formParam(key, default)}'")
@Deprecated("Use pathParam(key, class) instead.")
fun validatedPathParam(key: String) = Validator(pathParam(key), "Path parameter '$key' with value '${pathParam(key)}'")
@Deprecated("Use queryParam(key, class) instead")
@JvmOverloads
fun validatedQueryParam(key: String, default: String? = null) = Validator(queryParam(key, default), "Query parameter '$key' with value '${queryParam(key, default)}'")
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy