«phpClub» — архив тем ("тредов"), посвящённых изучению PHP и веб-технологий.
someApprentice 2018/10/09 16:39:58  №1276937 1
Как получить переменную из асинхронной функции в ReactPHP?

function getSmtn()
{
$loop = \React\EventLoop\Factory::create();
$connector = new \React\Socket\Connector($loop);

$connector->connect('...')->then(function (\React\Socket\ConnectionInterface $connection) use ($loop) {
$connection->on('data', function ($data) use ($connection) {
$smtn = $data;

$connection->close();
});
});

$loop->run();

return $smtn;
}
Ответы: >>1277072
Аноним 2018/10/09 20:26:42  №1277072 2
>>1276937

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

$promise = getSmtnAsync();
$promise->done(...);

У промисов PHP есть неприятная особенность, в сравнении с JS промисами: они не сообщают об необработанных исключениях. Если ты не поставил обработчик (в done/then), то исключение просто потеряется. А ведь это может быть любое исключение, в том числе ErrorException вызванное например опечаткой в имени переменной или функции.

Аналогично, в PHP ошибки чтения/записи в поток по умолчанию игнорируются, если ты не поставил обработчик на on('error', ...). В общем, обработка ошибок сделана максимально плохо.

В твоем коде вообще логичнее всего было бы вернуть объект connection. Если же надо прочитать все данные до конца, тогда надо их читать в функции и по окончанию передать в промис через Deferred:

$def = new Deferred();
// На основании исследования пальца и потолка я решил что это эффективнее, чем строка. В реальности лучше проверить тестами.
$buffer = [];
$connection->on('data', function ($d) use ... { $buffer[] = $d });
$connection->on('error', function ($e) .. { $def->reject(4e); });
$connection->on('end', function () {
$content = implode('', $buffer);
$def->resolve($content);
});

return $def->promise();

Тут только надо проверить последовательность прихода событий, чтобы например ошибка не потерялась, если end приходит раньше error.

Не забывай, обработка ошибок сделана максимально плохо и тебе надо делать ее полностью самостоятельно.
Ответы: >>1277349
someApprentice 2018/10/10 14:34:35  №1277349 3
>>1277072
Я пытался самостоятельно закончить шифровальный мессенджер на JS фреймворке и с PHP на бэкенде, но оказалось, что Angular не способен в server-side rendering с другими языками.

Я даже сделал PR для одного из их инструментов для этого, чтобы починить ленивую подгрузку роутов

https://github.com/angular/universal/issues/1000
https://github.com/angular/universal/issues/1069
https://github.com/angular/universal/commit/c16860c06ed645a0f3a6937191d1f9b7a6e2dc48

Когда я сделал это, я тестировал подгрузку шаблона просто:

$connector->connect('...')->then(function ($connection) use ($loop) {
$options = [
'id' => 1,
'url' => '/',
'document' => '<app-root></app-root>'
];

$connection->write(json_encode($options));

$connection->on('data', function ($data) use ($connection) {
$template = json_decode($data, JSON_OBJECT_AS_ARRAY)['html'];

include __DIR__ . '/../templates/index.phtml';

$connection->close();
});
});


Мне удавалось просто заинклудить шаблон, но проблемы начались когда я начал писать реальный код и установил Symfony, где рендеринг шаблона выполняется через return, и, к тому же, я обнаружил что с Angular server-side rendering даже нет причины использовать PHP, потому что он уже и так всё делает.


Я хотел написать чтобы приложение способно было работать без JS, а JS лишь добавлял плавности для подгрузки и/или перехода между компонентами.
Мне нужно чтобы я мог выдать шаблон с помощью PHP, а JS просто повесился поверх него. Чтобы максимум что мне пришлось сделать для этого, это скомпилировать JS код через Webpack или через что ещё это делают?. Ни больше не меньше!

Такой фронтэнд фреймворк существует? Вы можете посоветовать?

Я готов написать такое приложение и на ES6 и транспилировать его через Babel, чтобы получить поддержку относительно старых браузеров, но, опять же, я не уверен какие библиотеки я должен использовать.

Мне ни один из предложенных гуглом шаблонизаторов не нравится, потому что в них нужно определять шаблон либо как строку, либо как скрипт с типом type="script/name of template engine".

Не совсем ясно какую библиотеку использовать для роутинга - в гугле только одна библиотека по первой ссылке и куча блогов о том как написать собственный, и, наконец, документация по роутингу для Vue.js и ReactJS.

Все это не понятно и нужно тестировать самому.

Может мне просто взять ReactJS? Он подойдет для такой задачи?


А я потратил всё лето на изучение Angular, как напрасно как жаль, эх...
Ответы: >>1277389
Аноним 2018/10/10 16:25:59  №1277389 4
>>1277349

Ну, я думаю, что изучение Angular все же будет полезно. Хотя бы кругозор расширит.

> Я хотел написать чтобы приложение способно было работать без JS, а JS лишь добавлял плавности для подгрузки и/или перехода между компонентами.

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

Есть подход, когда мы рендерим страницы на сервере и пишем JS код, который добавляет им интерактивности (progressive enhancement). Это не очень тебе подойдет, так как это подходит для "сайтов", а не "приложений". И придется дублировать шаблоны в PHP и JS коде.

Что касается, server side rendering - его первоочередная цель - это сделать сайты, построенные на SPA, индексируемыми (что, как мне кажется, проще решить отказом от SPA для таких сайтов).

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

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

1) любое действие требует перезагрузки страницы. Ну представь, например, фильтр по контактам, работающий по мере набора текста - это нельзя сделать без JS (только если превратить его в классическую форму с кнопкой). Или например открывающийся интерактивно попап для выбора смайликов.

2) нужно как-то сохранять состояние при перезагрузках страницы. Представь сценарий: пользователь вводит текст сообщения, затем жмет на кнопку добавления смайлика (которая открывает попап с ними). Нам надо перезагрузить страницу, но не потерять введенный текст. Это сама по себе сложная задача. Какой-то майкрософтовский фреймворк делал так: он в конце выполнения серверного скрипта сериализовал состояние (значения переменных) и вставлял в страницу в виде строки. При выполнении любого действия это состояние (с помощью простого JS) в запросе POST отправлялось на сервер, и значения переменных восстанавливались. Эта штука называлась viewState: https://msdn.microsoft.com/en-us/library/bb386448.aspx

Или представь такой сценарий: пользователь в оффлайне отправляет сообщение, оно сохраняется в localStorage, пользователь закрывает вкладку. Затем пользователь снова открывает вкладку - и в отрендеренной сервером странице этого сообщения нету.

Именно поэтому, мне кажется, есть смысл оставить только минимальный функционал в версии без JS.

Я вижу такие варианты:

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

По твоему PHP коду: в нем ошибка. Событие data не говорит о том, что все данные приняты. Оно говорит о поступлении куска данных. Ты должен собирать приходящие данные в буфер до получения события end, которое говорит о том, что пир закончил передачу данных и закрыл TCP-соединение на передачу. В ответ на это ты делаешь close(). Также ты должен проверять событие error, которое говорит об ошибке в процессе работы соединения.

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

Также, ты должен прокидывать в ангулар-приложение HTTP-заголовки. Например, авторизационные куки, если они проверяются в приложении.

> Мне удавалось просто заинклудить шаблон, но проблемы начались когда я начал писать реальный код и установил Symfony, где рендеринг шаблона выполняется через return

Напиши, в чем проблемы.

> я обнаружил что с Angular server-side rendering даже нет причины использовать PHP, потому что он уже и так всё делает.

Это тоже вариант - оставить в PHP только API (и, может быть, страницы авторизации/регистрации), а рендеринг страниц мессенджера отдать Node.JS приложению. Почему нет?

> Я хотел написать чтобы приложение способно было работать без JS, а JS лишь добавлял плавности для подгрузки и/или перехода между компонентами.

Это хороший подход при написании веб-приложений, которые больше похожи на сайты - где есть страницы, меню, а JS лишь добавляет интерактивности. Но мессенджер это приложение, а не сайт. И тут имеет смысл основной сделать именно SPA версию.

А так, это умеет делать реакт. Можно отрендерить страницу на сервере, и потом к ней подцепить скрипты, когда они загрузятся. Я видел, например, дата-атрибуты в HTML-коде медузы (https://meduza.io) - можешь поковырять, если интересно.

Судя по мануалу, Ангулар умеет что-то похожее: https://angular.io/guide/universal#the-root-appmodule
Это позволяет показать какие-то данные до загрузки кода, но страница будет неинтерактивной (разве что на ней будут работать ссылки и классические формы).

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

А после подгрузки JS кода будут отрендерены дополнительные элементы, нажатия на контакты будут обрабатываться приложением, как и форма отправки сообщения.

Это, как мне кажется, умеет и Ангулар, и Реакт.

Если тебя беспокоит скорость загрузки JS кода и потребление трафика при загрузке (типичная проблема для SPA приложений), то я бы думал об его разделении на части и уменьшении. Ну, условно говоря, если у нас есть страница настроек, нам не надо подгружать контроллер и шаблон для нее сразу - можно это сделать, когда пользователь ее откроет. Вебпак позволяет разделять код на бандлы.

Также, для ускорения загрузки стоит "впекать" часть данных при генерации страницы на сервере. Если приложению после загрузки надо, например, загрузить список контактов, то можно его весь или часть вставить в тело страницы, например в теге script type="application/json" data-url="/api/contacts". Наверно, это как-то можно автоматизировать.

> Я готов написать такое приложение и на ES6 и транспилировать его через Babel, чтобы получить поддержку относительно старых браузеров, но, опять же, я не уверен какие библиотеки я должен использовать.

Babel транспилирует в ES5, для поддержки совсем старых браузеров добавляется ES5 shim, который при удачном стечении обстоятельств позволяет работать коду в ES3 браузерах (стоит только помнить, что совсем старые браузеры могут не поддерживать часть HTML5 тегов и новые CSS правила. Тут нужно тестирование). При этом современным браузерам в идеале хорошо бы отдавать ES6. Ну и при разработке желательно избежать запуска Babel, который не очень быстро работает, а грузить ES6 код напрямую.
Ответы: >>1277500 >>1277501
someApprentice 2018/10/10 21:25:29  №1277500 5
>>1277389
>То есть ты можешь, например, отрендерить на сервере страницу со списком контактов и последними сообщениями, а потом подцепить к ней приложение. Причем отрендерить стоит упрощенную версию страницы, чтобы пользователь не тыкал зря на неработающие кнопки. Сделать контакты ссылками - это позволит переключаться между ними. Сделать форму отправки классической HTML формой.
>
>А после подгрузки JS кода будут отрендерены дополнительные элементы, нажатия на контакты будут обрабатываться приложением, как и форма отправки сообщения.
А вот моё "черновое" приложение чата на чистом JS, как раз так и работало: - сначала отдавалась отрендеренная страница на PHP, а потом инициализировался JS, и все переходы по контактам, подгрузка новых или старых сообщений, и поиск контактов происходили "плавно" (если не считать времени на получение сообщений через http и дешифровку). Кстати, и если отключить JS в браузере, то можно так же было отправлять сообщения из той же формы, искать контакты, и подгружать старые сообщения, переходя по ссылке которая при включенном JS скрывалась при скролле вверх. Только это было моей первой работой и код получался запутанным. Хотелось бы, с моими новыми знаниями о JS, написать что-то погибче, используя современные инструменты и протоколы.


>В твоем случае логичнее всего сделать основным режимом SPA, а если хочется поддержка браузеров без JS, то можно поддерживать ограниченный функционал (например, упрощенный просмотр сообщений и упрощенная отправка). Сделать полноценный аналог без JS будет сложно. По таким причинам:
>
>1) любое действие требует перезагрузки страницы. Ну представь, например, фильтр по контактам, работающий по мере набора текста - это нельзя сделать без JS (только если превратить его в классическую форму с кнопкой). Или например открывающийся интерактивно попап для выбора смайликов.
>2) нужно как-то сохранять состояние при перезагрузках страницы. Представь сценарий: пользователь вводит текст сообщения, затем жмет на кнопку добавления смайлика (которая открывает попап с ними). Нам надо перезагрузить страницу, но не потерять введенный текст. Это сама по себе сложная задача. Какой-то майкрософтовский фреймворк делал так: он в конце выполнения серверного скрипта сериализовал состояние (значения переменных) и вставлял в страницу в виде строки. При выполнении любого действия это состояние (с помощью простого JS) в запросе POST отправлялось на сервер, и значения переменных восстанавливались. Эта штука называлась viewState: https://msdn.microsoft.com/en-us/library/bb386448.aspx
Ну вот, как у меня и было, в случае с поиском контактов, можно сделать классическую форму с кнопкой, затем, при инициализации JS, отменить интерфейс её отправки и обрабатывать эту форму уже с помощью самого JS.
В случае со смайлами, можно с сервера отрендерить форму без кнопки смайлов, а после загрузки JS, плавно вывести её (а при JS роутенге можно выводить её сразу).

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

В общем, везде можно придумать что-то.

>Именно поэтому, мне кажется, есть смысл оставить только минимальный функционал в версии без JS.
Да, на это и расчет. Чтобы без JS, по крайней мере, и работало.


>> я обнаружил что с Angular server-side rendering даже нет причины использовать PHP, потому что он уже и так всё делает.
>Это тоже вариант - оставить в PHP только API (и, может быть, страницы авторизации/регистрации), а рендеринг страниц мессенджера отдать Node.JS приложению. Почему нет?
А можно и API написать на Node.js. Только я не умею писать сервера, а в мануале по Angular'у написано что их пример сервера не безопасен https://angular.io/guide/universal#universal-web-server

>This sample server is not secure! Be sure to add middleware to authenticate and authorize users just as you would for a normal Angular application server.

Или я что-то путаю?


Так как мне следует поступить? Я склоняюсь к тому чтобы попробовать React, и если с ним не получится, то написать на ES6, и если и на нём не получится, то на Node.js уж железно получится написать приложение на Angular'е!
Ответы: >>1278721 >>1278722
someApprentice 2018/10/10 21:27:14  №1277501 6
1-pic.png (151, 1920x1080)
1080x1920
2-pic.png (217, 1920x1080)
1080x1920
>>1277389
>Я вижу такие варианты:
>
>- сделать отдельные страницы на отдельных роутах для упрощенной HTML-версии (как в Gmail). Они могут быть сделаны на PHP, или же на JS, но отдельным модулем, и не включены в браузерное приложение.
То есть сделать, например роут '/conversation/:id/' отдельно на PHP и отдельно на JS? Как понять отдельным модулем? Отдельным от чего? Как модули JS могут быть не включены в браузерное приложение? То есть, имеется ввиду, пререндерить сначала модуль, например, на Node.js?


>По твоему PHP коду: в нем ошибка. Событие data не говорит о том, что все данные приняты. Оно говорит о поступлении куска данных. Ты должен собирать приходящие данные в буфер до получения события end, которое говорит о том, что пир закончил передачу данных и закрыл TCP-соединение на передачу. В ответ на это ты делаешь close(). Также ты должен проверять событие error, которое говорит об ошибке в процессе работы соединения.
>
>Не может быть проблемы из-за этого, что ты не получил все данные до конца?
Я конечно же об это знал, что сокеты могут отправить сигнал о завершении трансляции, но в этом случае не подумал об этом, потому что так было в примере предложенным самими разработчиками этого инструмента, и мне самому очень не понятно как устроен серверный код Angular'а и JS.

https://github.com/angular/universal/issues/1000

>import * as net from 'net';
>
>const client = net.createConnection(9090, 'localhost', () => {
> const renderOptions = {id: 1, url: '/path',
> document: '<app-root></app-root>'} as SocketEngineRenderOptions;
> client.write(JSON.stringify(renderOptions));
>});
>
>client.on('data', data => {
> const res = JSON.parse(data.toString()) as SocketEngineResponse;
> server.close();
> done();
>});

Здесь нет проблемы с получением данных, потому что я проверял вывод шаблонов без Symfony и всё было в порядке.1-pic

>Также, ты должен прокидывать в ангулар-приложение HTTP-заголовки. Например, авторизационные куки, если они проверяются в приложении.
Я не знаю как это сделать и думаю что информации об этом не существует. Я вспоминаю вот это обсуждение по этому поводу https://github.com/angular/universal/issues/1000#issuecomment-391463462

>>What are your thoughts about cookies?
>I havn't put any effort into cookies just yet, but that's definitely something we can add onto these new additions. Right now i'd just like to keep it simple and make sure it solves the problems that people have with Universal on other platforms.

Кстати, полноценная возможность коммуникации Angular Universal с другими языками ещё только в разработке https://github.com/angular/universal#in-progress

>Node.js bridge protocol to communicate with different language backends - Django, Go, PHP etc.

Интересно как это будет и избавит ли это меня от текущих проблем.


>> Мне удавалось просто заинклудить шаблон, но проблемы начались когда я начал писать реальный код и установил Symfony, где рендеринг шаблона выполняется через return
>Напиши, в чем проблемы.
В том что шаблон я получаю в асинхронной функции, а в Symfony я могу отрендерить его только в синхроном коде контроллера, вызвав return.2-pic


>Если тебя беспокоит скорость загрузки JS кода и потребление трафика при загрузке (типичная проблема для SPA приложений), то я бы думал об его разделении на части и уменьшении. Ну, условно говоря, если у нас есть страница настроек, нам не надо подгружать контроллер и шаблон для нее сразу - можно это сделать, когда пользователь ее откроет. Вебпак позволяет разделять код на бандлы.
В Ангуляре есть ленивая подгрузка роутов когда модули загружаются только когда они нужны. Не уверен только как именно это работает - лениво загружается код по сети или лениво загружается инициализация модулей. Мне ещё предстоит узнать, какую конкретно играет роль Webpack по отношению к Ангуляру.

https://angular.io/guide/router#milestone-6-asynchronous-routing


>> Я готов написать такое приложение и на ES6 и транспилировать его через Babel, чтобы получить поддержку относительно старых браузеров, но, опять же, я не уверен какие библиотеки я должен использовать.
>Babel транспилирует в ES5, для поддержки совсем старых браузеров добавляется ES5 shim, который при удачном стечении обстоятельств позволяет работать коду в ES3 браузерах (стоит только помнить, что совсем старые браузеры могут не поддерживать часть HTML5 тегов и новые CSS правила. Тут нужно тестирование). При этом современным браузерам в идеале хорошо бы отдавать ES6. Ну и при разработке желательно избежать запуска Babel, который не очень быстро работает, а грузить ES6 код напрямую.
Разве Babel не транспилирует ES6 ещё? Почему-то его оффициальный сайт не открывается.
Ответы: >>1277505 >>1278720
Аноним 2018/10/11 09:50:01  №1277618 7
MegaFun.png (53, 1920x1052)
1052x1920
После уныло-похоронного списка студентов обменник решил сделать поярче. Жаль только, что я вообще никак не могу понять, как сменить стиль поля выбора файла.
Ответы: >>1277681 >>1278720
Аноним 2018/10/14 03:35:56  №1278720 8
>>1277618

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

>>1277501

Давай я еще раз объясню возможные сценарии.

1) Главное у нас JS приложение. Браузеры без JS получают упрощенные HTML версии отрендеренных на сервере страниц

Запускаем на сервере наше JS-приложение. Пользователь заходит на главную, приложение пререндерит ему упрощенную главную страницу, отдает. Дополнительно отдает JS-код. Если браузер поддерживает JS, то код запускается и прицепляется к странице, делая ее полноценной.

Когда пользователь куда-то жмет, код перехватывает нажатие, получает данные через АПИ и отрисовывает на стороне браузера. Если у пользователя отключен JS, то при нажатии происходит переход по ссылке и упрощенная страница рендерится на сервере.

PHP тут отвечает только на запросы к API. Этот роутинг между PHP и JS можно настроить на нгинксе.

2) Есть отдельная упрощенная версия приложения

Запускаем на сервере наше JS-приложение. Пользователь заходит на главную, получает пустую страницу + ссылку на упрощенную версию (/html/) + JS код. Если он поддерживает JS, то приложение запускается и рисует в браузере интерфейс. Если нет - то он жмет на ссылку (или редиректится автоматом) на упрощенную версию.

Упрощенная версия рендерится запущенным на сервере JS приложением. У нее отдельный URL, и может быть, отдельные шаблоны. А может, она использует стандартные шаблоны, но с флагом для скрытия части элементов.

PHP тут отвечает только на запросы к API

> То есть сделать, например роут '/conversation/:id/' отдельно на PHP и отдельно на JS?

Если ты хочешь, чтобы запросы проходили через PHP, то сделать в нем этот роут и проксировать его на серверное JS-приложение, которое отрендерит его. Если ты не хочешь усложнять, то на уровне нгинкс проксируешь этот роут в серверное JS-приложение.

