LINUX.ORG.RU

Реализация API на вебсокетах

 ,


0

2

Для работы с API я предлагаю использовать JSON RPC поверх вебсокетов. Зачем так делать? - Это экономия ресурсов сервера, причем нехилая. Как работает обычное веб-приложение через REST API? - Оно с помощью JavaScript, используя функцию fetch либо инстанс XMLHttpRequest, посылает запросы. На каждый запрос создается новое соединение и после получения данных, оно закрывается. Для клиента 2/3 времени от выполнения запроса уходит на установку соединения с сервером. Сервер же для каждого соединения порождает процесс, подпроцесс либо тред в зависимости от программной реализации. Таким образом выигрывают клиент и сервер. Для ускорения работы можно использовать балансировку через nginx: `создать количество_процессоров * 2 + 1` воркеров и распределять запросы между ними.

Еще недостаток REST (RESTful) - это то что данные предлагается получать методом GET, а он неможет иметь тела, поэтому приходится параметры из QueryString цеплять, приводить к нужным типам (некоторые драйверы баз данных типа того же asyncpg не приводят автоматически строки к нужным типам).

Я сейчас занимаюсь только тем что штампую SPA. Так вот у меня все равно уведомления через веб сокеты работают. Зачем мне сранный REST?

Наброски, сделанные за пару часов:

Исходники

Вообщем, хочу услышать мнения.

★★

Понял ли я что ты несешь ? Врятли. Имеет лои это отношение к вебсокетам ? Как-то не очень. Пытался ли ты искать альтернативы ресту раз он тебе не подходит ? Судя по тому что graphql ты не упомянул - не искал. Описал ли ты необходимость использовать RESTful, когда данные можно и POST-запросами получать ? Нет, не описал

Dred ★★★★★
()
Последнее исправление: Dred (всего исправлений: 2)
Ответ на: комментарий от Dred

Про GET имелось в виду под получением данных READ из CRUD (C - POST, R - GET, U - PUT, D - DELETE). В REST у нас две разнцые операции чтения для извлечения одного объекта /user/:id и множества /users?order_by=xxx&offset=xxx&limit=xxx (<-- вот тут приходится параметры из QueryString к типам приводить и значения по-умолчанию навешивать). Знаю я про graphql мне он не нравится. Недостаток при работе через http с api в том, что БРАУЗЕР ВСЕГДА ПРИ АЯКС ЗАПРОСЕ СОЗДАЕТ НОВОЕ СОЕДИНЕНИЕ И ЗАКРЫВАЕТ ЕГО ПОСЛЕ ПОЛУЧЕНИЯ ОТВЕТА. При использовании вебсокетов у тебям всегда одно соединение. Есть еще реализации RESTFull через вебсокеты.

tz4678 ★★
() автор топика

Для клиента 2/3 времени от выполнения запроса уходит на установку соединения с сервером

«Connection: keep-alive» решает эту проблему, причем очень давно.

Сервер же для каждого соединения порождает процесс, подпроцесс либо тред в зависимости от программной реализации

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

можно использовать балансировку через nginx

Можно просто использовать nginx.

недостаток REST (RESTful) - это то что данные предлагается получать методом GET

Недостаток человека, что он прямоходящ. Вот бы на четвереньках бегал — удобнее же.

некоторые драйверы баз данных типа того же asyncpg не приводят автоматически строки к нужным типам

Откуда ей знать какой тип тебе нужен в этом контексте твоей бизнес-логики?

Я сейчас занимаюсь только тем что штампую SPA

А, так ты решил хлебнуть модных смузей. А про недостатки реста тебе смузихлебы напели?

у меня все равно уведомления через веб сокеты работают. Зачем мне сранный REST?

Действительно, зачем?

deep-purple ★★★★★
()

Сервер же для каждого соединения порождает процесс, подпроцесс либо тред в зависимости от программной реализации

Не верно. Ещё есть стильный-модный-молодёжный асинхронный подход. Та же нода использует его.

KivApple ★★★★★
()
Ответ на: комментарий от tz4678

