JavaScript: Server-side and Client-side

| No Comments | No TrackBacks

I'm currently experimenting with some stuff related to using JavaScript on the server and the client. The results I'm getting are somewhat mixed. The idea I had was to see about letting someone customize some aspects of an application I'm developing that is intended to help you live more healthy. Basically, based upon the statistics of a food or other parts of the application, you could create custom metrics to measure your success or failure according to your particular preferences. (Some people monitor carbs, some monitor Weight Watcher points, others are interested in fat and protein, etc.)

I've successfully added tools that will allow you to add your custom metrics to the system using your own JavaScript snippet. This snippet is passed a object representing the food item (and eventually to include other information). I can then calculate your metric based upon the current food your viewing and your code.

This calculation is performed on the server side using Mozilla Spidermonkey
and the JavaScript
module on CPAN. On the server, this ends up looking something like this:

use JavaScript;
my $runtime = JavaScript::Runtime->new;
my $context = $runtime->create_context;
$context->eval_file('benchmark.js'); # base API library

# do some other setup of the objects I need in __tmp.food

$context->eval(
q/function evalIndicator(food) {/
.$user_calculation # this variable contains code from the end-user
.q/}/
);
$result = $context->eval(q/evalIndicator(__tmp.food)/);

I've added a little more in there for error handling and such, but that's the core of how I handle the work on the server. The really nice thing here is that the user's JavaScript can't really do much bad other than a denial of service by looping or recursing internally. The server-side JavaScript doesn't include any BOM objects that could be used to do terrible things like steal user information, etc. Furthermore, if I run this code as a separate process that communicates back to the server, I can easily wipe it out if it takes longer than a few seconds to work.

Then, using a combination of Dean Edward's sandbox
and some custom hacking of my own, I can then provide on-the-fly updates from the client using most of the same JavaScript code. This allows the JavaScript to run in a relatively safe container within a hidden IFRAME.

On the client, I've implemented the sandbox in this form. I've left out some of the error handling and other details for simplicity:

// Dean Edward's sandbox in OO form:
function Sandbox(preloads) {
  this.iframe = document.createElement("iframe");
  this.iframe.style.display = "none";
  document.body.appendChild(this.iframe);

this.frame = frames{eval:function(s){return eval(s)}};"+
"top=opener=parent=null"+ // cut off access back to the parent
"<\/script>"
);
this.frame.document.close(); // stop the throbbing
}

Sandbox.prototype.eval = function(code) {
var sandboxObject = Sandbox.functions[this.serialNumber

;
return sandboxObject.eval(code);
}

Sandbox.prototype.close = function() {
document.body.removeChild(this.iframe);
Sandbox.functions[this.serialNumber] = null;

this.frame = null;
this.iframe = null;
this.serialNumber = null;
}

Sandbox.serialNumber = 0;
Sandbox.functions = new Array();

Now that I have the sandbox to run things in, I need to setup a similar environment as I use on the server. My initial implementation embeds the user's code as a special attribute in the middle of the page, but it could be special block in the header or an Ajax request once to fetch the code string into a variable just after load, etc.

// This code runs onload, to make sure benchmark.js is loaded completely
var sandbox = new Sandbox(['benchmark.js']); // base API library

// When the user changes a value inthe page, the calculation can be made again on the fly...
var result = sandbox.eval(
""

// do some other setup of the objects I need in __tmp.food

+ "function evalIndicator(food) {"
+ user_calculation // this variable contains code from the end-user
+ "}"
+ "evalIndicator(__tmp.food)"
);

Again, I've omitted some of the other details to keep this article at a manageable length. That's the gist of how I run this on the client.

Now, after I've done the work, I probably am going to drop the client-side effort and provide updates by contacting the server using Ajax-ish requests. Why? Well, I can control the server-side environment much more closely by running the code in a separate process that I can kill off if it runs away. If someone's code accidentally (or intentional) tries to do something malicious on the client, I get the blame. The browser is a much less controllable environment. They could fire off requests passing information regarding the end user off to an unknown third party or compromise something even worse if I'm not very careful. I'd have to do a very thorough job of stripping the sandbox area completely. Even then, I don't know if I can actually protect the user from a malicious attacker since there are so many unknowns regarding browser implementations. I'd need to do a lot more studying before I'd be willing to run untrusted code this way for sure.

On the other hand, this trick might work swimmingly in an environment where I trusted all the code being executed in both boxes. I'd certainly be willing to do this on a project at some point to provide calculations on both server and client using the same embedded code, which is why I'm writing the article for others. Anyway, this has been an interesting adventure in JavaScript.

Cheers.

No TrackBacks

TrackBack URL: http://contentment.org/mt/mt-tb.cgi/564

Leave a comment

About this Entry

This page contains a single entry by Andrew Sterling Hanenkamp published on January 22, 2008 10:46 AM.

Stream of Consciousness Work Style was the previous entry in this blog.

Twitter and OpenResty is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.