14 minutes read

You are already familiar with Ktor routing and know how to create handlers for different URLs. You also know how to get route and query parameters. Let's look at what other information we can get in the routing handler.

GET or POST

You already know that the HTTP protocol provides several methods of sending data to a server. The two most popular are GET and POST.

GET is used to retrieve data from the server. But during the execution of a GET request, the server may need additional data to respond correctly to your request. Therefore, in some cases, you will need to pass this additional data in a GET request. In order to pass any parameters to the server in a GET request, you must embed them in the URL. For example, http://localhost/page?pageid=4&count=30 or, http://localhost/page/pageid/4/count/30. You already know how to extract such data from URLs and process them.

You might have wondered why you shouldn't use GET parameters to send data to the server all the time. The fact is that sending data using the GET method has three significant drawbacks:

  1. Servers have a limitation on the length of the URL, so you can't transfer large data using the GET method.

  2. For security reasons, you should not send important user data such as passwords and credit card numbers via GET as it may be stored in browser history or server logs along with the URL.

  3. Binary data, such as files, cannot be passed through URL parameters.

So a special method was invented to send data to the server: POST. It sends the data in a separate message (called the request body). Let's see how we can access the body of the POST request using Ktor.

Getting raw payload

Suppose we have a /page handler.

routing {
    post("/page") {
            
    }
}

Let's use Postman to send a POST request to localhost:8080/page. There are two parameters in the request: name and lastname.

request settings in postman

Using the call.receiveText() function, we can easily get the entire request body. Then we can display it in the console to see what it looks like.

routing {
    post("/page") {
        call.application.environment.log.info("\n" + call.receiveText())
    }
}

request body logged in a console

In the console, we will see the whole body of the request along with all the parameters. But how can we access a specific parameter? Don't worry, you won't have to process the body of the request and find the parameters there. Ktor already has tools that do this for you.

Getting post parameters

To access the data passed in the body of the request, call the call.receiveParameters() function, which will return an associative array of the passed parameters. Let's get the user's name and last name and greet him.

routing {
    post("/page") {
        val formParameters = call.receiveParameters()
        val name = formParameters["name"]
        val lastname = formParameters["lastname"]
        call.respondText("Hello, $name $lastname!")
    }
}

If we go back to Postman and repeat our request, we will see the response from the server:

request body in postman

This is how you can get any text data sent via POST. But what if you want to receive files?

Getting files

There are two main ways to encode form data when sending it to the server: multipart/form-data and application/x-www-form-urlencoded. The fundamental difference is that multipart/form-data allows you to send both text data and files, while application/x-www-form-urlencoded allows you to send only the text. Therefore, multipart is considered more universal and is used more often.

In Postman, you can always choose the appropriate encoding for sending.

encode settings in postman

Now let's go directly to file uploads.

The POST method with multipart/form-data encoding allows you to send not only text data but also binary data (that is, files). When you create a real website, this task arises often.

Let's create a way for the user to upload an avatar. We will pass the picture as another POST parameter named avatar. Place your cursor over the avatar cell and select File option. After that you will be able to select a file.

body request settings in postman

Now, accept that picture on the server and save it in the /avatars folder with the original name.

post("/page") {
    val parts = call.receiveMultipart().readAllParts()
    for (part in parts) {
        if (part is PartData.FormItem) {
            call.application.environment.log.info(part.name + ":" + part.value)
        }
        if (part is PartData.FileItem) {
            val fileName = part.originalFileName
            val fileBytes = part.streamProvider().readBytes()
            File("avatars/$fileName").writeBytes(fileBytes)
        }
    }
}

By calling call.receiveMultipart().readAllParts() we got a list of PartData objects. Each object in the list is a POST request parameter. The call.receiveParameters() from the previous example received only text parameters, if we want full access to all parameters including files, we should use call.receiveMultipart().readAllParts().

Then, in the loop, we go through all our parameters. If the parameter is a text parameter, part is PartData.FormItem, we just print its name and value to the console. But if the parameter turns out to be a file, we get its name and bytes. Finally, we create a new file in the /avatars folder with the received name and content: File("avatars/$fileName").writeBytes(fileBytes).

All that's left to do is to create an /avatars folder in the root folder of our project. This folder will be used to save the uploaded pictures.

project structure in explorer

Let's go back to Postman and send our request with an image.

The server processed the request and saved the image in the specified folder. Great!

avatars folder

And the remaining text parameters of the query were displayed in the console:

text parameters in a console

Getting headers

In addition to the body, the request also contains additional information that may be useful. This information is contained in so-called query headers. All headers of the current request are stored in the call.request.headers array. To get all the headers, you can use forEach:

call.request.headers.forEach { title, data -> call.application.environment.log.info("$title: ${data[0]}") }

This will display all the headers of the query in the console:

titles of the request in a console

User-Agent

One of the important headers that the browser sends to the server is User-Agent. This header contains information about the browser from which the request is made. We can get it: call.request.headers["User-Agent"]. Let's display the User-Agent in the console and see how it looks.

post("/page") {
    call.application.environment.log.info(call.request.headers["User-Agent"].toString())
}

Now let's send a POST request with the Postman. In the console you will see the Postman's User-Agent: postman's user-agent

If you make a POST request from Chrome, the User-Agent will be different:

Chrome's user-agent

This header is often used by site administrators to collect query statistics, which contains information about the devices and browsers from which users access the site.

Getting cookies

Another important part of headers is cookies. They are used to store data on the client's side. Your server tells the client's browser to store certain information in cookies. And the browser will include them in the Cookie header for all future requests to your server. Let's add a cookie to our Postman request.

cookies settings buttonmanage cookies window

add cookies button

add cookies window

Now Postman will send a cookie with the name "cookie1" and the value "hello" in the headers with each request.

Ktor provides easy access to cookies with the call.request.cookies object. By using it, we can easily get the value of a cookie by its name. In our case, call.request.cookies["cookie1"] will return "hello".

Tip: Since cookies are contained in a Cookie header, we can access all cookies as a raw string using call.request.headers["Cookie"] and display them in the console. Just like we output the User-Agent header.

post("/page") {
    call.application.environment.log.info(call.request.headers["Cookie"])
}

In the console we will see our cookies: cookie logged in a console

But analyzing this kind of string can be quite difficult, especially if you have a lot of cookies. Therefore, it is recommended to use construction call.request.cookies["cookieName"], which immediately allows you to access the value of a cookie by name.

You will often deal with headers and cookies in your future work because no major website can do without them.

Getting method, host and uri

Using call.request, besides headers and cookies, we can get the method, host, and URI. This is also very easy to do:

post("/page") {
    call.application.environment.log.info(call.request.httpMethod.toString())
    call.application.environment.log.info(call.request.host())
    call.application.environment.log.info(call.request.uri)
}

And we get them in the console:

method, host and uri logged in a console

This information is used rarely, but you should still know that it exists, and you should be able to get it.

Conclusion

In this topic, we learned why you have to use the POST method to send data. Now you know how to get not only the text parameters of a POST request but also the files.

We also looked at how to handle headers, cookies, and other useful information about the request.

Don't be afraid if you can't remember all the methods at once. Start using them in practice. You can always go back to the theory to refresh your memory.

19 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo