Кофе на любителя..
Начнем, в связи с тем, что я потратил не одну чашку кофе и не одну пачку сигарет на понимание как запустить полноценные сокеты на 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()
У вас должно остаться только это:
PHP код:
$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 И приводим его к такому виду:
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\\Websockets\\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
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 код:
<?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\AbstractUserAuthentication;
/**
* 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_SCRIPT'), '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\AbstractUserAuthentication;
*
*************************/
/**
* 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)
Убираем лишнее (комментируем)
PHP код:
// 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
Код HTML:
В шапке сайта ставим ссылку на скрипт <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 к виду:
PHP код:
// в Шапке
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 код:
<?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 обработчик) пишем следующее:
PHP код:
$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 использовал, или порты не те.
Собственно все, спасибо за внимание.