https://kotlinlang.org logo
#ktor
Title
# ktor
n

nilTheDev

10/30/2021, 8:34 AM
call.receiveOrNull<T>()
isn't working as expected. This is how you can reproduce, 1. Install the
ContentNegotiation
plugin for automatic deserialization of
json
content. (I am using
kotlinx.serialization
) 2. Define a model for the expected data 3. Send request with missing json field 4. Instead of returning null, it's throwing the exception and causing a
500 internal server error
Code,
Copy code
fun Application.module() {
    install(ContentNegotiation) {
        json(
            Json { ignoreUnknownKeys = true }
        )
    }

    routing {
        post("/") {
            val user = call.receiveOrNull<User>()
                ?: return@post call.respondText(
                    text = "invalid request",
                    status = HttpStatusCode.BadRequest
                )
            call.respondText(text = "valid request", status = HttpStatusCode.Accepted)
        }
    }
}
Model,
Copy code
@Serializable
data class User(
    @SerialName("username")
    val userName: String,
    val password: String
)
Sent request,
Copy code
POST / HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 24
Content-Type: application/json
Host: localhost:8080
User-Agent: HTTPie/2.5.0

{
    "username": "nilanjan"
}
Received response,
Copy code
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 0
Exception thrown in the server,
kotlinx.serialization.MissingFieldException: Field 'password' is required for type with serial name 'com.jetbrains.handson.httpapi.User'
As a workaround I had to define my own
receiveOrNull
extension function.
Copy code
suspend inline fun <reified T : Any> ApplicationCall.receiveOrNull(): T? {
    return try {
        receive<T>()
    } catch (cause: Exception) {
        null
    }
}
The actual
receiveOrNull
function in the
io.ktor.request
package looks like that,
Copy code
public suspend fun <T : Any> ApplicationCall.receiveOrNull(type: KType): T? {
    return try {
        receive<T>(type)
    } catch (cause: ContentTransformationException) {
        application.log.debug("Conversion failed, null returned", cause)
        null
    }
}
Apparently, it's only catching the
ContentTransformationException
nothing else. This issue in YourTrack is similar to my issue, https://youtrack.jetbrains.com/issue/KTOR-545 So, my questions are, 1. Is this how the
receiveOrNull
function supposed to work? Or it is a bug? Do I have to report it? 2. If it's not a bug then why it is designed this way? Isn't it a good design to catch all kind of errors related to deserialization in the
receiveOrNull
function rather than causing an internal server error?
a

andylamax

10/30/2021, 8:55 AM
I have no answer, but just wanted to say. Nice catch and proper presentation of the problem. This should be an example of asking a question, or bug reporting
🙂 1
e

enleur

10/30/2021, 10:50 AM
n

nilTheDev

10/30/2021, 12:37 PM
@enleur Didn't understand what you wanted to say. The link you provided points to the response validation section of the ktor client. How is it relevant?
e

enleur

10/30/2021, 12:42 PM
response validation enabled by default. everything not 2xx goes with exception. in your case it responded with 500 status, so I guess it was a response validation exception
n

nilTheDev

10/30/2021, 12:43 PM
But I am not even using ktor client. It's about ktor server. It's more about request validation rather than response validation.
e

enleur

10/30/2021, 12:46 PM
I see now. Yep, sorry
a

Aleksei Tirman [JB]

11/01/2021, 8:29 AM
@nilTheDev the bug you found KTOR-545 is exactly the same problem you described. The inline documentation for this method isn't clear because it's meant to return
null
only if a suitable converter isn't found so the transformation isn't applied. The server responds with the 500 status code because it's the default behavior for any uncaught exceptions and it's overridable by the StatusPages plugin.
10 Views