Перейти к содержимому
HeroBrine1st

Реализация WebSocket

Рекомендуемые сообщения

 

Библиотечка является аналогом уже существующей библиотеки, но имеет нативную (если можно так выразиться) поддержку EEPROM и TLS с помощью dependency injection (банально передать методы для коннекта и остального надо). Если я всё правильно читал, то эта библиотека может быть даже эталонной реализацией, насколько это вообще возможно на OpenComputers.

Внимание: библиотека слабо протестирована. Я написал её для собственных целей и выкладываю в надежде, что она может пригодиться кому-либо ещё, но без каких-либо гарантий на её работоспособность или пригодность к каким-то конкретным условиям.

 

Реализовано:

  • Пинг-понг
  • Полная обработка WebSocket Closing Handshake (за исключением того, что из-за невозможности получения TCP/FIN библиотека в нарушение RFC закрывает соединение сама после завершения рукопожатия)
  • Поддержка как бинарных, так и текстовых фреймов в обе стороны (используется один и тот же код, поскольку в луа string является одновременно и ByteArray, ну или как-то так)
  • Поддержка фрагментированных пакетов в обе стороны (не протестировано)
  • Куча проверок входящих данных, из-за которых в том числе исходник разбух до 13 килобайт
  • Поддержка payload размером до 2^63 - 1 байт (очевидно, в ОЗУ не поместится, а обработки во время получения нет)
  • Работа как с картой данных, так и без неё (код без карты не прилагается, нужно передать функцию base64.encode аргументом)
  • Поддержка TLS (опять же, аргумент с функцией подключения)
    • Из-за того, что в библиотеке фингера метод read не принимает количество байт для чтения, она может не подходить к данной библиотеке, поскольку она не имеет собственного буфера и читает ровно столько данных, сколько ей нужно
  • Документация и типовые аннотации EmmyLua

Библиотека живёт на гисте, хотя, может быть, куда-нибудь переедет. Использовать её очень просто:

local component = require("component")
local websocket = require("websocket")

local socket = websocket.connect(component.internet.connect, component.data.encode64, "websocket.endpoint.com", 80, "/some/path", {
	protocols = {"optionalSubprotocol", "anotherSubprotocol"},
	headers = {authentication = "Bearer cA9KDPgVQU2IHK7SYEvq5K3O2IUzRYJPVf6mLdWmMTKXJ2FG6VynLa3rfcHBXbHv"}
})

while socket:update() do
	local message = socket:getMessage() -- Если передать сюда true, то функция будет отдавать ещё и незавершённые сообщения
	if message then -- Пришло сообщение
		local payload = message.payload
		local mType = message.type -- binary или text
		local fin = message.fin -- true, если сообщение завершено и все фреймы приняты
		if mType == "text" then 
			print(payload) 
			socket:sendText("Echo " .. payload)
		else socket:startClosingHandshake(1003, "Binary payload is not supported by this endpoint") end -- Закрываем соединение при получении бинарного сообщения
	end
end

Сервер для проверки не прилагается.

 

Функции библиотеки:

  • connect(connect_function: function, base64_encode_function: function, host: string, port: number, path: string, [options: table]) - подключиться к серверу по паре `host:port` к пути `path` с помощью сокета, который создаст функция connect_function. Options является таблицей дополнительных параметров. Заголовки устанавливаются как пары ключ-значение опции headers (см. пример), а параметр protocols в опциях указывает значение заголовка `Sec-WebSocket-Protocol`, который может быть полезен (RFC) в некоторых случаях. Возвращает объект WebSocket. Выкидывает ошибку при любом удобном случае (может быть переделаю на всякие nil и "Server has refused connection")

Методы WebSocket:

  • update() - вызывает чтение низлежащего сокета, отвечает на пинги, обрабатывает закрытие соединения и т.д.. Возвращает true, если соединение ещё живое (даже если вы закрыли соединение! там целое рукопожатие для этого) и false, если нет. Достаточно его вызывать с таким же периодом, с которым к вам приходят пинги. Может выкинуть ошибку, если произойдёт ошибка при чтении.
  • startClosingHandshake([closeCode: number, [closeReason: string]]) - запускает WebSocket Closing Handshake
  • sendText(payload: string, [fin: boolean]), sendBinary(payload: string, [fin: boolean]), sendFrame(initialOpcode, payload, [fin: boolean]) - отправляет, соответственно, текстовый, бинарный и произвольный фрейм с возможностью фрагментации (fin = false). Если вы указали fin = false, то вы должны в будущем отправить следующий фрагмент, при этом вы не можете отправлять другие сообщения. Никаких проверок там нет, отправляйте фреймы в закрытое соединение на свой страх и риск.
  • isClosed() - true, если соединение закрыто
  • isClosing() - true, если закрывающее рукопожатие активно
  • getCloseCode(), getCloseReason() - возвращают код и причину закрытия соответственно
  • getSocketId() - возвращает id низлежащего сокета для использования с ивентом internet_ready (лично у меня не завелось)
  • getMessage([canBeFragmented: boolean]) - возвращает первое полученное сообщение из "непрочитанных". Если сообщение завершено, то удаляет его из внутреннего буфера. Если canBeFragmented=true, то может вернуть копию незавершённого сообщения, но не удалит его из буфера.

Библиотека после минификации почти влезает в EEPROM, а после сжатия влезает в тысячу с чем-то байт. Код ниже вполне себе влезает в еепром (где-то 2 КБ), но в нём не поддерживаются (выкидывают ошибку) субпротоколы (поле protocols в опциях), поскольку баг я заметил слишком поздно, а минифицированная версия у меня уже была. Минифицировать ещё раз мне лень, поскольку нужно за минификатором поставить скобки, которые он убирает, так что как-нибудь в следующий раз.

local data = component.proxy(component.list("data")())
local internet = component.proxy(component.list("internet")())

local websocket = load(data.inflate(data.decode64("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")))()

Тестировал вручную без TLS с сервером KTor на движке CIO.

Изменено пользователем HeroBrine1st
Обновление

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Для какой версии мода OC библиотека? на 1.6.2 работать будет? 

Изменено пользователем num_pi

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
3 минуты назад, num_pi сказал:

Для какой версии мода OC библиотека? на 1.6.2 работать будет? 

По идее для любой, на которой есть луа 5.3. Лично я тестировал на 1.7.10 и мод был версии 1.7.5

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Добавил поддержку заголовков и сделал пример загрузки библиотеки на EEPROM.

Библиотека всё так же слабо протестирована, хоть уже и активно используется.. Буду исправлять разные баги по мере обнаружения.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...