Skip to content

Instantly share code, notes, and snippets.

@believer-ufa
Last active August 29, 2015 14:26
Show Gist options
  • Save believer-ufa/3fc79bc01c8c33eb16a5 to your computer and use it in GitHub Desktop.
Save believer-ufa/3fc79bc01c8c33eb16a5 to your computer and use it in GitHub Desktop.
Riot serverside render example
<?php
/**
* Делает запрос на Node.js-приложение для получения результата
* рендеринга определённого riot-модуля
* @param string $name Название модуля
* @param array $params Параметры, которые будут переданые модулю
* @return string
*/
function render_riot_module($name, $params) {
$result = shell_exec('riot-server-start.sh');
if ($result === "starting new server\n") {
// Чуток подождём, если riot-сервер был запущен в данном запросе,
// чтобы он успел стартануть
sleep(2);
}
$response = UnixSocketHttpClient::quickPost('unix://riot-server.socket', '/riot-module', [
'tag' => $name,
'params' => json_encode($params)
]);
if ($response === false OR $response === 'RENDER ERROR') {
throw HTTP_Exception::factory(500, 'Riot component rendering error. Component name: ' . $name);
}
return $response;
}
<?
/**
* Основной шаблон для расширенного списка объектов
*/
$list_id = uniqid('adv-list-');
$module_params = [];
?>
<div id="<?=$list_id?>">
<?=render_riot_module('advanced_list', $module_params)?>
</div>
<script>
AdvancedListApp = riot.mount('#<?=$list_id?>', 'advanced-list', <?=json_encode($module_params)?>)[0];
RiotApps.create(AdvancedListApp);
</script>
{
"private": true,
"dependencies": {
"body-parser": "^1.13.3",
"express": "^4.13.3",
"jquery": "^2.1.4",
"jsdom": "^3.1.2",
"riot": "^2.2.2",
"sugar": "^1.4.1"
}
}
#!/bin/bash
BASEDIR=`dirname $0`
PROJECT_PATH=`cd $BASEDIR; pwd`
# Эта переменная используется для названия screen-сессии
PROJECT_PATH_DASHED="${PROJECT_PATH//\//--}"
if ! screen -ls | grep -q "riot_server_${PROJECT_PATH_DASHED}"; then
echo "starting new server"
screen -dmS "riot_server_${PROJECT_PATH_DASHED}" node ${PROJECT_PATH}/riot-server.js
else
echo "already started"
fi
/**
* Данный файл позволяет запустить собственный nodejs сервер и принимать соединения на unix-сокет,
* создающийся в директории веб-приложения.
*
* Через подключение к данному сокету мы можем получить HTML-представление определённого
* riot-компонента, что и делается в PHP-коде. Функция, отвечающая за подключение к
* данному серверу, называется render_riot_module($name, $params) и располагается
* в файле functions.php
*/
var app = require('express')();
var riot = require('riot');
require('sugar');
var env = require('jsdom').env, html = '<html><body></body></html>';
env(html, function (errors, window) {
// Подключим жиквери к нашему серверу
var jQuery = require('jquery')(window);
global.$ = jQuery;
global.jQuery = jQuery;
require('./themes/js/class/riot-components/raw.tag');
try { // Удалим старый сокет, если он есть. Если нет - ну и ладно.
require('fs').unlinkSync('riot-server.socket');
} catch (error) {}
/**
* Список наших riot-компонентов, которые мы будем рендерить на сервере
*/
var tags = {
advanced_list : require('./modules/36n6-widget-advanced-list/assets/advanced-list.tag')
};
app.use(require('body-parser').urlencoded({ extended: true }));
app.post('/riot-module', function(request, response) {
if (! request.body.params || ! request.body.tag) {
response.send('please send params');
return;
}
var params = JSON.parse(request.body.params);
try {
var html = riot.render(tags[request.body.tag], params);
} catch (error) {
console.log('component render error :(', error);
response.send('RENDER ERROR');
}
console.log('Generated a "' + request.body.tag + '" riot module');
response.send(html);
});
app.listen('./riot-server.socket', function() {
console.log('Riot modules app listening');
});
});
<?php
/**
* Этот код основан наоснове HTTPClient Simon Willison ( http://simon.incutio.com/ )
* Manual: http://scripts.incutio.com/httpclient/
*
* Я лишь немного переделал его для возможности отправлять данные
* на сокет через один статический метод.
**/
class UnixSocketHttpClient {
// Request vars
var $file;
var $path;
var $method;
var $postdata = '';
var $cookies = array();
var $referer;
var $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
var $accept_encoding = 'gzip';
var $accept_language = 'en-us';
var $user_agent = 'Incutio UnixSocketHttpClient v0.9';
// Options
var $timeout = 20;
var $use_gzip = true;
var $persist_cookies = true; // If true, received cookies are placed in the $this->cookies array ready for the next request
// Note: This currently ignores the cookie path (and time) completely. Time is not important,
// but path could possibly lead to security problems.
var $persist_referers = true; // For each request, sends path of last request as referer
var $debug = false;
var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found
var $max_redirects = 5;
var $headers_only = false; // If true, stops receiving once headers have been read.
// Basic authorization variables
var $username;
var $password;
// Response vars
var $status;
var $headers = array();
var $content = '';
var $errormsg;
// Tracker variables
var $redirect_count = 0;
var $cookie_host = '';
function __construct($file) {
$this->file = $file;
}
function get($path, $data = false) {
$this->path = $path;
$this->method = 'GET';
if ($data) {
$this->path .= '?'.$this->buildQueryString($data);
}
return $this->doRequest();
}
function post($path, $data) {
$this->path = $path;
$this->method = 'POST';
$this->postdata = $this->buildQueryString($data);
return $this->doRequest();
}
function buildQueryString($data) {
$querystring = '';
if (is_array($data)) {
// Change data in to postable data
foreach ($data as $key => $val) {
if (is_array($val)) {
foreach ($val as $val2) {
$querystring .= urlencode($key).'='.urlencode($val2).'&';
}
} else {
$querystring .= urlencode($key).'='.urlencode($val).'&';
}
}
$querystring = substr($querystring, 0, -1); // Eliminate unnecessary &
} else {
$querystring = $data;
}
return $querystring;
}
function doRequest() {
// Performs the actual HTTP request, returning true or false depending on outcome
if (!$fp = @fsockopen($this->file, 0, $errno, $errstr, $this->timeout)) {
// Set error message
switch($errno) {
case -3:
$this->errormsg = 'Socket creation failed (-3)';
case -4:
$this->errormsg = 'DNS lookup failure (-4)';
case -5:
$this->errormsg = 'Connection refused or timed out (-5)';
default:
$this->errormsg = 'Connection failed ('.$errno.')';
$this->errormsg .= ' '.$errstr;
$this->debug($this->errormsg);
}
return false;
}
socket_set_timeout($fp, $this->timeout);
$request = $this->buildRequest();
$this->debug('Request', $request);
fwrite($fp, $request);
// Reset all the variables that should not persist between requests
$this->headers = array();
$this->content = '';
$this->errormsg = '';
// Set a couple of flags
$inHeaders = true;
$atStart = true;
// Now start reading back the response
while (!feof($fp)) {
$line = fgets($fp, 4096);
if ($atStart) {
// Deal with first line of returned data
$atStart = false;
if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
$this->errormsg = "Status code line invalid: ".htmlentities($line);
$this->debug($this->errormsg);
return false;
}
$http_version = $m[1]; // not used
$this->status = $m[2];
$status_string = $m[3]; // not used
$this->debug(trim($line));
continue;
}
if ($inHeaders) {
if (trim($line) == '') {
$inHeaders = false;
$this->debug('Received Headers', $this->headers);
if ($this->headers_only) {
break; // Skip the rest of the input
}
continue;
}
if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
// Skip to the next header
continue;
}
$key = strtolower(trim($m[1]));
$val = trim($m[2]);
// Deal with the possibility of multiple headers of same name
if (isset($this->headers[$key])) {
if (is_array($this->headers[$key])) {
$this->headers[$key][] = $val;
} else {
$this->headers[$key] = array($this->headers[$key], $val);
}
} else {
$this->headers[$key] = $val;
}
continue;
}
// We're not in the headers, so append the line to the contents
$this->content .= $line;
}
fclose($fp);
// If data is compressed, uncompress it
if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
$this->debug('Content is gzip encoded, unzipping it');
$this->content = substr($this->content, 10); // See http://www.php.net/manual/en/function.gzencode.php
$this->content = gzinflate($this->content);
}
// If $persist_cookies, deal with any cookies
if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->file == $this->cookie_host) {
$cookies = $this->headers['set-cookie'];
if (!is_array($cookies)) {
$cookies = array($cookies);
}
foreach ($cookies as $cookie) {
if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
$this->cookies[$m[1]] = $m[2];
}
}
// Record domain of cookies for security reasons
$this->cookie_host = $this->file;
}
// If $persist_referers, set the referer ready for the next request
if ($this->persist_referers) {
$this->debug('Persisting referer: '.$this->getRequestURL());
$this->referer = $this->getRequestURL();
}
// Finally, if handle_redirects and a redirect is sent, do that
if ($this->handle_redirects) {
if (++$this->redirect_count >= $this->max_redirects) {
$this->errormsg = 'Number of redirects exceeded maximum ('.$this->max_redirects.')';
$this->debug($this->errormsg);
$this->redirect_count = 0;
return false;
}
$location = isset($this->headers['location']) ? $this->headers['location'] : '';
$uri = isset($this->headers['uri']) ? $this->headers['uri'] : '';
if ($location || $uri) {
$url = parse_url($location.$uri);
// This will FAIL if redirect is to a different site
return $this->get($url['path']);
}
}
return true;
}
function buildRequest() {
$headers = array();
$headers[] = "{$this->method} {$this->path} HTTP/1.0"; // Using 1.1 leads to all manner of problems, such as "chunked" encoding
$headers[] = "Host: {$this->file}";
$headers[] = "User-Agent: {$this->user_agent}";
$headers[] = "Accept: {$this->accept}";
if ($this->use_gzip) {
$headers[] = "Accept-encoding: {$this->accept_encoding}";
}
$headers[] = "Accept-language: {$this->accept_language}";
if ($this->referer) {
$headers[] = "Referer: {$this->referer}";
}
// Cookies
if ($this->cookies) {
$cookie = 'Cookie: ';
foreach ($this->cookies as $key => $value) {
$cookie .= "$key=$value; ";
}
$headers[] = $cookie;
}
// Basic authentication
if ($this->username && $this->password) {
$headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password);
}
// If this is a POST, set the content type and length
if ($this->postdata) {
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$headers[] = 'Content-Length: '.strlen($this->postdata);
}
$request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata;
return $request;
}
function getStatus() {
return $this->status;
}
function getContent() {
return $this->content;
}
function getHeaders() {
return $this->headers;
}
function getHeader($header) {
$header = strtolower($header);
if (isset($this->headers[$header])) {
return $this->headers[$header];
} else {
return false;
}
}
function getError() {
return $this->errormsg;
}
function getCookies() {
return $this->cookies;
}
function getRequestURL() {
$url = 'http://'.$this->file;
$url .= $this->path;
return $url;
}
// Setter methods
function setUserAgent($string) {
$this->user_agent = $string;
}
function setAuthorization($username, $password) {
$this->username = $username;
$this->password = $password;
}
function setCookies($array) {
$this->cookies = $array;
}
// Option setting methods
function useGzip($boolean) {
$this->use_gzip = $boolean;
}
function setPersistCookies($boolean) {
$this->persist_cookies = $boolean;
}
function setPersistReferers($boolean) {
$this->persist_referers = $boolean;
}
function setHandleRedirects($boolean) {
$this->handle_redirects = $boolean;
}
function setMaxRedirects($num) {
$this->max_redirects = $num;
}
function setHeadersOnly($boolean) {
$this->headers_only = $boolean;
}
function setDebug($boolean) {
$this->debug = $boolean;
}
// "Quick" static methods
function quickGet($url) {
$bits = parse_url($url);
$host = $bits['host'];
$port = isset($bits['port']) ? $bits['port'] : 80;
$path = isset($bits['path']) ? $bits['path'] : '/';
if (isset($bits['query'])) {
$path .= '?'.$bits['query'];
}
$client = new UnixSocketHttpClient($host, $port);
if (!$client->get($path)) {
return false;
} else {
return $client->getContent();
}
}
static function quickPost($file, $path, $data) {
$client = new self($file);
if (!$client->post($path, $data)) {
return false;
} else {
return $client->getContent();
}
}
function debug($msg, $object = false) {
if ($this->debug) {
print '<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>HttpClient Debug:</strong> '.$msg;
if ($object) {
ob_start();
print_r($object);
$content = htmlentities(ob_get_contents());
ob_end_clean();
print '<pre>'.$content.'</pre>';
}
print '</div>';
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment