Статьи

Подключение веб-сервера Ktor к узлу Corda

Подготовка к этому сообщению началась несколько недель назад (возможно, через месяц). Прежде чем я смог написать о слиянии Корды и Ктора, мне сначала нужно было заложить основу и сосредоточиться исключительно на Кторе. Именно здесь появился мой пост в блоге Ktor — веб-фреймворк Kotlin . Если вы не использовали или не видели Ktor раньше, я рекомендую просмотреть этот пост до или после прочтения этого поста. Читать его заранее — это, пожалуй, лучшая идея, но вы сами контролируете свою жизнь ?.

Этот пост будет посвящен реализации веб-сервера Ktor, который подключается к узлу Corda. Я не буду говорить о том, почему вы должны использовать Ktor. Это решение зависит от вас. Что я делаю, так это предоставляю вам некоторую информацию и позволяю вам самим сформулировать решение (как и все, что вы читаете в Интернете ?).

зависимости

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
buildscript {
  ext.ktor_version = '1.2.2'
  ext.kotlin_version_for_app = '1.3.41'
 
  repositories {
    mavenCentral()
    jcenter()
  }
 
  dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version_for_app"
  }
}
 
apply plugin: 'java'
apply plugin: 'kotlin'
 
java {
  disableAutoTargetJvm()
}
 
dependencies {
  compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version_for_app"
  compile "$corda_release_group:corda-jackson:$corda_release_version"
  compile "$corda_release_group:corda-rpc:$corda_release_version"
  compile "$corda_release_group:corda:$corda_release_version"
  compile "io.ktor:ktor-server-netty:$ktor_version"
  compile "ch.qos.logback:logback-classic:1.2.3"
  implementation ("io.ktor:ktor-jackson:$ktor_version") {
    exclude group: 'com.fasterxml.jackson.module', module: 'jackson-module-kotlin'
  }
  compile project(":contracts")
  compile project(":workflows")
}

Здесь нужно выделить несколько вещей. Во-первых, свойство kotlin_version_for_app . Ktor требует Kotlin 1.3 (поскольку он использует сопрограммы), но на момент написания Corda поддерживает только Kotlin 1.2. Поэтому для кода веб-сервера и узла Corda необходимо использовать разные версии. Во-вторых, исключен jackson-module-kotlin как он вызывает ошибку времени выполнения из-за несоответствия версий.

Высокий уровень взгляда на код

Ниже приведен небольшой фрагмент кода, показывающий начальную точку реализации веб-сервера:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
fun main() {
  embeddedServer(
    Netty,
    port = System.getProperty("server.port").toInt(),
    module = Application::module
  ).start().addShutdownHook()
}
 
fun Application.module() {
  val connection: CordaRPCConnection = connectToNode()
  install(CallLogging) { level = Level.INFO }
  install(ContentNegotiation) { cordaJackson(connection.proxy) }
  routing { messages(connection.proxy) }
  addShutdownEvent(connection)
}

Этот код связывает все вместе, так как все функциональные возможности сервера разветвляются на функции выше.

Содержание module будет рассмотрено в следующих разделах.

Подключение к узлу

Могу поспорить, у вас может быть хорошая идея, что делает connectToNode . Я надеюсь, что вы все равно делаете … Ниже приведено содержимое connectToNode :

01
02
03
04
05
06
07
08
09
10
fun connectToNode(
  host: String = System.getProperty("config.rpc.host"),
  rpcPort: Int = System.getProperty("config.rpc.port").toInt(),
  username: String = System.getProperty("config.rpc.username"),
  password: String = System.getProperty("config.rpc.password")
): CordaRPCConnection {
  val rpcAddress = NetworkHostAndPort(host, rpcPort)
  val rpcClient = CordaRPCClient(rpcAddress)
  return rpcClient.start(username, password)
}

Если вы видели какой-либо из примеров Corda, то вы, вероятно, знакомы с этим фрагментом кода. Короче говоря, он подключается к узлу с заданными деталями подключения. Я решил сгенерировать параметры функции по умолчанию из системных свойств приложения. Эта реализация не особенно важна, она просто подключается к узлу и может быть написана несколькими различными способами.

