Handling HTTP Requests

When handling routes, or directly intercepting the pipeline, youget a context with an ApplicationCall.That call contains a property called request that includes information about the request.

Also, the call itself has some useful convenience properties and methods that rely on the request.

Table of contents:

Introduction

When using the Routing feature, or when intercepting requests, you can accessthe call property inside handlers. That call includes a request property with relevant information about the request:

  1. routing {
  2. get("/") {
  3. val uri = call.request.uri
  4. call.respondText("Request uri: $uri")
  5. }
  6. }
  7. intercept(ApplicationCallPipeline.Call) {
  8. if (call.request.uri == "/") {
  9. call.respondText("Test String")
  10. }
  11. }

Request information

As part of the request, you can get access to its internal context:

  1. val call: ApplicationCall = request.call
  2. val pipeline: ApplicationReceivePipeline = request.pipeline

URL, method, scheme, protocol, host, path, httpVersion, remoteHost, clientIp

  1. val version: String = request.httpVersion // "HTTP/1.1"
  2. val httpMethod: HttpMethod = request.httpMethod // GET, POST...
  3. val uri: String = request.uri // Short cut for `origin.uri`
  4. val scheme: String = request.origin.scheme // "http" or "https"
  5. val host: String? = request.host() // The host part without the port
  6. val port: Int = request.port() // Port of request
  7. val path: String = request.path() // The uri without the query string
  8. val document: String = request.document() // The last component after '/' of the uri
  9. val remoteHost: String = request.origin.remoteHost // The IP address of the client doing the request

Reverse proxy support: origin and local

When behind a reverse-proxy (for example an nginx or a load balancer), the received request is not performed by the end-user, but that reverse proxy.That means that the client IP address of the connection would be the one of the proxy instead of the client.Also the reverse proxy might be serving via HTTPS and requesting to your server via HTTP.Popular reverse proxies send X-Forwarded- headers to be able to access this information.

Note that for this to work when under a reverse-proxy you have to install the XForwardedHeaderSupport feature.

As part of the request object, there are two properties local and origin that allows to get information of the original requestor the local/proxied one.

  1. val local : RequestConnectionPoint = request.local // Local information
  2. val origin: RequestConnectionPoint = request.origin // Local / Origin if XForwardedHeaderSupport feature is installed.

The local/origin information you can get:

  1. interface RequestConnectionPoint {
  2. val scheme: String // "http" or "https": The provided protocol (local) or `X-Forwarded-Proto`
  3. val version: String // "HTTP/1.1"
  4. val port: Int
  5. val host: String // The provided host (local) or `X-Forwarded-Host`
  6. val uri: String
  7. val method: HttpMethod
  8. val remoteHost: String // The client IP (the direct ip for `local`, or the redirected one `X-Forwarded-For`)
  9. }

GET / Query parameters

If you need to access the query parameters ?param1=value&param2=value as a collection,you can use queryParameters. It implements the StringValues interface whereeach key can have a list of Strings associated with it.

  1. val queryParameters: Parameters = request.queryParameters
  2. val param1: String? = request.queryParameters["param1"] // To access a single parameter (first one if repeated)
  3. val repeatedParam: List<String>? = request.queryParameters.getAll("repeatedParam") // Multiple values

You can also access the raw queryString (param1=value&param2=value):

  1. val queryString: String = request.queryString()

POST, PUT and PATCH

POST, PUT and PATCH requests has an associated request body (the payload).That payload is usually encoded.

All the receive methods consume the whole payload sent by the client so an attempt to receive a request body twicewill lead to RequestAlreadyConsumedException error unless you have DoubleReceive feature installed.

Raw payload

To access the raw bits of the payload, you can use receiveChannel, but it isdirectly part of the call instead of call.request:

  1. val channel: ByteReadChannel = call.receiveChannel()

And it provide some convenience methods for common types:

  1. val channel: ByteReadChannel = call.receiveChannel()
  2. val text: String = call.receiveText()
  3. val inputStream: InputStream = call.receiveStream() // NOTE: InputStream is synchronous and blocks the thread
  4. val multipart: MultiPartData = call.receiveMultipart()

All those receive* methods are aliases of call.receive<T> with the specified type.The types ByteReadChannel, ByteArray, InputStream, MultiPartData, String and Parameters are handled byApplicationReceivePipeline.installDefaultTransformations that is installed by default.

Form Parameters (urlencoded or multipart)

To parse a form urlencoded or with multipart, you can use receiveParameters or receive<Parameters>:

  1. val postParameters: Parameters = call.receiveParameters()

Receive Typed Objects, Content-Type and JSON

The call also supports receiving generic objects:

  1. val obj: T = call.receive<T>()
  2. val obj: T? = call.receiveOrNull<T>()

In order to receive custom objects from the payload,you have to use the ContentNegotiation feature.This is useful for example to receive and send JSON payloads in REST APIs.

  1. install(ContentNegotiation) {
  2. gson {
  3. setDateFormat(DateFormat.LONG)
  4. setPrettyPrinting()
  5. }
  6. }

If you configure the ContentNegotiation to use gson,you will need to include the ktor-gson artifact:

  1. compile("io.ktor:ktor-gson:$ktor_version")

Then you can, as an example, do:

  1. data class HelloWorld(val hello: String)
  2. routing {
  3. post("/route") {
  4. val helloWorld = call.receive<HelloWorld>()
  5. }
  6. }

Remember that your classes must be defined top level (outside of any other class or function) to be recognized by Gson.

Multipart, Files and Uploads

Check the uploads section.

Custom receive transformers

You can create custom transformers by callingapplication.receivePipeline.intercept(ApplicationReceivePipeline.Transform) { query ->and then calling proceedWith(ApplicationReceiveRequest(query.type, transformed)) as does the ContentNegotiation feature.

Cookies

There is a cookies property to access the Cookie headers sent by the client,just as if it was a collection:

  1. val cookies: RequestCookies = request.cookies
  2. val mycookie: String? = request.cookies["mycookie"]

To handle sessions using cookies, have a look to the Sessions feature.

Headers

To access the headers the request objects has a headers: Headers property.It implements the StringValues interface where each key can have a list of Strings associated with it.

  1. val headers: Headers = request.headers
  2. val header: String? = request.header("HeaderName") // To access a single header (first one if repeated)
  3. val repeatedHeader: List<String>? = request.headers.getAll("HeaderName") // Multiple values

And several convenience methods to access some common headers:

  1. val contentType: ContentType = request.contentType() // Parsed Content-Tpe
  2. val contentCharset: Charset? = request.contentCharset() // Content-Type JVM charset
  3. val authorization: String? = request.authorization() // Authorization header
  4. val location: String? = request.location() // Location header
  5. val accept: String? = request.accept() // Accept header
  6. val acceptItems: List<HeaderValue> = request.acceptItems() // Parsed items of Accept header
  7. val acceptEncoding: String? = request.acceptEncoding() // Accept-Encoding header
  8. val acceptEncodingItems: List<HeaderValue> = request.acceptEncodingItems() // Parsed Accept-Encoding items
  9. val acceptLanguage: String? = request.acceptLanguage() // Accept-Language header
  10. val acceptLanguageItems: List<HeaderValue> = request.acceptLanguageItems() // Parsed Accept-Language items
  11. val acceptCharset: String? = request.acceptCharset() // Accept-Charset header
  12. val acceptCharsetItems: List<HeaderValue> = request.acceptCharsetItems() // Parsed Accept-Charset items
  13. val userAgent: String? = request.userAgent() // User-Agent header
  14. val cacheControl: String? = request.cacheControl() // Cache-Control header
  15. val ranges: RangesSpecifier? = request.ranges() // Parsed Ranges header
  16. val isChunked: Boolean = request.isChunked() // Transfer-Encoding: chunked
  17. val isMultipart: Boolean = request.isMultipart() // Content-Type matches Multipart