looking for a full-immersion/face-to-face HTML5, JavaScript, and Mobile Web Development training in the heart of London? Book your spot
Showing posts with label proxy. Show all posts
Showing posts with label proxy. Show all posts

Tuesday, December 13, 2011

Please, Give Us Back __noSuchMethod__ !

For those who don't know what __noSuchMethod__ is here the quick summary: it was a bloody handy non-standard method able to provide a fallback whenever we invoked an object method that did not exist.

var o = {};
o.__noSuchMethod__(function (name, args) {
alert(name); // "iDoNotExist"
alert([].slice.call(args)); // 1,2,3
});
o.iDoNotExist(1, 2, 3); // will produce above alerts


A Bit Of Background

Well, if you are patient enough, you may consider to read this never-ending post in Mozilla mailing list.
The reason that post is called Proxies: get+fn vs. invoke is because Proxy supposes to be the new way to go able to bring us much more power than we probably ever need ... but hey, this is welcome, while what is not welcome, is that Proxy may not be implemented first, which means browsers vendors should have waited to remove __noSuchMethod__ 'cause right now we may not have a pseudo equivalent, and second, but surely not less important, , Proxy does not provide the same functionality.

The Minified Theory Against The Practice

The main argument from @BrendanEich is that JavaScript has properties only so that o.fn() is the equivalent of o.fn.apply(o).
While this is true with any normal object, this is totally different with __noSuchMethod__.
The equivalent of __noSuchMethod__ for that operation, and behind the scene, is:
  • is there a property in o or its __proto__ called fn ?
    • yes, proceed as usual as if it was o.fn.call(o) and throw error if that property was not callable
    • no, is there a __noSuchMethod__ callback to burn instead of throwing an error due undefined property?
      • yes, perform the current operation: nsm_callback.apply(o, arguments)
      • no, throw an error since property was undefined and obviously not callable
Got it? The equivalent of o.fn() in an environment where __noSuchMethod__ was supported is potentially different from o.fn.apply(o) ... I am 100% sure Brendan knows this before and better than me and this is the reason I don't really get his strongest point.
Once again, o.fn() may be the equivalent of nsm_callback.apply(o, arguments) and not o.fn.apply(o).

The Inexistent Theory Against The Practice

If above "reason" was not enough, I have read even worst in the same thread. I am sorry guys, but sometimes you must be realistic and understand that if a developer does, as example, this:

// de-context fn from o and invoke it
(o.fn)();

// exact equivalent of
var fn = o.fn; // GETTER, no invokation
// and after ...
fn()
// this is a problem? we have same with missing bind then ...

rather than this:

// invoke fn through o as default context
o.fn();
// can we see the difference?

it means that developer has much bigger problems than __noSuchMethod__ inexistent ambiguity, that developer does not even know that __noSuchMethod__ exist ... come on!
Going on and on, another point is that get should be all we need to simulate the __noSuchMethod__ behavior through proxies ... but this is completely misleading!

A Getter IS A Getter

Is that trivial ... if we access a generic object property we are doing nothing different from invoking a getter with such object as property context.

o.whatever;
// look for "whatever" property name in o
// if found returns the "whatever" associated value


o.whatever.call
// nothing change, THIS IS NOT AN INVOKE
// look for "whatever" property name in o
// if found returns the "whatever" associated value
// since the value was a function, the call method is usable
// if whatever was not defined, the call method won't exist

Nobody should ever even consider to use property accessor and expect a __noSuchMethod__ behavior ... that property did not exist, what kind of method would you expect to look for?
call is a property of the Function.prototype so following the accessor/getter logic, nothing is ambiguous here.
Accordingly, lattest example is simply an inexistent mistake that hopefully no developer would ever do ... but you know, shit happens, then we learn, then hopefully we don't repeat same shit.

Other Programming Languages

When it comes to PHP, they perfectly managed to make the behavior not ambiguous through the __call magic keyword in classes definition but no, we decided that in JavaScript we cannot even think to put an invoke to make the life easier and completely NOT ambiguous for all of us ... do we?
I still cannot understand where and what is the ambiguous part if we have an explicit invoke declaration ... maybe something a bit harder to solve behind the scene for these poor JS engines? It could be ... should we all limit JS because of this? I don't think so.

Think About Libraries APIs Migrations

I give you the most basic example, the most used JS library with a fake getter and setter behavior: jQuery.

// jQuery simulation of getters and setters behaviors

// the getter
$("body").html(); // return string with content

// the setter
$("body").html("
whatever
");
// set the string with content

If you want, specially for chainability reasons through the simulated setter, the fact html is a method is convenient for the library but this library is stuck forever behind these two methods.
jQuery, at current ECMAScript status, will never be able to switch gradually to real getters and setters ... why that? A simple example:

$("body").html; // returns the string in jQuery 3000

$("body").html(); // shows a "deprecated warning"
// ... and returns the "html" getter

// implementation example
function setInnerHTML(node) {
node.innerHTML = this;
}
Object.defineProperty($.fn, "html", {
get: function () {
return this[0].innerHTML;
},
set: function (html) {
this.each(setInnerHTML, html);
}
});

// the deprecation warning
$.fn.__noSuchMethod__(function (property, args) {
if (property in this) {
console.log("Warning: " + property + " is not a method anymore");
if (args.length) {
// invoke the setter
this[property] = args[0];
// preserve behavior
return this;
} else {
// invoke the getter
return this[property];
}
} else {
throw "Y U NO READ DOCUMENTATION";
}
});

That's it, we can migrate from two different APIs implementing getters and setters whenever we had a similar behavior and bringing gracefully users to the new usage ... no wa can't!

It Is Not About jQuery

I don't even use jQuery so don't get me wrong, this is not my battle here ... the point is that for another private project I am working on I would like to educate developers to use properties correctly but I understand developers may already got use to invoke methods as if it is normal, even when they are simply looking for a getter behavior.

var o = {
whatever:"cool bro",
__noSuchMethod__: function (property, args) {
console.log("we got a bro-blem here, " +
"don't invoke if you want a getter");
return this[property];
}
};

// so that
o.whatever === o.whatever();

Secially last line of code is apparently impossible to reproduce with Proxies, those that suppose to be the new and best way to go, those that give us control on things rarely needed until now, those that made JS.Next group decide that __noSuchMethod__ was evil and it had to be abandoned.
I really hope that JS.Next will not be non-developer expectations behaviors driven because guys, somebody tries to do cool things with this cool language, and if the reason you drop something is because we are all morons, as example misunderstanding the difference of a referenced property through parenthesis ... oh well ... good luck kkfuture JavaScript ...

How To Solve This

Please put an invoke or even better an invokeProperty, preserving invoke for when the object itself is used as if it was callable, in the current Proxy specifications so that who knows what is doing, can keep doing it and who never even bothered with this stuff, won't be affected at all.
Thank you for listening.

Thursday, October 15, 2009

DOM Node Proxy

This is just a quick post from home sweet home.
A common DOM related problem is to create an association between a node and a generic object. The most dirty, memory leaks prone, and obtrusive way to perform this task is this one:

document.body.obj = {
prop:"value",
otherProp:function(){}
};

Above snippet is a bad practice for different reasons.
  1. obtrusive, it's assuming that no other libraries will use "obj" property name to perform an analogue task
  2. dirty, if we associate a primitive value Internet Explorer will expose it in the node string representation
  3. memory leaks, if the object points something "live", another node, or a HTMLCollection, the generic node will never be collected by the garbage


Alternatives

Specially to avoid last problem, the memory consumption, it's a good practice to store an index, rather than an object. To make things less obtrusive and get rid of conflicts, we usually create a "unique id".

// the array with all objects
var stack = [];

// the unobtrusive property name
var expando = "prefix" + new Date().getTime();

// the obejct to relate
var o = {};

stack.push(o);

// the relation via index (last object)
document.body[expando] = stack.length - 1;

As I have already linked and explained, this technique is still dirty because Internet Explorer will show the unique id via outerHTML or generic node representation.

Strategies

jQuery, and many others, create an association for each manipulated dom node. This could consume RAM without a reason since there could be a lot of nodes with an associated object that will never be used.
Next version of jQuery, right now in alpha stage, understood this point changing the object association logic. I have not read how yet, but I would like to write something I've been used for a while, a sort of proxy object created for DOM nodes and object relations.

DOM Node Proxy

var proxy = (function(){
// another (C) WebReflection silly idea
var expando = "@".concat(+new Date, Math.random()),
stack = []
;
return function proxy(){
return stack[this[expando]] || stack[
this[expando] = new Number(stack.push({}) - 1)
];
};
})();

Above snippet uses almost all strategies I know to avoid obtrusive property, dirty layout, and direct object assignment (index strategy).
To better understand what exactly is above function I have commented each part of it:

var proxy = (function(){

// another (C) WebReflection silly idea

// one function to associate as proxy
// Being assignment a direct one
// standard browsers won't modify
// attributes while being proxy
// an object (not primitive value)
// IE won't expose it in node string
// representation (e.g. outerHTML)
function proxy(){

// a proxy call costs only once:
// the first time it's called
// Other calls will return the object
// This is to avoid objects association
// even if these are not necessary
return stack[this[expando]] || stack[

// the index is the last one in the
// private stack Array. To avoid
// leaks we don't associate directly
// an object but simply an integer.
// If we directly associate
// a primitive value, IE will expose
// is in the dom string representation
// (e.g. outerHTML)
// To avoid this we can just assign
// a Number instance, rather than
// a primitive "number"
this[expando] = new Number(

// push returns the new length
// we need last inserted object
// index to relate the object
stack.push({}) - 1
)
];
};

var
// private unique expando with
// an invalid char as prefix
// in order to make attr name
// easy to recognize in a possible
// IE attributes loop
expando = "@".concat(
+new Date,
Math.random()
),

// list of associated object
stack = []
;

// ready to go!
return proxy;

})();

Is it clear enough? This is a simple usage example (please reado NOTEs):

onload = function(){

// associate a proxy
// NOTE: this is still obtrusive
// the property name should be a unique id
// or it should have library prefix
// otherwise we could have conflicts
document.body.proxy = proxy;
// aka: node[expando] = proxy;

// retrieve the proxy object
var p = document.body.proxy();

// test proxy: true
alert(p === document.body.proxy());

// test clean body string representation
alert(document.documentElement.innerHTML);

// find proxy created property
for(var k in document.body){
if(k.charAt(0) === "@")
alert([k, document.body[k]])
;
}
};


As Summary

This is more a proof of concept but I hope showed code will help us to replicate the behavior. The main missed part is the internal stack management: how can I clean the stack index when I don't need the node anymore? All we need is an extra in-proxy-scope function or a specific associated instance rather than a raw object.
In ew words there are no best strategies for this second problem, it just depends what we need.
From a logical point of view, if we give indirect access to that stack, exposing its length or via functions able to modify it, stack safety could be compromised. What I could suggest is something like:
var proxy = (function(){
// (C) WebReflection - Mit Style License
function proxy(){
return stack[this[expando]] || stack[
this[expando] = new Number(stack.push(new $proxy) - 1)
];
};
function $proxy(){
this._index = stack.length;
};
$proxy.prototype.destroy = function destroy(){
delete stack[this._index];
};
var expando = "@".concat(+new Date, Math.random()),
stack = []
;
return proxy;
})();
where the stack is manipulated indirectly while nothing is publicly exposed.

Sunday, May 24, 2009

PHP Full Proxy ... A Work In Progress

Something "truly dangerous" to play with: a proxy file able to understand everything via XMLHttpRequest, enabling any sort of cross site requests (no COMET yet, it could arrive soon).
Dunno even why I am posting this "little monster", but I am sure a lot of people would like to have "access to the entire web" simply using Ajax, and nothing else, something like this:

function website_exists(url){
var xhr = new XMLHttpRequest;
xhr.open("HEAD", "proxy.php?url=" + (url), false);
xhr.send(null);
return 199 < xhr.status && xhr.status < 400;
};

if(website_exists("http://gogle.com"))
alert("Of Course, It's Big G!");


WebReflection PHP Proxy



<?php

/** XMLHttpRequest PHP Proxy
* @author Andrea Giammarchi
* @blog http://webreflection.blogspot.com/
* @license Mit Style License
* @requires curl and Apache webserver
* @description basic authentication, GET, POST, HEAD, PUT, DELETE, others requests types.
* Nothing to do on the client side, except put "proxy.php?url=" as request prefix.
* The rest should be like normal in-server interaction
* @note DON'T TRY AT HOME
*/

// if no url has been provided, exit
if(!isset($_GET['url'])){
header('HTTP/1.1 400 Bad Request');
header('X-Proxy-Error: no url');
exit;
}

// work in progress
/* without Apache ... requires alternatives for Authorization and other stuff not in $_SERVER
if(!function_exists('getallheaders')){
function getallheaders(){
$headers= array();
foreach($_SERVER as $key => $value){
if(0 === strpos($key, 'HTTP_'))
$headers[str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))))] = $value;
}
return $headers;
}
}
// */

// GET, POST, PUT, HEAD, DELETE, ect ...
$method = $_SERVER['REQUEST_METHOD'];

// curl headers array
$headers= array();
foreach(getallheaders() as $key => $value)
$headers[] = $key.': '.$value;

// curl options
$opts = array(
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_BINARYTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers
);

// if request is post ...
if($method === 'POST'){
// populate the array of keys/values to send
$headers = array();
foreach($_POST as $key => $value)
$headers[] = rawurlencode($key).'='.rawurlencode($value);
$opts[CURLOPT_POST] = true;
$opts[CURLOPT_POSTFIELDS] = implode('&', $headers);
}

// if it is a basic authorization request ...
if(isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])){
// create user and pass parameters to send
$opts[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
$opts[CURLOPT_PROXYUSERPWD] = '['.
rawurlencode($_SERVER['PHP_AUTH_USER'])
.']:['.
rawurlencode($_SERVER['PHP_AUTH_PW'])
.']'
;
}

// init curl session
$call = $session = curl_init(substr($_SERVER['QUERY_STRING'], 4));

// set all options
curl_setopt_array($call, $opts);

// clear unnecessary variables
unset($opts);
unset($headers);

// retrieve the output
$result = explode(PHP_EOL, curl_exec($call));

// nothing else to do so far (this version is not compatible with COMET)
curl_close($call);

// for each returned information ...
for($i = 0, $length = count($result), $sent = array(); $i < $length; ++$i){
$value = $result[$i];

// if all headers has been sent ...
if($value === '')
// send the output
exit(implode(PHP_EOL, array_splice($result, ++$i)));
else {
// ... or send the header (do not overwrite if already sent)
$tmp = explode(':', $value);
header($value, !isset($sent[strtolower($tmp[0])]));
}
}

?>


Have fun exploring the net ;)