commonMain.com.caesarealabs.rpc4k.runtime.api.RpcServerUtils.kt Maven / Gradle / Ivy
package com.caesarealabs.rpc4k.runtime.api
import com.caesarealabs.logging.Logging
import com.caesarealabs.rpc4k.runtime.implementation.RpcResult
import com.caesarealabs.rpc4k.runtime.user.RPCContext
public interface ServerData {
}
public object RpcServerUtils {
/**
* Should be called by server implementations whenever a new request is received, and pass the request's bytes as [input].
* A response must then be returned to the client according to the [RpcResult].
* This will call the RPC Service, using code gen and such.
*
* @param serverData Information specific to the server implementation calling this. RPC services may then
* reference this data through [RPCContext.serverData] (usually needing an `is` check specific to the server implementation)
* @param initialLogs Logs to perform on the call, independent of the endpoint.
*/
public suspend fun routeCall(
input: ByteArray,
config: ServerConfig,
serverData: ServerData? = null,
initialLogs: Logging.() -> Unit = {}
): RpcResult {
// Logging not available yet - make do with normal prints
val method = try {
Rpc.peekMethodName(input)
} catch (e: InvalidRpcRequestException) {
return config.config.logging.wrapCall("Other Request Failures") {
initialLogs()
invalidRequest(e, this@wrapCall)
}
} catch (e: Throwable) {
return config.config.logging.wrapCall("Other Internal Req Failures") {
initialLogs()
serverError(e, this@wrapCall) { "Failed to call Rpc.peekMethodName()" }
}
}
// Logging available
return config.config.logging.wrapCall(method) {
initialLogs()
val logging = this@wrapCall
try {
(config.router as RpcRouter).routeRequest(input, method, config.config, SimpleRpcContext(serverData, logging))
?.let { RpcResult.Success(it) }
?: RpcResult.Error("Non existent procedure $method", RpcError.InvalidRequest)
} catch (e: InvalidRpcRequestException) {
invalidRequest(e, logging)
} catch (e: Throwable) {
serverError(e, logging) { "Failed to call routeRequest() on method $method" }
}
}
}
private fun invalidRequest(exception: InvalidRpcRequestException, logging: Logging): RpcResult {
logging.logWarn(exception) { "Invalid request. Is the content-type header correct?" }
// RpcServerException messages are trustworthy
return RpcResult.Error(exception.message, RpcError.InvalidRequest)
}
private fun serverError(exception: Throwable, logging: Logging, logMessage: () -> String): RpcResult {
logging.logError(exception, logMessage)
// Don't send arbitrary throwable messages because it could leak data
return RpcResult.Error("Server failed to process request", RpcError.InternalError)
}
}
public class SimpleRpcContext(override val serverData: ServerData?, private val logging: Logging) : RPCContext, Logging by logging