I'm trying to use fcimg and wkhtmltoimage in order to export a chart to a PDF report. I keep getting an error from fcimg saying "Fatal error: Uncaught exception 'FCImageException' with message 'There was an error with wkhtmltopdf'[...]".
By debbuging the function inside de fcimg library I've noticed that a couple of 'fwrite()' functions in charge of writing the minified JS scripts of FusionCharts into the file are returning a value of '0' (no writing, but no error), even do the string to write is properly saved into a variable (as the code below). Is there some kind of limit or restrinction to the 'fwrite' function that I'm not aware of? Previous calls to the function work just fine. Any help or advice would be really appreciated.
[...]
function fusioncharts_to_image($outputFilePath, $swfName, $inputString, $height, $width, $options = array())
{
$jsonFlag = false;
if($inputString[0] === '<') // Check for the first character
{
// do nothing. jsonFlag is set to false anyways
}
else if($inputString[0] === "{")
{
$jsonFlag = true;
}
else
{
throw new FCImageException("The input string doesn't seem to be either JSON or XML");
}
$fileType = "png";
if(isset($options['imageType']))
$fileType = $options['imageType'];
$renderDelay = 1000;
if (isset($options['render_delay']) && is_numeric($options['render_delay']))
$renderDelay = $options['render_delay'];
/*
* Note: sys_get_temp_dir returns /tmp on Linux (don't know about osx)
* but on the other hand, it returns C:\user\foo\Local\Temp\ on Windows
*
* so we need to add a directory separator on Linux but not on windows
*/
$separator = "";
if(DIRECTORY_SEPARATOR === "/") // check if unix system. TODO: There will be better ways to do this
$separator = DIRECTORY_SEPARATOR;
$imageFileName = sys_get_temp_dir().$separator."\FCImage".rand(0, 1000).'.'.$fileType;
// $imageFileName = sys_get_temp_dir().”\FCImage”.rand(0, 1000).’.’.$fileType;
// $imageFileName = 'C:\PRUEBA\FCImage'.rand(0, 1000).'.'.$fileType;
$cwd = __DIR__; // change working directory to the current script's directory
$env = array(); // set any environment variables here
// configure the arguments
$args = "--format $fileType";
$args = $args." --crop-w ".($width - 1);
$args = $args." --crop-h ".($height - 1);
if(isset($options['nodelay']))
{
if($options['nodelay'] === true)
$args = $args." --javascript-delay 0";
}
else
{
$args = $args." --javascript-delay {$renderDelay}";
}
if(isset($options['quality']))
{
$args = $args." --quality {$options['quality']}";
}
$debugFlag = false;
if(isset($options['debug']))
{
$debugFlag = true;
$debugFile = fopen("debug.html", "w");
echo "\n\nCall to:\n fusioncharts_to_image ($outputFilePath, $swfName, [removing input string], $height, $width)";
}
// now, determine the platform this is running on
$os = php_uname("s");
$arch = php_uname("m");
if($os==="Windows NT"){
$platform = _FC_IMG_PLATFORM_WINDOWS;
// var_dump($platform);
}
else if($os === "Linux")
{
if($arch === "i386")
$platform = _FC_IMG_PLATFORM_LINUX;
else if($arch === "i686")
$platform = _FC_IMG_PLATFORM_LINUX;
else if ($arch === "x86_64")
$platform = _FC_IMG_PLATFORM_LINUX_64;
else
throw new FCImageException ("This Linux architecture is not supported");
}
else if($os === "Darwin") {
$platform = _FC_IMG_PLATFORM_OSX;
}
else
throw new FCImageException("Your server OS is currently not supported");
$fcRoot = dirname(__FILE__);
$wkCommand = $platform;
if(isset($options['wkhtmltoimage_path'])) {
$wkCommand = $options['wkhtmltoimage_path'];
}
$command = "$wkCommand $args - $imageFileName";
// var_dump($command); echo "<br>";
if($debugFlag)
{
echo "\n Command: $command";
}
$wkstdin = popen($command, "w");
// var_dump($wkstdin);
if(!is_resource($wkstdin))
{
throw new FCImageException("An error took place while trying to open wkhtmltopdf");
}
$templateHead = <<<TOP
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style>
body{
padding:0 0 0 0;
margin:0 0 0 0;
}
</style>
<script>
TOP;
// ok. write template.txt into the process stdin
fwrite($wkstdin, $templateHead);
if($debugFlag)
{
fwrite($debugFile, $templateHead);
}
$contFC = file_get_contents($options['licensed_fusioncharts_js']);
$contFCC = file_get_contents($options['licensed_fusioncharts_charts_js']);
// echo $contFC;
// echo $contFCC;
if(isset($options['licensed_fusioncharts_charts_js']) && isset($options['licensed_fusioncharts_js'])) {
$temp1 = fwrite($wkstdin, $contFC);
var_dump($temp1);
$temp2 = fwrite($wkstdin, $contFCC);
var_dump($temp2);
if($debugFlag)
{
fwrite($debugFile, file_get_contents($options['licensed_fusioncharts_js']));
}
if($debugFlag)
{
fwrite($debugFile, file_get_contents($options['licensed_fusioncharts_charts_js']));
}
}
else {
throw new FCImageException("Need to provide fusioncharts licensed version here");
}
$functionToCall = "setXMLData";
if($jsonFlag === true)
$functionToCall = "setJSONData";
// replace all EOL with ""
$escapedData = str_replace("\n", "", $inputString);
$escapedData = addslashes($escapedData);
$templateCode = "</script>
</head>
<body>
<div id='chartContainer'><small>Loading chart...</small></div>
</body>
<script>
FusionCharts.setCurrentRenderer('javascript');
var chart = new FusionCharts('$swfName', 'chart0', '$width', '$height', '0', '1');
chart.$functionToCall('$escapedData');
chart.render('chartContainer');
</script>
</html>";
$temp = fwrite($wkstdin, $templateCode);
var_dump($temp);
if($debugFlag)
{
fwrite($debugFile, $templateCode);
}
$returnCode = pclose($wkstdin);
// echo(var_dump($returnCode)." returnCode");
if($returnCode !== 0)
{
// var_dump($imageFileName);
if(file_exists($imageFileName)){
unlink($imageFileName);
}
throw new FCImageException("There was an error with wkhtmltopdf ");
}
// success!
rename($imageFileName, $outputFilePath);
return true;
}
Related
I am working with a file chunking solution ( cant remember where I found it ) but I have modified the solution to my needs.
The issue is that although most of the time the file will upload successfully sometimes I get an error
Uncaught SyntaxError: Unexpected token < in JSON at position 0
I also get another error at the same time
( unlink(test/H2jig-6.png): Resource temporarily unavailable in ... )
I was hoping that you guys will be able to spot the issue in my code that is causing this problem. I think that it manages to unlink the files too early before the uploads are done and then is unable to find them.
Upload file HTML/JS
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>test upload by chunk</title>
</head>
<body>
<input type="file" id="f" />
<script>
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
(function() {
var f = document.getElementById('f');
if (f.files.length)
processFile();
f.addEventListener('change', processFile, false);
function processFile(e) {
var scount = 0;
var file = f.files[0];
console.log(file);
var ext = file.name.split('.').pop();
ext = '.'+ext;
var size = file.size;
var sliceSize = 250000;
var num_of_slices = Math.ceil(size / sliceSize);
var fileid = makeid();
var start = 0;
setTimeout(loop, 1);
function loop() {
var end = start + sliceSize;
if (size - end < 0) {
end = size;
}
var s = slice(file, start, end);
scount ++;
send(s, start, end,scount,size,num_of_slices,ext,fileid);
if (end < size) {
start += sliceSize;
setTimeout(loop, 1);
}
}
}
function send(piece, start, end,scount,size,num_of_slices,ext,fileid) {
var formdata = new FormData();
var xhr = new XMLHttpRequest();
xhr.open('POST', 'uploadchunk2.php', true);
formdata.append('start', start);
formdata.append('end', end);
formdata.append('file', piece);
formdata.append('scount', scount);
formdata.append('fsize', size);
formdata.append('num_of_slices', num_of_slices);
formdata.append('ext', ext);
formdata.append('fileid', fileid);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myArr = JSON.parse(this.responseText);
console.log(myArr);
// myFunction(myArr);
}
};
xhr.send(formdata);
}
/**
* Formalize file.slice
*/
function slice(file, start, end) {
var slice = file.mozSlice ? file.mozSlice :
file.webkitSlice ? file.webkitSlice :
file.slice ? file.slice : noop;
return slice.bind(file)(start, end);
}
function noop() {
}
})();
</script>
</body>
</html>
PHP upload
<?php
$target_path = '/test/';
$tmp_name = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
$target_file = $target_path.$filename;
$num_chunks_uploaded = $_POST['scount'];
$num_chunks_created = $_POST['num_of_slices'];
$extension = $_POST['ext'];
$file_id = $_POST['fileid'];
$file_location = 'test/';
$file_path = $file_location.$file_id.$extension;
$chunked_file_path = $file_location.$file_id.'-'.$num_chunks_uploaded.$extension;
move_uploaded_file(
$_FILES['file']['tmp_name'],
$chunked_file_path
);
// count amount of uploaded chunks
$chunksUploaded = 0;
for ($i=1; $i <= $num_chunks_created ; $i++) {
if ( file_exists($file_location.$file_id.'-'.$i.$extension) ) {
++$chunksUploaded;
}
}
// when this triggers - that means the chunks are uploaded
if ($chunksUploaded == $num_chunks_created) {
/* here you can reassemble chunks together */
for ($i = 1; $i <= $num_chunks_created; $i++) {
$file = fopen($file_location.$file_id.'-'.$i.$extension, 'rb');
$buff = fread($file, 2097152);
fclose($file);
$final = fopen($file_path, 'ab');
$write = fwrite($final, $buff);
fclose($final);
unlink($file_location.$file_id.'-'.$i.$extension);
}
}
$data = $chunksUploaded;
header('Content-Type: application/json');
echo json_encode($_POST);
?>
This isn't the answer to your problem, but it solves one problem you do have in your JavaScript.
PHP requires that a MAX_FILE_SIZE field be sent with uploaded files. This field must appear before the file field in the POST variables. So you need to add:
formdata.append('MAX_FILE_SIZE', 1000000) // or whatever you want the max size to be
before the field containing the file to be sent.
Currently my entire code is working almost flawlessly, how ever upon upload the ajax data is not being displayed on my webpage after a successful upload.
Below is the code I am using (completely) because I am rather new and have been reviewing various stack over flow posts to guide me in building this.
Sorry for lack of structure below.. I don't know how to adapt to stack overflows structure requirements..
The actual form input / submit
<form action="upload2.php" class="upload" enctype="multipart/form-data" id="upload" method="post" name="upload">
<fieldset>
<legend>Uplaod Files</legend> <input id="file" multiple name="file[]" required="" type="file"> <input id="submit" name="submit" type="submit" value="upload">
</fieldset>
<div class="bar">
<span class="bar-fill" id="pb"><span class="bar-fill-text" id="pt"></span></span>
</div>
<div class="uploads" id="uploads"></div>
</form>
Here's the PHP code I am using to upload the file.
<?
header('Content-Type: application/json');
$upload = [];
$allowed = ['mp3', 'm4a'];
$succeeded = [];
$failed = [];
if (!empty($_FILES['file'])) {
foreach ($_FILES['file']['name'] as $key => $name) {
if ($_FILES['file']['error'][$key] === 0) {
$temp = $_FILES['file']['tmp_name'][$key];
$file_name = $_FILES['file']['name'][$key];
$file_size = $_FILES['file']['size'][$key];
$ext = explode('.', $name);
$ext = strtolower(end($ext));
$s = substr(str_shuffle(str_repeat("0123456789abcdefghijklmnopqrstuvwxyz", 4)), 0, 4);
$file = $s . '.' . $ext;
$data = $file_name . PHP_EOL . $file_size;
file_put_contents('d/' . $s . '.txt', $data);
if (in_array($ext, $allowed) === true && move_uploaded_file($temp, "v/{$file}") === true) {
$succeeded[] = array(
'name' => $name,
'file' => $file,
);
} else {
$failed[] = array(
'name' => $name
);
}
}
}
if (isset($_POST['ajax'])) {
echo json_encode(array(
'succeeded' => $succeeded,
'failed' => $failed
));
}
}
Below is javascript event listener + the returned data from ajax
document.getElementById('submit').addEventListener('click', function (e) {
e.preventDefault();
var f = document.getElementById('file'),
pb = document.getElementById('pb'),
pt = document.getElementById('pt');
app.uploader({
files: f,
progressBar: pb,
progressText: pt,
processor: 'upload2.php',
finished: function (data) {
var uploads = document.getElementById('uploads'),
succeeded = document.createElement('div'),
failed = document.createElement('div'),
anchor,
span,
x;
if (data.failed.length) {
failed.innerHTML = '<p>file didnt upload<\/p>';
}
uploads.innerText = '';
for (x = 0; x < data.succeeded.length; x = x + 1) {
anchor = document.createElement('a');
anchor.href = 'uploads/' + data.succeeded[x].file;
anchor.innerText = data.succeeded[x].name;
archor.target = '_blank';
console.log(anchor);
succeeded.appendChild(anchor);
}
uploads.appendChild(succeeded);
},
});
});
Below is the javascript we're using for ajax (I think the issue is here but I keep reviewing it and I'm not able to find the problem.)
var app = app || {};
(function (o) {
"use strict";
var ajax, getFormData, setProgress;
ajax = function (data) {
var xmlhttp = new XMLHttpRequest(),
uploaded;
xmlhttp.addEventListener('readystatechange', function () {
if (this.readystate === 4) {
if (this.status === 200) {
uploaded = JSON.parse(this.response);
if (typeof o.options.finished === 'function') {
o.options.finished(uploaded);
}
} else {
if (typeof o.options.error === 'function') {
o.options.error();
}
}
}
});
xmlhttp.upload.addEventListener('progress', function (event) {
var percent;
if (event.lengthComputable === true) {
percent = Math.round((event.loaded / event.total) * 100);
setProgress(percent);
}
});
xmlhttp.open('post', o.options.processor);
xmlhttp.send(data);
};
getFormData = function (source) {
var data = new FormData(),
i;
for (i = 0; i < source.length; i = i + 1) {
data.append('file[]', source[i]);
}
data.append('ajax', true);
return data;
};
setProgress = function (value) {
if (o.options.progressBar !== undefined) {
o.options.progressBar.style.width = value ? value + '%' : 0;
}
if (o.options.progressText !== undefined) {
o.options.progressText.innerText = value ? value + '%' : '';
}
};
o.uploader = function (options) {
o.options = options;
if (o.options.files !== undefined) {
ajax({});
ajax(getFormData(o.options.files.files));
}
}
}(app));
My file is uploading, the progress bar is working and without returning e.preventDefault(); when it redirect to upload2.php I'm able to see the contents of the json i really don't know what my problem could be..
First of all, you have a typo in anchor here:
archor.target = '_blank';
Maybe this throws error and stops other code execution
Also you could check if your uploads div is visible via element inspector in browser.
If console.log shows nothing - do you mean by this it is not even called or it shows undefined? If it doesn't show at all, then look into the for that uses data.succeeded.length param as a limit - maybe it equals 0 and thus console.log is never called
I am currently working on an upload system with a combination of Javascript and PHP. The Javascript essentially reads a file in by splicing blobs of cutoffSize size (or, the remaining file size, if less). Each splice is sent to the php script via Post. Then, the PHP script re-assembles the file blob by blob.
However, in my implementation (as shown below), the output file is too large. I am not sure why the file is not being written correctly, but I believe that I have localized the problem to the php script. All of my javascript testing appears to have succeeded. However, I have included the Javascript as well, just in case.
I believe the problem may be in the way I write/append files. Unfortunately, I am not sure how to move forward, so I cannot investigate this further or describe in more detail.
Any help would be greatly appreciated.
Javascript:
function getBigStringAndPost(files,i,p) {
var key = $("#keyText")[0].value
var reader = new FileReader();
var blob = files[i].slice(p*cutoffSize,Math.min((p+1)*cutoffSize,files[i].size));
var folderName = $("#folderInput")[0].value;
reader.onload = function(e) {
var result = reader.result;
console.log(result.length);
$.post("http://brandonquinndixon.com/PHP/uploadFile.php",{
uploadKey: key,
doUpload: 1,
folderName: folderName,
splitPart: p,
fileName: files[i].name,
fileContents: reader.result
},function(e) {
console.log(e);
if ((p+1)*cutoffSize<files[i].size) {
currentUploaded += cutoffSize;
updateProgressBar();
reader.abort();
getBigStringAndPost(files,i,p+1);
} else {
$("#statusDiv").html($("#statusDiv").html()+"<p class='normalP'>File: "+files[i].name+" uploaded successfully.</p>");
currentUploaded += files[i].size%cutoffSize;
updateProgressBar();
reader.abort();
loadNextFile(files,i+1);
}
});
};
reader.onerror = function(e) {
console.log(e);
totalSizeOfFiles -= files[i].size;
$("#statusDiv").html($("#statusDiv").html()+"<p class='normalP'>File: "+files[i].name+" could not be read.</p>");
loadNextFile(files,i+1);
};
reader.readAsBinaryString(blob);
}
PHP:
$targetDir = $_SERVER['DOCUMENT_ROOT']."/Downloads";
$getKey = $_POST['uploadKey'];
$data = array();
error_log("upload attempt started");
//first, check the upload key
if ($getKey !== $uploadKey) {
$data['status'] = "failure";
$data['message'] = "Incorrect Upload Key";
error_log("bad key");
} else if ($_POST['doUpload'] == 1) {
$fileName = "";
if ($_POST['folderName']!=="") {
$folderName = cleanInput($_POST['folderName']);
if (!is_dir($targetDir."/".$folderName)) {
mkdir($targetDir."/".$folderName, 0777, true);
}
$fileName = $targetDir."/".$folderName."/".$_POST['fileName'];
} else {
$fileName = $targetDir."/".$_POST['fileName'];
}
/*
Don't allow executables
*/
if (strpos($fileName,".exe") > -1 || strpos($fileName,".sh") > -1 || strpos($fileName,".deb") > -1 || strpos($fileName,".rpm") > -1) {
$data['status'] = 'failure';
$data['message'] = 'Executable type files are not allowed';
} else {
if ($_POST['splitPart'] == 0) {
overWriteFile($fileName,$_POST['fileContents']);
$data['type'] = 'overWrite';
$data['details'] = 'P '.$_POST['splitPart'];
} else {
appendFile($fileName,$_POST['fileContents']);
$data['type'] = 'append';
$data['details'] = 'P '.$_POST['splitPart'];
}
$data['status'] = 'success';
}
} else {
$data['status'] = 'success';
$data['type'] = 'none';
error_log("success, no upload");
}
echo json_encode(array($data));
function overWriteFile($fName,$fContents) {
$fName = lookupFile($fName);
$file = fopen($fName,"w");
fwrite($file,$fContents);
fclose($file);
}
/*
Append to end of file
*/
function appendFile($fName,$fContents) {
$fName = lookupFile($fName);
//$file = fopen($fName,"a");
//fwrite($file,$fContents);
//fclose($file);
file_put_contents($fName,$fContents,FILE_APPEND);
}
I have the following code:
<script type='text/javascript' src='js/jquery.js'></script>
<script type='text/javascript'>
var last_song = '$song[1]';
var jqy = jQuery.noConflict()
var change_radio = setInterval(function ()
{
jqy.ajax({
url : 'modules/ajax/refresh.php',
type : 'GET',
data : {lastsong: 'last_song'},
success: function(data)
{
last_song = data;
jqy('#radio').html(last_song);
}
});
}, ($refresh*1000));
change_radio;
</script>
refresh.php
<?php
$lastsong = $_GET['lastsong'];
$timeout = "2"; // Number of seconds before connecton times out.
$ip[1] = "198.101.13.110"; // IP address of shoutcast server
$port[1] = "8000"; // Port of shoutcast server
//$song = [];
//$msg = [];
//END CONFIGURATION
$servers = count($ip);
$i = "1";
while ($i <= $servers)
{
$fp = #fsockopen($ip[$i], $port[$i], $errno, $errstr, $timeout);
if (!$fp)
{
$listeners[$i] = "0";
$msg[$i] = "<span class=\"red\">ERROR [Connection refused / Server down]</span>";
$error[$i] = "1";
}
else
{
fputs($fp, "GET /7.html HTTP/1.0\r\nUser-Agent: Mozilla\r\n\r\n");
while (!feof($fp)) $info = fgets($fp);
$info = str_replace('<HTML><meta http-equiv="Pragma" content="no-cache"></head><body>', "", $info);
$info = str_replace('</body></html>', "", $info);
$stats = explode(',', $info);
if (empty($stats[1]))
{
$listeners[$i] = "0";
$msg[$i] = "<span class=\"red\">ERROR [There is no source connected]</span>";
$error[$i] = "1";
}
else
{
if ($stats[1] == "1")
{
$song[$i] = $stats[6];
$listeners[$i] = $stats[4];
$max[$i] = $stats[3];
// Both IFs the same? Please check that and make one only if its the case
if ($stats[0] == $max[$i]) $msg[$i] = "<span class=\"red\">";
if ($stats[0] == $max[$i]) $msg[$i] .= "</span>";
}
else
{
$listeners[$i] = "0";
$msg[$i] = " <span class=\"red\">ERROR [Cannot get info from server]</span>";
$error[$i] = "1";
}
}
}
$i++;
}
if($song[1] != "")
{
echo $song[1];
exit();
}
echo uriencode($lastsong);
?>
And I want to change it to use prototype instead. I use prototype and jQuery already, and this is conflicting as written. I've no idea where to begin, but any assistance would be appreciated... especially since there are a lot of folks in my situation. Thanks so much.
I'm new to websockets and I'm facing a problem that I can't manage to fix. I've downloaded server and client from the web and modified it to have it working my way. The problem is I want to store users' data(e.g. name,color, whatever) on the serverside. When I want to spread the message from one user to all other users it sometimes works and sometimes it doesn't. Funny thing is that it reads user's id perfectly every time but sometimes it looses name or color, which all are, in fact, the fields of the same class and I don't see the difference... Please help.
Server:
<?php
/**
* Simple server class which manage WebSocket protocols
* #author Sann-Remy Chea <http://srchea.com>
* #license This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
* #version 0.1
*/
class Server {
/**
* The address of the server
* #var String
*/
private $address;
/**
* The port for the master socket
* #var int
*/
private $port;
/**
* The master socket
* #var Resource
*/
private $master;
/**
* The array of sockets (1 socket = 1 client)
* #var Array of resource
*/
private $sockets;
/**
* The array of connected clients
* #var Array of clients
*/
private $clients;
/**
* If true, the server will print messages to the terminal
* #var Boolean
*/
private $verboseMode;
private $maxConnections = 3;
/**
* Server constructor
* #param $address The address IP or hostname of the server (default: 127.0.0.1).
* #param $port The port for the master socket (default: 5001)
*/
function Server($address = '127.0.0.1', $port = 5001, $verboseMode = false) {
$this->console("Server starting...");
$this->address = $address;
$this->port = $port;
$this->verboseMode = $verboseMode;
// socket creation
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
if (!is_resource($socket))
$this->console("socket_create() failed: ".socket_strerror(socket_last_error()), true);
if (!socket_bind($socket, $this->address, $this->port))
$this->console("socket_bind() failed: ".socket_strerror(socket_last_error()), true);
if(!socket_listen($socket, 20))
$this->console("socket_listen() failed: ".socket_strerror(socket_last_error()), true);
$this->master = $socket;
$this->sockets = array($socket);
$this->console("Server started on {$this->address}:{$this->port}");
}
/**
* Create a client object with its associated socket
* #param $socket
*/
private function connect($socket) {
$this->console("Creating client...");
$client = new Client(uniqid(), $socket);
$this->clients[] = $client;
$this->sockets[] = $socket;
$this->console("Client #{$client->getId()} is successfully created!");
}
/**
* Do the handshaking between client and server
* #param $client
* #param $headers
*/
private function handshake($client, $headers) {
$this->console("Getting client WebSocket version...");
if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
$version = $match[1];
else {
$this->console("The client doesn't support WebSocket");
return false;
}
$this->console("Client WebSocket version is {$version}, (required: 13)");
if($version == 13) {
// Extract header variables
$this->console("Getting headers...");
if(preg_match("/GET (.*) HTTP/", $headers, $match))
$root = $match[1];
if(preg_match("/Host: (.*)\r\n/", $headers, $match))
$host = $match[1];
if(preg_match("/Origin: (.*)\r\n/", $headers, $match))
$origin = $match[1];
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
$key = $match[1];
$this->console("Client headers are:");
$this->console("\t- Root: ".$root);
$this->console("\t- Host: ".$host);
$this->console("\t- Origin: ".$origin);
$this->console("\t- Sec-WebSocket-Key: ".$key);
$this->console("Generating Sec-WebSocket-Accept key...");
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: $acceptKey".
"\r\n\r\n";
$this->console("Sending this response to the client #{$client->getId()}:\r\n".$upgrade);
socket_write($client->getSocket(), $upgrade);
$client->setHandshake(true);
$this->console("Handshake is successfully done!");
return true;
}
else {
$this->console("WebSocket version 13 required (the client supports version {$version})");
return false;
}
}
/**
* Disconnect a client and close the connection
* #param $socket
*/
private function disconnect($client)
{
$this->console("Disconnecting client #{$client->getId()}");
$i = array_search($client, $this->clients);
$j = array_search($client->getSocket(), $this->sockets);
if($j >= 0)
{
array_splice($this->sockets, $j, 1);
socket_shutdown($client->getSocket(), 2);
socket_close($client->getSocket());
$this->console("Socket closed");
}
if($i >= 0)
array_splice($this->clients, $i, 1);
$this->console("Client #{$client->getId()} disconnected");
/*foreach ($this->clients as $cnt)
$this->send($cnt,$this->usernames[$client->getId()] ." left!");*/
}
/**
* Get the client associated with the socket
* #param $socket
* #return A client object if found, if not false
*/
private function getClientBySocket($socket) {
foreach($this->clients as $client)
if($client->getSocket() == $socket) {
$this->console("Client found");
return $client;
}
return false;
}
/**
* Do an action
* #param $client
* #param $action
*/
private function action($client, $action)
{
$action = $this->unmask($action);
$this->console("Performing action: ".$action);
if($action == "exit" || $action == "quit")
{
$this->console("Killing a child process");
posix_kill($client->getPid(), SIGTERM);
$this->console("Process {$client->getPid()} is killed!");
}
}
/**
* Run the server
*/
public function run()
{
$this->console("Start running...");
while(true)
{
$changed_sockets = $this->sockets;
#socket_select($changed_sockets, $write = NULL, $except = NULL, 1);
foreach($changed_sockets as $socket)
{
if($socket == $this->master)
{
if(($acceptedSocket = socket_accept($this->master)) < 0)
{
$this->console("Socket error: ".socket_strerror(socket_last_error($acceptedSocket)));
}
else
{
$this->connect($acceptedSocket);
}
}
else
{
$this->console("Finding the socket that associated to the client...");
$client = $this->getClientBySocket($socket);
if($client)
{
$this->console("Receiving data from the client");
$data=null;
while($bytes = #socket_recv($socket, $r_data, 2048, MSG_DONTWAIT))
{
$data.=$r_data;
}
if(!$client->getHandshake())
{
$this->console("Doing the handshake");
if($this->handshake($client, $data))
$this->startProcess($client);
}
elseif($bytes === 0)
{
$this->console("disconnecting...");
$this->disconnect($client);
}
else
{
$data = (string)$this->unmask($data);
$data = json_decode($data);
if( $data != "" )$this->console("Got data from user: |" . implode(',',$data) . "|");
// When received data from client
if( $data[0] == "setName" )//set name, color for user with given id
{
$id = $client->getId();
$colors = array( '990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000' );
//$col = '#'.$colors[ rand(0, sizeof($colors)-1) ];
$col = '#'.$colors[ sizeof( $this->clients ) ];
$this->console( "Setting attributes for user $id | ". $data[1] ." | $col |" );
$client->setName( $data[1] );
$client->setColor( $col );
}
else if( $data != "" )
{
$counter = 0 ;
$id = $client->getId();
$name = null ;
$color = null ;
//while($name == null)
if(1)
{
foreach ($this->clients as $c)
{
if( $c->getId() == $id )
{
$name = $c->getName();
$color = $c->getColor();
break;
}
}
++$counter;
}
foreach ($this->clients as $c)
{
$this->send( $c, json_encode( array(
$name . "[$counter]",
$color,
$data[1] )
) );
}
}
}
}
}
}
}
}
/**
* Start a child process for pushing data
* #param unknown_type $client
*/
private function startProcess($client)
{
$this->console("Start a client process");
$pid = pcntl_fork();
if($pid == -1)
{
die('could not fork');
}
elseif($pid)
{ // process
$client->setPid($pid);
}
else
{
if( sizeof( $this->clients ) > $this->maxConnections )
{
$this->send( $client, json_encode(array("No free slots!")) );
$this->disconnect($client);
}
else
$this->send($client, json_encode($client->getId()));
// we are the child
while(false)
{
//if the client is broken, exit the child process
if($client->exists==false)
{
break;
}
// push something to the client
$seconds = rand(2, 5);
$this->send($client, "I am waiting {$seconds} seconds");
sleep($seconds);
}
}
}
/**
* Send a text to client
* #param $client
* #param $text
*/
private function send($client, $text) {
$this->console("Send '".$text."' to client #{$client->getId()}");
$text = $this->encode($text);
if( socket_write($client->getSocket(), $text, strlen($text)) === FALSE )
{
$client->exists=false; //flag the client as broken
$this->console("Unable to write to client #{$client->getId()}'s socket");
$this->disconnect($client);
}
else
$this->console ("data sent!");
}
/**
* Encode a text for sending to clients via ws://
* #param $text
* #param $messageType
*/
function encode($message, $messageType='text') {
switch ($messageType) {
case 'continuous':
$b1 = 0;
break;
case 'text':
$b1 = 1;
break;
case 'binary':
$b1 = 2;
break;
case 'close':
$b1 = 8;
break;
case 'ping':
$b1 = 9;
break;
case 'pong':
$b1 = 10;
break;
}
$b1 += 128;
$length = strlen($message);
$lengthField = "";
if ($length < 126) {
$b2 = $length;
} elseif ($length <= 65536) {
$b2 = 126;
$hexLength = dechex($length);
//$this->stdout("Hex Length: $hexLength");
if (strlen($hexLength)%2 == 1) {
$hexLength = '0' . $hexLength;
}
$n = strlen($hexLength) - 2;
for ($i = $n; $i >= 0; $i=$i-2) {
$lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
}
while (strlen($lengthField) < 2) {
$lengthField = chr(0) . $lengthField;
}
} else {
$b2 = 127;
$hexLength = dechex($length);
if (strlen($hexLength)%2 == 1) {
$hexLength = '0' . $hexLength;
}
$n = strlen($hexLength) - 2;
for ($i = $n; $i >= 0; $i=$i-2) {
$lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
}
while (strlen($lengthField) < 8) {
$lengthField = chr(0) . $lengthField;
}
}
return chr($b1) . chr($b2) . $lengthField . $message;
}
/**
* Unmask a received payload
* #param $buffer
*/
private function unmask($payload) {
$length = ord($payload[1]) & 127;
if($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
}
elseif($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
}
else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i%4];
}
return $text;
}
/**
* Print a text to the terminal
* #param $text the text to display
* #param $exit if true, the process will exit
*/
private function console($text, $exit = false) {
$text = date('[Y-m-d H:i:s] ').$text."\r\n";
if($exit)
die($text);
if($this->verboseMode)
echo $text;
}
}
error_reporting(E_ALL);
require_once 'client.php';
set_time_limit(0);
// variables
$address = '127.0.0.1';
$port = 5001;
$verboseMode = true;
$server = new Server($address, $port, $verboseMode);
$server->run();
?>
Client:
<?php
/**
* Define a Client object
* #author Sann-Remy Chea <http://srchea.com>
* #license This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
* #version 0.1
*/
class Client {
private $id;
private $socket;
private $handshake;
private $pid;
private $name;
private $color;
public $exists=true; //to check if client is broken
function Client($id, $socket) {
$this->id = $id;
$this->socket = $socket;
$this->handshake = false;
$this->pid = null;
}
public function setName( $n )
{
$this->name = $n;
}
public function getName()
{
return $this->name;
}
public function setColor( $c )
{
$this->color = $c;
}
public function getColor()
{
return $this->color;
}
public function getId() {
return $this->id;
}
public function getSocket() {
return $this->socket;
}
public function getHandshake() {
return $this->handshake;
}
public function getPid() {
return $this->pid;
}
public function setId($id) {
$this->id = $id;
}
public function setSocket($socket) {
$this->socket = $socket;
}
public function setHandshake($handshake) {
$this->handshake = $handshake;
}
public function setPid($pid) {
$this->pid = $pid;
}
}
?>
Index:
<!DOCTYPE html>
<html lang="pl">
<head>
<title>Websockets</title>
<meta name="BB" content="" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta charset="UTF-8" />
<style>
html, body
{
padding: 0;
margin: 0;
}
#out
{
width:840px;
height:500px;
mragin-right: -20px;
color:#FFF;
font-family: Courier New;
overflow-x: hidden;
overflow-y: auto;
}
#outwrap
{
width:800px;
height:500px;
margin:40px auto 0 auto;
display: block;
border: 1px solid #F00;
background-color: #000;
padding: 10px;
overflow:hidden;
}
#field
{
width:820px;
margin:10px auto;
display: block;
background-color: #000;
color: #FFF;
height: 50px;
}
#btn
{
background-color: #000;
margin:0 auto;
display:block;
width:100px;
height:50px;
font-size:20px;
border-radius: 10px;
}
#btn:hover
{
background-color: #666;
}
</style>
<script>
"use strict";
function $(id){return document.getElementById(id);}
document.addEventListener("DOMContentLoaded",function(){
var name = "";
while( name === "" || name === null )name = window.prompt( "Name" );
var myId = '';
$("field").value = "";
$("field").focus();
//var colors = ['990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000'];
//var myColor = '#' + colors[Math.floor( Math.random()*colors.length )] ;
//$("btn").style.color = myColor ;
var ws = new WebSocket('ws://127.0.0.1:5001');
ws.onopen = function(msg) {
write( "connected as "+ name +"!" );
ws.send( JSON.stringify( new Array( "setName",name ) ) );
$("field").focus();
//ws.send( name+": connected" );
//write('Connection successfully opened (readyState ' + this.readyState+')');
};
ws.onclose = function(msg) {
if(this.readyState === 2)write( "disconnected!" );
/*write(
'Closing... The connection is going throught'
+ 'the closing handshake (readyState '+this.readyState+')'
);*/
else if(this.readyState === 3)write( "disconnected!" );
/*write(
'Connection closed... The connection has been closed'
+ 'or could not be opened (readyState '+this.readyState+')'
);*/
else write( "disconnected!" );
//write('Connection closed... (unhandled readyState '+this.readyState+')');
};
ws.onerror = function(event) {
if(typeof (event.data) != 'undefined')write( "ERROR:" + event.data );
};
ws.onmessage = function(e){
var msg = JSON.parse(e.data);
msg = msg.toString().split(",");
if( msg.length == 1 )myId = msg.toString();
else
{
write( "<p style='color:"+ msg[1] +";'>"+ msg[0] +": "+ msg[2] +"</p>" );
}
console.log( "Server -> " + msg );
//write( "inc: " + msg );
};
$("field").addEventListener("keydown",function(e){
if( e.keyCode == 13 )
{
$("btn").click();
}
});
$("btn").addEventListener("click",function(){
if( ws.readyState === 1 )
ws.send( JSON.stringify( new Array( myId, $("field").value ) ) );
else write( "Couldn't send the message..." );
$("field").value = "";
$("field").focus();
});
window.onbeforeunload = function()
{
//ws.send( name+": disconnected" );
ws.close();
}
function write( info )
{
//$("out").innerHTML = info + "<br />" + $("out").innerHTML;
var p = document.createElement( "p" );
p.innerHTML = info ;
$("out").appendChild( p );
$("out").scrollTop = $("out").scrollHeight;
}
});
</script>
</head>
<body>
<div id="outwrap">
<div id="out"></div>
</div>
<input type="text" id="field" />
<input type="button" value="SEND" id="btn" />
</body>
</html>
If you uncomment while on line 278(server.php) and comment if a line below: In case about which I'm talking about the loop goes infinite...
You are making the common (beginners?) mistake to assume that writing/reading to/from a TCP socket does send/receive the amount of bytes the calls were told to send/receive. This is not necessarily the case.
It is essential to inspect the value those functions (socket_recv()/socket_send()) return and loop around them until all data had been sent/received.