Browse Source

remoting rewrite

Nicolas Cannasse 17 years ago
parent
commit
14e189accf

+ 1 - 0
doc/CHANGES.txt

@@ -32,6 +32,7 @@ TODO inlining : substitute class+function type parameters in order to have fully
 	removed neko.io.Input/Output/Eof/Error/Logger/Multiple/StringInput/StringOutput
 	changed neko apis to use haxe.io and Bytes instead of String buffers
 	fixed big bug in js/flash8 debug stack handling
+	complete rewrite of haxe.remoting package
 
 2008-04-05: 1.19
 	fixed flash9 Array.toString

+ 8 - 7
std/haxe/ImportAll.hx

@@ -73,21 +73,22 @@ import haxe.io.Output;
 
 import haxe.remoting.Connection;
 import haxe.remoting.AsyncConnection;
-/*
+import haxe.remoting.ExternalConnection;
+import haxe.remoting.HttpConnection;
+import haxe.remoting.HttpAsyncConnection;
+import haxe.remoting.AMFConnection;
 import haxe.remoting.AsyncAdapter;
 import haxe.remoting.AsyncDebugConnection;
+import haxe.remoting.Proxy;
 import haxe.remoting.AsyncProxy;
+import haxe.remoting.LocalConnection;
 import haxe.remoting.DelayedConnection;
-#if !neko
 import haxe.remoting.FlashJsConnection;
-#end
-#if flash
-import haxe.remoting.LocalConnection;
-#end
+
+/*
 #if neko
 import haxe.remoting.NekoSocketConnection;
 #end
-import haxe.remoting.Proxy;
 import haxe.remoting.SocketConnection;
 import haxe.remoting.SocketProtocol;
 #if flash

+ 8 - 8
std/haxe/Timer.hx

@@ -26,6 +26,8 @@ package haxe;
 
 class Timer {
 
+	#if !neko
+
 	private var id : Null<Int>;
 
 	#if js
@@ -33,19 +35,17 @@ class Timer {
 	private var timerId : Int;
 	#end
 
-	#if !neko
-
-	public function new( time : Int ){
+	public function new( time_ms : Int ){
 		#if flash9
 			var me = this;
-			id = untyped __global__["flash.utils.setInterval"](function() { me.run(); },time);
+			id = untyped __global__["flash.utils.setInterval"](function() { me.run(); },time_ms);
 		#elseif flash
 			var me = this;
-			id = untyped _global["setInterval"](function() { me.run(); },time);
+			id = untyped _global["setInterval"](function() { me.run(); },time_ms);
 		#elseif js
 			id = arr.length;
 			arr[id] = this;
-			timerId = untyped window.setInterval("haxe.Timer.arr["+id+"].run();",time);
+			timerId = untyped window.setInterval("haxe.Timer.arr["+id+"].run();",time_ms);
 		#end
 	}
 
@@ -73,8 +73,8 @@ class Timer {
 	public dynamic function run() {
 	}
 
-	public static function delay( f : Void -> Void, time : Int ) {
-		var t = new haxe.Timer(time);
+	public static function delay( f : Void -> Void, time_ms : Int ) {
+		var t = new haxe.Timer(time_ms);
 		t.run = function() {
 			t.stop();
 			f();

+ 60 - 0
std/haxe/TimerQueue.hx

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe;
+
+#if neko
+#error
+#end
+
+class TimerQueue {
+
+	var delay : Int;
+	var t : haxe.Timer;
+	var q : Array<Void->Void>;
+
+	public function new( ?delay ) {
+		this.delay = delay == null ? 1 : delay;
+		q = new Array();
+	}
+
+	public function add(f) {
+		q.push(f);
+		if( t == null ) {
+			t = new haxe.Timer(delay);
+			t.run = process;
+		}
+	}
+
+	function process() {
+		var f = q.shift();
+		if( f == null ) {
+			t.stop();
+			t = null;
+			return;
+		}
+		f();
+	}
+
+}

+ 95 - 0
std/haxe/remoting/AMFConnection.hx

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe.remoting;
+
+class AMFConnection implements AsyncConnection, implements Dynamic<AsyncConnection> {
+
+	var __data : {
+		error : Dynamic -> Void,
+		#if flash9
+		cnx : flash.net.NetConnection,
+		#elseif flash
+		cnx : flash.NetConnection,
+		#else
+		cnx : Dynamic,
+		#end
+	};
+	var __path : Array<String>;
+
+	function new( data, path ) {
+		__data = data;
+		__path = path;
+	}
+
+	public function resolve( name ) : AsyncConnection {
+		var s = new AMFConnection(__data,__path.copy());
+		s.__path.push(name);
+		return s;
+	}
+
+	public function setErrorHandler(h) {
+		__data.error = h;
+	}
+
+	public function close() {
+		__data.cnx.close();
+	}
+
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) : Void {
+		if( onResult == null ) onResult = function(e) {};
+		var p = params.copy();
+		#if flash9
+		p.unshift(new flash.net.Responder(onResult,__data.error));
+		#else
+		p.unshift({ onStatus : __data.error, onResult : onResult });
+		#end
+		p.unshift(__path.join("."));
+		untyped __data.cnx.call.apply(__data,p);
+	}
+
+	#if flash
+	public static function urlConnect( gatewayUrl : String ) {
+		#if flash9
+		var c = new flash.net.NetConnection();
+		var cnx = new AMFConnection({ cnx : c, error : function(e) throw e },[]);
+		c.addEventListener(flash.events.NetStatusEvent.NET_STATUS,function(e:flash.events.NetStatusEvent) {
+			cnx.__data.error(e);
+		});
+		c.connect(gatewayUrl);
+		return cnx;
+		#else
+		var c = new flash.NetConnection();
+		if( !c.connect(gatewayUrl) )
+			throw "Could not connected to gateway url "+gatewayUrl;
+		return new AMFConnection({ cnx : c, error : function(e) throw e },[]);
+		#end
+	}
+
+	public static function connect( nc ) {
+		return new AMFConnection({ cnx : nc, error : function(e) throw e },[]);
+	}
+	#end
+
+}

+ 21 - 13
std/haxe/remoting/AsyncAdapter.hx

@@ -27,29 +27,37 @@ package haxe.remoting;
 /**
 	Build an AsyncConnection from a synchronized Connection.
 **/
-class AsyncAdapter extends AsyncConnection {
+class AsyncAdapter implements AsyncConnection {
 
-	public override function __resolve( name ) {
-		var s = new AsyncAdapter(__data.__resolve(name),null);
-		s.__error = __error;
-		return s;
+	var __cnx : Connection;
+	var __error : { ref : Dynamic -> Void };
+
+	function new(cnx,error) {
+		__cnx = cnx;
+		__error = error;
+	}
+
+	public function resolve( name ) : AsyncConnection {
+		return new AsyncAdapter(__cnx.resolve(name),__error);
+	}
+
+	public function setErrorHandler(h) {
+		__error.ref = h;
 	}
 
-	public override function call( params : Array<Dynamic>, ?onData : Dynamic -> Void ) : Void {
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) {
 		var ret;
 		try {
-			var c : Connection = __data;
-			ret = c.call(params);
+			ret = __cnx.call(params);
 		} catch( e : Dynamic ) {
-			onError(e);
+			__error.ref(e);
 			return;
 		}
-		if( onData != null )
-			onData(ret);
+		if( onResult != null ) onResult(ret);
 	}
 
-	public static function create( c : Connection ) {
-		return new AsyncAdapter(c,null);
+	public static function create( cnx : Connection ) : AsyncConnection {
+		return new AsyncAdapter(cnx,{ ref : function(e) throw e });
 	}
 
 }

+ 46 - 40
std/haxe/remoting/AsyncDebugConnection.hx

@@ -24,62 +24,68 @@
  */
 package haxe.remoting;
 
-class AsyncDebugConnection extends AsyncConnection, implements Dynamic<AsyncDebugConnection> {
+class AsyncDebugConnection implements AsyncConnection, implements Dynamic<AsyncDebugConnection> {
 
-	var lastCalls : List<{ path : Array<String>, params : Array<Dynamic> }>;
+	var __path : Array<String>;
+	var __cnx : AsyncConnection;
+	var __data : {
+		error : Dynamic -> Void,
+		oncall : Array<String> -> Array<Dynamic> -> Void,
+		onerror : Array<String> -> Array<Dynamic> -> Dynamic -> Void,
+		onresult : Array<String> -> Array<Dynamic> -> Dynamic -> Void,
+	};
 
-	public function new(cnx : AsyncConnection) {
-		super(cnx,[]);
-		lastCalls = new List();
-		__error = cnx.__error;
-		setErrorHandler(__error.ref);
+	function new(path,cnx,data) {
+		__path = path;
+		__cnx = cnx;
+		__data = data;
 	}
 
-	override function setErrorHandler(f) {
-		var me = this;
-		__error.ref = function(e) {
-			var l = me.lastCalls.pop();
-			if( l != null )
-				me.onErrorDisplay(l.path,l.params,e);
-			f(e);
-		}
-		return f;
+	public function resolve( name ) : AsyncConnection {
+		var cnx = new AsyncDebugConnection(__path.copy(),__cnx.resolve(name),__data);
+		cnx.__path.push(name);
+		return cnx;
 	}
 
-	public override function __resolve(field : String) : AsyncConnection {
-		var s = new AsyncDebugConnection(__data.__resolve(field));
-		s.lastCalls = lastCalls;
-		s.__error = __error;
-		// late binding of events
-		var me = this;
-		s.onCall = function(p,pa) { me.onCall(p,pa); };
-		s.onResult = function(p,pa,r) { me.onResult(p,pa,r); };
-		s.onErrorDisplay = function(p,pa,e) { me.onErrorDisplay(p,pa,e); };
-		return s;
+	public function setErrorHandler(h) {
+		__data.error = h;
 	}
 
-	public dynamic function onErrorDisplay( path : Array<String>, params : Array<Dynamic>, err : Dynamic ) {
-		trace(path.join(".")+"("+params.join(",")+") = ERROR "+Std.string(err));
+	public function setErrorDebug(h) {
+		__data.onerror = h;
 	}
 
-	public dynamic function onCall( path : Array<String>, params : Array<Dynamic> ) {
+	public function setResultDebug(h) {
+		__data.onresult = h;
 	}
 
-	public dynamic function onResult( path : Array<String>, params : Array<Dynamic>, result : Dynamic ) {
-		trace(path.join(".")+"("+params.join(",")+") = "+Std.string(result));
+	public function setCallDebug(h) {
+		__data.oncall = h;
 	}
 
-	override public function call( params : Array<Dynamic>, ?onData : Dynamic -> Void ) : Void {
-		lastCalls.add({ path : __data.__path, params : params });
-		onCall(__data.__path,params);
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) {
 		var me = this;
-		__data.call(params,function(r) {
-			var x = me.lastCalls.pop();
-			if( x != null )
-				me.onResult(x.path,x.params,r);
-			if( onData != null )
-				onData(r);
+		__data.oncall(__path,params);
+		__cnx.setErrorHandler(function(e) {
+			me.__data.onerror(me.__path,params,e);
+			me.__data.error(e);
+		});
+		__cnx.call(params,function(r) {
+			me.__data.onresult(me.__path,params,r);
+			if( onResult != null ) onResult(r);
+		});
+	}
+
+	public static function create( cnx : AsyncConnection ) {
+		var cnx = new AsyncDebugConnection([],cnx,{
+			error : function(e) throw e,
+			oncall : function(path,params) {},
+			onerror : null,
+			onresult : null,
 		});
+		cnx.setErrorDebug(function(path,params,e) trace(path.join(".")+"("+params.join(",")+") = ERROR "+Std.string(e)));
+		cnx.setResultDebug(function(path,params,e) trace(path.join(".")+"("+params.join(",")+") = "+Std.string(e)));
+		return cnx;
 	}
 
 }

+ 58 - 0
std/haxe/remoting/Context.hx

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe.remoting;
+
+class Context {
+
+	var objects : Hash<{ obj : Dynamic, rec : Bool }>;
+
+	public function new() {
+		objects = new Hash();
+	}
+
+	public function addObject( name : String, obj : {}, ?recursive ) {
+		objects.set(name,{ obj : obj, rec : recursive });
+	}
+
+	public function call( path : Array<String>, params : Array<Dynamic> ) : Dynamic {
+		if( path.length < 2 ) throw "Invalid path '"+path.join(".")+"'";
+		var inf = objects.get(path[0]);
+		if( inf == null )
+			throw "No such object "+path.join(".");
+		var o = inf.obj;
+		var m = Reflect.field(o,path[1]);
+		if( path.length > 2 ) {
+			if( !inf.rec ) throw "Can't access "+path.join(".");
+			for( i in 2...path.length ) {
+				o = m;
+				m = Reflect.field(o,path[i]);
+			}
+		}
+		if( !Reflect.isFunction(m) )
+			throw "No such method "+path.join(".");
+		return Reflect.callMethod(o,m,params);
+	}
+
+}

+ 40 - 27
std/haxe/remoting/DelayedConnection.hx

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, The haXe Project Contributors
+ * Copyright (c) 2005-2008, The haXe Project Contributors
  * All rights reserved.
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -24,57 +24,70 @@
  */
 package haxe.remoting;
 
-private typedef InternalData = {
-	var ref : AsyncConnection;
-	var msg : Array<{ path : Array<String>, params : Array<Dynamic>, onData : Dynamic -> Void }>;
-}
-
-class DelayedConnection extends AsyncConnection, implements Dynamic<DelayedConnection> {
+class DelayedConnection implements AsyncConnection, implements Dynamic<AsyncConnection> {
 
 	public var connection(getConnection,setConnection) : AsyncConnection;
 
-	public override function __resolve( field : String ) : AsyncConnection {
+	var __path : Array<String>;
+	var __data : {
+		cnx : AsyncConnection,
+		error : Dynamic -> Void,
+		cache : Array<{
+			path : Array<String>,
+			params : Array<Dynamic>,
+			onResult : Dynamic -> Void,
+			onError : Dynamic -> Void
+		}>,
+	};
+
+	function new(data,path) {
+		__data = data;
+		__path = path;
+	}
+
+	public function setErrorHandler(h) {
+		__data.error = h;
+	}
+
+	public function resolve( name ) : AsyncConnection {
 		var d = new DelayedConnection(__data,__path.copy());
-		d.__error = __error;
-		d.__path.push(field);
+		d.__path.push(name);
 		return d;
 	}
 
 	function getConnection() {
-		var d : InternalData = __data;
-		return d.ref;
+		return __data.cnx;
 	}
 
 	function setConnection(cnx) {
-		var d : InternalData = __data;
-		d.ref = cnx;
-		process(d);
+		__data.cnx = cnx;
+		process(this);
 		return cnx;
 	}
 
-	override public function call( params, ?onData ) {
-		var d : InternalData = __data;
-		d.msg.push({ path : __path, params : params, onData : onData });
-		process(d);
+	public function call( params : Array<Dynamic>, ?onResult ) {
+		__data.cache.push({ path : __path, params : params, onResult : onResult, onError : __data.error });
+		process(this);
 	}
 
-	static function process( d : InternalData ) {
-		if( d.ref == null )
+	static function process( d : DelayedConnection ) {
+		var cnx = d.__data.cnx;
+		if( cnx == null )
 			return;
 		while( true ) {
-			var m = d.msg.shift();
+			var m = d.__data.cache.shift();
 			if( m == null )
 				break;
-			var r = d.ref;
+			var c = cnx;
 			for( p in m.path )
-				r = r.__resolve(p);
-			r.call(m.params,m.onData);
+				c = c.resolve(p);
+			c.setErrorHandler(m.onError);
+			c.call(m.params,m.onResult);
 		}
 	}
 
 	public static function create() {
-		var d : InternalData = { ref : null, msg : [] };
-		return new DelayedConnection(d,[]);
+		return new DelayedConnection({ cnx : null, error : function(e) throw e, cache : new Array() },[]);
 	}
 
 }

+ 128 - 0
std/haxe/remoting/ExternalConnection.hx

@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe.remoting;
+
+/**
+	Synchronous communications between Flash and Javascript.
+**/
+class ExternalConnection implements Connection, implements Dynamic<Connection> {
+
+	var __data : { name : String, ctx : Context, #if js flash : String #end };
+	var __path : Array<String>;
+
+	function new( data, path ) {
+		__data = data;
+		__path = path;
+	}
+
+	public function resolve(field) : Connection {
+		var e = new ExternalConnection(__data,__path.copy());
+		e.__path.push(field);
+		return e;
+	}
+
+	public function close() {
+		connections.remove(__data.name);
+	}
+
+	#if flash9
+	static function escapeString( s : String ) {
+		return s.split("\\").join("\\\\");
+	}
+	#elseif flash
+	static function escapeString( s : String ) {
+		return s.split("\\").join("\\\\").split("&").join("&amp;");
+	}
+	#else
+	static inline function escapeString(s) {
+		return s;
+	}
+	#end
+
+	public function call( params : Array<Dynamic> ) : Dynamic {
+		var s = new haxe.Serializer();
+		s.serialize(params);
+		var params = escapeString(s.toString());
+		var data = null;
+		#if flash
+			data = flash.external.ExternalInterface.call("haxe.remoting.ExternalConnection.doCall",__data.name,__path.join("."),params);
+		#elseif js
+			var fobj : Dynamic = untyped window.document[__data.flash];
+			if( fobj == null ) throw "Could not find flash object '"+__data.flash+"'";
+			try	data = fobj.externalRemotingCall(__data.name,__path.join("."),params) catch( e : Dynamic ) {};
+		#end
+		if( data == null )
+			throw "Call failure : ExternalConnection is not " + #if flash "compiled in JS" #else "initialized in Flash" #end;
+		return new haxe.Unserializer(data).unserialize();
+	}
+
+	static var connections = new Hash<ExternalConnection>();
+
+	static function doCall( name : String, path : String, params : String ) : String {
+		try {
+			var cnx = connections.get(name);
+			if( cnx == null ) throw "Unknown connection : "+name;
+			var params = new haxe.Unserializer(params).unserialize();
+			var ret = cnx.__data.ctx.call(path.split("."),params);
+			var s = new haxe.Serializer();
+			s.serialize(ret);
+			#if flash
+			return escapeString(s.toString());
+			#else
+			return s.toString()+"#";
+			#end
+		} catch( e : Dynamic ) {
+			var s = new haxe.Serializer();
+			s.serializeException(e);
+			return s.toString();
+		}
+	}
+
+	#if flash
+
+	public static function jsConnect( name : String, ctx : Context ) {
+		if( !flash.external.ExternalInterface.available )
+			throw "External Interface not available";
+		#if flash9
+		try flash.external.ExternalInterface.addCallback("externalRemotingCall",doCall) catch( e : Dynamic ) {};
+		#else
+		flash.external.ExternalInterface.addCallback("externalRemotingCall",null,doCall);
+		#end
+		var cnx = new ExternalConnection({ name : name, ctx : ctx },[]);
+		connections.set(name,cnx);
+		return cnx;
+	}
+
+	#elseif js
+
+	public static function flashConnect( name : String, flashObjectID : String, ctx : Context ) {
+		var cnx = new ExternalConnection({ ctx : ctx, name : name, flash : flashObjectID },[]);
+		connections.set(name,cnx);
+		return cnx;
+	}
+
+	#end
+
+}

+ 113 - 33
std/haxe/remoting/FlashJsConnection.hx

@@ -1,58 +1,138 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
 package haxe.remoting;
 
-class FlashJsConnection extends haxe.remoting.AsyncConnection {
+class FlashJsConnection #if flash implements AsyncConnection #end {
 
-	#if flash
+#if flash
 
-	public override function __resolve( field : String ) : AsyncConnection {
+	var __path : Array<String>;
+	var __data : {
+		id : String,
+		name : String,
+		ctx : Context,
+		error : Dynamic -> Void,
+		queue : haxe.TimerQueue,
+	};
+
+	function new( data, path ) {
+		__data = data;
+		__path = path;
+	}
+
+	public function close() {
+		connections.remove(__data.name);
+	}
+
+	public function resolve( name ) : AsyncConnection {
 		var c = new FlashJsConnection(__data,__path.copy());
-		c.__error = __error;
-		c.__path.push(field);
+		c.__path.push(name);
 		return c;
 	}
 
-	override public function call( params : Array<Dynamic>, ?onData : Dynamic -> Void ) {
-		var p = __path.copy();
-		var f = p.pop();
-		var path = p.join(".");
+	public function setErrorHandler(h) {
+		__data.error = h;
+	}
+
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) {
 		var s = new haxe.Serializer();
 		s.serialize(params);
-		var cnx : { private function escapeString(s : String) : String; } = haxe.remoting.Connection;
-		var params = cnx.escapeString(s.toString());
+		var params = escapeString(s.toString());
+		var error = __data.error;
 		var me = this;
-		haxe.Timer.queue(function() {
-			var s = flash.external.ExternalInterface.call("haxe.remoting.FlashJsConnection.flashCall",me.__data,path,f,params);
-			var v = null;
+		__data.queue.add(function() {
+			var data = flash.external.ExternalInterface.call("haxe.remoting.FlashJsConnection.flashCall",me.__data.id,me.__data.name,me.__path.join("."),params);
+			var v : Dynamic;
 			try {
-				if( s == null )
-					throw "Failed to call JS method "+path;
-				v = { r : new haxe.Unserializer(s).unserialize() };
+				if( data == null )
+					throw "Call failure : FlashJsConnection is not compiled in JS";
+				v = new haxe.Unserializer(data).unserialize();
 			} catch( e : Dynamic ) {
-				me.onError(e);
+				error(e);
+				return;
 			}
-			if( v != null )
-				onData(v.r);
+			if( onResult != null )
+				onResult(v);
 		});
 	}
 
-	public static function flashConnect( objId : String ) : AsyncConnection {
-		if( !flash.external.ExternalInterface.available )
-			throw "External Interface not available";
-		if( flash.external.ExternalInterface.call("haxe.remoting.FlashJsConnection"+".jsRemoting") != "yes" )
-			throw "haxe.remoting.FlashJsConnection"+" is not available in JavaScript";
-		return new FlashJsConnection(objId,[]);
+	static var connections = new Hash<FlashJsConnection>();
+
+	static function escapeString( s : String ) {
+		#if flash9
+		return s.split("\\").join("\\\\");
+		#else
+		return s.split("\\").join("\\\\").split("&").join("&amp;");
+		#end
 	}
 
-	#else js
+	static function doCall( name : String, path : String, params : String ) : String {
+		try {
+			var cnx = connections.get(name);
+			if( cnx == null ) throw "Unknown connection : "+name;
+			var params = new haxe.Unserializer(params).unserialize();
+			var ret = cnx.__data.ctx.call(path.split("."),params);
+			var s = new haxe.Serializer();
+			s.serialize(ret);
+			return escapeString(s.toString());
+		} catch( e : Dynamic ) {
+			var s = new haxe.Serializer();
+			s.serializeException(e);
+			return s.toString();
+		}
+	}
 
-	static function jsRemoting() {
-		return "yes";
+	public static function connect( objId : String, name : String, ctx : Context ) {
+		if( !flash.external.ExternalInterface.available )
+			throw "External Interface not available";
+		#if flash9
+		try flash.external.ExternalInterface.addCallback("flashJsRemotingCall",doCall) catch( e : Dynamic ) {};
+		#else
+		flash.external.ExternalInterface.addCallback("flashJsRemotingCall",null,doCall);
+		#end
+		var cnx = new FlashJsConnection({
+			id : objId,
+			name : name,
+			ctx : ctx,
+			error : function(e) throw e,
+			queue : new haxe.TimerQueue(),
+		},[]);
+		connections.set(name,cnx);
+		return cnx;
 	}
 
-	static function flashCall( flashObj : String, path : String, f : String, params : String ) : String {
+#elseif js
+
+	static function flashCall( flashObj : String, name : String, path : String, params : String ) : String {
 		try {
-			var cnx : { private var __data : Dynamic; } = haxe.remoting.Connection.flashConnect(flashObj);
-			return cnx.__data.remotingCall(path,f,params);
+			var fobj : Dynamic = untyped window.document[flashObj];
+			if( fobj == null ) throw "Could not find flash object '"+flashObj+"'";
+			var data = null;
+			try data = fobj.flashJsRemotingCall(name,path,params) catch( e : Dynamic ) {};
+			if( data == null ) throw "Flash object "+flashObj+" does not have an active FlashJsConnection";
+			return data;
 		} catch( e : Dynamic ) {
 			var s = new haxe.Serializer();
 			s.serializeException(e);
@@ -60,6 +140,6 @@ class FlashJsConnection extends haxe.remoting.AsyncConnection {
 		}
 	}
 
-	#end
+#end
 
 }

+ 79 - 0
std/haxe/remoting/HttpAsyncConnection.hx

@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe.remoting;
+
+class HttpAsyncConnection implements AsyncConnection {
+
+	var __data : { url : String, error : Dynamic -> Void };
+	var __path : Array<String>;
+
+	function new(data,path) {
+		__data = data;
+		__path = path;
+	}
+
+	public function resolve( name ) : AsyncConnection {
+		var c = new HttpAsyncConnection(__data,__path.copy());
+		c.__path.push(name);
+		return c;
+	}
+
+	public function setErrorHandler(h) {
+		__data.error = h;
+	}
+
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) {
+		var h = new haxe.Http(__data.url);
+		#if (neko && no_remoting_shutdown)
+			h.noShutdown = true;
+		#end
+		var s = new haxe.Serializer();
+		s.serialize(__path);
+		s.serialize(params);
+		h.setHeader("X-Haxe-Remoting","1");
+		h.setParameter("__x",s.toString());
+		var error = __data.error;
+		h.onData = function( response : String ) {
+			var ok = true;
+			var ret;
+			try {
+				if( response.substr(0,3) != "hxr" ) throw "Invalid response : '"+response+"'";
+				var s = new haxe.Unserializer(response.substr(3));
+				ret = s.unserialize();
+			} catch( err : Dynamic ) {
+				ok = false;
+				error(err);
+			}
+			if( ok && onResult != null ) onResult(ret);
+		};
+		h.onError = error;
+		h.request(true);
+	}
+
+	public static function urlConnect( url : String ) : AsyncConnection {
+		return new HttpAsyncConnection({ url : url, error : function(e) throw e },[]);
+	}
+
+}

+ 100 - 0
std/haxe/remoting/HttpConnection.hx

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2005-2008, The haXe Project Contributors
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+package haxe.remoting;
+
+class HttpConnection implements Connection {
+
+	var __url : String;
+	var __path : Array<String>;
+
+	function new(url,path) {
+		__url = url;
+		__path = path;
+	}
+
+	public function resolve( name ) : Connection {
+		var c = new HttpConnection(__url,__path.copy());
+		c.__path.push(name);
+		return c;
+	}
+
+	public function call( params : Array<Dynamic> ) : Dynamic {
+		var data = null;
+		var h = new haxe.Http(__url);
+		#if js
+			untyped h.async = false;
+		#end
+		#if (neko && no_remoting_shutdown)
+			h.noShutdown = true;
+		#end
+		var s = new haxe.Serializer();
+		s.serialize(__path);
+		s.serialize(params);
+		h.setHeader("X-Haxe-Remoting","1");
+		h.setParameter("__x",s.toString());
+		h.onData = function(d) { data = d; };
+		h.onError = function(e) { throw e; };
+		h.request(true);
+		if( data.substr(0,3) != "hxr" )
+			throw "Invalid response : '"+data+"'";
+		data = data.substr(3);
+		return new haxe.Unserializer(data).unserialize();
+	}
+
+	#if (js || neko)
+
+	public static function urlConnect( url : String ) : Connection {
+		return new HttpConnection(url,[]);
+	}
+
+	#end
+
+	#if neko
+	public static function handleRequest( ctx : Context ) {
+		var v = neko.Web.getParams().get("__x");
+		if( neko.Web.getClientHeader("X-Haxe-Remoting") == null || v == null )
+			return false;
+		neko.Lib.print(processRequest(v,ctx));
+		return true;
+	}
+	#end
+
+	public static function processRequest( requestData : String, ctx : Context ) : String {
+		try {
+			var u = new haxe.Unserializer(requestData);
+			var path = u.unserialize();
+			var args = u.unserialize();
+			var data = ctx.call(path,args);
+			var s = new haxe.Serializer();
+			s.serialize(data);
+			return "hxr" + s.toString();
+		} catch( e : Dynamic ) {
+			var s = new haxe.Serializer();
+			s.serializeException(e);
+			return "hxr" + s.toString();
+		}
+	}
+
+}

+ 105 - 89
std/haxe/remoting/LocalConnection.hx

@@ -24,128 +24,144 @@
  */
 package haxe.remoting;
 
-#if flash
-#else error
-#end
+class LocalConnection implements AsyncConnection {
 
-class LocalConnection extends AsyncConnection {
+	static var ID = 0;
 
-	var __funs : List<Dynamic -> Void>;
+	var __path : Array<String>;
+	var __data : {
+		ctx : Context,
+		results : IntHash<{ error : Dynamic -> Void, result : Dynamic -> Void }>,
+		error : Dynamic -> Void,
+		target : String,
+		#if flash9
+		cnx : flash.net.LocalConnection,
+		#elseif flash
+		cnx : flash.LocalConnection,
+		#else
+		cnx : Dynamic,
+		#end
+	};
+
+	function new(data,path) {
+		this.__path = path;
+		this.__data = data;
+	}
 
-	public override function __resolve(field) : AsyncConnection {
+	public function resolve( name ) : AsyncConnection {
 		var s = new LocalConnection(__data,__path.copy());
-		s.__error = __error;
-		s.__funs = __funs;
-		s.__path.push(field);
+		s.__path.push(name);
 		return s;
 	}
 
-	override public function call( params : Array<Dynamic>, ?onData : Dynamic -> Void ) : Void {
+	public function setErrorHandler(h) {
+		__data.error = h;
+	}
+
+	public function call( params : Array<Dynamic>, ?onResult : Dynamic -> Void ) : Void {
 		try {
-			var s = new haxe.Serializer();
-			var p = __path.copy();
-			var f = p.pop();
-			if( f == null )
-				throw "No method specified";
-			s.serialize(params);
+			var id = ID++;
 			#if flash9
-			__data.send(__data.client.target,"remotingCall",p.join("."),f,s.toString());
-			#else true
-			if( !__data[untyped "send"](__data.target,"remotingCall",p.join("."),f,s.toString()) )
+			__data.cnx.send(__data.target,"remotingCall",id,__path.join("."),haxe.Serializer.run(params));
+			#else
+			if( !__data.cnx.send(__data.target,"remotingCall",id,__path.join("."),haxe.Serializer.run(params)) )
 				throw "Remoting call failure";
 			#end
-			__funs.add(onData);
+			__data.results.set(id,{ error : __data.error, result : onResult });
 		} catch( e : Dynamic ) {
-			__error.ref(e);
+			__data.error(e);
 		}
 	}
 
-	public function closeConnection() {
-		#if flash9
-		var cnx : flash.net.LocalConnection = __data;
-		#else true
-		var cnx : flash.LocalConnection = __data;
-		#end
-		cnx.close();
+	public function close() {
+		__data.cnx.close();
 	}
 
-	static function remotingCall( c : LocalConnection, path, f, args ) {
-		var r = untyped Connection.doCall(path,f,args);
-		#if flash9
-		c.__data.send(c.__data.client.target,"remotingResult",r);
-		#else true
-		if( !c.__data[untyped "send"](c.__data.target,"remotingResult",r) )
-			c.__error.ref("Remoting response failure");
-		#end
+	static function remotingCall( c : LocalConnection, id : Int, path : String, args : String ) {
+		var r;
+		try {
+			var ret = c.__data.ctx.call(path.split("."),haxe.Unserializer.run(args));
+			r = haxe.Serializer.run(ret);
+		} catch( e : Dynamic ) {
+			var s = new haxe.Serializer();
+			s.serializeException(e);
+			r = s.toString();
+		}
+		// don't forward 'send' errors on connection since it's only the receiving side
+		c.__data.cnx.send(c.__data.target,"remotingResult",id,r);
 	}
 
-	static function remotingResult( c : LocalConnection, r : String ) {
-		var f : Dynamic -> Void;
+	static function remotingResult( c : LocalConnection, id : Int, result : String ) {
+		var f = c.__data.results.get(id);
+		if( f == null )
+			c.__data.error("Invalid result ID "+id);
+		c.__data.results.remove(id);
 		var val : Dynamic;
 		try {
-			if( c.__funs.isEmpty() )
-				throw "No response expected";
-			f = c.__funs.pop();
-			val = new haxe.Unserializer(r).unserialize();
-			if( f == null )
-				return;
+			val = new haxe.Unserializer(result).unserialize();
 		} catch( e : Dynamic ) {
-			c.__error.ref(e);
+			f.error(e);
 			return;
 		}
-		f(val);
+		if( f.result != null )
+			f.result(val);
 	}
 
-	public static function connect( name : String, ?allowDomains : Array<String> ) {
+	#if flash
+	public static function connect( name : String, ctx : Context, ?allowDomains : Array<String> ) {
 		#if flash9
-		var l = new flash.net.LocalConnection();
-		#else flash
-		var l = new flash.LocalConnection();
+			var l = new flash.net.LocalConnection();
+		#else
+			var l = new flash.LocalConnection();
 		#end
-		var c = new LocalConnection(l,[]);
-		c.__funs = new List();
-		var recv = name+"_recv";
-		var api = {
-			remotingCall : function(path,f,args) { remotingCall(c,path,f,args); },
-			remotingResult : function(r) { remotingResult(c,r); },
-			onStatus : function(s:Dynamic) { if( s[untyped "level"] != "status" ) c.__error.ref("Failed to send data on LocalConnection"); },
-			target : null,
-		}
+		var recv = name + "_recv";
+		var c = new LocalConnection({
+			ctx : ctx,
+			error : function(e) throw e,
+			results : new IntHash(),
+			cnx : l,
+			target : recv,
+		},[]);
 		#if flash9
-		l.client = api;
-		l.addEventListener(flash.events.StatusEvent.STATUS, api.onStatus);
-		try {
-			api.target = recv;
-			l.connect(name);
-		} catch( e : Dynamic ) {
-			api.target = name;
-			l.connect(recv);
-		}
-		#else true
-		Reflect.setField(l,"remotingCall",api.remotingCall);
-		Reflect.setField(l,"remotingResult",api.remotingResult);
-		l.onStatus = api.onStatus;
-		if( allowDomains != null )
-		#if flash9
-			for( d in allowDomains )
-				l.allowDomain(d);
-		#else true
-			l.allowDomain = function(dom) {
+			l.client = {
+				remotingCall : callback(remotingCall,c),
+				remotingResult : callback(remotingResult,c),
+			};
+			l.addEventListener(flash.events.StatusEvent.STATUS, function(s:flash.events.StatusEvent) {
+				if( s.level != "status" )
+					c.__data.error("Failed to send data on LocalConnection");
+			});
+			try
+				l.connect(name)
+			catch( e : Dynamic ) {
+				l.connect(recv);
+				c.__data.target = name;
+			}
+			if( allowDomains != null )
 				for( d in allowDomains )
-					if( d == dom )
-						return true;
-				return false;
+					l.allowDomain(d);
+		#else
+			Reflect.setField(l,"remotingCall",callback(remotingCall,c));
+			Reflect.setField(l,"remotingResult",callback(remotingResult,c));
+			l.onStatus = function(s:Dynamic) {
+				if( s[untyped "level"] != "status" )
+					c.__data.error("Failed to send data on LocalConnection");
 			};
-		#end
-		if( l.connect(name) )
-			untyped l.target = recv;
-		else {
-			if( !l.connect(recv) )
-				throw "Could not assign a LocalConnection to the name "+name;
-			untyped l.target = name;
-		}
+			if( !l.connect(name) ) {
+				if( !l.connect(recv) )
+					throw "Could not assign a LocalConnection to the name "+name;
+				c.__data.target = name;
+			}
+			if( allowDomains != null )
+				l.allowDomain = function(dom) {
+					for( d in allowDomains )
+						if( d == dom )
+							return true;
+					return false;
+				};
 		#end
 		return c;
 	}
+	#end
 
 }

+ 35 - 0
tests/unit/RemotingApi.hx

@@ -0,0 +1,35 @@
+package unit;
+
+class RemotingApi {
+
+	var sub : RemotingApi;
+
+	public function new() {
+		sub = this;
+	}
+
+	public function add(x : Int,y : Int) {
+		return x + y;
+	}
+
+	public function id( str : String ) {
+		return str;
+	}
+
+	public function arr( a : Array<String> ) {
+		return a.join("#");
+	}
+
+	public function exc( v : Dynamic ) {
+		if( v != null )
+			throw v;
+	}
+
+	public static function context() {
+		var ctx = new haxe.remoting.Context();
+		ctx.addObject("api",new RemotingApi());
+		ctx.addObject("apirec",new RemotingApi(),true);
+		return ctx;
+	}
+
+}

+ 11 - 0
tests/unit/RemotingServer.hx

@@ -0,0 +1,11 @@
+package unit;
+
+class RemotingServer {
+
+	static function main() {
+		var ctx = RemotingApi.context();
+		if( !haxe.remoting.HttpConnection.handleRequest(ctx) )
+			throw "Invalid request";
+	}
+
+}

+ 55 - 4
tests/unit/Test.hx

@@ -39,31 +39,81 @@ class Test {
 		reportInfos = m;
 	}
 
+	function async<Args,T>( f : Args -> (T -> Void) -> Void, args : Args, v : T, ?pos : haxe.PosInfos ) {
+		asyncWaits.push(pos);
+		f(args,function(v2) {
+			count++;
+			if( !asyncWaits.remove(pos) ) {
+				report("Double async result",pos);
+				return;
+			}
+			if( v != v2 )
+				report(v2+" should be "+v,pos);
+			checkDone();
+		});
+	}
+
+	function asyncExc<Args>( seterror : (Dynamic -> Void) -> Void, f : Args -> (Dynamic -> Void) -> Void, args : Args, ?pos : haxe.PosInfos ) {
+		asyncWaits.push(pos);
+		seterror(function(e) {
+			count++;
+			if( !asyncWaits.remove(pos) )
+				report("Multiple async events",pos);
+		});
+		f(args,function(v) {
+			count++;
+			if( asyncWaits.remove(pos) )
+				report("No exception occured",pos);
+			else
+				report("Multiple async events",pos);
+		});
+	}
+
 	static var count = 0;
 	static var reportInfos = null;
 	static var reportCount = 0;
+	static var checkCount = 0;
+	static var asyncWaits = new Array<haxe.PosInfos>();
 
-	static function report( msg : String, pos : haxe.PosInfos ) {
+	dynamic static function report( msg : String, pos : haxe.PosInfos ) {
 		if( reportInfos != null ) {
 			msg += " ("+reportInfos+")";
 			reportInfos = null;
 		}
 		haxe.Log.trace(msg,pos);
 		reportCount++;
-		if( reportCount > 10 ) throw "Too many errors";
+		if( reportCount == 10 ) {
+			trace("Too many errors");
+			report = function(msg,pos) {};
+		}
+	}
+
+	static function checkDone() {
+		if( asyncWaits.length == 0 )
+			report("DONE ["+count+" tests]",here);
+	}
+
+	static function asyncTimeout() {
+		for( pos in asyncWaits )
+			report("TIMEOUT",pos);
 	}
 
 	static function main() {
 		#if neko
-		if( neko.Web.isModNeko ) neko.Lib.print("<pre>");
+		if( neko.Web.isModNeko )
+			neko.Lib.print("<pre>");
+		#else
+		haxe.Timer.delay(asyncTimeout,10000);
 		#end
 		var classes = [
 			new TestBytes(),
 			new TestInt32(),
 			new TestIO(),
+			new TestRemoting(),
 		];
 		var current = null;
 		try {
+			asyncWaits.push(null);
 			for( inst in classes ) {
 				current = Type.getClass(inst);
 				for( f in Type.getInstanceFields(current) )
@@ -72,7 +122,8 @@ class Test {
 						reportInfos = null;
 					}
 			}
-			report("DONE ["+count+" tests]",here);
+			asyncWaits.remove(null);
+			checkDone();
 		} catch( e : Dynamic ) {
 			reportInfos = null;
 			var msg = "???";

+ 94 - 0
tests/unit/TestRemoting.hx

@@ -0,0 +1,94 @@
+package unit;
+
+class TestRemoting extends Test {
+
+	static var _ = init();
+	static var ecnx : haxe.remoting.ExternalConnection;
+	static var ecnx2 : haxe.remoting.ExternalConnection;
+	static var ecnx3 : haxe.remoting.ExternalConnection;
+	static var lcnx : haxe.remoting.LocalConnection;
+	static var fjscnx : haxe.remoting.FlashJsConnection;
+
+	static function init() {
+		var ctx = RemotingApi.context();
+		#if flash
+		ecnx = haxe.remoting.ExternalConnection.jsConnect("cnx",ctx);
+		ecnx3 = haxe.remoting.ExternalConnection.jsConnect("unknown",ctx);
+		lcnx = haxe.remoting.LocalConnection.connect("local",ctx,["dev.unit-tests"]);
+		fjscnx = haxe.remoting.FlashJsConnection.connect(#if flash9 "haxeFlash8" #else "haxeFlash9" #end,"cnx",ctx);
+		#elseif js
+		ecnx = haxe.remoting.ExternalConnection.flashConnect("cnx","haxeFlash8",ctx);
+		ecnx2 = haxe.remoting.ExternalConnection.flashConnect("cnx","haxeFlash9",ctx);
+		ecnx3 = haxe.remoting.ExternalConnection.flashConnect("nothing","haxeFlash8",ctx);
+		#end
+	}
+
+	public function test() {
+		#if (flash || js)
+		doTestConnection(ecnx);
+		#end
+		exc(function() ecnx3.api.add.call([1,3]));
+		#if js
+		doTestConnection(ecnx2);
+		#end
+		#if flash
+		doTestAsyncConnection(lcnx);
+		doTestAsyncConnection(fjscnx);
+		#end
+		#if (js || neko)
+		var hcnx = haxe.remoting.HttpConnection.urlConnect("http://dev.unit-tests/remoting.n");
+		doTestConnection(hcnx);
+		var dcnx = haxe.remoting.AsyncDebugConnection.create(haxe.remoting.AsyncAdapter.create(hcnx));
+		dcnx.setErrorDebug(function(path,args,e) {});
+		dcnx.setResultDebug(function(path,args,ret) {});
+		dcnx.setCallDebug(function(path,args) {});
+		doTestAsyncConnection(dcnx);
+		#end
+		var hcnx = haxe.remoting.HttpAsyncConnection.urlConnect("http://dev.unit-tests/remoting.n");
+		doTestAsyncConnection(hcnx);
+		var dcnx = haxe.remoting.DelayedConnection.create();
+		dcnx.connection = hcnx;
+		doTestAsyncConnection(dcnx);
+	}
+
+	function doTestConnection( cnx : haxe.remoting.Connection ) {
+		eq( cnx.api.add.call([1,2]), 3 );
+		var strings = ["bla","\n","\r","\n\r","\t","    "," ","&","<",">","&nbsp;","&gt;","<br/>"];
+		for( s in strings ) {
+			infos("using "+s);
+			eq( cnx.api.id.call([s]), s );
+			eq( cnx.api.arr.call([[s,s,s]]), [s,s,s].join("#") );
+		}
+		infos(null);
+		eq( cnx.api.exc.call([null]), null );
+		exc( function() cnx.api.exc.call([5]) );
+
+		exc( function() cnx.api.call([]) );
+		exc( function() cnx.call([]) );
+
+		exc( function() cnx.api.unknown.call([]) );
+		exc( function() cnx.api.sub.add.call([1,2]) );
+		eq( cnx.apirec.sub.add.call([1,2]), 3 );
+	}
+
+	function doTestAsyncConnection( cnx : haxe.remoting.AsyncConnection ) {
+		var asyncExc = callback(asyncExc,cnx.setErrorHandler);
+
+		async( cnx.api.add.call, [1,2], 3 );
+		var strings = ["bla","\n","\r","\n\r","\t","    "," ","&","<",">","&nbsp;","&gt;","<br/>"];
+		for( s in strings ) {
+			async( cnx.api.id.call, [s], s );
+			async( cnx.api.arr.call, [[s,s,s]], [s,s,s].join("#") );
+		}
+		async( cnx.api.exc.call, [null], null );
+		asyncExc( cnx.api.exc.call, [5] );
+
+		asyncExc( cnx.api.call, [] );
+		asyncExc( cnx.call, [] );
+
+		asyncExc( cnx.api.unknown.call, [] );
+		asyncExc( cnx.api.sub.add.call, [1,2] );
+		async( cnx.apirec.sub.add.call, [1,2], 3 );
+	}
+
+}

+ 12 - 8
tests/unit/unit.html

@@ -6,7 +6,11 @@
 	<script type="text/javascript" src="unit.js"></script>
 </head>
 
-<body bgcolor="#dddddd" onLoad="setTimeout('unit.Test.main()',100)">
+<body bgcolor="#dddddd" onLoad="setTimeout('unit.Test.main()',1000)">
+
+<p>
+	Note : These tests need to be run from a local domain called <code>dev.unit-tests</code> which is running mod_neko in this directory.
+</p>
 
 <table>
 <tr>
@@ -14,15 +18,15 @@
 
 <p>JS :</p>
 
-<div id="haxe:trace" style="{ background-color : white; width : 390px; padding : 5px; }">
-</div>
+<pre id="haxe:trace" style="{ background-color : white; height : 290px; width : 390px; padding : 5px; overflow : auto auto; }">
+</pre>
 
 </td>
 <td>
 
 <p>Neko :</p>
 
-<iframe src="http://dev.unit-tests/unit.n" style="{ border : none; background-color : white; width : 400px; }">
+<iframe src="http://dev.unit-tests/unit.n" style="{ border : none; background-color : white; width : 400px; height : 300px; }">
 </iframe>
 
 </td>
@@ -36,7 +40,7 @@
 <object	classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
         width="400"
 	height="300"
-	id="haxe"
+	id="haxeFlash8"
 	align="middle">
 <param name="movie" value="unit8.swf"/>
 <param name="allowScriptAccess" value="always" />
@@ -48,7 +52,7 @@
        bgcolor="#ffffff"
        width="400"
        height="300"
-       name="haxe"
+       name="haxeFlash8"
        quality="high"
        align="middle"
        allowScriptAccess="always"
@@ -67,7 +71,7 @@ Flash 9 :
 <object	classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
         width="400"
 	height="300"
-	id="haxe"
+	id="haxeFlash9"
 	align="middle">
 <param name="movie" value="unit9.swf"/>
 <param name="allowScriptAccess" value="always" />
@@ -79,7 +83,7 @@ Flash 9 :
        bgcolor="#ffffff"
        width="400"
        height="300"
-       name="haxe"
+       name="haxeFlash9"
        quality="high"
        align="middle"
        allowScriptAccess="always"

+ 6 - 0
tests/unit/unit.hxml

@@ -25,3 +25,9 @@ unit.Test
 -main unit.Test
 -debug
 -cp ..
+
+--next
+# RemotingServer
+-neko remoting.n
+-main unit.RemotingServer
+-cp ..

+ 5 - 1
tests/unit/unit.hxp

@@ -1,12 +1,16 @@
 <haxe selected="0">
-  <output name="Flash" mode="swf" out="unit8.swf" class="unit.Test" lib="" cmd="unit.html" main="True" debug="True">-cp ..</output>
+  <output name="Flash" mode="swf" out="unit8.swf" class="unit.Test" lib="" cmd="http://dev.unit-tests/unit.html" main="True" debug="True">-cp ..</output>
   <output name="Flash9" mode="swf9" out="unit9.swf" class="unit.Test" lib="" cmd="" main="True" debug="True">-cp ..</output>
   <output name="JS" mode="js" out="unit.js" class="unit.Test" lib="" cmd="" main="False" debug="True">-cp ..</output>
   <output name="Neko" mode="neko" out="unit.n" class="unit.Test" lib="" cmd="" main="True" debug="True">-cp ..</output>
+  <output name="RemotingServer" mode="neko" out="remoting.n" class="unit.RemotingServer" lib="" cmd="" main="True" debug="False">-cp ..</output>
   <files path="/">
+    <file path="RemotingApi.hx" />
+    <file path="RemotingServer.hx" />
     <file path="Test.hx" />
     <file path="TestBytes.hx" />
     <file path="TestInt32.hx" />
     <file path="TestIO.hx" />
+    <file path="TestRemoting.hx" />
   </files>
 </haxe>