Przeglądaj źródła

fixes in Socket connections

Nicolas Cannasse 18 lat temu
rodzic
commit
b83226454c

+ 3 - 0
doc/CHANGES.txt

@@ -13,6 +13,9 @@
 	added haxe.xml.Proxy
 	some flash6 fixes
 	allowed simplequotes for xml attributes in Flash9 and JS
+	flash9 optimizing compiler
+	new faster neko binary AST
+	added haxe.remoting.NekoSocketConnection and SocketProtocol
 
 2007-05-18: 1.13
 	fixed bug with local variable masking package in catch type

+ 4 - 2
std/haxe/ImportAll.hx

@@ -67,12 +67,14 @@ import haxe.remoting.LocalConnection;
 import haxe.remoting.SocketWrapper;
 #end
 import haxe.remoting.Proxy;
-#if !js
+import haxe.remoting.SocketProtocol;
 import haxe.remoting.SocketConnection;
-#end
 #if !neko
 import haxe.remoting.FlashJsConnection;
 #end
+#if neko
+import haxe.remoting.NekoSocketConnection;
+#end
 
 import haxe.rtti.Infos;
 import haxe.rtti.Type;

+ 1 - 1
std/haxe/remoting/Connection.hx

@@ -29,7 +29,7 @@ class Connection implements Dynamic<Connection> {
 	var __data : Dynamic;
 	var __path : Array<String>;
 
-	function new( data, path ) {
+	function new( data : Dynamic, path ) {
 		__data = data;
 		__path = path;
 	}

+ 80 - 0
std/haxe/remoting/NekoSocketConnection.hx

@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2005-2007, 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;
+import haxe.remoting.SocketProtocol;
+
+class NekoSocketConnection extends Connection {
+
+	var __r : neko.net.RemotingServer;
+
+	override function __resolve(field) : Connection {
+		var s = new NekoSocketConnection(__data,__path.copy());
+		s.__r = __r;
+		s.__path.push(field);
+		return s;
+	}
+
+	override public function call( params : Array<Dynamic> ) : Dynamic {
+		var sock = getSocket();
+		SocketProtocol.sendRequest(sock,__path,params);
+		while( true ) {
+			var data = SocketProtocol.readMessage(sock.input);
+			if( SocketProtocol.isRequest(data) ) {
+				if( __r == null )
+					throw "Request received";
+				SocketProtocol.processRequest(sock,data,__r.resolvePath,onRequestError);
+				continue;
+			}
+			return SocketProtocol.decodeAnswer(data);
+		}
+		return null;
+	}
+
+	public function processRequest() {
+		var sock = getSocket();
+		if( __r == null )
+			throw "No RemotingServer defined";
+		var data = SocketProtocol.readMessage(sock.input);
+		SocketProtocol.processRequest(sock,data,__r.resolvePath,onRequestError);
+	}
+
+	public function onRequestError( path : Array<String>, method : String, args : Array<Dynamic>, exc : Dynamic ) {
+	}
+
+	public function getSocket() : Socket {
+		return __data;
+	}
+
+	public function closeConnection() {
+		try getSocket().close() catch( e : Dynamic ) { };
+	}
+
+	public static function socketConnect( s : neko.net.Socket, ?r : neko.net.RemotingServer ) {
+		var sc = new NekoSocketConnection(s,[]);
+		sc.__r = r;
+		return sc;
+	}
+
+}

+ 48 - 193
std/haxe/remoting/SocketConnection.hx

@@ -23,13 +23,7 @@
  * DAMAGE.
  */
 package haxe.remoting;
-#if flash9
-import flash.net.XMLSocket;
-#else flash
-import flash.XMLSocket;
-#else js
-import js.XMLSocket;
-#end
+import haxe.remoting.SocketProtocol;
 
 class SocketConnection extends AsyncConnection {
 
@@ -40,7 +34,6 @@ class SocketConnection extends AsyncConnection {
 
 	override function __resolve(field) : AsyncConnection {
 		var s = new SocketConnection(__data,__path.copy());
-		var me = this;
 		s.__error = __error;
 		s.__funs = __funs;
 		#if neko
@@ -52,194 +45,72 @@ class SocketConnection extends AsyncConnection {
 
 	override public function call( params : Array<Dynamic>, ?onData : Dynamic -> Void ) : Void {
 		try {
-			var s = new haxe.Serializer();
-			s.serialize(true);
-			s.serialize(__path);
-			s.serialize(params);
-			sendMessage(__data,s.toString());
+			SocketProtocol.sendRequest(getSocket(),__path,params);
 			__funs.add(onData);
 		} catch( e : Dynamic ) {
 			__error.ref(e);
 		}
 	}
 
-	public function closeConnection() {
-		#if neko
-		var s : neko.net.Socket = __data;
-		try s.close() catch( e : Dynamic ) { };
-		#else true
-		var s : XMLSocket = __data;
-		s.close();
-		#end
-	}
-
-	public static function decodeChar(c) : Null<Int> {
-		// A...Z
-		if( c >= 65 && c <= 90 )
-			return c - 65;
-		// a...z
-		if( c >= 97 && c <= 122 )
-			return c - 97 + 26;
-		// 0...9
-		if( c >= 48 && c <= 57 )
-			return c - 48 + 52;
-		// +
-		if( c == 43 )
-			return 62;
-		// /
-		if( c == 47 )
-			return 63;
-		return null;
-	}
-
-	public static function encodeChar(c) : Null<Int> {
-		if( c < 0 )
-			return null;
-		// A...Z
-		if( c < 26 )
-			return c + 65;
-		// a...z
-		if( c < 52 )
-			return (c - 26) + 97;
-		// 0...9
-		if( c < 62 )
-			return (c - 52) + 48;
-		// +
-		if( c == 62 )
-			return 43;
-		// /
-		if( c == 63 )
-			return 47;
-		return null;
+	public function getSocket() : Socket {
+		return __data;
 	}
 
-	public static function sendMessage( __data : Dynamic, msg : String ) {
-		var len = msg.length + 3;
-		#if neko
-		#else true
-		for( i in 0...msg.length ) {
-			var c = msg.charCodeAt(i);
-			if( c < 0x7F )
-				continue;
-			if( c < 0x7FF ) {
-				len++;
-				continue;
-			}
-			if( c < 0xFFFF ) {
-				len += 2;
-				continue;
-			}
-			len += 3;
-		}
-		#end
-		var c1 = encodeChar(len>>6);
-		if( c1 == null )
-			throw "Message is too big";
-		var c2 = encodeChar(len&63);
-		#if neko
-		var s : neko.net.Socket = __data;
-		s.output.writeChar(c1);
-		s.output.writeChar(c2);
-		s.output.write(msg);
-		s.output.writeChar(0);
-		#else (flash || js)
-		var s : XMLSocket = __data;
-		s.send(Std.chr(c1)+Std.chr(c2)+msg);
-		#else error
-		#end
+	public function closeConnection() {
+		try getSocket().close() catch( e : Dynamic ) { };
 	}
 
-	/**
-		Returns an exception only if one occured either in an onData answer callback
-		or in a request callback (in that case, the exception is also sent through
-		the network). No exception should ever escape this method.
-	**/
-	public static function processMessage( sc : SocketConnection, data : String ) {
-		var f : Dynamic -> Void;
-		var val : Dynamic;
-		var s = new haxe.Unserializer(data);
+	public function processMessage( data : String ) {
+		var request;
 		try {
-			var isrequest : Bool = s.unserialize();
-			if( !isrequest ) {
-				if( sc.__funs.isEmpty() )
-					throw "No response expected ("+data+")";
-				f = sc.__funs.pop();
-				val = s.unserialize();
-				if( f == null )
-					return null;
-			}
+			request = SocketProtocol.isRequest(data);
 		} catch( e : Dynamic ) {
-			sc.__error.ref(e);
-			return null;
+			__error.ref(e); // protocol error
+			return;
 		}
-		if( f != null ) {
+		// request
+		if( request ) {
 			try {
-				f(val);
-				return null;
-			} catch( val : Dynamic ) {
-				return { exc : val };
+				var me = this;
+				var eval =
+					#if neko
+						__r.resolvePath
+					#else flash
+						function(path:Array<String>) { return flash.Lib.eval(path.join(".")); }
+					#else js
+						function(path:Array<String>) { return js.Lib.eval(path.join(".")); }
+					#end
+				;
+				SocketProtocol.processRequest(getSocket(),data,eval,function(path,name,args,e) {
+					// exception inside the called method
+					var astr, estr;
+					try astr = args.join(",") catch( e : Dynamic ) astr = "???";
+					try estr = Std.string(e) catch( e : Dynamic ) estr = "???";
+					var header = "Error in call to "+path.join(".")+"."+name+"("+astr+") : ";
+					me.__error.ref(header + estr);
+				});
+			} catch( e : Dynamic ) {
+				__error.ref(e); // protocol error or invalid object/method
 			}
+			return;
 		}
-		// ---------------------------
-		var exc = false;
+		// answer
+		var f, v;
 		try {
-			var path : Array<String> = s.unserialize();
-			var args = s.unserialize();
-			var fname = path.pop();
-			#if flash
-			var obj = flash.Lib.eval(path.join("."));
-			#else neko
-			var obj = sc.__r.resolvePath(path);
-			#else js
-			var obj = js.Lib.eval(path.join("."));
-			#else error
-			#end
-			var fptr = #if flash9 if( obj != null ) #end Reflect.field(obj,fname);
-			if( !Reflect.isFunction(fptr) )
-				throw "Calling not-a-function '"+path.join(".")+"."+fname+"'";
-			val = Reflect.callMethod(obj,fptr,args);
+			if( __funs.isEmpty() )
+				throw "No response excepted ("+data+")";
+			f = __funs.pop();
+			v = SocketProtocol.decodeAnswer(data);
 		} catch( e : Dynamic ) {
-			val = e;
-			exc = true;
+			__error.ref(e); // protocol error or answer exception
+			return;
 		}
-		try {
-			var s = new haxe.Serializer();
-			s.serialize(false);
-			if( exc )
-				s.serializeException(val);
-			else
-				s.serialize(val);
-			sendMessage(sc.__data,s.toString());
-		} catch( e : Dynamic ) {
-			sc.__error.ref(e);
-			return null;
-		}
-		if( exc )
-			return { exc : val };
-		return null;
+		if( f != null )
+			f(v); // answer callback exception
 	}
 
 	#if neko
 
-	public static function readAnswers( cnx : SocketConnection ) {
-		var sock : neko.net.Socket = cnx.__data;
-		while( cnx.__funs.length > 0 ) {
-			var c1 = decodeChar(sock.input.readChar());
-			var c2 = decodeChar(sock.input.readChar());
-			if( c1 == null || c2 == null )
-				throw "Invalid answer";
-			var len = (c1 << 6) | c2;
-			var data = sock.input.read(len-3);
-			if( sock.input.readChar() != 0 )
-				throw "Invalid answeur";
-			if( !haxe.Unserializer.run(data) != true )
-				throw "Request received";
-			var r = processMessage(cnx,data);
-			if( r != null )
-				neko.Lib.rethrow(r.exc);
-		}
-	}
-
 	public static function socketConnect( s : neko.net.Socket, r : neko.net.RemotingServer ) {
 		var sc = new SocketConnection(s,[]);
 		sc.__funs = new List();
@@ -247,20 +118,14 @@ class SocketConnection extends AsyncConnection {
 		return sc;
 	}
 
-	public static function getSocket( cnx : SocketConnection ) : neko.net.Socket {
-		return cnx.__data;
-	}
-
 	#else (flash || js)
 
-	public static function socketConnect( s : XMLSocket ) {
+	public static function socketConnect( s : Socket ) {
 		var sc = new SocketConnection(s,[]);
 		sc.__funs = new List();
 		#if flash9
 		s.addEventListener(flash.events.DataEvent.DATA, function(e : flash.events.DataEvent) {
-			var e = processMessage(sc,e.data.substr(2,e.data.length-2));
-			if( e != null )
-				throw e.exc;
+			sc.processMessage(e.data.substr(2,e.data.length-2));
 		});
 		#else true
 		// we can't deliver directly the message
@@ -269,25 +134,15 @@ class SocketConnection extends AsyncConnection {
 		// where a new onData is called is a parallel thread
 		// ...with the buffer of the previous onData (!)
 		s.onData = function(data : String) {
+			trace(data);
 			haxe.Timer.queue(function() {
-				var e = processMessage(sc,data.substr(2,data.length-2));
-				// error happened in response handler, not in request
-				if( e != null )
-					#if neko
-					neko.Lib.rethrow(e.exc);
-					#else true
-					throw e.exc;
-					#end
+				sc.processMessage(data.substr(2,data.length-2));
 			});
 		};
 		#end
 		return sc;
 	}
 
-	public static function getSocket( cnx : SocketConnection ) : XMLSocket {
-		return cnx.__data;
-	}
-
 	#else error
 	#end
 

+ 197 - 0
std/haxe/remoting/SocketProtocol.hx

@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2005-2007, 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;
+
+typedef Socket =
+	#if flash9
+		flash.net.XMLSocket
+	#else flash
+		flash.XMLSocket
+	#else js
+		js.XMLSocket
+	#else neko
+		neko.net.Socket
+	#end
+
+/**
+	The haXe Remoting Socket Protocol is composed of serialized string exchanges.
+	Each string is prefixed with a 2-chars header encoding the string size (up to 4KB)
+	and postfixed with the \0 message delimiting char.
+	A request string is composed of the following serialized values :
+		- the boolean true for a request
+		- an array of strings representing the object+method path
+		- an array of parameters
+	A response string is composed of the following serialized values :
+		- the boolean false for a response
+		- a serialized value representing the result
+	Exceptions are serialized with [serializeException] so they will be thrown immediatly
+	when they are unserialized.
+**/
+class SocketProtocol {
+
+	public static function decodeChar(c) : Null<Int> {
+		// A...Z
+		if( c >= 65 && c <= 90 )
+			return c - 65;
+		// a...z
+		if( c >= 97 && c <= 122 )
+			return c - 97 + 26;
+		// 0...9
+		if( c >= 48 && c <= 57 )
+			return c - 48 + 52;
+		// +
+		if( c == 43 )
+			return 62;
+		// /
+		if( c == 47 )
+			return 63;
+		return null;
+	}
+
+	public static function encodeChar(c) : Null<Int> {
+		if( c < 0 )
+			return null;
+		// A...Z
+		if( c < 26 )
+			return c + 65;
+		// a...z
+		if( c < 52 )
+			return (c - 26) + 97;
+		// 0...9
+		if( c < 62 )
+			return (c - 52) + 48;
+		// +
+		if( c == 62 )
+			return 43;
+		// /
+		if( c == 63 )
+			return 47;
+		return null;
+	}
+
+	public static function dataLength( c1 : Int, c2 : Int ) {
+		var e1 = decodeChar(c1);
+		var e2 = decodeChar(c2);
+		if( e1 == null || e2 == null )
+			throw "Invalid header";
+		return ((e1 << 6) | e2) - 3;
+	}
+
+	public static function sendRequest( sock : Socket, path : Array<String>, params : Array<Dynamic> ) {
+		var s = new haxe.Serializer();
+		s.serialize(true);
+		s.serialize(path);
+		s.serialize(params);
+		sendMessage(sock,s.toString());
+	}
+
+	public static function sendAnswer( sock : Socket, answer : Dynamic, ?isException : Bool ) {
+		var s = new haxe.Serializer();
+		s.serialize(false);
+		if( isException )
+			s.serializeException(answer);
+		else
+			s.serialize(answer);
+		sendMessage(sock,s.toString());
+	}
+
+	public static function sendMessage( sock : Socket, msg : String ) {
+		var len = msg.length + 3;
+		var c1 = encodeChar(len>>6);
+		if( c1 == null )
+			throw "Message is too big";
+		var c2 = encodeChar(len&63);
+		#if neko
+		sock.output.writeChar(c1);
+		sock.output.writeChar(c2);
+		sock.output.write(msg);
+		sock.output.writeChar(0);
+		#else true
+		sock.send(Std.chr(c1)+Std.chr(c2)+msg);
+		#end
+	}
+
+	public static function isRequest( data : String ) {
+		return switch( haxe.Unserializer.run(data) ) {
+		case true: true;
+		case false: false;
+		default: throw "Invalid data";
+		}
+	}
+
+	public static function processRequest( sock : Socket, data : String, eval : Array<String> -> Dynamic, ?onError : Array<String> -> String -> Array<Dynamic> -> Dynamic -> Void ) {
+		var s = new haxe.Unserializer(data);
+		var result : Dynamic;
+		var isException = false;
+		if( s.unserialize() != true )
+			throw "Not a request";
+		var path : Array<String> = s.unserialize();
+		var args : Array<Dynamic> = s.unserialize();
+		var fname = path.pop();
+		var obj = eval(path);
+		if( obj == null )
+			throw "Object does not exists '"+path.join(".")+"'";
+		var fptr = Reflect.field(obj,fname);
+		if( !Reflect.isFunction(fptr) )
+			throw "Calling not-a-function '"+path.join(".")+"."+fname+"'";
+		try {
+			result = Reflect.callMethod(obj,fptr,args);
+		} catch( e : Dynamic ) {
+			result = e;
+			isException = true;
+			if( onError != null )
+				onError(path,fname,args,e);
+		}
+		// send back result/exception over network
+		var s = new haxe.Serializer();
+		s.serialize(false);
+		if( isException )
+			s.serializeException(result);
+		else
+			s.serialize(result);
+		sendMessage(sock,s.toString());
+	}
+
+	public static function decodeAnswer( data : String ) : Dynamic {
+		var s = new haxe.Unserializer(data);
+		if( s.unserialize() != false )
+			throw "Not an answer";
+		return s.unserialize();
+	}
+
+	#if neko
+
+	public static function readMessage( i : neko.io.Input ) {
+		var c1 = i.readChar();
+		var c2 = i.readChar();
+		var data = i.read(dataLength(c1,c2));
+		if( i.readChar() != 0 )
+			throw "Invalid message";
+		return data;
+	}
+
+	#end
+
+}

+ 3 - 2
std/neko/net/RemotingServer.hx

@@ -48,13 +48,14 @@ class RemotingServer {
 	}
 
 	public function resolvePath( path : Array<String> ) : Dynamic {
-		var objname = path.shift();
+		var it = path.iterator();
+		var objname = it.next();
 		if( objname == null )
 			throw "Empty path";
 		var obj = objects.get(objname);
 		if( obj == null )
 			throw "Object '"+objname+"' is not accessible";
-		for( x in path ) {
+		for( x in it ) {
 			if( obj == null || (prefix != null && x.indexOf(prefix,0) == 0) )
 				return null;
 			obj = Reflect.field(obj,x);

+ 9 - 12
std/neko/net/ThreadRemotingServer.hx

@@ -35,10 +35,6 @@ class ThreadRemotingServer extends ThreadServer<haxe.remoting.SocketConnection,S
 		throw "Not implemented";
 	}
 
-	function decodeChar(c) {
-		return haxe.remoting.SocketConnection.decodeChar(c);
-	}
-
 	public override function clientConnected( s : neko.net.Socket ) {
 		var r = new neko.net.RemotingServer();
 		var cnx = haxe.remoting.SocketConnection.socketConnect(s,r);
@@ -53,8 +49,8 @@ class ThreadRemotingServer extends ThreadServer<haxe.remoting.SocketConnection,S
 	}
 
 	public override function readClientMessage( cnx, buf : String, pos : Int, len : Int ) {
-		var c1 = decodeChar(buf.charCodeAt(pos));
-		var c2 = decodeChar(buf.charCodeAt(pos+1));
+		var c1 = haxe.remoting.SocketProtocol.decodeChar(buf.charCodeAt(pos));
+		var c2 = haxe.remoting.SocketProtocol.decodeChar(buf.charCodeAt(pos+1));
 		if( c1 == null || c2 == null ) {
 			if( buf.charCodeAt(pos) != 60 )
 				throw "Invalid remoting message '"+buf.substr(pos,len)+"'";
@@ -82,16 +78,17 @@ class ThreadRemotingServer extends ThreadServer<haxe.remoting.SocketConnection,S
 		throw "Unhandled XML data '"+data+"'";
 	}
 
-	public override function clientMessage( cnx, msg : String ) {
+	public override function clientMessage( cnx : haxe.remoting.SocketConnection, msg : String ) {
 		if( msg.charCodeAt(0) == 60 ) {
 			onXml(cnx,msg);
 			return;
 		}
-		var r = haxe.remoting.SocketConnection.processMessage(cnx,msg);
-		if( r != null ) {
-			if( !Std.is(r.exc,neko.io.Eof) && !Std.is(r.exc,neko.io.Error) )
-				logError(r.exc);
-			stopClient(haxe.remoting.SocketConnection.getSocket(cnx));
+		try {
+			cnx.processMessage(msg);
+		} catch( e : Dynamic ) {
+			if( !Std.is(e,neko.io.Eof) && !Std.is(e,neko.io.Error) )
+				logError(e);
+			stopClient(cnx.getSocket());
 		}
 	}