Дебаг-сервер

Итак, опираясь на материалы о работе с XMLSocket и возможностях отладки haXe-кода делаем собственный дебаг-сервер.

Серверная часть у нас будет на Ruby, и она будет небольшая и простая:

#!/usr/bin/ruby -w
puts 'Debug Server v 0.01'
policyInfo = '<?xml version="1.0"?>'
policyInfo += '<cross-domain-policy>'
policyInfo += '<allow-access-from domain="*" to-ports="4444,80" />'
policyInfo += "</cross-domain-policy>\0"
require 'socket'
server = TCPServer.new('localhost', 4444);
while(session = server.accept)
        Thread.new(session) do |s|
                s.print policyInfo
                while(msg = s.gets)
                        msg.strip!
                        if msg == '<policy-file-request/>'
                                puts "\n =========== \n\n"
                        else
                                puts msg
                        end
                end
        end
end

Сервер просто принимает соединение, отсылает клиенту cross-domain-policy и пишит в консоль все, что присылает клиент.

Клиентская часть будет состоять из двух классов. Основной класс приложения:

/**
 * @author Yzh
 */

package com.yzh44yzh.site;

import com.yzh44yzh.core.Application;
import com.yzh44yzh.core.Debug;

class ThreeManApp extends Application
{      
        // static
        static public function main():Void { new ThreeManApp(); }

        // constructor
        public function new()
        {
                super();
                this.version = '0.1';

                Debug.Init(this.Start);
        }

        // methods
        private override function Start():Void
        {
                trace(this + ' version: ' + version);
                super.Start();
                trace('Hello world!');
                trace('Error: some error');
                trace('And some more trace');
        }

        public override function toString():String { return 'ThreeManApp'; }            
}

В конструкторе класса инициализируется дебаг. Ему отдается метод, который нужно вызывать, когда инициализация закончится (в данном случае это метод Start). Дебагу нужно некоторое время, чтобы установить соединение с сервером.

А теперь сам класс дебага:

/**
 * @author Yzh
 */

package com.yzh44yzh.core;

import flash.events.DataEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.XMLSocket;
import flash.system.Security;
import flash.text.TextField;
import flash.text.TextFormat;

class Debug
{
        // constants
        static inline var DEFAULT:      String = '\033[00m';
        static inline var GREY:         String = '\033[01;30m';
        static inline var RED:          String = '\033[01;31m';
        static inline var GREEN:        String = '\033[00;32m';
        static inline var YELLOW:       String = '\033[01;33m';
        static inline var BLUE:         String = '\033[00;34m';
        static inline var MAGENTA:      String = '\033[00;35m';
        static inline var CYAN:         String = '\033[00;36m';

        // static vars
        static private var instance:    Debug;

        // static methods
        static public function Init(ConnectHandler:Void->Void):Void
        {
                Debug.instance = new Debug();
                Debug.instance.ConnectHandler = ConnectHandler;
                Debug.instance.Connect();

                haxe.Log.trace = Debug.Trace;
        }

        static public function Trace(data:Dynamic, ?pos:haxe.PosInfos):Void
        {
                var msg = data.toString();
                if(Debug.instance.connected)
                {
                        var data = BLUE;
                        data += pos.className + '.' + pos.methodName + ':' + pos.lineNumber + ':\n';
                        data += if(msg.toLowerCase().indexOf('error') != -1) RED else YELLOW;
                        data += msg.toString() + '\n' + DEFAULT;
                        Debug.instance.socket.send(data);
                }
                else
                {
                        Debug.instance.Display('no connection to debug-server\n' + msg);
                }
        }
       
        // properties
        private var connected:          Bool;
        private var socket:             XMLSocket;
        private var tfDisplay:          TextField;
        private var ConnectHandler:     Void->Void;

        // constructor
        public function new() { }

        // methods
        private function Display(msg:String):Void
        {
                if(this.tfDisplay == null)
                {
                        this.tfDisplay = new TextField();
                        this.tfDisplay.defaultTextFormat = new TextFormat(null, 24, 0xff0000);
                        this.tfDisplay.autoSize = 'left';
                        this.tfDisplay.border = true;
                        this.tfDisplay.text = '';
                        flash.Lib.current.addChild(this.tfDisplay);
                }
                this.tfDisplay.text += msg + '\n';
        }

        private function Connect():Void
        {
                this.connected = false;
                try
                {
                        Security.loadPolicyFile('xmlsocket://localhost:4444');

                        this.socket = new XMLSocket('localhost', 4444);
                        this.socket.addEventListener(Event.CONNECT, OnConnect);
                        this.socket.addEventListener(Event.CLOSE, OnClose);
                        this.socket.addEventListener(IOErrorEvent.IO_ERROR, OnIOError);
                        this.socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, OnSecurityError);
                }
                catch(e:Dynamic) { this.Display(e.toString()); }
        }

        private function OnConnect(event:Event):Void
        {
                this.connected = true;
                this.ConnectHandler();
        }

        private function OnClose(event:Event):Void
        {
                this.Display('lost connection to debug-server');
        }

        private function OnIOError(event:IOErrorEvent):Void
        {
                this.Display(event.toString());
        }

        private function OnSecurityError(event:SecurityErrorEvent):Void
        {
                this.Display(event.toString());
        }

        public function toString():String { return 'Debug'; }          
}

Здесь все немного сложнее. Во-первых, что будет, если дебаг-сервер не доступен (не запущен)? Нужно как-то сообщить об этом и как-то показывать сообщения. Поэтому у нас будет резервная система. Эту роль выполняет метод Display
        private function Display(msg:String):Void
        {
                if(this.tfDisplay == null)
                {
                        this.tfDisplay = new TextField();
                        this.tfDisplay.defaultTextFormat = new TextFormat(null, 24, 0xff0000);
                        this.tfDisplay.autoSize = 'left';
                        this.tfDisplay.border = true;
                        this.tfDisplay.text = '';
                        flash.Lib.current.addChild(this.tfDisplay);
                }
                this.tfDisplay.text += msg + '\n';
        }

Он создает текстовое поле в корневом мувике (если оно не создано), и выводит сообщения туда.

Все начинается с метода Init

        static public function Init(ConnectHandler:Void->Void):Void
        {
                Debug.instance = new Debug();
                Debug.instance.ConnectHandler = ConnectHandler;
                Debug.instance.Connect();

                haxe.Log.trace = Debug.Trace;
        }

Здесь создается экземпляр класса Debug. Сохраняется ссылка на метод, который нужно будет вызвать, когда Debug будет готов к работе, устанавливается соединение с дебаг-сервером и переопределяется haxe.Log.trace

Установка соединения уже рассматривалась в статье про XMLSocket. После того, как соединение установлено, вызывается ConnectHandler, которым, в данном случае, является метод TreeManApp.Start.

Наконец, метод Trace, который заменяет собой haxe.Log.trace.

        static public function Trace(data:Dynamic, ?pos:haxe.PosInfos):Void
        {
                var msg = data.toString();
                if(Debug.instance.connected)
                {
                        var data = BLUE;
                        data += pos.className + '.' + pos.methodName + ':' + pos.lineNumber + ':\n';
                        data += if(msg.toLowerCase().indexOf('error') != -1) RED else YELLOW;
                        data += msg.toString() + '\n' + DEFAULT;
                        Debug.instance.socket.send(data);
                }
                else
                {
                        Debug.instance.Display('no connection to debug-server\n' + msg);
                }
        }

Если нет соединения с дебаг-сервером, сообщение передается методу Display. А в нормальном режиме формируется сообщение и отправляется дебаг-серверу. Сообщение раскрашивается цветами: PosInfo -- синим, msg -- желтым. Если сообщение содержит подскроку "error", то оно окрашивается красным. Для этого используются коды терминала. Это работает только в Linux, в Windows терминал, к сожалению, нельзя расскрасить разными цветами.

Вот и все. В одном окне открываем терминал и запускаем дебаг-сервер, в другом окне (в браузере или в stand-alone плеере) запускаем флэшку. Любуемся сообщениями в терминале. В аттаче пара скриншотов, как это выглядит у меня.