CordaRPCConnection возвращается из функции. Первоначально я хотел вернуть CordaRPCOps как само соединение не делает слишком много. Но, не возвращая соединение, нет способа изящно отключиться от узла. Другими словами, должен быть способ вызвать notifyServerAndClose когда сервер останавливается. Это рассматривается далее в посте.

Настройка Джексона

Чтобы правильно использовать Джексона с Кордой, необходимо выполнить некоторые дополнительные настройки:

01
02
03
04
05
06
07
08
09
10
11
fun ContentNegotiation.Configuration.cordaJackson(proxy: CordaRPCOps) {
  val mapper: ObjectMapper = JacksonSupport.createDefaultMapper(proxy)
  mapper.apply {
    setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
      indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
      indentObjectsWith(DefaultIndenter("  ", "\n"))
    })
  }
  val converter = JacksonConverter(mapper)
  register(ContentType.Application.Json, converter)
}

Corda ObjectMapper инициализируется с помощью createDefaultMapper , что позволяет сериализовать или десериализовать классы, такие как Party или X509Certificate . Это может быть важно в зависимости от того, что возвращается из вашего собственного API.

Остальная часть кода украдена из модуля ktor-jackson . Это немного изменяет вывод JSON, чтобы быть более желательным.

Создание конечных точек

HTTP-запросы направляются на эти конечные точки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
fun Routing.messages(proxy: CordaRPCOps) {
  route("/messages") {
    get("/") {
      call.respond(
        HttpStatusCode.OK,
        proxy.vaultQueryBy<MessageState>().states.map { it.state.data })
    }
    post("/") {
      val received = call.receive<Message>()
      try {
        val message = proxy.startFlow(
          ::SendMessageFlow,
          state(proxy, received, UUID.randomUUID())
        ).returnValue.getOrThrow().coreTransaction.outputStates.first() as MessageState
        call.respond(HttpStatusCode.Created, message)
      } catch (e: Exception) {
        call.respond(HttpStatusCode.InternalServerError, e.message ?: "Something went wrong")
      }
    }
  }
}

С точки зрения логики, здесь мало что происходит. Для подробного объяснения этого кода я рекомендую прочитать Ktor — веб-фреймворк Kotlin, как я упоминал ранее.

Изящно отключаясь от узла

Чтобы корректно отключиться от узла, веб-сервер должен вызвать CordaRPCConnection.notifyServerAndClose . Реализация этого потребовала немного работы, которую я не ожидал. Ниже приведен код, который запускает notifyServerAndClose :

01
02
03
04
05
06
07
08
09
10
11
12
fun NettyApplicationEngine.addShutdownHook() {
  Runtime.getRuntime().addShutdownHook(Thread {
    stop(1, 1, TimeUnit.SECONDS)
  })
  Thread.currentThread().join()
}
 
fun Application.addShutdownEvent(connection: CordaRPCConnection) {
  environment.monitor.subscribe(ApplicationStopped) {
    connection.notifyServerAndClose()
  }
}

Хук отключения добавлен на сервер. Как объясняется в «Изящном закрытии приложений Ktor» , подписка на событие ApplicationStopped не достаточна для выполнения кода при завершении работы приложения. NettyApplicationEngine shutdown вызывает stop чтобы изящно закрыть NettyApplicationEngine , на котором работает сервер. Приводит к корректному запуску и выполнению события отключения.

Это все, что есть

Да, действительно, это все. Реализация супер базового веб-сервера совсем не требует большого количества кода. На самом деле больше нечего писать. Я показал вам, что есть еще одна веб-платформа, которую можно использовать для подключения к узлу Corda. Вам не нужно использовать Spring по умолчанию только потому, что их используют образцы Corda. Если вы предпочитаете Ktor, используйте Ktor. Если нет, не надо. Если вам понравился внешний вид Ktor, а если нет, я рекомендую взглянуть на Ktor — фреймворк Kotlin .

Из этого поста было исключено много кода, поскольку я сосредоточился на более важных аспектах реализации. Если вы заинтересованы в остальной части кода, вы можете найти его на моем GitHub .

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Подключение веб-сервера Ktor к узлу Corda

Мнения, высказанные участниками Java Code Geeks, являются их собственными.