Generating HTTP Responses
When handling routes, or directly intercepting the pipeline, youget 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:
- Context
- Controlling the HTTP headers and the status
- HTTP/2 pushing and HTTP/1
Link
header - Redirections
- Sending response content
- Making files downloadable
- Content negotiation
Context
When using the Routing feature, you can accessthe 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 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 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")
}
}
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)
Remember that once this function is executed, the rest of the function is still executed. Therefore, if you have it in a guardclause, 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.
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 additionaltypes 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.