/user/:id и множества /users?order_by=xxx&offset=xxx&limit=xxx

Обернись в единообразный вид запроса:

sendGetRequest('/api?r=' + encodeURIComponent(JSON.stringify(requestData)));
Это не совет. Отвечаю таким примером только потому, что тебе тупо не нравится как выглядит строка запроса. А еще мой пример уже очень похож на SOAP, только в xml можно нормально провалидировать присланные данные (xsd), а в JSON ты охереешь проверять все виды запросов.

deep-purple ★★★★★
()

Я тоже приходил к той же мысли, что и автор, но есть нюанс, постоянное соединение может рваться, его надо постоянно проверять. Обычный http запрос надежнее, да и нет таких задач, чтоб требовалась высочайшая эффективность работы с апи

С другой стороны работа с апи через вебсокеты должна во много раз повысить отзывчивость интерфейса. А если скрестить вебсокеты с graphql, хто будет ядреная вещь, особенно в сочетании с vue или react

ism ★★★
()
Ответ на: комментарий от deep-purple

JSON ты охереешь проверять все виды запросов.

Есть валидаторы для приходящих json, вплоть до преобразования в нужные типы данных, например такое есть в http://www.django-rest-framework.org/api-guide/serializers/#deserializing-obj...

ism ★★★
()
Последнее исправление: ism (всего исправлений: 2)
Ответ на: комментарий от ism

Это всё — костыли безысходности. В xsd же описываются любые мыслимые (вплоть до порядка следования полей) вариации типов данных, в том числе и кастомные. И всё это описано в спецификации. И парсеры-валидаторы обязаны уметь и умеют в эти спеки.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

В xsd же описываются любые мыслимые

да, в плане валидации у xml и soap очень мощный тулчейн, но у xsd куча недостатков, там очень медленно развивается стандарт и у того что используется куча минусов. Например там действительно можно задать порядок, но куда сложнее задать отсуствие порядка. Если мне без разницы как расположены элементы - это очень проблемно указывать.

Dred ★★★★★
()
Ответ на: комментарий от Dred

у xsd куча недостатков, там очень медленно развивается стандарт и у того что используется куча минусов

Ты как-то написал, что выглядит как «из пустого в порожнее». Но я тебя распарсил. Основная проблема не в том что оно медленно развивается, а в том, что все валидаторы полностью умеют только в первую версию xsd. И вот она, да, немного кривовата. Реализовать по спекам более новую, почему-то никто пока не спешит.

куда сложнее задать отсуствие порядка

Пример приведёшь?

deep-purple ★★★★★
()
Ответ на: комментарий от tz4678

Я тебя вообще не понимаю. Не хочешь много запросов - общайся веб сокетами, правда соединение приходится держать даже когда оно не надо. Такое используют когда сервер шлет данные к клиенту без явного события со стороны клиента (напрмиер нотификации). При чем тут RESTful ? Твои сообщения выглядят как «мне не нравится ходить пешком, предлагаю всем летать на самолетах»

Но вообще тут правильно привели SOAP, это вполне себе стандарт(один из) построения энтерпрайзных API, благо wsdl позволяет описывать доступные методы клиенту.

Dred ★★★★★
()
Ответ на: комментарий от deep-purple

Пример приведёшь?

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

все валидаторы полностью умеют только в первую версию xsd

да, ты прав, я про это.

Dred ★★★★★
()
Ответ на: комментарий от Dred

Хак организации игнора порядка следования в следующем:

<xsd:complexType name="itemsSequenceType"><!-- тип, содержащий список элементов -->
    <xsd:sequence>
        <xsd:element name="item"<!-- тег элемента списка -->
                        type="itemType"<!-- тип элемента списка -->
                        minOccurs="0"<!-- минимум ни одного -->
                        maxOccurs="unbounded"/><!-- максимум сколько угодно -->
    </xsd:sequence>
