webscoket implemented
This commit is contained in:
2
gen.sh
2
gen.sh
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
haxelib run protohx generate protohx.json
|
haxelib run protohx generate protohx.json
|
||||||
@@ -6,7 +6,9 @@
|
|||||||
<source path="src/client/haxe"/>
|
<source path="src/client/haxe"/>
|
||||||
<source path="src-gen/haxe"/>
|
<source path="src-gen/haxe"/>
|
||||||
<assets path="res" include="*"/>
|
<assets path="res" include="*"/>
|
||||||
|
<!--<haxelib name="openfl-bitfive"/>-->
|
||||||
<haxelib name="openfl"/>
|
<haxelib name="openfl"/>
|
||||||
|
<!--<haxelib name="openfl-html5-dom"/>-->
|
||||||
<haxelib name="protohx"/>
|
<haxelib name="protohx"/>
|
||||||
<haxelib name="haxework" version="git"/>
|
<haxelib name="haxework" version="git"/>
|
||||||
<window width="800" height="600" if="desktop"/>
|
<window width="800" height="600" if="desktop"/>
|
||||||
|
|||||||
16
protohx.json
16
protohx.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"protoPath": ".",
|
"protoPath": ".",
|
||||||
"protoFiles": [
|
"protoFiles": [
|
||||||
"proto/base.proto"
|
"proto/base.proto"
|
||||||
],
|
],
|
||||||
"cleanOut": true,
|
"cleanOut": true,
|
||||||
"haxeOut": "src-gen/haxe",
|
"haxeOut": "src-gen/haxe",
|
||||||
"javaOut": null
|
"javaOut": null
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
haxe server.hxml && neko target/server.n
|
haxe server.hxml && neko target/server.n
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ru.m.armageddon.client;
|
package ru.m.armageddon.client;
|
||||||
|
|
||||||
|
import flash.text.TextFieldType;
|
||||||
|
import flash.Lib;
|
||||||
|
import flash.text.TextField;
|
||||||
|
import haxework.log.JSLogger;
|
||||||
import ru.m.armageddon.client.data.GameData;
|
import ru.m.armageddon.client.data.GameData;
|
||||||
import haxework.frame.IFrameSwitcher;
|
import haxework.frame.IFrameSwitcher;
|
||||||
import haxework.provider.Provider;
|
import haxework.provider.Provider;
|
||||||
@@ -10,6 +14,7 @@ import haxework.gui.GuiBuilder;
|
|||||||
import haxe.Json;
|
import haxe.Json;
|
||||||
import openfl.Assets;
|
import openfl.Assets;
|
||||||
import ru.m.armageddon.core.connect.flash.FlashConnection;
|
import ru.m.armageddon.core.connect.flash.FlashConnection;
|
||||||
|
import ru.m.armageddon.core.connect.js.JsConnection;
|
||||||
import ru.m.armageddon.core.connect.IConnection;
|
import ru.m.armageddon.core.connect.IConnection;
|
||||||
import haxework.log.TraceLogger;
|
import haxework.log.TraceLogger;
|
||||||
|
|
||||||
@@ -19,6 +24,7 @@ class Client implements IConnectionHandler {
|
|||||||
|
|
||||||
public static function main() {
|
public static function main() {
|
||||||
L.push(new TraceLogger());
|
L.push(new TraceLogger());
|
||||||
|
L.push(new JSLogger());
|
||||||
L.d(TAG, Meta.getVersion());
|
L.d(TAG, Meta.getVersion());
|
||||||
new Client();
|
new Client();
|
||||||
}
|
}
|
||||||
@@ -34,9 +40,20 @@ class Client implements IConnectionHandler {
|
|||||||
|
|
||||||
Provider.setFactory(GameData, GameData);
|
Provider.setFactory(GameData, GameData);
|
||||||
Provider.set(IFrameSwitcher, switcher);
|
Provider.set(IFrameSwitcher, switcher);
|
||||||
Provider.set(IConnection, new FlashConnection("localhost", 5000, this));
|
//#if flash
|
||||||
|
//Provider.set(IConnection, new FlashConnection("localhost", 5001, this));
|
||||||
|
//#elseif html5
|
||||||
|
Provider.set(IConnection, new JsConnection("localhost", 5001, this));
|
||||||
|
//#end
|
||||||
|
|
||||||
switcher.change(AuthFrame.ID);
|
switcher.change(AuthFrame.ID);
|
||||||
|
|
||||||
|
/*var tf = new TextField();
|
||||||
|
tf.type = TextFieldType.INPUT;
|
||||||
|
tf.text = "Azaza";
|
||||||
|
tf.border = true;
|
||||||
|
tf.borderColor = 0xff0000;
|
||||||
|
Lib.current.addChild(tf);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onConnected():Void {}
|
public function onConnected():Void {}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class AuthFrame extends VGroupView implements IPacketHandler {
|
|||||||
loginInput.text = so.data.login;
|
loginInput.text = so.data.login;
|
||||||
passwordInput.text = so.data.password;
|
passwordInput.text = so.data.password;
|
||||||
onPress(null);
|
onPress(null);
|
||||||
|
} else {
|
||||||
|
loginInput.text = "shmyga";
|
||||||
|
passwordInput.text = "xkbp8jh9z2";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ class AuthFrame extends VGroupView implements IPacketHandler {
|
|||||||
var connection:IConnection = Provider.get(IConnection);
|
var connection:IConnection = Provider.get(IConnection);
|
||||||
connection.connect()
|
connection.connect()
|
||||||
.success(function(_) {
|
.success(function(_) {
|
||||||
|
L.d(TAG, "Connected");
|
||||||
connection.send(new LoginRequest().setLogin(login).setPassword(password));
|
connection.send(new LoginRequest().setLogin(login).setPassword(password));
|
||||||
})
|
})
|
||||||
.fail(function(error) {
|
.fail(function(error) {
|
||||||
|
|||||||
26
src/client/webapp/index.html
Normal file
26
src/client/webapp/index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script type="text/javascript" src="swf.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="swf" style="width: 100%; height: 100%;"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var swf = new Swf("swf", "flash/bin/armageddon.swf");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
148
src/client/webapp/swf.js
Normal file
148
src/client/webapp/swf.js
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
(function(window) {
|
||||||
|
|
||||||
|
if (!window.Swf) {
|
||||||
|
|
||||||
|
window.Swf = function(eid, src, listener, params) {
|
||||||
|
this.id = "adman" + window.Swf.i++;
|
||||||
|
this.listener = listener;
|
||||||
|
this.embed(eid, src, params);
|
||||||
|
|
||||||
|
window.SWF_CALLBACKS = window.SWF_CALLBACKS || {};
|
||||||
|
window.SWF_CALLBACKS[this.id] = this.onSwfEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Swf.i = 0;
|
||||||
|
|
||||||
|
window.Swf.prototype = {
|
||||||
|
|
||||||
|
dispatch: function(type) {
|
||||||
|
if (this.listener && this.listener["on_" + type]) {
|
||||||
|
var args = [].slice.call(arguments, 1);
|
||||||
|
this.listener["on_" + type].apply(this.listener, args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSwfEvent: function() {
|
||||||
|
var self = this;
|
||||||
|
return function(type, arg1, arg2) {
|
||||||
|
self.dispatch(type, arg1, arg2);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
insertAfter :function(elem, refElem) {
|
||||||
|
var parent = refElem.parentNode;
|
||||||
|
var next = refElem.nextSibling;
|
||||||
|
if (next) {
|
||||||
|
return parent.insertBefore(elem, next);
|
||||||
|
} else {
|
||||||
|
return parent.appendChild(elem);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
embed:function(target, src, params) {
|
||||||
|
params = params || {};
|
||||||
|
params["callback"] = "SWF_CALLBACKS." + this.id;
|
||||||
|
var flashData = {
|
||||||
|
params: {
|
||||||
|
id: this.id,
|
||||||
|
quality: "high",
|
||||||
|
allowScriptAccess: "always",
|
||||||
|
allowFullScreen: true,
|
||||||
|
wMode: "opaque",
|
||||||
|
base: null,
|
||||||
|
swLiveConnect: true
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
name: this.id,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%"
|
||||||
|
},
|
||||||
|
vars: params
|
||||||
|
};
|
||||||
|
this.swf = this.buildFlashElement(src, flashData);
|
||||||
|
this.swf.style["position"] = "absolute";
|
||||||
|
this.swf.style["left"] = 0;
|
||||||
|
this.swf.style["top"] = 0;
|
||||||
|
//this.swf.style["visibility"] = "hidden";
|
||||||
|
if (target) {
|
||||||
|
target = document.getElementById(target);
|
||||||
|
this.insertAfter(this.swf, target);
|
||||||
|
} else {
|
||||||
|
document.body.appendChild(this.swf);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
typeOf: function (item) {
|
||||||
|
if (item == null) return "null";
|
||||||
|
if (item.nodeName) {
|
||||||
|
if (item.nodeType == 1) return "element";
|
||||||
|
if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? "textnode" : "whitespace";
|
||||||
|
} else if (typeof item.length == "number") {
|
||||||
|
if (item.callee) return "arguments";
|
||||||
|
}
|
||||||
|
return typeof item;
|
||||||
|
},
|
||||||
|
|
||||||
|
toFlashVars: function (object, base) {
|
||||||
|
var queryString = [];
|
||||||
|
for (var key in object) {
|
||||||
|
var value = object[key];
|
||||||
|
if (base) key = base + ":" + key;
|
||||||
|
var result;
|
||||||
|
switch (this.typeOf(value)) {
|
||||||
|
case "object":
|
||||||
|
result = this.toFlashVars(value, key);
|
||||||
|
break;
|
||||||
|
case "array":
|
||||||
|
var qs = {};
|
||||||
|
value.each(function (val, i) {
|
||||||
|
qs[i] = val;
|
||||||
|
});
|
||||||
|
result = this.toFlashVars(qs, key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = key + "=" + encodeURIComponent(value);
|
||||||
|
}
|
||||||
|
if (value != null) queryString.push(result);
|
||||||
|
}
|
||||||
|
return queryString.join("&");
|
||||||
|
},
|
||||||
|
|
||||||
|
buildFlashElement: function (path, options) {
|
||||||
|
var params = options.params;
|
||||||
|
var vars = options.vars;
|
||||||
|
var properties = options.properties;
|
||||||
|
|
||||||
|
params.flashVars = this.toFlashVars(vars);
|
||||||
|
var isIE = /*@cc_on!@*/false;
|
||||||
|
if (isIE) {
|
||||||
|
properties.classid = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
|
||||||
|
params.movie = path;
|
||||||
|
} else {
|
||||||
|
properties.type = "application/x-shockwave-flash";
|
||||||
|
}
|
||||||
|
properties.data = path;
|
||||||
|
|
||||||
|
var build = "<object id=\"" + params.id + "\"";
|
||||||
|
for (var property in properties) build += " " + property + "=\"" + properties[property] + "\"";
|
||||||
|
build += ">";
|
||||||
|
for (var param in params) {
|
||||||
|
if (params[param]) build += "<param name=\"" + param + "\" value=\"" + params[param] + "\" />";
|
||||||
|
}
|
||||||
|
build += "</object>";
|
||||||
|
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.innerHTML = build;
|
||||||
|
return div.firstChild;
|
||||||
|
},
|
||||||
|
|
||||||
|
dispose: function() {
|
||||||
|
if (this.swf) {
|
||||||
|
this.swf.dispose();
|
||||||
|
this.swf.parentNode.removeChild(this.swf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})(window);
|
||||||
52
src/common/haxe/ru/m/armageddon/core/Base64.hx
Normal file
52
src/common/haxe/ru/m/armageddon/core/Base64.hx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package ru.m.armageddon.core;
|
||||||
|
|
||||||
|
#if haxe3
|
||||||
|
import haxe.crypto.BaseCode;
|
||||||
|
#else
|
||||||
|
import haxe.BaseCode;
|
||||||
|
#end
|
||||||
|
|
||||||
|
class Base64 {
|
||||||
|
private inline static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
private static var codec:BaseCode;
|
||||||
|
|
||||||
|
public function new() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static function getCodec():BaseCode {
|
||||||
|
if (codec == null) {
|
||||||
|
var bytes = haxe.io.Bytes.ofString(BASE64);
|
||||||
|
codec = new BaseCode(bytes);
|
||||||
|
}
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function encodeBase64(content:haxe.io.Bytes):String {
|
||||||
|
var suffix = switch (content.length % 3){
|
||||||
|
case 2: "=";
|
||||||
|
case 1: "==";
|
||||||
|
default: "";
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes = getCodec().encodeBytes(content);
|
||||||
|
return bytes.toString() + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function removeNullbits(s:String):String {
|
||||||
|
var len = s.length;
|
||||||
|
while (len > 0 && s.charAt(len - 1) == "=") {
|
||||||
|
len--;
|
||||||
|
if (len <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.substr(0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decodeBase64(content:String):haxe.io.Bytes {
|
||||||
|
var bytes:haxe.io.Bytes = haxe.io.Bytes.ofString(removeNullbits(content));
|
||||||
|
return getCodec().decodeBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,8 +10,7 @@ class BaseConnection implements IConnection {
|
|||||||
public var packetHandler(default,default):IPacketHandler;
|
public var packetHandler(default,default):IPacketHandler;
|
||||||
public var connected(default, null):Bool;
|
public var connected(default, null):Bool;
|
||||||
public var queue(default, null):PacketQueue;
|
public var queue(default, null):PacketQueue;
|
||||||
|
public var builder(default, null):IPacketBuilder;
|
||||||
private var builder:IPacketBuilder;
|
|
||||||
|
|
||||||
public function new(?handler:IConnectionHandler = null, ?packetHandler:IPacketHandler) {
|
public function new(?handler:IConnectionHandler = null, ?packetHandler:IPacketHandler) {
|
||||||
this.builder = new PacketBuilder();
|
this.builder = new PacketBuilder();
|
||||||
@@ -44,7 +43,7 @@ class BaseConnection implements IConnection {
|
|||||||
L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop());
|
L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function receive(packet:Message):Void {
|
public function receive(packet:Message):Void {
|
||||||
L.d("Receive", Type.getClassName(Type.getClass(packet)).split(".").pop());
|
L.d("Receive", Type.getClassName(Type.getClass(packet)).split(".").pop());
|
||||||
if (packetHandler == null) return;
|
if (packetHandler == null) return;
|
||||||
var name = "on" + Type.getClassName(Type.getClass(packet)).split(".").pop();
|
var name = "on" + Type.getClassName(Type.getClass(packet)).split(".").pop();
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ interface IConnection {
|
|||||||
public var handler(default,default):IConnectionHandler;
|
public var handler(default,default):IConnectionHandler;
|
||||||
public var packetHandler(default,default):IPacketHandler;
|
public var packetHandler(default,default):IPacketHandler;
|
||||||
|
|
||||||
private var builder:IPacketBuilder;
|
public var builder(default,null):IPacketBuilder;
|
||||||
|
|
||||||
public function connect():ICallback<Dynamic>;
|
public function connect():ICallback<Dynamic>;
|
||||||
public function disconnect():Void;
|
public function disconnect():Void;
|
||||||
public function send(packet:Message):Void;
|
public function send(packet:Message):Void;
|
||||||
public function pushData(bytes:Bytes):Void;
|
public function pushData(bytes:Bytes):Void;
|
||||||
|
|
||||||
private function receive(packet:Message):Void;
|
public function receive(packet:Message):Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IConnectionHandler {
|
interface IConnectionHandler {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package ru.m.armageddon.core.connect;
|
||||||
|
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
import haxe.io.BytesOutput;
|
||||||
|
import ru.m.armageddon.core.connect.IConnection;
|
||||||
|
import protohx.Message;
|
||||||
|
|
||||||
|
class WebSocketTools {
|
||||||
|
|
||||||
|
public static function packet2string(packet:Message, builder:IPacketBuilder):String {
|
||||||
|
var meta = builder.packetMeta(packet);
|
||||||
|
var b = new BytesOutput();
|
||||||
|
packet.writeTo(b);
|
||||||
|
var data = b.getBytes();
|
||||||
|
var res = new BytesOutput();
|
||||||
|
res.writeByte(meta.family);
|
||||||
|
res.writeByte(meta.id);
|
||||||
|
//res.writeUInt16(data.length);
|
||||||
|
res.write(data);
|
||||||
|
return Base64.encodeBase64(res.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function string2packet(data:String, builder:IPacketBuilder):Message {
|
||||||
|
var bytes = Base64.decodeBase64(data);
|
||||||
|
var family = bytes.get(0);
|
||||||
|
var id = bytes.get(1);
|
||||||
|
var packet = builder.buildPacket({family:family, id:id});
|
||||||
|
packet.mergeFrom(bytes.sub(2, bytes.length - 2));
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package ru.m.armageddon.core.connect.js;
|
||||||
|
|
||||||
|
import ru.m.armageddon.core.Base64;
|
||||||
|
import haxework.net.callback.Callback;
|
||||||
|
import haxework.net.callback.ICallback;
|
||||||
|
import ru.m.armageddon.core.connect.IConnection.IConnectionHandler;
|
||||||
|
import protohx.Message;
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
|
||||||
|
typedef WebSocket = {
|
||||||
|
var onopen:Dynamic->Void;
|
||||||
|
var onclose:Dynamic->Void;
|
||||||
|
var onmessage:Dynamic->Void;
|
||||||
|
var onerror:Dynamic->Void;
|
||||||
|
function send(message:String):Void;
|
||||||
|
function emit(a:String, b:String):Void;
|
||||||
|
function close():Void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsConnection extends BaseConnection {
|
||||||
|
|
||||||
|
private var host:String;
|
||||||
|
private var port:Int;
|
||||||
|
private var socket:WebSocket;
|
||||||
|
|
||||||
|
private var callback:ICallback<Dynamic>;
|
||||||
|
|
||||||
|
public function new(host:String, port:Int, ?handler:IConnectionHandler = null) {
|
||||||
|
super(handler);
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSocket(host:String, port:Int):WebSocket {
|
||||||
|
return untyped __js__("self.socket = new WebSocket('ws://'+host+':'+port); ");
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function connect():ICallback<Dynamic> {
|
||||||
|
callback = Callback.build();
|
||||||
|
var self = this;
|
||||||
|
var decodeBytes = Base64.decodeBase64;
|
||||||
|
socket = buildSocket(host, port);
|
||||||
|
socket.onopen = this.onConnect;
|
||||||
|
socket.onclose = this.onClose;
|
||||||
|
socket.onerror = this.onError;
|
||||||
|
socket.onmessage = this.onSocketData;
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function disconnect():Void {
|
||||||
|
socket.close();
|
||||||
|
connected = false;
|
||||||
|
if (handler != null) handler.onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onError(event:Dynamic):Void {
|
||||||
|
socket.close();
|
||||||
|
connected = false;
|
||||||
|
if (handler != null) handler.onError(event);
|
||||||
|
if (callback != null) {
|
||||||
|
var c = callback;
|
||||||
|
callback = null;
|
||||||
|
c.callFail(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onConnect(_):Void {
|
||||||
|
connected = true;
|
||||||
|
if (handler != null) handler.onConnected();
|
||||||
|
if (callback != null) {
|
||||||
|
var c = callback;
|
||||||
|
callback = null;
|
||||||
|
c.callSuccess(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onClose(_):Void {
|
||||||
|
socket.close();
|
||||||
|
connected = false;
|
||||||
|
if (handler != null) handler.onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onSocketData(event:Dynamic):Void {
|
||||||
|
try {
|
||||||
|
var data:String = event.data;
|
||||||
|
var packet = WebSocketTools.string2packet(data, builder);
|
||||||
|
receive(packet);
|
||||||
|
} catch (error:Dynamic) {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function send(packet:Message):Void {
|
||||||
|
super.send(packet);
|
||||||
|
socket.send(WebSocketTools.packet2string(packet, builder));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import ru.m.armageddon.core.connect.IConnection;
|
|||||||
|
|
||||||
class NekoConnection extends BaseConnection {
|
class NekoConnection extends BaseConnection {
|
||||||
|
|
||||||
private var socket:Socket;
|
public var socket(default, null):Socket;
|
||||||
|
|
||||||
public function new(socket:Socket, ?handler:IConnectionHandler = null, ?packetHandler:IPacketHandler = null) {
|
public function new(socket:Socket, ?handler:IConnectionHandler = null, ?packetHandler:IPacketHandler = null) {
|
||||||
super(handler, packetHandler);
|
super(handler, packetHandler);
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package ru.m.armageddon.core.connect.neko;
|
||||||
|
|
||||||
|
import haxe.crypto.BaseCode;
|
||||||
|
import haxe.crypto.Sha1;
|
||||||
|
import protohx.Message;
|
||||||
|
import haxe.io.Bytes;
|
||||||
|
import sys.net.Socket;
|
||||||
|
import ru.m.armageddon.core.connect.IConnection;
|
||||||
|
|
||||||
|
class NekoWebConnection extends NekoConnection {
|
||||||
|
|
||||||
|
private var opened:Bool;
|
||||||
|
|
||||||
|
public function new(socket:Socket, ?handler:IConnectionHandler = null, ?packetHandler:IPacketHandler = null) {
|
||||||
|
super(socket, handler, packetHandler);
|
||||||
|
opened = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function send(packet:Message):Void {
|
||||||
|
L.d("Send", Type.getClassName(Type.getClass(packet)).split(".").pop());
|
||||||
|
try {
|
||||||
|
var data = WebSocketTools.packet2string(packet, builder);
|
||||||
|
writeData(data, socket);
|
||||||
|
} catch (e:Dynamic) {
|
||||||
|
trace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public function pushData(bytes:Bytes):Void {
|
||||||
|
if (!opened) {
|
||||||
|
var str:String = bytes.getString(0, bytes.length);
|
||||||
|
if (StringTools.startsWith(str, "GET")) {
|
||||||
|
var r = ~/Sec-WebSocket-Key:\s*([A-z0-9=+\/]+)/;
|
||||||
|
r.match(str);
|
||||||
|
opened = true;
|
||||||
|
sendServerHandShake(socket, r.matched(1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var data = parseData(bytes);
|
||||||
|
var packet = WebSocketTools.string2packet(data, builder);
|
||||||
|
receive(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function encodeBase64(content:String):String {
|
||||||
|
var suffix = switch (content.length % 3)
|
||||||
|
{
|
||||||
|
case 2: "=";
|
||||||
|
case 1: "==";
|
||||||
|
default: "";
|
||||||
|
};
|
||||||
|
return BaseCode.encode(content, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hex2data(hex:String):String {
|
||||||
|
var data = "";
|
||||||
|
for (i in 0...Std.int(hex.length / 2)) {
|
||||||
|
data += String.fromCharCode(Std.parseInt("0x" + hex.substr(i * 2, 2)));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendServerHandShake(socket:sys.net.Socket, inpKey:String) {
|
||||||
|
var outKey = encodeBase64(hex2data(Sha1.encode(StringTools.trim(inpKey) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
|
||||||
|
|
||||||
|
var s = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
+ "Upgrade: websocket\r\n"
|
||||||
|
+ "Connection: Upgrade\r\n"
|
||||||
|
+ "Sec-WebSocket-Accept: " + outKey + "\r\n"
|
||||||
|
+ "\r\n";
|
||||||
|
|
||||||
|
socket.output.writeString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeData(data:String, socket:sys.net.Socket, isServer = true):Void {
|
||||||
|
socket.output.writeByte(0x81);
|
||||||
|
|
||||||
|
var len = 0;
|
||||||
|
if (data.length < 126) len = data.length;
|
||||||
|
else if (data.length < 65536) len = 126;
|
||||||
|
else len = 127;
|
||||||
|
|
||||||
|
socket.output.writeByte(len | (!isServer ? 0x80 : 0x00));
|
||||||
|
|
||||||
|
if (data.length >= 126)
|
||||||
|
{
|
||||||
|
if (data.length < 65536)
|
||||||
|
{
|
||||||
|
socket.output.writeByte((data.length >> 8) & 0xFF);
|
||||||
|
socket.output.writeByte(data.length & 0xFF);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
socket.output.writeByte((data.length >> 24) & 0xFF);
|
||||||
|
socket.output.writeByte((data.length >> 16) & 0xFF);
|
||||||
|
socket.output.writeByte((data.length >> 8) & 0xFF);
|
||||||
|
socket.output.writeByte(data.length & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isServer)
|
||||||
|
{
|
||||||
|
socket.output.writeString(data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var mask = [ Std.random(256), Std.random(256), Std.random(256), Std.random(256) ];
|
||||||
|
socket.output.writeByte(mask[0]);
|
||||||
|
socket.output.writeByte(mask[1]);
|
||||||
|
socket.output.writeByte(mask[2]);
|
||||||
|
socket.output.writeByte(mask[3]);
|
||||||
|
var maskedData = new StringBuf();
|
||||||
|
for (i in 0...data.length)
|
||||||
|
{
|
||||||
|
maskedData.addChar(data.charCodeAt(i) ^ mask[i % 4]);
|
||||||
|
}
|
||||||
|
socket.output.writeString(maskedData.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseData(bytes:Bytes):String {
|
||||||
|
var p = 0;
|
||||||
|
var opcode = bytes.get(p++);
|
||||||
|
|
||||||
|
if (opcode == 0x00)
|
||||||
|
{
|
||||||
|
var s = "";
|
||||||
|
var b : Int;
|
||||||
|
while ((b = bytes.get(p++)) != 0xFF)
|
||||||
|
{
|
||||||
|
s += String.fromCharCode(b);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opcode == 0x81) // 0x81 = fin & text
|
||||||
|
{
|
||||||
|
var len = bytes.get(p++);
|
||||||
|
|
||||||
|
if (len & 0x80 != 0) // mask
|
||||||
|
{
|
||||||
|
len &= 0x7F;
|
||||||
|
|
||||||
|
if (len == 126)
|
||||||
|
{
|
||||||
|
var b2 = bytes.get(p++);
|
||||||
|
var b3 = bytes.get(p++);
|
||||||
|
len = (b2 << 8) + b3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (len == 127)
|
||||||
|
{
|
||||||
|
var b2 = bytes.get(p++);
|
||||||
|
var b3 = bytes.get(p++);
|
||||||
|
var b4 = bytes.get(p++);
|
||||||
|
var b5 = bytes.get(p++);
|
||||||
|
len = (b2 << 24) + (b3 << 16) + (b4 << 8) + b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Lib.println("len = " + len);
|
||||||
|
|
||||||
|
// direct array init not work corectly!
|
||||||
|
var mask = [];
|
||||||
|
mask.push(bytes.get(p++));
|
||||||
|
mask.push(bytes.get(p++));
|
||||||
|
mask.push(bytes.get(p++));
|
||||||
|
mask.push(bytes.get(p++));
|
||||||
|
|
||||||
|
//Lib.println("mask = " + mask);
|
||||||
|
|
||||||
|
var data = new StringBuf();
|
||||||
|
for (i in 0...len)
|
||||||
|
{
|
||||||
|
data.addChar(bytes.get(p++) ^ mask[i % 4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Lib.println("readed = " + data.toString());
|
||||||
|
return data.toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw "Expected masked data.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opcode == 136) {
|
||||||
|
//socket.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw "Unsupported websocket opcode: " + opcode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,34 @@
|
|||||||
package ru.m.armageddon.server;
|
package ru.m.armageddon.server;
|
||||||
|
|
||||||
|
import ru.m.armageddon.core.connect.neko.NekoWebConnection;
|
||||||
|
import ru.m.armageddon.core.connect.neko.NekoConnection;
|
||||||
import haxework.log.TraceLogger;
|
import haxework.log.TraceLogger;
|
||||||
import ru.m.armageddon.server.session.Session;
|
import ru.m.armageddon.server.session.Session;
|
||||||
import haxe.io.Bytes;
|
import haxe.io.Bytes;
|
||||||
import sys.net.Socket;
|
import sys.net.Socket;
|
||||||
import neko.net.ThreadServer;
|
import neko.net.ThreadServer;
|
||||||
|
|
||||||
|
enum ServerMode {
|
||||||
|
NORMAL_SOCKET;
|
||||||
|
WEB_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
class Server extends ThreadServer<Session, Bytes> {
|
class Server extends ThreadServer<Session, Bytes> {
|
||||||
|
|
||||||
private static inline var TAG = "Server";
|
private static inline var TAG = "Server";
|
||||||
|
|
||||||
public function new() {
|
private var mode:ServerMode;
|
||||||
|
|
||||||
|
public function new(mode:ServerMode) {
|
||||||
super();
|
super();
|
||||||
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
override function clientConnected(s:Socket):Session {
|
override function clientConnected(s:Socket):Session {
|
||||||
var session = new Session(s);
|
var session = new Session(s, switch(mode) {
|
||||||
|
case ServerMode.NORMAL_SOCKET: NekoConnection;
|
||||||
|
case ServerMode.WEB_SOCKET: NekoWebConnection;
|
||||||
|
});
|
||||||
L.d(TAG, "Client connected");
|
L.d(TAG, "Client connected");
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
@@ -35,7 +48,9 @@ class Server extends ThreadServer<Session, Bytes> {
|
|||||||
public static function main() {
|
public static function main() {
|
||||||
L.push(new TraceLogger());
|
L.push(new TraceLogger());
|
||||||
L.d(TAG, "Running");
|
L.d(TAG, "Running");
|
||||||
var server = new Server();
|
//var server = new Server(ServerMode.NORMAL_SOCKET);
|
||||||
server.run("localhost", 5000);
|
//server.run("localhost", 5001);
|
||||||
|
var wserver = new Server(ServerMode.WEB_SOCKET);
|
||||||
|
wserver.run("localhost", 5001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,9 +16,11 @@ class Session implements IConnectionHandler implements IPacketHandler {
|
|||||||
|
|
||||||
public var account(default, null):Account;
|
public var account(default, null):Account;
|
||||||
public var connection(default, null):IConnection;
|
public var connection(default, null):IConnection;
|
||||||
|
public var opened:Bool;
|
||||||
|
|
||||||
public function new(socket:Socket) {
|
public function new(socket:Socket, connectionFactory:Class<IConnection>) {
|
||||||
connection = new NekoConnection(socket, this, this);
|
opened = false;
|
||||||
|
connection = Type.createInstance(connectionFactory, [socket, this, this]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onConnected():Void {
|
public function onConnected():Void {
|
||||||
|
|||||||
Reference in New Issue
Block a user