Если пользователь зашел напрямую на этот URL - он получает HTML страницу + JS приложение (на случай если у него включен JS). Если же пользователь внутри JS приложения нажимает на контакт, то никаких страниц не загружается, а приложение отправляет запрос к АПИ и своими силами выводит данные.

То есть роут и шаблон будет один - в JS приложении. Но оно может работать как в браузере (если в нем включен JS), так и на сервере.

Есть еще третий вариант - обрабатывать этот роут в PHP и выдавать упрощенную версию страницы. Мне он не очень нравится, так как подразумевает дублирование кода между JS и PHP приложением.

> Как понять отдельным модулем?

Если ты решил сделать отдельные, упрощенные версии страниц на отдельных URL (/html/conversation/:id/), то код для них незачем включать в браузерное приложение - он нужен только на сервере (и наоборот, код для отображения полноценных страниц в браузере не нужен на сервере). Логично разделить код на "бандлы" и не отправлять серверный "бандл" в браузер.

> Как модули JS могут быть не включены в браузерное приложение?

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

> Я конечно же об это знал, что сокеты могут отправить сигнал о завершении трансляции, но в этом случае не подумал об этом, потому что так было в примере предложенным самими разработчиками этого инструмента, и мне самому очень не понятно как устроен серверный код Angular'а и JS.

Я понимаю так: в ангуларе есть модуль platform-server. Он предоставляет функцию https://angular.io/api/platform-server/renderModuleFactory, которая берет HTML-код (пустышку с тегом <app-root>), URL, и рендерит в него содержимое данной страницы, как браузере. Также, в platform-server есть простейшая эмуляция браузерных API вроде document, DOM, window, location, чтобы код не падал с ошибкой из-за их отстутствия.

Ты берешь

1) Node.JS
2) модуль platform-server
3) свое ангулар-приложение

И добавляешь к ним небольшой вспомогательный скрипт. Этот скрипт поднимает HTTP-сервер, принимает HTTP-запросы от браузера и вызывает renderModuleFactory, которая настраивает окружение, вызывает твое приложение и оно рендерит запрошенную страницу. И отдает назад в браузер. То есть на сервере выполняется по сути тот же код, что в браузере, и рендерится статическая HTML страница. И почти без усилий ты получаешь серверную версию своего ангулар-приложения для браузеров без JS.

Твое приложение думает, что оно запущено в браузере, и работает точно так же. Шлет запросы к АПИ, берет шаблон, отрисовывает данные в нем (упс, не знаю, как там с промисами - не отрисует ли приложение просто индикатор загрузки, не дождавшись ответа АПИ?).

Описано тут: https://angular.io/guide/universal#how-it-works

> Здесь нет проблемы с получением данных, потому что я проверял вывод шаблонов без Symfony и всё было в порядке.

Это не значит, что код 100% корректный. Может, у тебя просто время передачи данных было такое, что они передались одним блоком. Плюс, ты наверно передавал данные на локалхосте между 2 процессами, почти без задержек.

Код некорректный. Событие data обозначает приход одной порции данных (причем размер порции никак не документирован и зависит от многих факторов), а не всех переданных данных. У тебя просто так вышло, что данные влезли в одну порцию.

Кстати, у тебя там server.close() вместо client.close().

> Я не знаю как это сделать и думаю что информации об этом не существует.

Даже если ее не существует, если понимать, как Angular universal работает, то можно попробовать придумать решение.

Тебе приходит HTTP-запрос от пользователя. В нем есть куки. Твое ангулар-приложение скорее всего использует их 2 путями:

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

На сервере, естественно, этого всего по умолчанию нет.

В platform-server наверно есть какая-то эмуляция видимых из браузера кук (document.cookie). Тебе надо их в нее как-то передать. Если там нет эмулции кук - придется сделать или найти библиотеку для этого.

Плюс, в platform-server есть эмуляция XMLHttpRequest для отправки аякс-запросов. Нужно как-то куки и туда прокинуть, если твое АПИ как-то их проверяет (вот мы и поняли, почему авторизация в АПИ без кук в данном случае удобнее).

При прокидывании кук ты должен помнить про асинхронность ноды и про то, что в теории она может обрабатывать несколько запросов параллельно. От разных пользователей с разными куками. Значит, установить куки глобально нельзя - нужно для каждого обрабатываемого запроса работать со своими куками.

Я погуглил по "angular platform-server pass cookies".

Судя по https://github.com/angular/angular/issues/15730 - возможность прокинуть куки есть, хоть и с багами.

> Кстати, полноценная возможность коммуникации Angular Universal с другими языками ещё только в разработке

В описанном мной варианте она не нужна.

> В том что шаблон я получаю в асинхронной функции, а в Symfony я могу отрендерить его только в синхроном коде контроллера

Ты получаешь шаблон (точнее, HTML код) из сокета. Используй синхронную библиотеку или функцию для работы с сокетами. Например: socket_create() + fwrite() в цикле (он с первого раза может не записать все данные) + stream_get_contents(). Не забудь проверку на ошибки. Ты разве не работал с сокетами Беркли, с TCP?

Там JS код для Ноды, он асинхронный. В PHP мы можем писать синхронный код и не мучаться.

> Разве Babel не транспилирует ES6 ещё?

Да, он транспилирует ES6 в ES5. А к ES5 коду мы можем добавить ES5 shim дял браузеров, которые поддерживают только ES3, и надеяться, что это заработает. Если не заработает - давать им HTML версию.

Если у тебя есть время, я бы советовал полностью разобраться в этом. Так как пока есть пробелы в знаниях. Я всегда готов ответить на вопросы и пояснить.
Ответы: >>1279602 >>1280664
Аноним 2018/10/14 03:36:27  №1278721 9
>>1277500

> сначала отдавалась отрендеренная страница на PHP, а потом инициализировался JS, и все переходы по контактам, подгрузка новых или старых сообщений, и поиск контактов происходили "плавно" (если не считать времени на получение сообщений через http и дешифровку).

Так тоже можно, пока приложение простое. Как только оно превращается в полноценное MVC приложение, начинает само хранить данные, рендерить их в шаблон, работать в оффлайне - получается дублирование кода - тебе нужен одинаковый шаблон в PHP и в JS, одинаковый код для обработки данных, одинаковые классы и там, и там.

Если твой код просто посылает аякс-запрос на сервер, получает HTML, вставляет его в DOM (или делает небольшие изменения через jQuery) - это работает. Но если ты хочешь в браузере работать с моделями и рендрить их в шаблоны - это уже не работает. Так как у тебя получается дублирование кода.

>>Или представь такой сценарий: пользователь в оффлайне отправляет сообщение, оно сохраняется в localStorage, пользователь закрывает вкладку. Затем пользователь снова открывает вкладку - и в отрендеренной сервером странице этого сообщения нету.

> Тут нужно сначала определить, где именно будут хранится сообщения... Или, если это не имеет значения, то всегда отправлять сообщения из localStorage на сервер при восстановлении соединения и обновлять вкладку.

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

> А можно и API написать на Node.js

Тогда можно на PHP. По идее синхронный код на PHP написать проще. Хотя асинхронный с ReactPHP (или с нодой, или с Го) будет быстрее, что важно для мессенджера. Ну и сложные задачи ведь интереснее.

> Только я не умею писать сервера

Надо изучить сокеты Беркли и попробовать что-то написать. Я могу придумать задачку. Для начала: сделай 2 приложения, клиент и сервер. Сервер открывает порт X, слушает его, принимает соединение, выводит, все, что передано, в консоль, закрывает соединение, ждет следущее и так бесконечно. Клиент при запуске соединяется с сервером на порту X и передает указанное в аргументах командной строки сообщение.

Писать можно на любом языке, синхронно или асинхронно.

Если вдруг ты это уже делал и знаешь, и хочешь писать именно на Ноде, то изучай express например. Сделай на нем hello world.

> в мануале по Angular'у написано что их пример сервера не безопасен
Ну значит надо написать безопасный. Хотя было бы хорошо пояснить, что в нем не так.

> Я склоняюсь к тому чтобы попробовать React, и если с ним не получится, то написать на ES6, и если и на нём не получится, то на Node.js уж железно получится написать приложение на Angular'е!

Ты по моему смешал все в кучу. ES6 - это версия JS, а React/Angular - фреймворки. Писать можно на любом.

> то на Node.js уж железно получится написать приложение на Angular'е!
Angular предназначен для браузера. На ноде он запускается только для серверного рендеринга и это дополнительная фича, а не основное предназначение. На сервере, если ты пишешь чисто серверное приложение, ни ангулар, ни реакт не нужны, это оверинжиниринг, там хватит обычного шаблонизатора, коих под ноду предостаточно.

Не путай:

- обычный шаблонизатор: получает данные и шаблон, рендерит HTML. Самое то для сервера.
- ангулар/реакт: получает данные ("модель" или view model, что точнее) и шаблон, рендерит, записывает в DOM, данные меняются, он снова рендерит и снова обновляет ДОМ. Так пока пользователь не закроет вкладку. Самое то для браузерного приложения.

Разница между реакт и ангулар в том, как они ищут изменения.

- ангулар сравнивает текущее состояние модели с сохраненным предыдущим, находит различия и обновляет части DOM, которые зависели от этих данных (то есть рендерит заново только кусок шаблона с измененными данными). Например, если ты в поле ввода вывел значение из модели, потом поменял его в модели, ангулар перерендерит и обновит поле ввода.
- реакт берет модель, рендерит из нее DOM и сранивает этот DOM с текущим на странице. Находит различия и вносит изменения в DOM на странице.

Ну и еще, реакт это только библиотека для view, а англуар это полноценный фреймворк, который предоставляет еще например DI, и многое другое.
Ответы: >>1279602
someApprentice 2018/10/15 16:36:34  №1279602 10
>>1278720
>>1278721
Мне нравится 3-ий вариант с дублированием кода, но я не могу понять, почему это плохо - это плата за то чтобы опыт пользователя был максимально комфортным.

В отличие от первых двух вариантов, не нужно иметь отдельно и JS и PHP приложения на сервере, а можно обойтись чем-то одним - либо PHP, либо JS.

И к тому же, всё равно не получится не дублировать код. Допустим в упрощенной HTML версии есть формы (поиск или отправка сообщения), их всё равно нужно будет обработать на сервере так же как и в фронтенд JS приложении. Разве это не так?

Если не существует универсальной архитектуры, то почему бы не применить и такой способ?


Однако, как я уже писал, первые два способа можно улучшить, отказавшись от PHP и писать API на Node.js.

И даже можно ещё сильнее улучшить это, отрисовав Angular приложение на сервере Node.js, и тогда не придётся дублировать код. Только придётся самому написать библиотеку для проброса кукисов и не ясно какой ответ будет от запросов отправленных с упрощенной HTML страницы, и как там с промисами. Но об этом позже.

>> Я склоняюсь к тому чтобы попробовать React, и если с ним не получится, то написать на ES6, и если и на нём не получится, то на Node.js уж железно получится написать приложение на Angular'е!
>Ты по моему смешал все в кучу. ES6 - это версия JS, а React/Angular - фреймворки. Писать можно на любом.
Я имел ввиду, что у меня есть три варианта на чем написать приложение.

1) PHP (Symfony) + React на фронтенде

+Использование популярного фронтенд фреймворка
+Сверх быстрая отрисовка DOM

-Нужно изучить React, чтобы понять может ли он подхватывать отрендеренную на сервере HTML страницу и дальше работать как SPA приложение
-Дублирование серверного и клиентского кода

2) PHP (Symfony) + нативное ES6 приложение на фронтенде

+Не нужно ничего изучать

-Вспомогательные библиотеки могут не соответствовать заданным требованиям
-Дублирование серверного и клиентского кода

3) Node.JS + Angular

+Использование популярного фронтенд фреймворка
+Возможность подключить модуль platform-server на сервере Node.js и легко вывести отрендеренную Angular'ом стрианицу

-Нужно изучить устройство Node.js сервера
-Нужно написать безопасный скрипт для подъема сервера и вызова renderModuleFactory
-Нужно пробрасывать кукисы используя не совершенный подход, ранее испытав неудачный опыт с Angular Socket Engine (это тяжело поддерживать)
-Неизвестно какой ответ будет от запросов отправленных с упрощенной HTML страницы и будут ли промисы выполняться тоже на серверной части или "подхватяться" на клиентской


И ещё мне сейчас пришел 4-ый вариант с вашей подсказкой про socket_create():

4) PHP (Symfony) + Angular

+Использование популярного фронтенд фреймворка
+PHP отвечает и за отрисовку и за API приложения
+Не нужно ничего изучать

-Нужно пробрасывать кукисы используя не совершенный подход, ранее испытав неудачный опыт с Angular Socket Engine (это тяжело поддерживать)
-Неизвестно какой ответ будет от запросов отправленных с упрощенной HTML страницы и будут ли промисы выполнятся тоже на серверной части или "подхватяться" на клиентской
-Тяжело поддерживать, в связи с тем, что данный подход мало известен, т.е. имеет низкий порог вхождения для сторонних разработчиков


У меня есть пробелы в знаниях, так как отсутствует опыт написания современных JS приложений, поэтому мне бы пригодился ваш совет что из этого выбрать, чтобы было просто надёжно, т.е. чтобы в дальнейшем не возникало каких-то проблем с подхватом HTML страницы JS кодом, чтобы после подхвата работало полноценное SPA приложение, чтобы на сервере не нужно было без конца применять не совершенные подходы. Если сократить, то вопрос будет простым - может ли React подхватывать HTML страницу и вешать на неё JS?



>Кстати, у тебя там server.close() вместо client.close().
Где? $connection это же вроде client, разве нет?
Ответы: >>1282527 >>1282687
Аноним 2018/10/22 12:18:54  №1282687 11
>>1279602

> Мне нравится 3-ий вариант с дублированием кода, но я не могу понять, почему это плохо - это плата за то чтобы опыт пользователя был максимально комфортным.

Потому, что это на практике очень плохо будет работать. Разработчик добавляет новую фичу в JS-версию. Не забудет ли он добавить ее в PHP-версию? Или исправление бага. Плюс, удваиваются затраты времени. Дублирование кода - почти всегда плохо. Если же код не дублировать, то такие ситуации, когда 2 версии кода работают по-разному, сразу исключаются.

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

Конечно, бывают случаи, когда это оправданно. Например, фейсбук делал легкую HTML версию для слабых телефонов. У Gmail есть легкая версия. У фейсбука это полностью отдельное приложение. Раньше многие сайты делали мобильную версию отдельными контроллерами и шаблонами.

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

Если и делать PHP версию, то максимально простую. Ведь цель - слабые устройства с ограниченным трафиком. Но тогда придется делать ее на отдельном URL (https://messenger/h/), к ней не получится прицепиться из реакт-приложения (да и если она выглядит по-другому, то пользователя не обрадует внезапная смена внешнего вида в процессе загрузки).

> И к тому же, всё равно не получится не дублировать код. Допустим в упрощенной HTML версии есть формы (поиск или отправка сообщения), их всё равно нужно будет обработать на сервере так же как и в фронтенд JS приложении. Разве это не так?

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

Кстати, в классическом HTML надо бы добавить в форму уникальное число - nonce - для защиты от случаев, когда плохая связь и пользователь несколько раз отправляет форму с сообщением.

> Однако, как я уже писал, первые два способа можно улучшить, отказавшись от PHP и писать API на Node.js.

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

> +Сверх быстрая отрисовка DOM
Ой-ой. "Быстрый" в реакте - это значит "быстрый в сравнении с другими фреймворками, делающими сравнение деревьев DOM", а не быстрее любых JS библиотек.

Но реакт имеет еще плюс в том, что есть React Native - это для написания нативных приложений (в первую очередь мобильных), где ты пишешь код на JS, но без HTML/CSS, вместо них ты используешь нативные для платформы виджеты (кнопки, текстовые блоки и тд).

> -Нужно изучить React, чтобы понять может ли он подхватывать отрендеренную на сервере HTML страницу и дальше работать как SPA приложение

Я погуглил и нашел это: https://reactjs.org/docs/react-dom-server.html - если ты используешь то же самое приложение на сервере, то думаю, что можно:

> If you call ReactDOM.hydrate() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.

> 2) PHP (Symfony) + нативное ES6 приложение на фронтенде
И еще там больше работы в сравнении с реакт.

> Неизвестно какой ответ будет от запросов отправленных с упрощенной HTML страницы и будут ли промисы выполняться тоже на серверной части или "подхватяться" на клиентской

Промисы никак не подхватятся. Они работают только внутри процесса ОС (промис это обертка для результата выполнения асинхронной операции). Если ты запустил чтение файла на сервере, оно не продолжится на клиенте (там банально нет этого файла). Если обработчик отправки сообщения на сервере сделан на промисах, надо дождаться их завершения перед генерацией HTML кода. Что касается ответа на отправку формы - он должен быть классическим HTML, чтобы браузер без JS мог его отобразить.

> И ещё мне сейчас пришел 4-ый вариант с вашей подсказкой про socket_create():

Получать запросы в PHP коде и проксировать их в серверное приложение? Можно, но не очень понятно, зачем, если можно поднять сервер на ноде и работать без посредника на PHP.

> что из этого выбрать, чтобы было просто надёжно, т.е. чтобы в дальнейшем не возникало каких-то проблем с подхватом HTML страницы JS кодом

Это есть и в реакт, и в ангулар. Чтобы это реализовать, страницу надо генерировать на сервере из того же шаблона тем же приложением. Из двух я бы выбрал реакт из-за популярности и наличия React Native.

> Если сократить, то вопрос будет простым - может ли React подхватывать HTML страницу и вешать на неё JS?
Да. https://reactjs.org/docs/react-dom-server.html
Ответы: >>1282804 >>1282816
someApprentice 2018/10/22 15:21:00  №1282816 12
>>1282687
* Забыл написать

>В нашем случае еще и немного ухудшается безопасность, идеальнее было бы расшифровывать сообщения только на клиенте.
Я тоже думал об этом, и мне пришла в голову мысль, что обычные чаты шифруются более просто, а для GPG использовать особые "секретные чаты" как в телеграмме, которые будт работать только с включенным JS или в нативных приложениях. Причем можно добавить функцию, чтобы пользователь мог сам загружать свои приватные/публичные ключи. Хранить приватные ключи на сервере, конечно же плохая идея, даже если они зашифрованы паролем.
Ответы: >>1282918 >>1282922
Аноним 2018/10/22 17:19:19  №1282918 13
>>1282816

А, кстати, я вспомнил, есть интересный мессенджер Tox - он работает вообще без серверов, за счет DHT сети (как в торрентах) и прямых соединений между клиентами. И тоже все шифрует. Может еще из него можно какие-то идеи подчерпнуть.
Ответы: >>1282992
Аноним 2018/10/22 17:20:24  №1282922 14
>>1282816

Такие компромиссы не очень хорошие, приложение либо надежно, либо нет. В твоем варианте, те, кто использует секретные чаты, будут выделяться среди пользователей и привлекать внимание.
Ответы: >>1282992
someApprentice 2018/10/22 19:08:24  №1282992 15
>>1282918
Помню-помню что вы писали об этом. У меня всё это отмечено и будет изучено когда дойдёт очередь.

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

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

Или просто хранить приватный ключ зашифрованный паролем на сервере. Но мне такая идея просто не нравится, хоть у меня и нету весомых причин для этого, кроме той, что если пароль от приватного ключа слишком простой, то его можно просто брутфорснуть (но это вина пользователя что он имеет слабый пароль).
Ответы: >>1285905
Аноним 2018/10/28 12:41:20  №1285905 16
>>1282992

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

Да. Получается, есть такие решения:

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

Второй вариант мне кажется адекватным. Он же используется в эппловском iMessage.

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

Заметь, что отправка сообщения на несколько устройств похожа на отправку сообщения в чат с несколькими пользователями.

Кстати, есть такая интересная штука, как 2FA. Это второй фактор для защиты от случая, когда у пользователя украли пароль. Есть такие протоколы TOTP и HOTP: https://ru.wikipedia.org/wiki/Google_Authenticator . Вместо программы от Гугла можно использовать свободную программу из F-Droid, не требующую доступа в интернет.

Выглядит это так:

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

Проверка: при логине пользователь запускает приложение на смартфоне и вводит показанный им цифровой код в дополнение к паролю.

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

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

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

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

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

Вообще, конечно, правильнее могло бы быть не изобретать свои протоколы, а взять какой-то сушществующий, благо их много: https://en.wikipedia.org/wiki/Comparison_of_instant_messaging_protocols (да, немного поздно об этом думать).