</xsd:complexType>
Если каждый элемент списка должен иметь разную структуру, то эти типы описываются отдельно и вкладываются в itemType, который принимает следующий вид:
<xsd:complexType name="itemType">
    <xsd:choice>
        <xsd:element name="A"
                        type="itemAType"
                        minOccurs="1"
                        maxOccurs="1"/>
        <xsd:element name="B"
                        type="itemBType"
                        minOccurs="1"
                        maxOccurs="1"/>
        <xsd:element name="C"
                        type="itemCType"
                        minOccurs="1"
                        maxOccurs="1"/>
    </xsd:choice>
</xsd:complexType>
Таким образом itemsSequenceType заведует беспорядком следования, а itemType возможным содержимым. В этом же заключается и ущербность первой версии.

deep-purple ★★★★★
()
Ответ на: комментарий от ism

работа с апи через вебсокеты должна во много раз повысить отзывчивость интерфейса

Неа, пользователь даже не заметит разницы.

ritsufag ★★★★★
()
Ответ на: комментарий от deep-purple

разве тогда не получается что я не могу толком определить какой тип будет у элементов внутри ? В целом там схема на пару тысяч строк,так перелопачивать ее не было времени

Dred ★★★★★
()
Ответ на: комментарий от Dred

не могу толком определить какой тип будет у элементов внутри

Можешь — через прослойку из дополнительного элемента:

<itemsList>
    <item>
        <C>...</C>
    </item>
    <item>
        <A>...</A>
    </item>
    <item>
        <B>...</B>
    </item>
</itemsList>

deep-purple ★★★★★
()
Ответ на: комментарий от Dred

И я чётко написал выше: xsd:choice и minOccurs=«1» maxOccurs=«1», что означает, что внутри будет только один из разрешенных, и только один, не ни одного.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

Когда надо сильно экономить трафик и разгрузить сервер, альтернатив vue или react както не видно

ism ★★★
()
Ответ на: комментарий от ism

А трафик на скачку вуя и реакта со всем их говном ты посчитал? А пользователей со старыми или медленными девайсами ты учёл?

Помни — ты сейчас говоришь с человеком, который в своё время в одиночку за пол года врукопашную на ванильном жс запилил систему «сайт в сайте», подключаемую на любой сайт не сложнее чем счетчик, которая умела в ИЕ6, языки и кодировки. Я эти ваши SPA еще тогда изобрёл — и оно летало, чего не скажешь о современных модных поделках. Нет, не покажу — NDA. Нет, в паблик не вышло — осталось уделом внутреннего пользования.

Трафик они экономят, ага...

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

А трафик на скачку вуя и реакта со всем их говном ты посчитал?

Даже для gprs сейчас это не проблема + gzip http, вообще на js + jquery трафика может быть не меньше. Это вопрос качества кода

А пользователей со старыми или медленными девайсами ты учёл?

Никогда еще не видел проекты, где была бы проблема быстродействия js. Каким монстром должно быть приложение, чтоб движок js его не переварил ?

ism ★★★
()

Бери kore, заводи кучу воркеров в памяти, хоть на post запросы keep-alive хоть на websockets для потоковых данных например, в фоне вешай кучу task обработчиков запросов всегда ждущих данных и всё. Не справляется сервер, заводишь ещё два на входящем nginx балансировщик, позади него 2 proxy-pass сервака на kore, блокировка данных и работы если надо через kore_msg + curl между двумя инстансами kore на физических серваках. И юморт в том что тут nginx уже будет узким местом, тогда роутинг до обрабатывающего сервера можно перенести в клиент, если у тебя для баланса от 2 до 8 серваков то можно не парится и балансировку перенести в клиентский js для пользователя никакой нагрузки и всё прозрачно, для тебя никаких прослоек. thread/

Deleted
()

некоторые драйверы баз данных типа того же asyncpg не приводят автоматически строки к нужным типам

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

Deleted
()
Ответ на: комментарий от deep-purple

я и написал что треды или сопроцессы создаются. ты написал нет, потом да. балансировка нужна и точка (если приложение не на 3,5 анонимуса). там и так nginx используется. другие драйвера спокойно строки проглатывают (как в пхпшных либах). причем тут смузи? это современный стандарт, где у тебя всего одна страница index.html и апишка к которой ты обращаешься. посмотри тот же вконтакте: он тебе не генерирует каждый раз страницу, делая 100500 запросов, а только раз в дальнейшем ты через апишку все данные получаешь, а url, который в браузере видишь - это хак с помощью хистори апи. вконтакте - вот тебе пример spa или gmail. с десяток лет технологии че. какая мода?

tz4678 ★★
() автор топика
Ответ на: комментарий от ritsufag

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

tz4678 ★★
() автор топика
Ответ на: комментарий от tz4678

Для этого не нужно SPA. Просто делаешь подгрузку новых постов на вебсокетах.
А для пользователя незаметны эти выигранные ms при коммуникации с сервером.

ritsufag ★★★★★
()

будет тормозить, лагать, дропать 50% пакетов, прекращать сессиб без возможности восстановления(пока страницу в браузере не перезагрузишь)

ничего сложнее ГЕТ запроса лучше не использовать, пока не появятся 100-ядрных процессоров чтоб браузеры перестали тормозить

missxu
()
Ответ на: комментарий от tz4678

Ну и сколько пользователей одновременно онлайн выдержит твой сервер, чтобы на каждого было по веб-сокет постоянному коннекту?

anonymous
()
Ответ на: комментарий от Deleted

И дураку понятно, что все вводимые данные проверять надо. Я валидацию с помощью marshmallow делаю:

# при:
#   class Meta:
#     strict = True
# генерируются исключения
data = UserSchema().load(payload).data
user = User(**data)
session.add(user)
session.commit()

А с фильтрами там QuerySet строить с кучей условий. Ниразу не удобно.

Про kore я не понял. Балансировку и так использовал. Напишу еще раз:

# вызвал ты через js это
fetch('/handler', { method: 'POST', body: formData })
# что делает браузе?
* открываю соединение *
* пишу данные в сокет *
* читаю ответ *
* закрываю соединение *
# при соединение через вебсокеты
* открываю соединение *
while (true) {
  * ожидаю данные от сервера и вызываю обработчик * 
  * если соединение разрывается выхожу из цикла * 
}
while (true) {
  * ожидаю вызова send и отправляю на сервер *
  * если соединение разрывается выхожу из цикла *   
}
причем две этих конструкции параллельно работают в разных тредах

У ТЕБЯ ОДНО СОЕДИНЕНИЕ ВСЕГДА ОДНО И ДАЖЕ КОГДА ОНО НЕ НУЖНО (А ТАКОГО БЫТЬ НЕ МОЖЕТ (ЭТО К РЕПЛИКЕ КАКОГО-ТО ОРАТОРА ВЫШЕ))

tz4678 ★★
() автор топика
Ответ на: комментарий от anonymous

сотни тысяч соединений через веб-сокеты сервера тянут

tz4678 ★★
() автор топика
Ответ на: комментарий от tz4678

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

Deleted
()

Портянка о том, как REST работал 10 лет назад

vertexua ★★★★★
()

Для клиента 2/3 времени от выполнения запроса уходит на установку соединения с сервером

Ты каждое движение мышью посылаешь на сервер для обработки?

no-such-file ★★★★★
()

давно уже есть фреймворки которые только скачивают скрипт, а все остальное загружается через ws

Int0l ★★
()
Ответ на: комментарий от ism

А с graphql можно подписаться на какое-то событие и тебе graphql сервер будет их автоматом передавать на все клиенты которые на него подписаны?

Int0l ★★
()
Ответ на: комментарий от ism

Даже для gprs

Т.е. ты перекладываешь свои собственные косяки на ограничения протокола-провайдера?

deep-purple ★★★★★
()
Ответ на: комментарий от tz4678

я и написал что да. ты написал нет, потом да.

Чот ты юлишь. Эммм, ты до сих пор не понимаешь о чём пишешь? А, почитал дальше — да ты упорот, ну, в смысле, не понимаешь как оно работает. Теперь я понял как вас вербуют. Давай попробуем так — ты задаешь ОДИН вопрос, а я отвечаю.

