My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Sunday, March 01, 2009

PHP to Jaxer Server And Vice-Versa

Basis

If we have a configuration Jaxer + PHP, as is as example in my pampa-j project, we should know that these two languages cannot directly interact as is, for example, with Jaxer and Java.
The interpreted and executed order is this one:

  1. the PHP module parses the request and the page before Jaxer module

  2. after PHP has finished its stuff, the Jaxer Server module parses the page which could has been modified during PHP execution

  3. after the Jaxer Server has finished its stuff, the user receives the generated page

Above order means that we can pass variables to Jaxer but we cannot pass back results to PHP

<?php // simple variable from PHP to Jaxer
$myVar = 'Hello World';
echo '<script runat="server">
myPHPVar = '.json_encode($myVar).';
onload = function(){
// will show Hello World
document.body.innerHTML = myPHPVar;
};
</script>';
?>

As summary, if we print out a script tag wth a valid JavaScript for server, client, or both, Jaxer Server will consider that tag as part of the page to execute.


The Other Way Round?


Since Jaxer Server parses tha page after PHP has finished, and since PHP will not be connecte to the Jaxer Module and will simply close its thread, during Jaxer page generation/parsing we cannot use PHP because there is no connector as is for Java.
At this point we could create a workaround using one of these strategies and considering that all we gonna do will be on the server, before the client will receive the page:

  • create a socket via PHP and connect to it via Jaxer during its page parsing

  • call somehow PHP runtime during Jaxer Execution


While the first point requires skills and the possibility to change server configuration, the second option could be implemented via Synchronous Ajax call from Server to Server.


Sounds Insane, but it is working!

These is a simple test case I created to show you how it is possible to communicate PHP to Jaxer (creating a specific object) and vice-versa, calling PHP functions runtime via Jaxer.
The final concept is showed in next snippet:

<?php
// require the Jaxer.php file to prepare
// the Jaxer.PHP object
// Jaxer is interpreted after PHP
require 'Jaxer.php';
?>
<script runat="server">
// a simple test case, monitor the tme
var t = new Date;
onload = function(){

// retrieve the length of a string
var len = Jaxer.PHP.call("strlen", "test"), // 4
myFuncResult;
try {

// change runtime the page to call
Jaxer.PHP.url = 'myFunc.php';

// call a function defined in that page
myFuncResult = Jaxer.PHP.call("myFunc"); // "Hello Jaxer Server"

// call something undefined to test errors
myFuncResult = Jaxer.PHP.call("noFunc"); // shows an error on the catch
} catch(e) {

// show the error
document.body.appendChild(document.createTextNode(e.message)); // error from PHP, if any
document.body.appendChild(document.createElement("br"));
};

// take the elapsed time
t = (new Date - t) / 1000;

// show some information
document.body.appendChild(document.createTextNode(len + " - " + myFuncResult));
document.body.appendChild(document.createElement("hr"));
document.body.appendChild(document.createTextNode("Executed in " + t + " seconds"));
};
</script>

To make above code possible, all we need are a couple of files, the Jaxer.php interpreter:

<?php
/** Runtime Jaxer to PHP
* (C) Andrea Giammarchi
* Mit Style License
*/

// @string your secret password, it will NOT be showed in the client
$JaxerSecret = sha1('my secret pass');

// if Jaxer Server sent a request with a valid secret and a function to call ...
if(isset($_POST['JaxerSecret'], $_POST['Jaxer']) && $_POST['JaxerSecret'] === $JaxerSecret){

/** Error manager
* @param int error level
* @param string file that generated the error
* @param int line that generated the error
* @param mixed the context (optional, not used)
*/
function JaxerError($level, $message, $file = null, $line = null, $context = null){
echo ":".json_encode(array('level' => $level, 'message' => $message, 'file' => $file, 'line' => $line));
exit(0);
}

/** Exception manager
* @param Exception the generic generated Exception
*/
function JaxerException(Exception $e){
JaxerError($e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine());
}

// error manager configuration
@set_error_handler('JaxerError');
@set_exception_handler('JaxerException');

// Jaxer variable is a serialized object with two properties
// - the name of the function to call
// - one or more arguments to send to use with called function
$js = json_decode($_POST['Jaxer']);

// the Jaxer to PHP result is always a JSON serialized result
echo json_encode(call_user_func_array($js->name, $js->arguments));
} else

