Skip to content

Instantly share code, notes, and snippets.

@Bloody-Badboy
Last active October 5, 2022 16:41
Show Gist options
  • Save Bloody-Badboy/5e45e315de0982f49aca0affcaa1e541 to your computer and use it in GitHub Desktop.
Save Bloody-Badboy/5e45e315de0982f49aca0affcaa1e541 to your computer and use it in GitHub Desktop.
MoshiContentConverter & MoshiWebSocketContentConverter for Ktor server
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi
import io.ktor.http.ContentType
import io.ktor.http.content.OutgoingContent
import io.ktor.http.content.TextContent
import io.ktor.http.withCharset
import io.ktor.serialization.ContentConverter
import io.ktor.server.plugins.contentnegotiation.ContentNegotiationConfig
import io.ktor.util.reflect.TypeInfo
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.buffer
import okio.source
import java.nio.charset.Charset
class MoshiContentConverter(private val moshi: Moshi = Moshi.Builder().build()) : ContentConverter {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any? {
val result = runCatching {
withContext(Dispatchers.IO) {
moshi.adapter(typeInfo.type.javaObjectType).fromJson(content.toInputStream().source().buffer())
}
}
if (result.isSuccess) {
return result.getOrThrow()
} else {
when (val ex = result.exceptionOrNull()) {
is JsonDataException -> error("Incorrect json format: ${ex.message}")
is JsonEncodingException -> error("Invalid json format: ${ex.message}")
else -> error(ex?.message ?: "")
}
}
}
override suspend fun serializeNullable(
contentType: ContentType,
charset: Charset,
typeInfo: TypeInfo,
value: Any?
): OutgoingContent? {
if (value == null) return null
val result = runCatching {
withContext(Dispatchers.IO) {
moshi.adapter<Any?>(typeInfo.type.javaObjectType).nullSafe().toJson(value)
}
}
if (result.isSuccess) {
return TextContent(result.getOrThrow(), contentType.withCharset(charset))
} else {
val ex = result.exceptionOrNull()
error(ex?.message ?: "")
}
}
}
fun ContentNegotiationConfig.moshi(moshi: Moshi) {
val converter = MoshiContentConverter(moshi)
register(ContentType.Application.Json, converter)
}
fun ContentNegotiationConfig.moshi(block: Moshi.Builder.() -> Unit) {
val builder = Moshi.Builder()
builder.apply(block)
val converter = MoshiContentConverter(builder.build())
register(ContentType.Application.Json, converter)
}
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi
import io.ktor.serialization.WebsocketContentConverter
import io.ktor.util.reflect.TypeInfo
import io.ktor.utils.io.charsets.Charset
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class MoshiWebSocketContentConverter(private val moshi: Moshi = Moshi.Builder().build()) : WebsocketContentConverter {
override suspend fun deserialize(
charset: Charset,
typeInfo: TypeInfo,
content: Frame
): Any? {
if (content !is Frame.Text) {
error("Unsupported frame ${content.frameType.name}")
}
val result = runCatching {
withContext(Dispatchers.IO) {
moshi.adapter(typeInfo.type.javaObjectType).fromJson(content.readText())
}
}
if (result.isSuccess) {
return result.getOrThrow()
} else {
when (val ex = result.exceptionOrNull()) {
is JsonDataException -> error("Incorrect json format: ${ex.message}")
is JsonEncodingException -> error("Invalid json format: ${ex.message}")
else -> error(ex?.message ?: "")
}
}
}
override suspend fun serializeNullable(charset: Charset, typeInfo: TypeInfo, value: Any?): Frame {
val result = runCatching {
withContext(Dispatchers.IO) {
moshi.adapter<Any?>(typeInfo.type.javaObjectType).nullSafe().toJson(value)
}
}
if (result.isSuccess) {
return Frame.Text(result.getOrThrow())
} else {
val ex = result.exceptionOrNull()
error(ex?.message ?: "")
}
}
override fun isApplicable(frame: Frame): Boolean {
return frame is Frame.Text
}
}
fun main() {
embeddedServer(Netty, port = 9000, host = "0.0.0.0") {
initDatabase()
install(ContentNegotiation) {
moshi {
}
}
...
...
...
}.start(wait = true)
}
fun main() {
embeddedServer(Netty, port = 9000, host = "0.0.0.0") {
initDatabase()
install(WebSockets) {
pingPeriod = Duration.ofSeconds(15)
timeout = Duration.ofSeconds(15)
maxFrameSize = Long.MAX_VALUE
masking = false
contentConverter = MoshiWebSocketContentConverter()
}
...
...
...
}.start(wait = true)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment