JSON RPC

Installation (Gradle)

dependencies {
    implementation(platform("org.http4k:http4k-bom:5.16.0.0"))
    implementation("org.http4k:http4k-jsonrpc")
}

About

Support for JSON-RPC handlers, with support for both manual and automatic marshalling modes.

Each service method "name" is bound to a particular endpoint function and then the entire API is exposed as a standard http4k HttpHandler, so it can be composed with other HttpHandlers and Filters.

A specialised ErrorHandler can also be assigned to the RPC contract.

Note that in order to activate JSON RPC, you need to import one of the supported JSON modules.

Code

package guide.reference.jsonrpc

import org.http4k.core.HttpHandler
import org.http4k.core.Method.POST
import org.http4k.core.Request
import org.http4k.format.Jackson
import org.http4k.format.Json
import org.http4k.jsonrpc.ErrorHandler
import org.http4k.jsonrpc.ErrorMessage
import org.http4k.jsonrpc.JsonRpc
import java.util.concurrent.atomic.AtomicInteger

class Counter {
    private val value = AtomicInteger()

    fun increment(amount: Increment): Int = when {
        amount.value == 10 -> throw RuntimeException("Boom!")
        amount.value < 0 -> throw NegativeIncrementException()
        else -> value.addAndGet(amount.value)
    }

    fun currentValue(): Int = value.get()

    data class Increment(val value: Int)

    class NegativeIncrementException : RuntimeException("negative increment not allowed")
}

object CounterErrorHandler : ErrorHandler {
    override fun invoke(error: Throwable): ErrorMessage? = when (error) {
        is Counter.NegativeIncrementException -> NegativeIncrementExceptionMessage()
        else -> null
    }

    private class NegativeIncrementExceptionMessage :
        ErrorMessage(1, "Increment by negative") {
        override fun <NODE> data(json: Json<NODE>) =
            json.string("cannot increment counter by negative")
    }
}

fun main() {
    val counter = Counter()

    val rpcHandler: HttpHandler = JsonRpc.auto(Jackson, CounterErrorHandler) {
        method("increment", handler(counter::increment))
        method("current", handler(counter::currentValue))
    }

    fun runRequest(s: String) {
        println(
            rpcHandler(
                Request(POST, "/rpc")
                    .header("Content-Type", "application/json")
                    .body(s)
            )
        )
    }

    val increment =
        """ {"jsonrpc": "2.0", "method": "increment", "params": {"value": 3}, "id": 1} """
    runRequest(increment)

    val incrementInvalid =
        """ {"jsonrpc": "2.0", "method": "increment", "params": {"value": -1}, "id": 2} """
    runRequest(incrementInvalid)

    val current = """ {"jsonrpc": "2.0", "method": "current", "id": 3} """
    runRequest(current)
}