Responses
note
This help topic is in development and will be updated in the future.
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.
Context
When 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")
}
}
Controlling the HTTP headers and the status
You can control how the response is generated, the HTTP status, the headers, cookies, and the payload.
Remember that since HTTP requests and 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 setresponse.contentType(ContentType.Text.Plain.withCharset(Charsets.UTF_8))
- Typed way for setting the Content-Type (forContentType.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 header
Custom 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 header
Convenience methods to set headers usually set by the infrastructure:
response.etag("33a64df551425fcc55e4d42a148795d9f25f89d4")
- Sets theETag
used for cachingresponse.lastModified(ZonedDateTime.now())
- Sets theLast-Modified
headerresponse.contentLength(1024L)
- Sets theContent-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 theExpires
headerresponse.contentRange(1024L until 2048L, 4096L)
- Sets theContent-Range
header (check the PartialContent feature)
HTTP/2 pushing and HTTP/1 Link header
The call
supports pushing.
In HTTP/2 it uses the push feature
In HTTP/1.2 it adds the
Link
header as a hint
routing {
get("/") {
call.push("/style.css")
}
}
note
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.
Redirections
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)
note
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 response content
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 StatusPages
Sending 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 theContent-Type
Sending byte arrays:
call.respondBytes(byteArrayOf(1, 2, 3))
- A ByteArray with a binary body
Sending files:
call.respondFile(File("/path/to/file"))
- Sends a filecall.respondFile(File("basedir"), "filename") { ... }
- Send a file and configures the OutgoingContent
Sending URL-encoded forms (application/x-www-form-urlencoded
):
- Use
Parameters.formUrlEncode
. Check the Utilities page for more information about this.
tip
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 status
Sending 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
}
Making files downloadable
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())
Content negotiation
When configuring plugins for content negotiation, the pipeline may accept additional types for the call.respond
methods.
Sending HTML with the DSL
Ktor includes an optional feature to send HTML content using a DSL.
Sending HTML with FreeMarker
Ktor includes an optional feature to send HTML content using FreeMarker.
Sending JSON with data classes
Ktor includes an optional feature to send JSON content using Content negotiation.