deep-purple ★★★★★
()
Ответ на: комментарий от tz4678

У ТЕБЯ ОДНО СОЕДИНЕНИЕ ВСЕГДА ОДНО И ДАЖЕ КОГДА ОНО НЕ НУЖНО

Ты дебил? Вебсокет соединение тоже одно, и тоже всегда, даже когда оно не нужно.

З.Ы.: Нет, я больше не имею желания отвечать на твои вопросы - ты не исправим — ты закрыл забрало, сорян.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 3)
Ответ на: комментарий от tz4678

Читаю подробнее твои ответы, не могу смолчать:

в разных тредах

Ахахаха... На клиенте в разных тредах? Ахахаха... (ладно, хром, процесс на каждую вкладку, хер с ним, это еще надо узнать даст ли он тебе отдельный тред для твоего жопаскрипта, скорее нет).

Балансировку и так использовал

Балансировку на клиенте? Да ты жесть упоротый.

Не, я понимаю, я пьян, но ты... Всё, я ушел.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

ебобо я написал что у тебя одно соединение при использование вебсокетов. это так разъяснение для жопоголовых. дохуища соединений создает браузер, отправляя запрсы к апишке, которая работет поверх хттп.

tz4678 ★★
() автор топика
Ответ на: комментарий от deep-purple

да ты не пьян. ты наркоман. с тредами это пример реализации клиента/сервера. под клиентом понимается браузер (программа такая). треды браузер создает для обработки запросов. забудь про яваскрипт не о нем, сука, речь. вот описание того как работает процесс хрома (отдельная вкладка) https://chromium.googlesource.com/chromium/src/ /lkgr/docs/threading_and_task...

tz4678 ★★
() автор топика
Ответ на: комментарий от Int0l

если через вебсокеты с graphql работать, то нужно будет для каждого запроса генерировать айди, чтобы потом по этому айди ответ отловить. смотри как у меня в коде. с await/async вообще все выглядит как обычный синхронный код.

tz4678 ★★
() автор топика
Ответ на: комментарий от tz4678

я написал что у тебя одно соединение при использование вебсокетов

А я тебе(?) написал что поверх хттп изза кип алайф тоже одно.

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

Нет, я пьян. Но это не отменяет того что ты наслушался говна от смузихлёбов. Иди потести — браузер для жопаскрипта на клиенте старается разделить нагрузку среди всех «асинхронных» задач поровну, о чем по твоей ссылке и сказано, никаких тредов там динамически не создается. Вообще, асинхронность на клиенте, считай, эмулируется. Почему? А потому: что ты мне про хром ссылки суёшь? Не одним хромым едины. Или ты из тех, у кого приложения только в хроме пашут? Видел я и таких.

забудь про яваскрипт

Щас, разбежался — он же инициатор задачи, ему и результат возвращают.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

я тебе пример скину, чтобы ты понял какую хуйню несешь. Создадим тестовое серверное приложение:

from aiohttp import web

app = web.Application()

def handler(request):
  peername = request.transport.get_extra_info('peername')
  print(peername)
  return web.Response(text='ok')

app.router.add_routes([web.route('*', '/{path:.*}', handler)])

web.run_app(app)

Запустим его и откроем в браузере http://127.0.0.1:8080. На странице откроем консоль Shift_Ctrl+I и выполним следующее:

> for (let i = 0; i < 10; ++i) fetch('/foo')
< Promise {<pending>}

А теперь посмотрим консоль приложения:

======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)
('127.0.0.1', 35976)
('127.0.0.1', 35976)
('127.0.0.1', 35980)
('127.0.0.1', 36016)
('127.0.0.1', 36020)
('127.0.0.1', 36024)
('127.0.0.1', 35976)
('127.0.0.1', 36028)
('127.0.0.1', 35976)
('127.0.0.1', 36024)
('127.0.0.1', 36020)

У нас десять соединений.

tz4678 ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.