Re[16]: REST: прохой\хороший интерфейс
От: Sharov Россия  
Дата: 13.02.20 14:52
Оценка: +1
Здравствуйте, Sinclair, Вы писали:

S>Может быть, это и хорошо? Системы проектируют под реальные требования, а не под абстрактные капризы.

S>Если в вашей задаче "параметром" ресурса становится слишком длинная строка — да, придётся приседать. Но это бывает а) редко, б) обходится нетрудно.

Трабования имеют св-во менятся, зачастаю неожиданным и непредсказуемым образом.

S>Вы неправильно понимаете rest. Этот ресурс, конечно же, есть, и мы ничего не "ломаем".

S>REST совершенно не обязывает вас ограничиваться только "реально существующими" ресурсами, или даже конечным их списком.
S>Вот, к примеру, таблица умножения — совершенно необязательно хранить её на сервере. Можно её вычислять — но клиенту-то всё равно!
S>У него создаётся иллюзия того, что на сервере "хранится" практически бесконечная таблица умножения. Зачем ему отличать "вычисленные" ресурсы от "реальных"?

В целом, Вы правы, так думать можно. Но, думается, когда Филдинг все это дизайнил речь шла о конретных, конечных ресурсах, типа стат. html страничек. Т.е. что-то сущ. и конечное.
Все можно положить на rest, но не всегда нужно.

S>Можно ссылку на эти рекомендации? Может, я что-то упустил.


Помню где-то читал, и недавно, но найти не могу.
Кодом людям нужно помогать!
Re[20]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 13.02.20 16:33
Оценка:
Здравствуйте, Ватакуси, Вы писали:
В>Телепат в студии? Откуда известно что и кому неизвестно?
Потому что DDOS — это когда у нас есть "нормальные" клиенты, которые пытаются получить сервис, и есть распределённая сеть ботов, которые посылают запросы не для того, чтобы они были выполнены, а для того, чтобы не дать "нормальным" клиентам получить сервис.
Один клиент DDOS обеспечить не в состоянии, по определению DDOS.
Множество "нормальных" клиентов DDOS-атаку тоже не проводят; то, что часть запросов отпадает, означает просто недостаточность серверных мощностей.

S>>1. Со стороны сервера, вам в любом случае нужно думать о throttling. Потому что нет никакой гарантии, что клиенты ведут себя прилично, и не захотят вас просто завалить.

В>Во-первых, это явно не проблема разработчика, скажем честно.
Не очень понимаю это утверждение. Кто должен решать эту проблему, по-вашему?
В>Во-вторых, зачем создавать самому себе (и коллегам) проблему на ровном месте? Если пришёл 5xx — это почти всегда означает, что сервер не справился и почти всегда означает, что повторная попытка приведёт к тому же самому результату. Иключений так мало, что можно пренебречь и обрабатывать отдельно.

S>>2. Со стороны клиента, достаточно брать паузу при ошибках. В реальной среде вы вряд ли получите DOS из-за проблем обратного канала: характерное время, за которое вы получаете таймаут — около 5 секунд (это оптимистично). Чаще делают таймауты в 30 секунд. "Долбя" сервер запросами раз в полминуты, вы никакого DOS не устроите.

В>Вообще не нужно, нужно сказать — ошибка, звоните в поддержку если чё и все дела. Ибо у вас падает сервер.

S>>3. Самое главное: а какая альтернатива? Никакого способа сделать лучше в природе не существует. См. "проблема двух генералов".

В>Альтернатива не долбиться головой об стенку после того как поставил шишку.
Вот прямо даже не знаю, с чего начать.
Ок, начнём с конца.
1. Предположим, клиент позвонил в поддержку, поддержка пнула кого надо, проблема исправлена. Что делать дальше? Есть ли у вас уверенность (а если есть, то откуда) в том, что надо повторять (или не повторять) вызов?
Вы предлагаете обсуждать этот вопрос с поддержкой для каждого из 150000 вызовов, упавших, пока сервер "не справлялся"?
2. Предположим, надо повторить заказ. Как мы узнаем, когда это делать?
Будем ждать, чтобы поддержка отписала в тикет "всё норм, попробуйте ещё раз"? Не дороговат ли канал для передачи Retry-After?
3. Теперь попробуем понять, а что вообще могло пойти не так.
Я так понял, что по вашему опыту, 5хх означает в подавляющем большинстве случаев то, что сервак ложится под нагрузкой, и любые повторные попытки будут делать только хуже.
По моему опыту, частота причин для 5хх примерно такая:
А. проблемы с администрированием: кончилось место на диске, протух пароль для внешнего сервера, проэкспайрился сертификат.
Б. техобслуживание: мы просто перезагружаем сервак, и клиент это видит либо как таймаут, либо как 502 от прокси-сервера.
В. проблемы с тестированием: одна из веток кода бросает необработанное исключение, потому что до её проверки не дошли руки. То есть она должна отдавать либо 4хх (недостаточная валидация инпута стреляет ниже по стеку), либо 2хх (тупо бага в коде).
Ситуация, при которой у нас не хватает мощностей — большая редкость для нас. Как правило, и наши сервисы, и сторонние сервисы проектируются с запасом по нагрузке. Если бы мы имели ситуации с неконтролируемыми вспышками активности, то звонки в саппорт бы мы и так получили.
Во всех остальных случаях повторные попытки никак не влияют на ситуацию: скажем, скорость перезагрузки сервера не зависит от того, долбят ли сейчас в прокси периметра что-то нетерпеливые клиенты.

Если посмотреть на ситуацию шире, то опыт примерно такой: на то, что наш софт продолжает ретраить в тупиковых ситуациях, жалоб на моей памяти не было. Ну долбит и долбит — это никому ничего не стоит.
А вот на то, что он перестаёт ретраить, жалобы были. Потому что ситуация "у нас упало 10000 заказов в очереди из-за проблемы в сервисе ХХХ" стоит разных убытков в случае "мы позвонили им в саппорт, они починили, и всё поехало", и в случае "мы позвонили им в саппорт, они починили, и теперь нам нужно определить, какие из 13000 заказов в очереди застряли из-за этой проблемы, и сделать им ручной рестарт".
А ситуация "те заказы, которые мы пытались протолкнуть, упали фатально, их нужно отменять и заказывать по новой" вообще может стать поводом для эскалации на уровень Senior Vice President с раздачей дюлей по цепочке подчинения.
Потому, что денег стоят человеко-часы. В первом варианте мы имеем ущерб в 1 час, во втором — в 100, в третьем — в 5000.
И каждый час лишней задержки исполнения заказа, висящего в очереди из-за 5хх в стороннем сервисе, стоит нам примерно 10% churn. То есть шансы на то, что клиент дождётся ручной разблокировки очереди, стремительно падают.

Именно поэтому я — фанат клиентов, которые умеют сами делать повторные обращения, и сервисов, которые умеют в случае проблем дирижировать этими обращениями.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[17]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 13.02.20 16:52
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Трабования имеют св-во менятся, зачастаю неожиданным и непредсказуемым образом.

1. Внезапность — это неосознанная неизбежность
Знаете, как говорят: "если вы ведёте машину, и на дороге происходит что-то неожиданное — значит, ваша квалификация как водителя недостаточна"
Это, конечно, преувеличение, но в целом слабые места дизайна можно предвидеть заранее. Например, длина URL в Get-запросах.
Вот у вас, как я понял, не очень много опыта в проектировании REST API, и тем не менее вас вопрос ограничения длины заинтересовал.
Это значит, что при прояснении требований вы этот вопрос зададите — заказчику, или себе. В каких-то случаях у вас будет гарантия успеха — потому что все нужные параметры имеют заведомо ограниченную длину.
В каких-то других случаях вы сможете предсказать, что достаточное количество реальных запросов уложится в ограничение. Например, поисковые запросы в Гугл теоретически могут превысить эти килобайты.
Тем не менее, гугл не постеснялся выставить свой поисковый API через GET.
Сочли, что желающие искать в интернете целые главы по их полному тексту обойдутся.

Вы можете столкнуться с задачей типа "скачать пачку книг (допустим, в .tar) по их ISBN". Реализация этой задачи через GET наложит ограничение на максимальное количество книг в батче.
Ну и что? Просто опишете это в документации (ну, или в теле 400 Bad Request) и всё.
Преимущества у такой реализации всё ещё перевешивают недостатки.
Только если значительное количество клиентов будут упираться в это ограничение, у вас будет повод усложнить протокол — скажем, сделать сущность "запрос на скачивание", который будет PUT-ом (или POST-ом) закидывать список ISBN, и потом уже отдельным GET закачивать данные по короткому URL.

Про RPC я и не говорю — сделать нормальную реализацию подобного сервиса через RPC близко к невозможному. То, что получится у 70% архитекторов, будет в разы хуже REST по наблюдаемым характеристикам, а то, что смогут сделать 30%, не одобрит менеджмент со словами "нафига козе баян, это как-то сложно и дорого".

S>В целом, Вы правы, так думать можно. Но, думается, когда Филдинг все это дизайнил речь шла о конретных, конечных ресурсах, типа стат. html страничек. Т.е. что-то сущ. и конечное.

Неважно, о чём он думал. Важно то, что он придумал в итоге.
S>Все можно положить на rest, но не всегда нужно.
S>Помню где-то читал, и недавно, но найти не могу.
Я думаю, что вы неверно поняли.
Вот, к примеру, гугл. Вы же не думаете, что существует такой ресурс, как "список страниц с упоминанием термина 'финтифлюшка' и его аналогов", так?
И никого-никого в мире не смущает то, что по адресу https://www.google.com/search расположено бесконечное количество страниц.
Так почему же должно смущать наличие бесконечной таблицы умножения по адресам типа https://multiplication.com/53423423/times/123121 ?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re: REST: прохой\хороший интерфейс
От: alexanderfedin США http://alexander-fedin.pixels.com/
Дата: 13.02.20 21:18
Оценка:
Здравствуйте, a.v.v, Вы писали:
AVV>Во многих вакансиях появилось требование звучащее как — уметь проектировать хороший rest интерфейс
AVV>вот и задумался а что такое этот хороший интерфейс, у самого есть конечно мысли но какие то они не очень убедительные
AVV>вот хотел бы послушать коллективное бессознательное
Есть два подхода к дизайну REST интерфейсов:
  1. Строго следовать парадигме CRUD (POST/GET/PUT/DELETE) и делать urls идентифицирующие ресурсы
  2. Понимать performance impacts от принятых решений и кое-где отходить от CRUD, поскольку batch операции нормально/правильно задизайнить можно только для GET.

AVV>заранее благодарю

Не благодари.
Respectfully,
Alexander Fedin.
Re[18]: REST: прохой\хороший интерфейс
От: Pavel Dvorkin Россия  
Дата: 14.02.20 03:30
Оценка:
Здравствуйте, Sinclair, Вы писали:

PD>>Если бы тут никакого ревизора не было, а просто надо было бы вновь приехавашего добавить в книгу проезжающих — POST классический.

S>Нет. POST — это самый плохой из всех HTTP глаголов, т.к. он предоставляет минимум гарантий. Чтобы его использовать, нужны веские причины.

Верно! Истину глаголешь.

POST прячем в карман. Что остается ?

PUT — методы, которые вносят какие-то изменения
GET — методы, которые не вносят никакие изменения
DELETE — особый случай : методы, которые вносят изменения, но только типа удаления.

Теперь заменяем странный способ передачи параметров в трех местах (header, line, body), вызывающий бесконечные дискуссии о том, где какой параметр надо передавать, на обычный вызов функции с параметрами

И получаем RPC.




PD>>Report addPerson(Person);

PD>>Можно поинтересоваться, какие механизмы контроля есть в обычном LPC ? Например, при добавлении картинки на холст ?
S>В обычном LPC эти механизмы и не нужны — там не бывает ситуации типа "мы не знаем, удалось ли нам сделать вызов, или нет".
Даже если между caller и callee возникла коммуникационная проблема (переполнился стек, ну или адрес вызова оказался булшитом), то мы получаем вполне внятное исключение. В RPC нет такой роскоши.

Почему ты так решил ? Если клиент thrift пытается сделать вызов, а сеть вообще упала или урл/порт не верен, он получит вполне внятное исключение — тот же самый TTransportException, внутри которого будет IOException с обычным объяснением причины ?

PD>>Нет, не устраивает, ибо гуси с гусенками не будут удалены, а Держиморда будет по-прежнему махать кулаками.

S>Павел, ты мухлюешь. Вот у нас есть клиент — то самое неопределённое лицо. Вот оно в чём заинтересовано? Ставит ли оно задачу удалить гусей с гусёнками? Или оно просто отправляет ревизора — а как там будет обрабатываться его появление, уже дело местных властей?

Мухлюешь ты. Или же плохо знаешь "Ревизор".
Зачем клиент послал ревизора — а бог его знает, в пьесе об этом ничего. Есть лишь отвергнутая гипотеза о поиске измены.
Послал и послал. Может, он от него ждет Report.
А вот в ходе этого действия надо, как оказалось, удалить гусей с гусенками.
Важное лицо, отправившее ревизора, я полагаю, о гусях вовсе не думало, и ревизор отправлен не для того, чтобы их на кухню отправить.
А вот надо, как выяснилось, именно это сделать — как следствие посылки ревизора.


S>Ты покажи мне десктопное приложение, которое работает с разделяемым состоянием — 90 против 1, что оно ходит на сервер по http.


Да бога ради, любой клиент SQL сервера. Ну не ходит MySQL Workbench или SQLYog на MySQL сервер по HTTP.
А Skype по HTTP ходит ?
А Windows клиенты для Active Directory ? Между прочим, домен может быть очень и очень распределенным, а то и целый лес доменов


PD>>https://www.javatips.net/api/org.apache.thrift.transport.ttransportexception

S>Не верю. Чудес не бывает. Никакими ексепшнами ты проблему двух генералов не решишь.

Что за проблема двух генералов ? Тех, которых мужик кормил у Салтыкова — Щедрина ? Если да — при чем они тут ?

PD>>Тоже TTransportException, если задать таймаут.

S>Ну, и? Как мне понять, где произошёл transportException — на этапе передачи запроса, или на этапе передачи ответа?

Смотреть, что там внутри, как всегда.

S>Вот я делаю placeOrder() — как сделать так, чтобы гарантированно разместить не более 1 заказа, при условии ненадёжности сети?


Вот я делаю добавление на холст новой картинки — как мне гарантированно дважды ее не разместить ? Проверить результат размещения, и если он неудачен, то размещать заново
Вот я делаю INSERT в таблицу БД — как мне гарантированно дважды ее не добавить ? Проверить результат добавления, и если он неудачен, то добавлять заново. Кстати, сеть тут почти наверняка задействована.

Ты почему-то решил, что наличие сети делает ситуацию совершенно особой. А это всего лишь компонент, конечно, гораздо менее надежный, чем другие. Вероятность того, что при записи в файл будет IOException, на порядки меньше, чем вероятность того, что при работе с thrift будет TTransportException. Но только идиот не будет предпринимать меры в отношении возможного IOException при работе с файлом или какого-то исключения от БД

PD>>Вот этот тезис я принимаю. Но этот факт не отменяет того, что для реализации этого стандарта был выбран инструмент, изначально заточенный для других, более простых задач, в результате чего его допиливали и натягивали на эти задачи. Вместо того, чтобы предложить иной протокол, более адекватный для современных приложений. А как его реализовать — вопрос десятый. Можно и поверх RPC, можно и просто поверх TCP/IP. RPC-то тоже через TCP/IP работает.


S>Например, инкрементальности передачи в RPC нету — примитив же, просто "байты по сети". Он лишь чуть лучше, чем голый TCP-стрим. А в HTTP инкрементальность заложена в сам протокол. При этом сервер не обязан её поддерживать — можно запустить proof of concept без неё, и все клиенты и прокси прекрасно будут с твоим сервером работать. Потом прикручиваешь инкрементальность — и все существующие клиенты продолжают работать. А те из них, которые умеют инкрементальность, получают преимущество в тот же момент.

S>Или компрессию.
S>Причём у тебя один и тот же протокол могут отдавать три сервера — в одном есть инкрементальная передача, в другом компрессия, в третьем и то и другое. И клиенты будут прекрасно работать со всеми тремя.
S>В RPC протоколе эта задача потребует настолько адских приседаний, что шансов получить под них бюджет очень мало.

Нету, нету. Согласен. RPC — голый вызов функций. Обычный вызов функций, только под спудом маршаллинг на другую машину. Естественно, ждать от него инкрементальности, кеширования и т.д. можно с тем же успехом, сколько от простого вызова функций в пределах десктопного приложения. Сделаете сами — будет, не сделаете — не будет.
Но я же не об этом. Я о том, что протокол, изначально заточенный под совсем другие задачи, натянули на современные клиент-серверные приложения. А нужно было что-то более логичное.
With best regards
Pavel Dvorkin
Re[19]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 04:17
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


PD>PUT — методы, которые вносят какие-то изменения

PD>GET — методы, которые не вносят никакие изменения
PD>DELETE — особый случай : методы, которые вносят изменения, но только типа удаления.

PD>Теперь заменяем странный способ передачи параметров в трех местах (header, line, body), вызывающий бесконечные дискуссии о том, где какой параметр надо передавать, на обычный вызов функции с параметрами


PD>И получаем RPC.

Не совсем так. Просто заменив обращения к HTTP глаголам на вызовы RPC, мы теряем информацию о том, какие методы safe, а какие — idempotent. Как ты поймёшь, можно ли кэшировать результат вызова RPC-метода?

Кроме того, сигнатуры RPC методов просто взорвутся. Ты себе представляешь, сколько метаданных там ездит в хидерах?
Попробуй для начала переписать простейший GET /people/{personId} в RPC.
Не забудь, что нужно уметь:
1. Делать перенаправление — когда сервер редиректит клиента на другой сервер,
2. Принимать таймстамп кэша и хеш от данных, и возвращать аналог Not modified
3. Поддерживать инкрементальную загрузку — если в прошлый раз доехала только половина данных, то мы должны уметь запросить вторую половину, не запрашивая всю передачу
4. Поддерживать сжатие результата
5. Поддерживать локализацию — клиент передаёт список известных ему культур в порядке убывания предпочтений, сервер выбирает наиболее предпочтительную из поддерживаемых им культур

Жду сигнатуру RPC метода, который это всё умеет.

PD>Почему ты так решил ? Если клиент thrift пытается сделать вызов, а сеть вообще упала или урл/порт не верен, он получит вполне внятное исключение — тот же самый TTransportException, внутри которого будет IOException с обычным объяснением причины ?

Павел, при чём тут причина? Может ли это исключение мне сказать, деньги-то списались или нет? Как мне сделать так, чтобы

PD>>>Нет, не устраивает, ибо гуси с гусенками не будут удалены, а Держиморда будет по-прежнему махать кулаками.

S>>Павел, ты мухлюешь. Вот у нас есть клиент — то самое неопределённое лицо. Вот оно в чём заинтересовано? Ставит ли оно задачу удалить гусей с гусёнками? Или оно просто отправляет ревизора — а как там будет обрабатываться его появление, уже дело местных властей?

PD>Мухлюешь ты. Или же плохо знаешь "Ревизор".

PD>Зачем клиент послал ревизора — а бог его знает, в пьесе об этом ничего. Есть лишь отвергнутая гипотеза о поиске измены.
PD>Послал и послал. Может, он от него ждет Report.
PD>А вот в ходе этого действия надо, как оказалось, удалить гусей с гусенками.
PD>Важное лицо, отправившее ревизора, я полагаю, о гусях вовсе не думало, и ревизор отправлен не для того, чтобы их на кухню отправить.
PD>А вот надо, как выяснилось, именно это сделать — как следствие посылки ревизора.
"Надо именно это" — это внутреннее состояние сервера. Протокол взаимодействия, контракт сервера, никаких гусёнков и держиморд не содержит. В твоей RPC реализации никаких Report клиент не ожидает, и ты считаешь такой протокол корректным. Ну, считаешь и считаешь — твоё право. Но тогда этому протоколу удовлетворяет и dev/null.

S>>Ты покажи мне десктопное приложение, которое работает с разделяемым состоянием — 90 против 1, что оно ходит на сервер по http.


PD>Да бога ради, любой клиент SQL сервера. Ну не ходит MySQL Workbench или SQLYog на MySQL сервер по HTTP.



PD>Что за проблема двух генералов ? Тех, которых мужик кормил у Салтыкова — Щедрина ? Если да — при чем они тут ?

https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%B4%D0%B2%D1%83%D1%85_%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D0%BB%D0%BE%D0%B2

PD>>>Тоже TTransportException, если задать таймаут.

S>>Ну, и? Как мне понять, где произошёл transportException — на этапе передачи запроса, или на этапе передачи ответа?

PD>Смотреть, что там внутри, как всегда.

Внутри чего?

PD>Вот я делаю INSERT в таблицу БД — как мне гарантированно дважды ее не добавить ? Проверить результат добавления, и если он неудачен, то добавлять заново. Кстати, сеть тут почти наверняка задействована.

Ну вот да, Павел. Поэтому в здравом уме никто напрямую в таблицу insert через океан и не делает. А когда делают репликацию, то по факту приходят именно к representational state transfer, хоть не обязательно через HTTP.
Приведи пример кода, который решает эту задачу на SQL. Полезное упражнение. А потом мы посмотрим, как так получается, что REST-метод, который в некотором смысле всего лишь надстройка над INSERT INTO, гарантированно не добавляет запись дважды.


PD>Ты почему-то решил, что наличие сети делает ситуацию совершенно особой. А это всего лишь компонент, конечно, гораздо менее надежный, чем другие. Вероятность того, что при записи в файл будет IOException, на порядки меньше, чем вероятность того, что при работе с thrift будет TTransportException. Но только идиот не будет предпринимать меры в отношении возможного IOException при работе с файлом или какого-то исключения от БД

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

PD>Но я же не об этом. Я о том, что протокол, изначально заточенный под совсем другие задачи, натянули на современные клиент-серверные приложения. А нужно было что-то более логичное.

Я не возражаю против более логичного. Но вот RPC — он заведомо менее логичный, чем REST.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[20]: REST: прохой\хороший интерфейс
От: Pavel Dvorkin Россия  
Дата: 14.02.20 05:07
Оценка:
Здравствуйте, Sinclair, Вы писали:

Ладно, пора заканчивать.

S>Кроме того, сигнатуры RPC методов просто взорвутся. Ты себе представляешь, сколько метаданных там ездит в хидерах?

S>Попробуй для начала переписать простейший GET /people/{personId} в RPC.
S>Не забудь, что нужно уметь:
S>1. Делать перенаправление — когда сервер редиректит клиента на другой сервер,
S>2. Принимать таймстамп кэша и хеш от данных, и возвращать аналог Not modified
S>3. Поддерживать инкрементальную загрузку — если в прошлый раз доехала только половина данных, то мы должны уметь запросить вторую половину, не запрашивая всю передачу
S>4. Поддерживать сжатие результата
S>5. Поддерживать локализацию — клиент передаёт список известных ему культур в порядке убывания предпочтений, сервер выбирает наиболее предпочтительную из поддерживаемых им культур

Ты упорно мыслишь в психологии HTTP.

Какое такое перенаправление ? В HTTP — понятно, а в вызове процедуры на сервере о чем вообще речь может идти ? Я обращаюсь к серверу, вызываю его метод. Нет тут URL (точнее, он один был при соединении) и нечего перенаправлять. Если нужно, пусть сервер и перенаправляет куда хочет — тем же способом. Клиента это не интересует.
Остальное аналогично. Пусть сервер там и разбирается, что modified, а что нет.
Какие метаданные и в каких хидерах ? Нет тут хидеров и нет метаданных. Есть Person, которую надо с сервера получить. Здесь столько же метаданных и хидеров, сколько в SELECT * FROM persons WHERE
Сжатие — ну если нужно, пусть метод делает zip и возвращает byte[] или ByteBuffer. thrift, кстати, ByteBuffer очень любит и все двоичные данные именно в виде его и передает
With best regards
Pavel Dvorkin
Re[21]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 05:39
Оценка: 21 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Ладно, пора заканчивать.

Ну естественно. Как дело доходит до конкретики, фанаты RPC разбегаются из треда.

PD>Ты упорно мыслишь в психологии HTTP.

Павел. Психология тут ни при чём.
PD>Какое такое перенаправление ? В HTTP — понятно, а в вызове процедуры на сервере о чем вообще речь может идти ? Я обращаюсь к серверу, вызываю его метод. Нет тут URL (точнее, он один был при соединении) и нечего перенаправлять. Если нужно, пусть сервер и перенаправляет куда хочет — тем же способом. Клиента это не интересует.
PD>Остальное аналогично. Пусть сервер там и разбирается, что modified, а что нет.
PD>Какие метаданные и в каких хидерах ? Нет тут хидеров и нет метаданных. Есть Person, которую надо с сервера получить. Здесь столько же метаданных и хидеров, сколько в SELECT * FROM persons WHERE
PD>Сжатие — ну если нужно, пусть метод делает zip и возвращает byte[] или ByteBuffer. thrift, кстати, ByteBuffer очень любит и все двоичные данные именно в виде его и передает
Павел, вот реально у тебя всё мышление с ног на голову повёрнуто. Пятнадцать лет на этом форуме — а ничего не изменилось.
Все рассуждения — на уровне байтовых буферов. "Нету хидеров и метаданных" — ну так это как раз и плохо!!!

Поясню ещё раз, в надежде, на проблески озарения.
Когда мы проектируем протокол, у нас задача не сводится к "передаче байтов по сети". Задача формулируется в терминах прикладных сценариев.
И в REST, даже если архитектор заранее не подумал о каком-то аспекте, то можно задним числом этот аспект добавить, не ломая прямую и обратную совместимость!
Одно это ставит REST на две ступени выше, чем любой RPC.
"Пусть сервер и перенаправляет" — отлично. Вот тебе сценарий: клиент запрашивает данные у сервера. Данных — относительно много, этот наш person содержит в себе его автобиографическое видео — 1 минута, 50 мегабайт.
Вот мы запустили всё это в производство, посмотрели метрики — тяжеловато, наш сервер перегружен. Перегружен он потому, что отдаёт данные со скоростью клиента; поэтому далёкие клиенты в Мексике, которых оказалось неожиданно много, удерживают соединение открытым очень долго, и наш COM-объект отжирает нужные другим ресурсы. Снижается степень параллелизма.
Что, Павел, ты будешь делать? Ссылаться на то, что заказчики — козлы, и никогда заранее не описывают все требования?
Ну ок, эффективность фермы — никакая. Если бы у тебя был REST, то ты, не трогая само приложение, спокойно бы начал с того, что развернул бы на исходном адресе reverse proxy, чтобы разгрузить свой бэк-енд. Не написав ни строчки кода, получил бы расшивание боттлнека на порядок.
Далее, проанализировав нагрузку, поставил бы локальный прокси в Мексике, а на исходном адресе нарулил бы правило "если клиент пришёл из такого-то IP диапазона, то перенаправь его на мексиканский гейт".
Теперь у клиентов раундтрип до точки входа не 1200мс, а 80мс.
Опять — не написав ни строчки кода, ты меняешь потоки данных, и масштабируешь приложение. Более того — тебе даже не надо обзванивать мексиканских клиентов и говорить им "пожалуйста, пропишите у себя в конфиге вот такой адрес сервера".
"Нечего перенаправлять" — это не значит "не нужно перенаправлять", а "тупой RPC неспособен перенаправить".
Я на голом HTTP могу изваять geo-redundancy и load-balancing. А что ты предложишь делать с RPC?
Далее, "пусть сервер разбирается, что modified, а что нет" — ты вот сядь, напиши сигнатуру метода. Это в теории всё кажется понятным. Если своего опыта работы с сетью нету — ты уж не стесняйся, поверь более опытным товарищам.
А на практике внезапно оказывается, что запилить хотя бы треть того, что в REST идёт из коробки, в RPC руки не доходят. Влаги в организме столько нету, сколько пота потребуется пролить.
Для начала тебе придётся сделать тип данных вариативным — чтобы можно было вернуть как person, так и "ничего".
Далее, тебе придётся добавить в person какой-то индикатор, который ты потом будешь использовать в условных запросах. И не только в person, а во все типы, которые ты собрался тащить к себе через RPC.
Сжатие — то же самое. Один сервер умеет сжатие, а другой — нет. То есть мало того, что клиент не обязан указывать свои предпочтения; сервер ещё и не обязан их выполнять.
То есть мы не можем просто выставить два метода GetPerson и GetPersonCompressed. У нас один метод возвращает либо person, либо "байтовый поток".
Что у нас со штатным маршалингом? Вкорзинку?
Ок, остаётся простой способ — делать свой велосипед поверх RPC. То есть приделываем метаданные — те самые "хидеры", которых в RPC нет — дату изменения контента или его тег, признак сжатия. Тащим данные в виде байтового потока.
Вытащив их в клиента, вручную выполняем маршаллинг в зависимости от того, какие флаги приехали в метаданных.
А потом заказчик говорит "а что, вы не можете сделать докачку частично загруженного person при обрыве соединения?", и ты начинаешь всё переписывать сначала — потому что теперь тебе нужен какой-то способ передать в GetPerson информацию о том, что "треть данных у меня уже есть, давай остальное", и опять расширять набор флагов. Потому что сервер по-прежнему не обязан уметь частичную закачку, и он может в ответ на такой запрос передать цельного пёрсона.

И на фоне этого делается вывод, что "ну, REST — это применение неудачной концепции за рамками изначальной задачи, бла-бла-бла". Да какая разница, кто для чего исходно предназначался. Мы же инженеры — нам важно то, какие результаты можем мы получить. Ай-яй-яй, атом изначально разлагали для получения оружия. А теперь его зачем-то применяют для выработки электричества. Ну давайте, вернёмся к основам! А электричество будем вырабатывать как деды завещали — бензиновым генератором.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Отредактировано 14.02.2020 5:57 Sinclair . Предыдущая версия . Еще …
Отредактировано 14.02.2020 5:47 Sinclair . Предыдущая версия .
Re[22]: REST: прохой\хороший интерфейс
От: Pavel Dvorkin Россия  
Дата: 14.02.20 07:14
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Ладно, пора заканчивать.

S>Ну естественно. Как дело доходит до конкретики, фанаты RPC разбегаются из треда.

Ладно, отвечу.

Все твои возражения можно изложить очень коротко

Если через RPC передавать "голые" объекты — будут проблемы. Нужен протокол верхнего уровня.
Такого протокола в RPC действительно нет. Там передается и возвращается поток байтов.
Что никак не мешает его иметь.

Вот давай гипотетический (подчеркиваю, гипотетический) вариант рассмотрим

RPC. Отсылается ByteBuffer. Возвращается ByteBuffer.

В исходящем ByteBuffer упаковано все , что сейчас посылается по HTTP. Все, без исключения
В возвращаемом — аналогично.

Вот тебе HTTP на основе RPC. Шли то же, получишь то же. Только без HTTP, а в ByteBuffer.

А теперь вместо HTTP делаем что-то более напоминающее год 2020, а не год 1980. Не раскидываем там параметры в трех местах. Не возвращаем коды в виде , напоминающем С времен K&R. Не пакуем параметры через амперсенд, что мне напоминает времена моей молодости. И т.д.

И все, что ты хочешь, будет, но только в виде, более подходящем для 2020 года

И модифицировать этот протокол будет намного легче. Нужен аналог PUT — будет там PUT. Нужен аналог POST — будет там POST. Нужен некий CONVERT — будет там и CONVERT.

Вот и все. Именно нечто подобное я в своем thrift-приложении и делал.

И на этом, извини, уж точно все. Не могу я эту дискуссию вести до второго пришествия.
With best regards
Pavel Dvorkin
Re[23]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 07:52
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Все твои возражения можно изложить очень коротко

PD>Если через RPC передавать "голые" объекты — будут проблемы. Нужен протокол верхнего уровня.
PD>Такого протокола в RPC действительно нет. Там передается и возвращается поток байтов.
PD>Что никак не мешает его иметь.
PD>В исходящем ByteBuffer упаковано все , что сейчас посылается по HTTP. Все, без исключения
PD>В возвращаемом — аналогично.
Хуже того. Такой протокол уже есть. Называется HTTP. У него внутри уже упаковано всё, что посылается по HTTP.
PD>А теперь вместо HTTP делаем что-то более напоминающее год 2020, а не год 1980. Не раскидываем там параметры в трех местах. Не возвращаем коды в виде , напоминающем С времен K&R. Не пакуем параметры через амперсенд, что мне напоминает времена моей молодости. И т.д.
PD>И все, что ты хочешь, будет, но только в виде, более подходящем для 2020 года
Ну да. И ждём ещё 30 лет, пока для твоего нового протокола появятся nginx и CDN-провайдеры. Делов-то.
PD>И модифицировать этот протокол будет намного легче. Нужен аналог PUT — будет там PUT. Нужен аналог POST — будет там POST. Нужен некий CONVERT — будет там и CONVERT.
Нее, Павел. Давай пример. Мы говорим про конкретный, уже стандартизованный протокол. А ты в ответ "да я могу гипотетически то, гипотетически сё...".
Сколько будет стоить эта твоя гипотетика? Ну, без стоимости разработки nginx, а просто сам протокол?
Сможешь ли ты её хотя бы спроектировать корректно? А то вон ты уже нацелился изобретать глагол CONVERT, явно не задумываясь о том, будет ли он safe, idempotent, или нет.
Не понимая, как это всё будет взаимодействовать со сторонним софтом вроде тех же проксей, лоад-балансеров, и delivery networks.

Причём заметь — ты всё это время будешь писать только "основу". Собственно, заворачивал HTTP в RPC. Когда ты закончишь, настанет время писать прикладной протокол поверх этого "HTTPoverRPC".
То есть для той работы, которую собственно ведёт разработчик REST каждый раз, как ему нужно изобрести API для конкретного приложения.
Внезапно оказывается, что само по себе использование HTTP никаких гарантий не даёт — вон, идиоты придумали SOAP. Страшно подумать, сколько было денег закопано в эту яму. Он-то ведь тоже вполне себе HTTP, только без преимуществ HTTP.
PD>Вот и все. Именно нечто подобное я в своем thrift-приложении и делал.
А теперь главный вопрос — а выигрыш-то в чём? Ну сделал ты свой протокол для реализации REST. Получил все те же преимущества, что и в нём.
Единственная разница — в этом протоколе ты, возможно, будешь разбираться. А то про HTTP у тебя какие-то мифологизированные представления, вроде "параметров через амперсанд".
Мы, кстати, это уже десять лет назад обсуждали — я тебе ещё тогда указывал на твои заблуждения. Увы, увы. Почему-то изобретение своего протокола по-прежнему представляется более простым делом, нежели изучение существующего.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[24]: REST: прохой\хороший интерфейс
От: Pavel Dvorkin Россия  
Дата: 14.02.20 08:16
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>Ну да. И ждём ещё 30 лет, пока для твоего нового протокола появятся nginx и CDN-провайдеры. Делов-то.


И ждем еще 10 лет, пока эта Ваша новая Windows NT 3.1 станет такой же удобной для пользователя, как существующая Windows 95. Ну и что, что эта Windows 95 архитектурное убожество. Зато <аргументы за Windows 95 против Windows NT 3.1 можешь сам написать>

Я разве говорю, что нужно сейчас везде HTTP заменить на RPC ?

Я говорю о том, как это нужно было бы сделать "как следует".

А то, что мы не дождемся Windows XP/7/10 — это и так ясно.

Увы.
With best regards
Pavel Dvorkin
Re[25]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 09:11
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Я говорю о том, как это нужно было бы сделать "как следует".
Пока что в обсуждении не прозвучало ни одной идеи, "как следует" делать прикладные протоколы. Ну, кроме заблуждений и недопонимания того, какие реальные проблемы заслуживают решения.

Ну вот, авторам исходного RPC казалось, что самое сложное — это упаковать параметры вызова и распаковать результат. Поэтому они уделили большое внимание маршалингу. "Оо, смотрите, вам не нужно беспокоится о переводе байтов в сетевой порядок и обратно, и не надо определять границы пакетов. Как же ж это круто!". Ну, а ошибки — чо там, бросим исключение да и всё.

Авторам SOAP казалось, что в RPC недостаточно ада. А вдруг злоумышленник исказит наш запрос или ответ на него! Дааа, давайте навернём свой собственный адски сложный и расширяемый стандарт цифровой подписи.
Главное — чтобы заказчик понимал, что вещь сложная, стоить дёшево не может.
Дальше там что у нас? Проблемы с консистентностью? Дааа, давайте навернём свой стандарт двухфазного коммита. Сделаем его опциональным. Больше тегов, больше расширяемости, больше риска взаимной несовместимости.
Зато выглядит копец как солидно — откроешь пакет вайршарком, а там ууууу! Неймспейсы, неймспейсы, хидер, боди, всё такое. Тулчейн весом полтора гигабайта, WSDL, генератор WSDL по коду, генератор кода по WSDL.

Энтерпрайзненько, чо.

REST на этом фоне выглядит, конечно, несолидно. Ну что это такое — взял да запросил респонс! Прямо вот так вот, GET — и всё! Любой школьник может написать клиента к публичному API прямо на коленке в notepad.exe. А проверить этот АПИ можно вообще гражданским браузером. Ну куда это годится! Нет, это не для кровавого энтерпрайза.

К счастью, здравый смысл победил. Ну, а WindowsXP/7/10 продолжают подьезжать — вон, Гугл своё видение прикладных протоколов проталкивает, поверх HTTP/2.
Посмотрим, к чему придут. Пока что у меня нет ощущения, что они там дуют в нужную сторону; ну да посмотрим.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[16]: REST: прохой\хороший интерфейс
От: Sharov Россия  
Дата: 14.02.20 11:06
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>Можно ссылку на эти рекомендации? Может, я что-то упустил.


https://stackoverflow.com/a/53853161/241446 -- тут говорят, что это не очень rest compatible, но ни этот источник я имел в виду.

Use nouns to represent resources

RESTful URI should refer to a resource that is a thing (noun) instead of referring to an action (verb) because nouns have properties which verbs do not have – similar to resources have attributes. Some examples of a resource are:


Правди далее допускается контроллер:

A controller resource models a procedural concept. Controller resources are like executable functions, with parameters and return values; inputs and outputs.

Use “verb” to denote controller archetype.

http://api.example.com/cart-management/users/{id}/cart/checkout
http://api.example.com/song-management/users/{id}/playlist/play

https://restfulapi.net/resource-naming/

Т.е. calculate?expression=((1800*999)-677)/65866556 все же неправильно, не rest compatible.
Кодом людям нужно помогать!
Re[17]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 12:29
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Т.е. calculate?expression=((1800*999)-677)/65866556 все же неправильно, не rest compatible.

Да без проблем.
GET /((1800*999)-677)/65866556/value

Так лучше?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[18]: REST: прохой\хороший интерфейс
От: Sharov Россия  
Дата: 14.02.20 12:52
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>Т.е. calculate?expression=((1800*999)-677)/65866556 все же неправильно, не rest compatible.

S>Да без проблем.
S>
S>GET /((1800*999)-677)/65866556/value
S>

S>Так лучше?

GET expression/"((1800*999)-677)/65866556"/calculate
Кодом людям нужно помогать!
Re[19]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 12:56
Оценка:
Здравствуйте, Sharov, Вы писали:

S>GET expression/"((1800*999)-677)/65866556"/calculate

Ну нет, зачем нам calculate? Вы совершенно правы — нас интересует value выражения. А посчитано оно или взято из диска — совершенно умозрительный вопрос.
Вот, например, таблицы Брадиса же можно просто отсканировать и распознать — будет у нас GET /tables/sin/valueAt/0/
А можно там внутри быстренько посчитать сумму ряда и вернуть результат
Заодно получим возможность передавать в аргументы синуса любое число, а не только от -Pi до Pi, как у Брадиса.
Или, например, берём
GET /5413513543123543543864524354351354/primeFactors
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[26]: REST: прохой\хороший интерфейс
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 14.02.20 13:03
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>Дальше там что у нас? Проблемы с консистентностью? Дааа, давайте навернём свой стандарт двухфазного коммита. Сделаем его опциональным. Больше тегов, больше расширяемости, больше риска взаимной несовместимости.

S>Зато выглядит копец как солидно — откроешь пакет вайршарком, а там ууууу! Неймспейсы, неймспейсы, хидер, боди, всё такое. Тулчейн весом полтора гигабайта, WSDL, генератор WSDL по коду, генератор кода по WSDL.

Ну есть Swagger говорят вполне приличный. Есть еще и Refit https://github.com/reactiveui/refit
Ну и ODATA и Linq https://docs.microsoft.com/ru-ru/dotnet/framework/data/wcf/linq-considerations-wcf-data-services
и солнце б утром не вставало, когда бы не было меня
Отредактировано 14.02.2020 14:25 Serginio1 . Предыдущая версия .
Re[26]: REST: прохой\хороший интерфейс
От: swimmers  
Дата: 17.02.20 08:51
Оценка:
Здравствуйте, Sinclair, Вы писали:
...

Антон, как по твоему надо поступать в следующей ситуации:
Есть некоторая ИС, в которой несколько сотен справочников.
Есть несколько (веб)приложений, которым для работы нужны в т.ч. эти справочники.
При этом разным приложениям надо читать разный состав атрибутов справочников, условно, кому-то достаточно пары ключ-значение, кому-то нужны все атрибуты.
Плюс, некоторые записи в некоторых справочниках должны ограничиваться по правам доступа, т.е. конкретному клиенту могут возвращаться 10 записей из 100.

Как, на твой взгляд нам реорганизовать Рабкрин наиболее эффективно спроектировать АПИ, чтобы воспользоваться преимуществами REST(HTTP)?
Re[27]: REST: прохой\хороший интерфейс
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 21.02.20 06:58
Оценка:
Здравствуйте, swimmers, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:

S>...

S>Антон, как по твоему надо поступать в следующей ситуации:

S>Есть некоторая ИС, в которой несколько сотен справочников.
S>Есть несколько (веб)приложений, которым для работы нужны в т.ч. эти справочники.
S>При этом разным приложениям надо читать разный состав атрибутов справочников, условно, кому-то достаточно пары ключ-значение, кому-то нужны все атрибуты.
S>Плюс, некоторые записи в некоторых справочниках должны ограничиваться по правам доступа, т.е. конкретному клиенту могут возвращаться 10 записей из 100.

S>Как, на твой взгляд нам реорганизовать Рабкрин наиболее эффективно спроектировать АПИ, чтобы воспользоваться преимуществами REST(HTTP)?

Ну, как обычно — берём, выставляем каждый справочник как ресурс, права доступа накладываем на стороне сервера, cache-control делаем private, чтобы случайно не перемешать ответы.
Если вариантов набора атрибутов в каждом справочнике мало, то можно просто выставить их как варианты справочника (например, передав в URL выбор проекции, типа ?attr=all илии ?attr=compact).
Если их слишком много — то даём клиенту выбрать нужную проекцию.
Проще всего — воспользоваться готовым протоколом и готовой реализацией, например OData.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[7]: REST: прохой\хороший интерфейс
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 25.04.20 09:32
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>>Здравствуйте, Serginio1, Вы писали:


S>>> Что скажешь по gRPC

S>>>https://habr.com/ru/company/infopulse/blog/265805/
S>>Надо читать. Выглядит подозрительно.

S>Вот нашел еще статью на Хабре

S>https://habr.com/ru/company/yandex/blog/484068/

Добавлю еще одну статью про gRPC
https://habr.com/ru/post/488102/

Посмотрел https://docs.microsoft.com/ru-ru/aspnet/core/grpc/client?view=aspnetcore-3.1
Там и потоковая передача данных предусмотрена,

Создание канала может потребовать значительных ресурсов. Повторное использование канала для вызовов gRPC обеспечивает выигрыш в производительности.
Клиенты gRPC создаются с помощью каналов. Клиенты gRPC являются облегченными объектами и не нуждаются в кэшировании или повторном использовании.
Из одного канала можно создать несколько клиентов gRPC, включая различные типы клиентов.
Канал и клиенты, созданные из канала, могут безопасно использоваться несколькими потоками.
Клиенты, созданные из канала, могут выполнять несколько одновременных вызовов.


https://habr.com/ru/company/microsoft/blog/487548/?mobile=no

Новые возможности с gRPC-Web

Вызов приложений ASP.NET Core gRPC из браузера — API браузера не могут вызывать gRPC HTTP/2. gRPC-Web предлагает совместимую альтернативу.
JavaScript SPA
Приложения .NET Blazor Web Assembly
Размещать приложения ASP.NET Core gRPC в IIS и службе приложений Azure. Некоторые серверы, такие как IIS и служба приложений Azure, в настоящее время не могут размещать службы gRPC. В то время как над этим активно работают, gRPC-Web предлагает интересную альтернативу, которая сегодня работает в любой среде.
Вызов gRPC с платформ, отличных от .NET Core. Некоторые платформы .NET HttpClient не поддерживают HTTP/2. gRPC-Web может использоваться для вызова сервисов gRPC на этих платформах (например, Blazor WebAssembly, Xamarin).

Обратите внимание, что gRPC-Web требует небольших затрат на производительность, и две функции gRPC больше не поддерживаются: клиентская потоковая передача и двусторонняя потоковая передача. (потоковая передача на сервер все еще поддерживается!)

и солнце б утром не вставало, когда бы не было меня
Отредактировано 25.04.2020 19:50 Serginio1 . Предыдущая версия . Еще …
Отредактировано 25.04.2020 10:37 Serginio1 . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.