I am trying to configure jackson-modules-java8 with Ktor and Jackson but to no avail.
The module is added to gradle.build
dependencies {
...
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.0-rc2'
...
}
According to the Jackson docs I should do this:
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
But in Ktor I can only do this:
install(ContentNegotiation) {
jackson {
// `this` is the ObjectMapper
this.enable(SerializationFeature.INDENT_OUTPUT)
// what to do here?
}
}
According with the official example if you want to add a module you could use
registerModule
as this:
install(ContentNegotiation) {
jackson {
configure(SerializationFeature.INDENT_OUTPUT, true)
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
indentObjectsWith(DefaultIndenter(" ", "\n"))
})
registerModule(JavaTimeModule()) // support java.time.* types
}
}
Related
ok I'm new to Ktor and I'm learning about it, one thing that I faced in this progress was this error :
kotlinx.serialization.SerializationException: Serializer for class 'FreeMarkerContent' is not found.
Mark the class as #Serializable or provide the serializer explicitly
and thats becouse in my code, Im using FreeMakcerContent and its trying to Serlialize it :
get {
call.respond(FreeMarkerContent("index.ftl", mapOf("articles" to articles)))
}
how can I fix this problem?
I faced the same problem and I fixed it by installing FreeMarker plugin before installing ContentNegotiation plugin.
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
}
install(ContentNegotiation) {
json()
}
routing {
get("/html-freemarker") {
call.respond(FreeMarkerContent("index.ftl", mapOf("name" to "Shalaga"), ""))
}
}
ok I think I found the solution, in the configuration of Serialization I did define the route that i need sterilization in it ( my Http Api route), something like this
fun Application.configureSerialization() {
routing {
route("api"){
install(ContentNegotiation) {
json()
}
}
}
}
so the Serialization will only work in this route
I trying to create API with ktor.
Test for ktor api using JUnit5 and I need use the custom properties for database information in 'Application.conf' like below:
ktor {
deployment {
.....
}
application {
.....
}
database {
host = ${?DB_HOST}
user = ${?DB_USER}
pass = ${?DB_PASS}
}
}
I following official guidline for useing custom properties:
HoconApplicationConfig
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
class MyTest {
#Test
fun Testing() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo").apply {
...
}
}
}
}
Succeed test when only one function in class, but defined second test function then test failed with Exception.
I want define multiple function to simplify the definition and checking in case of failure.
class MyTest {
#Test
fun Testing() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo").apply {
....
}
}
}
#Test
fun Testing2() {
withApplication(testEnv) {
handleRequest(HttpMethod.Get, "/foo/bar").apply {
...
}
}
}
}
io.ktor.server.engine.BaseApplicationResponse$ResponseAlreadySentException: Response has already been sent
I know all test will success using 'withTestApplication' but can't use the custom properties.
How can I use the custom properties, and define multiple test funcitons?
Environment:
macOS Big Sur 11.5
IntelliJ Ultimate 2021.1.3
kotlin 1.5.21
ktor 1.6.1
junit-jupiter 5.7.0
Sorry for bad English.
Best Regards.
I have a project that was running on Kotlin 1.4.0 and Ktor 1.3.2, so I decided to try and upgrade my versions to Kotlin 1.4.30 and Ktor 1.5.1 (since I was also upgrading to Java 11 due to adoptopenJDK8 not having JavaFx).
When I finally fixed import issues and whatnot and ran my webserver, the first page opens correctly (it's mapped to route "/"), but the other GET requests throw a "Lost in ambiguity tie" error on the RoutingResolve class. Apparently when comparing the quality of registered selectors, the "/ping" one has the same value as "//", hence throwing the error. I have validated this behaviour Ktor 1.5.1 and 1.5.0. Versions 1.4.x throw coroutine errors.
My server configurations are declared as such:
fun serverConfiguration(controller: Controller): NettyApplicationEngine {
SocketRoutes.controller = controller
WebUIRoutes.controller = controller
return embeddedServer(Netty, 9090) {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
dateFormat = DateFormat.getDateInstance()
deactivateDefaultTyping()
}
}
install(FreeMarker) {
templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
defaultEncoding = Charsets.UTF_8.toString()
}
install(CallLogging)
install(Sessions) {
cookie<UserPreferences>("preferences")
}
getRoutes(this)
}
}
My Routes.kt
fun getRoutes(pipeline: Application): Routing {
return Routing.install(pipeline) {
trace { application.log.trace(it.buildText()) }
static("/") {
resources("web-static")
}
home()
getTableBody()
ping()
}
}
And these routes themselves are on WebUIRoutes.kt
object WebUIRoutes {
lateinit var controller: Controller
fun Route.home() {
get("/") {
val session = call.sessions.get<UserPreferences>() ?: UserPreferences()
call.sessions.set(session)
val nrPages = calculateNrOfPages(session.nrElements)
call.response.cookies["nrElements"]?.copy(value = "$nrPages")
call.respondTemplate(
"showCodes.ftl", mapOf(
"total" to nrPages, "nrElements" to session.nrElements,
"validateOnInput" to session.validateOnInput
)
)
}
}
(...)
fun Route.ping() {
get("/ping") {
if (controller.ping())
call.respond(HttpStatusCode.OK)
else
call.respond(HttpStatusCode.NotFound)
}
}
}
So I have no idea what the hell is going on and I've found nothing online about migrating to newer versions of Ktor and related issues...
The problem is that the resources function adds a route with a wildcard under the / route and therefore the routing resolves it instead of the /ping route. You can just move the static function call to the end of Routing to make it work as you expect.
I'm using springdoc-openapi with Kotlin and WebFlux.fn.
I wanted to use #RouterOperation annotation at every path in CoRouterFunctionDsl but I couldn't.
#Configuration
class UserRouter(private val userHandler: UserHandler) {
// #RouterOperations annotation works here.
#Bean
fun userRouter = coRouter {
("/v1").nest {
// I want to use #RouterOperation annotation here.
GET("/users", userHandler::getUsers)
// I want to use #RouterOperation annotation here.
GET("/users/{userId}", userHandler::getUserById)
// I want to use #RouterOperation annotation here.
POST("/users", userHandler::postUser)
}
}
}
There doesn't seem to be any relevant documentation about this.
How can I use #RouterOperation in coRouter DSL?
The same principles applies to Kotlin DSL (coRouter): CoRouterFunctionDsl Beans are instances of RouterFunction.
Here is a sample syntax:
#FlowPreview
#Bean
#RouterOperations(
RouterOperation(path = "/test", method = arrayOf(RequestMethod.GET), beanClass = ProductRepositoryCoroutines::class, beanMethod = "getAllProducts"),
RouterOperation(path = "/test/{id}", method = arrayOf(RequestMethod.GET), beanClass = ProductRepositoryCoroutines::class, beanMethod = "getProductById"))
fun productRoutes(productsHandler: ProductsHandler) = coRouter {
GET("/test", productsHandler::findAll)
GET("/test/{id}", productsHandler::findOne)
}
I got something like this:
private val client = HttpClient {
install(JsonFeature) {
serializer = GsonSerializer()
}
install(ExpectSuccess)
}
and make request like
private fun HttpRequestBuilder.apiUrl(path: String, userId: String? = null) {
header(HttpHeaders.CacheControl, "no-cache")
url {
takeFrom(endPoint)
encodedPath = path
}
}
but I need to check request and response body, is there any way to do it? in console/in file?
You can achieve this with the Logging feature.
First add the dependency:
implementation "io.ktor:ktor-client-logging-native:$ktor_version"
Then install the feature:
private val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
Bonus:
If you need to have multiple HttpClient instances throughout your application and you want to reuse some of the configuration, then you can create an extension function and add the common logic in there. For example:
fun HttpClientConfig<*>.default() {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
// Add all the common configuration here.
}
And then initialize your HttpClient like this:
private val client = HttpClient {
default()
}
I ran into this as well. I switched to using the Ktor OkHttp client as I'm familiar with the logging mechanism there.
Update your pom.xml or gradle.build to include that client (copy/paste from the Ktor site) and also add the OkHttp Logging Interceptor (again, copy/paste from that site). Current version is 3.12.0.
Now configure the client with
val client = HttpClient(OkHttp) {
engine {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = Level.BODY
addInterceptor(loggingInterceptor)
}
}
Regardless of which client you use or framework you are on, you can implement your own logger like so:
private val client = HttpClient {
// Other configurations...
install(Logging) {
logger = CustomHttpLogger()
level = LogLevel.BODY
}
}
Where CustomHttpLogger is any class that implements the ktor Logger interface, like so:
import io.ktor.client.features.logging.Logger
class CustomHttpLogger(): Logger {
override fun log(message: String) {
Log.d("loggerTag", message) // Or whatever logging system you want here
}
}
You can read more about the Logger interface in the documentation here or in the source code here
It looks like we should handle the response in HttpReceivePipeline. We could clone the origin response and use it for logging purpose:
scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
val (loggingContent, responseContent) = response.content.split(scope)
launch {
val callForLog = DelegatedCall(loggingContent, context, scope, shouldClose = false)
....
}
...
}
The example implementation could be found here: https://github.com/ktorio/ktor/blob/00369bf3e41e91d366279fce57b8f4c97f927fd4/ktor-client/ktor-client-core/src/io/ktor/client/features/observer/ResponseObserver.kt
and would be available in next minor release as a client feature.
btw: we could implement the same scheme for the request.
A custom structured log can be created with the HttpSend plugin
Ktor 2.x:
client.plugin(HttpSend).intercept { request ->
val call = execute(request)
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url.build()} ($durationMillis ms)")
call
}
Ktor 1.x:
client.config {
install(HttpSend) {
intercept { call, _ ->
val request = call.request
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url} ($durationMillis ms)")
call
}
}
}
Check out Kotlin Logging, https://github.com/MicroUtils/kotlin-logging it isused by a lot of open source frameworks and takes care of all the prety printing.
You can use it simply like this:
private val logger = KotlinLogging.logger { }
logger.info { "MYLOGGER INFO" }
logger.warn { "MYLOGGER WARNING" }
logger.error { "MYLOGGER ERROR" }
This will print the messages on the console.