When handling routes, or directly intercepting the pipeline, you
get a context with an ApplicationCall.
That call
contains a property called response
that allows you to emit the response.
Also, the call itself has some useful convenience properties and methods that interact with the response.
Table of contents:
Link
headerWhen using the Routing feature, you can access
the call
property inside route handlers:
routing {
get("/") {
call.respondText("Request uri: ${call.request.uri}")
}
}
When intercepting requests, the lambda handler in intercept
has the call
property available too:
intercept(ApplicationCallPipeline.Call) {
if (call.request.uri == "/") {
call.respondText("Test String")
}
}
You can control how the response is generated, the HTTP status, the headers, cookies, and the payload.
Remember that since HTTP requests an responses are non-seekable streams, once you start emitting the response payload/content, the status and the headers are emitted, and you won’t be able to modify either the status or the headers/cookies.
As part of the response
, you can get access to its internal context:
val call: ApplicationCall = response.call
val pipeline: ApplicationSendPipeline = response.pipeline
Headers:
val headers: ResponseHeaders = response.headers
Convenience cookies
instance to set Set-Cookie
headers:
val cookies: ResponseCookies = response.cookies
Getting and changing the HTTP Status:
response.status(HttpStatusCode.OK)
- Sets the HttpStatusCode to a predefined standard oneresponse.status(HttpStatusCode(418, "I'm a tea pot"))
- Sets the HttpStatusCode to a custom status codeval status: HttpStatusCode? = response.status()
- Gets the currently set HttpStatusCode if set
response.contentType(ContentType.Text.Plain.withCharset(Charsets.UTF_8))
- Typed way for setting the Content-Type (for ContentType.Application.Json
the default charset is UTF_8 without making it explicit)response.contentType("application/json; charset=UTF-8")
- Untyped way for setting the Content-Type headerCustom headers:
response.header("X-My-Header", "my value")
- Appends a custom headerresponse.header("X-My-Times", 1000)
- Appends a custom headerresponse.header("X-My-Times", 1000L)
- Appends a custom headerresponse.header("X-My-Date", Instant.EPOCH)
- Appends a custom headerConvenience methods to set headers usually set by the infrastructure:
response.etag("33a64df551425fcc55e4d42a148795d9f25f89d4")
- Sets the ETag
used for cachingresponse.lastModified(ZonedDateTime.now())
- Sets the Last-Modified
headerresponse.contentLength(1024L)
- Sets the Content-Length
. This is generally automatically set when sending the payloadresponse.cacheControl(CacheControl.NoCache(CacheControl.Visibility.Private))
- Sets the Cache-Control header in a typed wayresponse.expires(LocalDateTime.now())
- Sets the Expires
headerresponse.contentRange(1024L until 2048L, 4096L)
- Sets the Content-Range
header (check the PartialContent feature)Link
headerThe call
supports pushing.
Link
header as a hintrouting {
get("/") {
call.push("/style.css")
}
}
Pushing reduces the time between the request and the display of the page. But beware that sending content beforehand might send content that is already cached by the client.
You can easily generate redirection responses with the respondRedirect
method,
to send 301 Moved Permanently
or 302 Found
redirects, with a Location
header.
call.respondRedirect("/moved/here", permanent = true)
Remember that once this function is executed, the rest of the function is still executed. Therefore, if you have it in a guard clause, you should return from the function to avoid continuing with the rest of the handler. If you want to make redirections that stop the control flow by throwing an exception, check out this sample from status pages.
Sending generic content (compatible with Content negotiation):
call.respond(MyDataClass("hello", "world"))
- Check the Content Negotiation sectioncall.respond(HttpStatusCode.NotFound, MyDataClass("hello", "world"))
- Specifies a status code, and sends a payload in a single call. Check StatusPagesSending plain text:
call.respondText("text")
- Just a string with the bodycall.respondText("p { background: red; }", contentType = ContentType.Text.CSS, status = HttpStatusCode.OK) { ... }
- Sending a text specifying the ContentType, the HTTP Status and configuring the OutgoingContentcall.respondText { "string" }
- Responding a string with a suspend providercall.respondText(contentType = ..., status = ...) { "string" }
- Responding a string with a suspend providercall.respond(TextContent("{}", ContentType.Application.Json))
- Responding a string without adding a charset to the Content-Type
Sending byte arrays:
call.respondBytes(byteArrayOf(1, 2, 3))
- A ByteArray with a binary bodySending files:
call.respondFile(File("/path/to/file"))
- Sends a filecall.respondFile(File("basedir"), "filename") { ... }
- Send a file and configures the OutgoingContentSending URL-encoded forms (application/x-www-form-urlencoded
):
Parameters.formUrlEncode
. Check the Utilities page for more information about this.When sending files based on the request parameters, be especially careful validating and limiting the input.
Sending chunked content using a Writer:
call.respondWrite { write("hello"); write("world") }
- Sends text using a writer. This is used with the HTML DSLcall.respondWrite(contentType = ..., status = ...) { write("hello"); write("world") }
- Sends text using a writer and specifies a contentType and a statusSending arbitrary data in chunks using WriteChannelContent
:
call.respond(object : OutgoingContent.WriteChannelContent() {
override val contentType = ContentType.Application.OctetStream
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.writeFully(byteArray1)
channel.writeFully(byteArray2)
// ...
}
})
To specify a default content type for the request:
call.defaultTextContentType(contentType: ContentType?): ContentType
The OutgoingContent interface for configuring responses:
class OutgoingContent {
val contentType: ContentType? get() = null // * Specifies [ContentType] for this resource.
val contentLength: Long? get() = null // Specifies content length in bytes for this resource. - If null, the resources will be sent as `Transfer-Encoding: chunked`
val status: HttpStatusCode? // Status code to set when sending this content
val headers: Headers // Headers to set when sending this content
fun <T : Any> getProperty(key: AttributeKey<T>): T? = extensionProperties?.getOrNull(key) // Gets an extension property for this content
fun <T : Any> setProperty(key: AttributeKey<T>, value: T?) // Sets an extension property for this content
}
You can make files “downloadable”, by adding the Content-Disposition
header.
In an untyped way, you can do something like:
call.response.header(HttpHeaders.ContentDisposition, "attachment; filename=\"myfilename.bin\"")
But Ktor also provides a typed way with proper escaping to generate this header:
call.response.header(HttpHeaders.ContentDisposition, ContentDisposition.Attachment.withParameter(ContentDisposition.Parameters.FileName, "myfilename.bin").toString())
When configuring plugins for content negotiation, the pipeline may accept additional
types for the call.respond
methods.
Ktor includes an optional feature to send HTML content using a DSL.
Ktor includes an optional feature to send HTML content using FreeMarker.
Ktor includes an optional feature to send JSON content using Content negotiation.