PDA

Просмотр полной версии : Typo3 7, Websockets и чашка кофе


alexk
05.01.2017, 23:54
Кофе на любителя..

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

Что и откуда брать:

https://getcomposer.org/ (У кого нет)
Ну и http://pecl.php.net/package/zmq модуль ZMQ для апача. (В моем случае все действо творится на XAMP 3.2.1 PHP 5.6 ) в моем случае заработал php_zmq-1.1.2-5.6-nts-vc11-x86
За основу возьмем часть функционала из этого экста: https://github.com/daCyberpunk/websockets (Спасибо Тарасу!)


TYPO3 7.4-7.6!!! 8ку еще не ставил, а ниже версии уже нет.

Ставим ZMQ для апача и composer в первую очередь:
ZMQ на XAMPP так:
libzmq.dll - php\
php_zmq.dll - php\ext
+ добавляем в php.ini

Ставим Ratchet

cmd > mysite
composer requier cboden/retchat v0.3.5
composer requier react/zmq v0.3.0

Ждем..

Чистим Кэш и папку typo3Temp

Ставим Websockets и правим.

По порядку:

Удаляем папку Vendor и все composer файлы из \websockets. Они нам не нужны, там стоит ratchet 2.0.
Открываем \websockets\Classes\ ServerCommand.php и правим action_start()

У вас должно остаться только это:

$extPath = ExtensionManagementUtility::extPath('websockets');

// Check whether websockets-server is already running
$thisProcess = new ThisProcess($extPath);

if (!$thisProcess->already_running) { // Start websockets-server
$websocket = new IoServer::factory(
new \Ratchet\Http\HttpServer(
new WsServer(
new Connection()
)
),
9091
);
$websocket ->run();
} else {
$GLOBALS['BE_USER']->writelog(4, 0, 1, 0, 'Attempting to start Notify WebSockets Server while it is already running', '');
}

Порт я специально поставил отличный от 8080.. но можно и его оставить.


Открываем \websockets\Classes\Service\ConnectionTest.php И приводим его к такому виду:


$ref->addJsInlineCode(
'WS_ConnectionTest',
// Authentication parameters for fe-users:
GeneralUtility::minifyJavaScript(
'var GP_auth = ' . json_encode(
array(
'FE_SESSION_KEY' => rawurlencode(
$GLOBALS['TSFE']->fe_user->id . '-' .
md5(
$GLOBALS['TSFE']->fe_user->id . '/' .
$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
)
),
'vC' => $GLOBALS['TSFE']->fe_user->veriCode()
)
) . ',' .
'WS_isRunning = ' . (GeneralUtility::makeInstance('TYPO3\\CMS\\Websock ets\\Server')->isRunning() ? 'true' : 'false') . ';'
) . "\n"

// Connection test:
. <<<'EOT'
if (WS_isRunning) {
// Set protocol
var protocol = 'ws';
if (window.location.protocol === 'https:') {
protocol += 's';
}
// Establish connection
var ws_conn = new WebSocket(protocol + '://127.0.0.1:9091/?FE_SESSION_KEY=' + GP_auth.FE_SESSION_KEY);
ws_conn.onopen = function(e) {
console.log("Connection established!\nNow you may use WebSocket 'ws_conn' object for testing...");
};
ws_conn.onmessage = function(e) {
console.log(e.data);
};
} else {
console.log('WebSockets in testing-mode. WS_Server is not started. Please, start the server from AdminPanel->Scheduler section.');
}
EOT


protocol + '://127.0.0.1:9091/ - тут я для наглядности оставил 127.0.0.1, можно оставить как в оригинале. Только порт меняем.

Собственно все, добавляем задачи в Scheduler и запускаем сервер!
Если все ок, то вы увидите в консоле что коннект есть, но вот FE-User: false.
В консоле можно отправить сообщение ws_conn.send(‘Hello Obama’);

Правим аутентификацию юзеров.

!Sic, для начало нужно залогиниться как FE User.
!Sic2, если все равно fe-user: false, то у вас та же проблема что и у меня.. (Может касаться только локального хоста, нужно еще раз проверять на сервере.. а по симу далее ненужные строки комментируем, а не удаляем.)
В чем проблема, а проблема в том, что $_SERVER не содержит REMOTE_HOST и HTTP_USER_AGENT, а без них проверка через EID сервис не работает. Вот и все.
Ну ок, не работает и ладно, мы откроем свой луна-парк с блэк-джеком и тайскими трансвеститами.

Делаем HOOK!


\websockets\Classes\Hook\AuthFeUserHook.php

Вписываем его в ext_localconf.php

### Hook for FE User Auth
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][] = MY\EXT\Hook\AuthFeUserHook::class . '->get_user';
###

Сам HOOK:

<?php

namespace MY\EXT\Hook;

/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Authentication\AbstractUserAuthenti cation;
/**
* Authentification Be USer Hook
*
*/
class AuthFeUserHook
{
/**
* Params
*/
public $params;

/**


/**
* @var DatabaseConnection
*/
protected $db = null;

/**
* Initialize some important variables
*/
public function __construct()
{
$this->db = $this->getDatabaseConnection();
}

/**
* Process User Authentification
*
* @param array $params
* @param AbstractUserAuthentication $authentication
* @see AbstractUserAuthentication
* @return void
*/
public function get_user($params, AbstractUserAuthentication $authentication){

if(
strpos(GeneralUtility::getIndpEnv('TYPO3_REQUEST_S CRIPT'), 'typo3\cli_dispatch') &&
$params['pObj']->session_table == 'fe_sessions' &&
!$params['pObj']->user
){
$this->params = $params['pObj'];
$statement = $this->fetchUserSessionFromDB();
if ($statement) {
$statement->execute();
$params['pObj']->user = $statement->fetch();
$statement->free();
}

}

}

/*************************
*
* SQL Functions FROM \TYPO3\CMS\Core\Authentication\AbstractUserAuthent ication;
*
*************************/
/**
* The session_id is used to find user in the database.
* Two tables are joined: The session-table with user_id of the session and the usertable with its primary key
* if the client is flash (e.g. from a flash application inside TYPO3 that does a server request)
* then don't evaluate with the hashLockClause, as the client/browser is included in this hash
* and thus, the flash request would be rejected
*
* @return \TYPO3\CMS\Core\Database\PreparedStatement
* @access private
*/
protected function fetchUserSessionFromDB()
{
$statement = null;
$statement = $this->db->prepare_SELECTquery('*', $this->params->session_table . ',' . $this->params->user_table, $this->params->session_table . '.ses_id = :ses_id
AND ' . $this->params->session_table . '.ses_name = :ses_name
AND ' . $this->params->session_table . '.ses_userid = ' . $this->params->user_table . '.' . $this->params->userid_column . ' ' .
$this->user_where_clause());
$statement->bindValues(array(
':ses_id' => $this->params->id,
':ses_name' => $this->params->name
));
return $statement;
}

/**
* This returns the where-clause needed to select the user
* with respect flags like deleted, hidden, starttime, endtime
*
* @return string
* @access private
*/
protected function user_where_clause()
{
$whereClause = '';
if ($this->params->enablecolumns['rootLevel']) {
$whereClause .= 'AND ' . $this->params->user_table . '.pid=0 ';
}
if ($this->params->enablecolumns['disabled']) {
$whereClause .= ' AND ' . $this->params->user_table . '.' . $this->params->enablecolumns['disabled'] . '=0';
}
if ($this->params->enablecolumns['deleted']) {
$whereClause .= ' AND ' . $this->params->user_table . '.' . $this->params->enablecolumns['deleted'] . '=0';
}
if ($this->params->enablecolumns['starttime']) {
$whereClause .= ' AND (' . $this->params->user_table . '.' . $this->params->enablecolumns['starttime'] . '<=' . $GLOBALS['EXEC_TIME'] . ')';
}
if ($this->params->enablecolumns['endtime']) {
$whereClause .= ' AND (' . $this->params->user_table . '.' . $this->params->enablecolumns['endtime'] . '=0 OR '
. $this->params->user_table . '.' . $this->params->enablecolumns['endtime'] . '>' . $GLOBALS['EXEC_TIME'] . ')';
}
return $whereClause;
}


/**
* Get global database connection
* @return DatabaseConnection
*/
protected function getDatabaseConnection()
{
return $GLOBALS['TYPO3_DB'];
}

}


Собственно что происходит, EidUtility во время проверки шлет в хук authdata, мы смотрим есть ли юзер или нет, проверяем что скрипт был запущен от имени typo3\cli_dispatch ну и что идет проверка FE User, т.е. таблица session_table == 'fe_sessions'.

Остальные функции это немного модифицированные из AbstractUserAuthentication.

Еще можно поправить \websockets\Classes\Service\Connection.php -> initFeUser($connection)

Убираем лишнее (комментируем)
// Set 'FE_SESSION_KEY'
$FE_SESSION_KEY = $connection->WebSocket->request->getQuery()->get('FE_SESSION_KEY');

if ($FE_SESSION_KEY) {
GeneralUtility::_GETset($FE_SESSION_KEY, 'FE_SESSION_KEY');
}
$fe_user = EidUtility::initFeUser();

if ($fe_user->user) { // Authenticated successfully
if ($this->writeDevLog) {
GeneralUtility::devLog('User Autherized: ' . $fe_user->user['username'], __CLASS__);
}
return $fe_user;
} else {
if ($this->writeDevLog) {
GeneralUtility::devLog('No User Autherized!', __CLASS__);
}
throw new \Exception('FE-user can not be authenticated.');
}


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

И так, что мы можем теперь? Не много… например чат.

Делаем PUSH

В шапке сайта ставим ссылку на скрипт <script src="http://autobahn.s3.amazonaws.com/js/autobahn.min.js"></script>

Важно, нам нужно версия 0.8.. т.к. Ratchet использует WAMP Server v.1!
Комментируем наш \Websockets\Classes\ ServerCommand.php, и делаем копию.
И приводим acton_start к виду:


// в Шапке
use \Ratchet\Wamp\WampServer;
use \Ratchet\WebSocket\WsServer;
use \Ratchet\Server\IoServer;
use \React\ZMQ\Context;

private function action_start() {
$extPath = ExtensionManagementUtility::extPath('websockets');

// Check whether websockets-server is already running
$thisProcess = new ThisProcess($extPath);

if (!$thisProcess->already_running) { // Start websockets-server

$loop = \React\EventLoop\Factory::create();
$pusher = new \MyApp\Pusher;

// Listen for the web server to make a ZeroMQ push after an ajax request
$context = new Context($loop);

$socketWrapper = $context->getSocket(\ZMQ::SOCKET_PULL);
$socketWrapper->bind('tcp://*:5555');
$socketWrapper->on('message', array($pusher, 'onBlogEntry'));

$webSock = new \React\Socket\Server($loop);
$webSock->listen(9092);

$notifyWamp = new IoServer(
new \Ratchet\Http\HttpServer(
new WsServer(
new WampServer(
$pusher
)
)
),
$webSock
);

$loop->run();
} else {
$GLOBALS['BE_USER']->writelog(4, 0, 1, 0, 'Attempting to start Notify WebSockets Server while it is already running', '');
}
}

Если сравните с документаций по Ratchet http://socketo.me/docs/push то там будет разница, в основном не существенная.

Далее, делаем $pusher = new \MyApp\Pusher;
Сохраняете куда пожелаете, главное namespace если меняете то и тут так же меняйте.

<?php
namespace MyApp;
use \Ratchet\ConnectionInterface;
use \Ratchet\Wamp\WampServerInterface;

class Pusher implements WampServerInterface {

/**
* A lookup of all the topics clients have subscribed to
*/
protected $subscribedTopics = array();

public function onSubscribe(ConnectionInterface $conn, $topic) {
$this->subscribedTopics[$topic->getId()] = $topic;
$topic->broadcast($topic->getId());
}

/**
* @param string JSON'ified string we'll receive from ZeroMQ
*/
public function onBlogEntry($entry) {

$entryData = json_decode($entry, true);

// If the lookup topic object isn't set there is no one to publish to
if (!array_key_exists($entryData['category'], $this->subscribedTopics)) {
return;
}

$topic = $this->subscribedTopics[$entryData['category']];

// re-send the data to all the clients subscribed to that category
$topic->broadcast($entryData);
}

/* The rest of our methods were as they were, omitted from docs to save space */

public function onUnSubscribe(ConnectionInterface $conn, $topic) {

}

public function onOpen(ConnectionInterface $conn) {
}

public function onClose(ConnectionInterface $conn) {
}

public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
// In this application if clients send data it's because the user hacked around in console
$conn->callError($id, $topic, 'You are not allowed to make calls')->close();
}

public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
// In this application if clients send data it's because the user hacked around in console
$conn->close();
}

public function onError(ConnectionInterface $conn, \Exception $e) {
}
}

В своем тестовом Классе, который можно будет вызвать (У меня Ajax обработчик) пишем следующее:

$entryData = array(
'category' => 'kittensCategory',
'title' => 'test',
'article' => 'test test test'
);

$context = new \ZMQContext(1);

$socket = $context->getSocket(\ZMQ::SOCKET_PUSH, 'my pusher');
$socket->setSockOpt(\ZMQ::SOCKOPT_LINGER, 30); //ADDED
$socket->connect("tcp://127.0.0.1:5555");
$socket->send(json_encode($entryData));

Обновляем, в косоле увидем kittensCategory - это значит что скрипт подключился и подписался.

Шлем аджакс запрос на запуск нашей аджакс функции и в консоле увидем:
Object {category: "kittensCategory", title: "test", article: "test test test"}

Что может быть не так:
1. Сообщение приходит через раз, проверяем ZMQ пробуем разные версии.
2. Не коннектится, у меня было из за кривых ручек, не ту версию autobhn использовал, или порты не те.

Собственно все, спасибо за внимание.

Николай Сипко
09.01.2017, 18:29
Начнем, в связи с тем, что я потратил не одну чашку кофе и не одну пачку сигарет на понимание как запустить полноценные сокеты на Typo

Вопрос из чистого любопытства:

https://typo3.org/extensions/repository/?id=23&L=0&q=chat&tx_solr[filter][extensionMinTYPO3Version]=&tx_solr[filter][extensionMaxTYPO3Version]=

Почему эти расширения не подошли?

"В чем разница между socket'ом и websocket'ом?"

http://ru.stackoverflow.com/questions/507746/%D0%92-%D1%87%D0%B5%D0%BC-%D1%80%D0%B0%D0%B7%D0%BD%D0%B8%D1%86%D0%B0-%D0%BC%D0%B5%D0%B6%D0%B4%D1%83-socket%D0%BE%D0%BC-%D0%B8-websocket%D0%BE%D0%BC

alexk
11.01.2017, 13:11
Может и подошло бы, да только мне не чат нужен.. мне нужен был голый Сокет.
На базе сокетов у меня нотификация, обновления активных элементов (например локальных курсов валют, которые обновляются каждые 5 мин и хранятся в базе), биллинговая нотификация, букинги в режиме онлайн.. короче, много своих экстеншенов, которые используют одно сервисное приложение как сервер, в котором есть пара разных сокетов.

Николай Сипко
11.01.2017, 16:37
Может и подошло бы, да только мне не чат нужен.. мне нужен был голый Сокет.
На базе сокетов у меня нотификация, обновления активных элементов (например локальных курсов валют, которые обновляются каждые 5 мин и хранятся в базе), биллинговая нотификация, букинги в режиме онлайн.. короче, много своих экстеншенов, которые используют одно сервисное приложение как сервер, в котором есть пара разных сокетов.
__________________
Фанат TYPO!

Но при чем здесь CMS TYPO3?

alexk
31.01.2017, 14:16
Ну как сказать))) Проект весь на Typo3.. думаю по этому.

Николай Сипко
31.01.2017, 15:59
Ну как сказать))) Проект весь на Typo3.. думаю по этому.

WebSocket — протокол полнодуплексной связи (может передавать и принимать одновременно) поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени. В настоящее время в W3C осуществляется стандартизация API Web Sockets.
https://ru.wikipedia.org/wiki/WebSocket

TYPO3 (тайпо три) — система управления сайтами (CMS/CMF) с открытым исходным кодом и свободной лицензией. Является гибкой расширяемой системой с большим количеством модулей и функций. Написана на PHP, для хранения данных использует любую реляционную базу данных, поддерживаемую TYPO3 DBAL, включая MySQL, Oracle Database, PostgreSQL и другие. Работает на таких серверах, как Apache или IIS, и на большинстве операционных систем, таких как Linux, Microsoft Windows, FreeBSD, Mac OS X и OS/2. Система создана Каспером Скорхёем и распространяется бесплатно под лицензией GNU GPL.
https://ru.wikipedia.org/wiki/TYPO3

TYPO3 использует LAMP (серверные порты, технологии и операционные системы), но не задает их свойства