В то время как основной процесс узла Electronic предоставляет полную стандартную библиотеку, а пакеты npm доступны для реализации всех видов функций, которые может понадобиться вашему настольному приложению, иногда вам нужно будет вызвать отдельную командную строку, чтобы получить желаемое поведение. Возможно, у вас уже есть значительный объем кода на другом языке, который вы хотите использовать повторно, или вам нужно вызвать уже существующий инструмент, например git.

Есть разные способы вызвать код на других языках. Случай с git (и ему подобные) довольно прост: просто вызовите командную строку с соответствующими аргументами, затем проанализируйте вывод (идеально фарфоровый), чтобы получить нужную информацию.

Другой вариант - разместить веб-сервер в вызываемом процессе. Практически любой язык знает, как разговаривать по HTTP, так что это может быть хорошим языково-независимым решением. Однако вам может показаться, что размещение полного веб-сервера для некоторых быстрых IPC является излишним, а размещение открытой конечной точки на локальном компьютере создает некоторые дыры в безопасности, которые необходимо закрыть.

В этой статье мы поговорим о другом варианте: использование JSON-RPC вместо стандартного ввода-вывода. Это похоже на хорошее решение: JSON-RPC - это минимальная спецификация для выполнения вызовов API через какую-то границу, а использование stdin / stdout для связи вместо HTTP сравнительно легкое, но при этом полностью кроссплатформенное.

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

В этом примере мы будем использовать пару библиотек, созданных для VS Code (а точнее, реализацию Language Server Protocol). Это vscode-jsonprc для конца узла и StreamJsonRpc для конца точки сети соответственно.

Эти две библиотеки по умолчанию спроектированы так, чтобы быть совместимыми друг с другом, что очень помогает. Одна из проблем с JSON-RPC заключается в том, что в спецификации фактически не указано, как объекты JSON передаются по сети. Базовая реализация JSON-RPC может просто выбрать кодировку, а затем последовательно связать серию объектов JSON. Хотя это было бы совершенно верно, большинство существующих парсеров JSON предназначены для работы с одной строкой, содержащей объект JSON, вместо того, чтобы сканировать текст в поисках начала и конца каждого объекта. Для многих транспортов (таких как HTTP или веб-сокеты) это не проблема, поскольку сам транспорт уже разбивает поток на сообщения - однако в stdio нет такой концепции.

Результатом вышеизложенного является то, что StreamJsonRpc и vscode-jsonrpc будут ожидать (по умолчанию), что каждое сообщение JSON будет разделено заголовками, подобными HTTP, за которыми следует пустая строка и фактическая строка JSON. Эти заголовки могут содержать множество различных параметров, но наиболее важным является значение Content-Length, которое сообщает библиотеке, сколько текста следует прочитать перед передачей его в синтаксический анализатор JSON. Для наших целей это работает просто незаметно, но важно помнить об этом при использовании различных библиотек или языков, которые также реализуют ту или иную форму JSON-RPC.

После того, как у нас установлены нужные пакеты, приступить к работе будет довольно просто, несмотря на скудную документацию:

Есть несколько альтернативных способов настройки RPC-сервера - например, мы могли бы определить целевой объект, а затем заставить библиотеку автоматически обнаруживать доступные методы посредством отражения - но использование AddLocalRpcMethod кажется наиболее простым вариантом и позволяет нам контролировать имена каналы API намного проще.

Код для настройки клиента также довольно прост:

У нас были проблемы с правильным завершением командной строки сервера. В идеале мы могли бы сделать что-то вроде rpc.Dispose() в обработчике shutdown, который должен разблокировать строку await rpc.Completion и позволить клиенту корректно завершить работу. Однако мы обнаружили, что вызов rpc.Dispose означал, что ответное сообщение shutdown никогда не отправлялось, что сбивало с толку клиентскую часть. В конце концов, мы остановились на ожидании завершения метода выключения, а затем в уничтожении дочернего процесса.

Для этого кода требуется дополнительная обработка ошибок, чтобы он был должным образом надежным, но наличие дочернего процесса упрощает довольно много вещей - нам в основном нужно только убить дочерний процесс, если он кажется зависшим, и перезапустить дочерний процесс, если он неожиданно умирает. В этот момент мы должны иметь возможность обрабатывать ошибки, исходящие от vscode-jsonrpc, и повторять, игнорировать, отменять или откатывать, если необходимо с новым процессом.

В целом это еще только начало, но JSON-RPC, похоже, пока работает для нас хорошо. Несомненно, ценится простота протокола и его реализации, и он работает полностью кросс-платформенный.