// Jaxer needs to know the secret
// and to use the Jaxer.PHP
// this is created runtime via PHP
echo '<script runat="server">'.
str_replace(
'{JaxerSecret}',
$JaxerSecret,
file_get_contents('Jaxer.php.js') // read the file
).
'</script>';
?>

the Jaxer.php.js file with the JavaScript runtime created object:

/** Runtime Jaxer to PHP
* (C) Andrea Giammarchi
* Mit Style License
*/
Jaxer.PHP = {

// the url with Jaxer.php file
url:"Jaxer.php",

// the PHP.call function
// Accepts the name of the PHP function to call
// plus zero, one, or more arguments to send
call:function(slice){
var send = {
async:false,
method:"POST",
onsuccess:function(response){
if(response.charAt(0)===":"){
var result = JSON.parse(response.substring(1)),
e = new Error;
for(var key in result)
e[key] = result[key];
send.result = null;
throw e;
} else
send.result = JSON.parse(response);
}
};
return function(name){
send.url = Jaxer.PHP.url;
Jaxer.XHR.send(
// {JaxerSecret} is replaced via PHP
"JaxerSecret={JaxerSecret}&Jaxer=" + encodeURIComponent(
JSON.stringify({name:name, arguments:slice.call(arguments, 1)}
)), send);
return send.result;
}
}(Array.prototype.slice)
}

... and finally, a generic page with one or more functions defined, in this case the myFunc.php file:

<?php
// require the Jaxer to PHP manager
// if this page is called directly
// the secret will NOT be whoed
// while if this page is called
// via Jaxer.PHP, the manager
// will simply execute the required code
require 'Jaxer.php';
function myFunc(){
return 'Hello Jaxer Server';
}
?>

The secret is a truly simple security system to avoid direct php page calls and since it is interpreted only on the server, the client will never know this secret (unless our server is not exposing source codes, in this case the secret should be already hashed via sha1 in the source).


Pros And Cons

This strategy could sound cool but it is not the ideal way to interact between these two different modules. The Jaxer to Java way is much better but at least here we have the possibility to send back something or to do via PHP something impossible to do via Jaxer. The fact I put a timer to monitor the delay means you should pay attention because this way is extremely slow. The ideal case scenario is, when absolutely necessary, call PHP only once, creating a clever function able to receive one or more arguments, delegates tasks in the current PHP code, and send back a single object with one or more results. In few words, if you are able to perform one single call to send and receive everything you need, this solution could be fine. In every other case, this solution could require an insane stress for the server and for a single page to show.

In any case, have fun :)

P.S. Here I am with a zip file to test directly this post code

6 comments:

Anonymous said...

I've not used Jaxer but the way you are using POST data without any form of escaping worries me. I'd suggest a whitelist of the inputs and make sure all harmful characters are escaped.

Andrea Giammarchi said...

Gareth, yes, I forgot encodeURIComponent for both variables ... anyway, this post is about the idea but I'll fix the zip asap, cheers

Anonymous said...

Hi. I'm a little bit worried because I see few comments in your posts. Definitely not enough as the posts are worth of. I like a lot reading your posts. They are really awesome. And I suspect that there are a lot of readers there too. Please, keep the quality of your work!

Andrea Giammarchi said...

@digitta, thank you. Dunno why there are few comments, in any case it depends on the subject (more about JS inheritance)

@Gareth, at least I changed the example (the secret variable is hashed via sha1 so encodeURIComponent it is not imporntant) ;-)

Anonymous said...

@digitta

Yeah agreed Andrea's posts are awesome, keep em coming! :)

Anonymous said...

Ajax รจ davvero un gran linguaggio anche se ha i suoi contro