redom 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2012 by Yi Hu
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,126 @@
1
+ DJS Tutorial
2
+
3
+ DJS is a distributed object library for Server-WebBrowser communication. This tutorial shows how to create a web application using DJS.
4
+
5
+ ===============
6
+ Getting started
7
+ ===============
8
+
9
+ - Creating a DJS connection class
10
+
11
+ A DJS connection class is where you write the scripts to manipulate the browser-side objects from server. The DJS connection class must be subclass of 'DJS::DJSConnection'. An instance of DJS connection class will be created when the connection between browser and DJS server is established. There are four methods in 'DJS::DJSConnection' that can be overridden to tell DJS what to do.
12
+
13
+ on_open() - Called when the connection is established.
14
+ on_error(err) - Called when an browser-side error occurs.
15
+ on_message(msg) - Called when an message is received from browser.(not implemented)
16
+ on_close() - Called when the connection is shutdown.(not implemented)
17
+
18
+ Here is an example:
19
+ #
20
+ class MyConnection < DJS::Connection
21
+ def on_open
22
+ window.alert "Hello world!"
23
+ end
24
+ def on_error(error)
25
+ p error
26
+ end
27
+ end
28
+ #
29
+
30
+ - Starting DJS server
31
+
32
+ After a DJS connection class is defined, you can start DJS server using 'DJS::start(klass)' method. Your DJS connection class must be passed as the argument.
33
+ #
34
+ DJS.start(MyConnection, {:host => 127.0.0.1, :port => 8080})
35
+ #
36
+
37
+ - Adding DJS library to HTML
38
+
39
+ Add DJS JavaScript library 'djs.js' in your HTML. And call 'djs.start(host)' to connect to a DJS server.
40
+ #
41
+ <script type="text/javascript" src="djs.js"></script>
42
+ <script type="text/javascript">
43
+ window.onload = function() {
44
+ djs.start("ws://127.0.0.1:8080");
45
+ }
46
+ </script>
47
+ #
48
+
49
+ - Stopping DJS server
50
+
51
+ Use 'DJS.stop' to stop a DJS server.
52
+ #
53
+ DJS.stop
54
+ #
55
+
56
+ ========
57
+ API Docs
58
+ ========
59
+
60
+ * Module: DJS
61
+
62
+ - DJS.start(klass, opts = {})
63
+
64
+ Start DJS server.
65
+
66
+ Parameters:
67
+ klass (Class) - A class that extends DJS::Connection
68
+ opts (Hash) - Options. Default values are as below:
69
+ :buff_size => 200 - Messages stored before synchronization with browser.
70
+
71
+ - DJS.stop
72
+
73
+ Stop DJS server.
74
+
75
+ - DJS.connections
76
+
77
+ Return all DJS connection instances that the DJS server is holding currently.
78
+
79
+ Returns:
80
+ (DJS::Connections) - An DJS::Connections instance that contains all DJS::Connection instances the DJS server is holding currently.
81
+
82
+ Examples:
83
+ DJS.connections.do_something
84
+
85
+
86
+ * Class: DJS::Connection
87
+
88
+ - sync
89
+
90
+ Synchronize with browser immediately.
91
+
92
+ - window
93
+
94
+ A reference for browser-side object 'window'.
95
+
96
+ Returns:
97
+ (DJS::ProxyObject) - A DJS::ProxyObject that references to browser-side object 'window'.
98
+
99
+ Examples:
100
+ window.alert "Hello world."
101
+
102
+ * Class: DJS:ProxyObject
103
+
104
+ - sync, to_s, to_f, to_i, to_a, to_h, to_b
105
+
106
+ Synchronize with browser immediately and return the true value of the referenced object.
107
+
108
+ Returns:
109
+ (Object) - Primitive type if the referenced object is primitive, otherwise the DJS::ProxyObject itself.
110
+
111
+ Examples:
112
+ value = document.getElementById("text").value.sync
113
+
114
+ =====
115
+ Hints
116
+ =====
117
+
118
+ - Define a event handler as 'object.event_name = :event_handler_name'
119
+ Examples:
120
+ def button_click_handler(event)
121
+ button = event.srcElement
122
+ end
123
+ document.getElementById("button").onclick = :button_click_handler
124
+
125
+
126
+
@@ -0,0 +1,83 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'em-websocket'
4
+ require 'optparse'
5
+ require 'redom'
6
+
7
+ options = {
8
+ :host => '0.0.0.0',
9
+ :port => 8080
10
+ }
11
+
12
+ parser = OptionParser.new { |opts|
13
+ opts.banner = "Usage: redom [options] app"
14
+
15
+ opts.separator ""
16
+ opts.separator "Server options:"
17
+
18
+ opts.on("-h", "--host HOST", "bind to HOST address (default: #{options[:host]})") { |host| options[:host] = host }
19
+ opts.on("-p", "--port PORT", "use PORT (default: #{options[:port]})") { |port| options[:port] = port.to_i }
20
+
21
+ opts.separator ""
22
+ opts.separator "Secure options:"
23
+
24
+ opts.on( "--secure", "accept secure wss:// connections") { options[:secure] = true }
25
+ opts.on( "--private-key-file PATH", "path to private key") { |path|
26
+ if tls = options[:tls_options]
27
+ tls[:private_key_file] = path
28
+ else
29
+ options[:tls_options] = { :private_key_file => path }
30
+ end
31
+ }
32
+ opts.on( "--cert-chain-file PATH", "path to certificate") { |path|
33
+ if tls = options[:tls_options]
34
+ tls[:cert_chain_file] = path
35
+ else
36
+ options[:tls_options] = { :cert_chain_file => path }
37
+ end
38
+ }
39
+ opts.on( "--secure-proxy", "running behind an SSL/Terminator") { options[:secure_proxy] = true }
40
+
41
+ opts.separator ""
42
+ opts.separator "Common options:"
43
+
44
+ opts.on( "--worker NUM", "number of worker thread (default: 5)") { |num| options[:worker] = num.to_i }
45
+ opts.on( "--buff-size NUM", "size of packed RPC message before sync") { |num| options[:buff_size] = num.to_i }
46
+ opts.on( "--log FILE", "path to log (default: STDOUT)") { |path| options[:log] = path }
47
+ opts.on( "--log-level fatal|error|warn|info|debug", "log level (default: error)") { |lv| options[:log_level] = lv }
48
+ opts.on_tail( "--help", "show this message") { puts opts; exit }
49
+ opts.on_tail( "--version", "show version") { puts Redom::VERSION; exit }
50
+ }
51
+
52
+ parser.parse ARGV
53
+
54
+
55
+ if ARGV.length > 0
56
+ #$LOAD_PATH << pwd unless $LOAD_PATH.include?(pwd)
57
+ require Dir.pwd + '/' + ARGV[-1]
58
+ else
59
+ puts 'No Redom app file specified.'
60
+ exit
61
+ end
62
+
63
+ Redom.start options
64
+
65
+ EventMachine.run do
66
+ EventMachine::WebSocket.start(options) do |ws|
67
+ ws.onopen {
68
+ Redom.on_open ws
69
+ }
70
+
71
+ ws.onmessage { |msg|
72
+ Redom.on_message ws, msg
73
+ }
74
+
75
+ ws.onclose {
76
+ Redom.on_close ws
77
+ }
78
+
79
+ ws.onerror { |error|
80
+ Redom.on_error ws, error
81
+ }
82
+ end
83
+ end
@@ -0,0 +1,94 @@
1
+ require 'fiber'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'thread'
5
+
6
+ $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
7
+ %w[
8
+ utils connection dispatcher proxy task worker thread_pool runtime version
9
+ ].each { |file|
10
+ require "redom/#{file}"
11
+ }
12
+
13
+ module Redom
14
+ DEFAULT_OPTIONS = {
15
+ :log => STDOUT,
16
+ :log_level => 'error',
17
+ :worker => 5,
18
+ :buff_size => 200
19
+ }
20
+
21
+ REQ_HANDSHAKE = 0
22
+ REQ_METHOD_INVOCATION = 1
23
+ REQ_PROXY_RESULT = 2
24
+
25
+ IDX_REQUEST_TYPE = 0
26
+ # REQ_HANDSHAKE
27
+ IDX_CONNECTION_CLASS = 1
28
+ # REQ_METHOD_INVOCATION
29
+ IDX_METHOD_NAME = 1
30
+ IDX_ARGUMENTS = 2
31
+ # REQ_PROXY_RESULT
32
+ IDX_TASK_ID = 1
33
+ IDX_PROXY_RESULT = 2
34
+
35
+ TYPE_UNDEFINED = 0
36
+ TYPE_PROXY = 1
37
+ TYPE_ARRAY = 2
38
+ TYPE_ERROR = 3
39
+ TYPE_METHOD = 4
40
+
41
+ T_INFO_METHOD_NAME = 0
42
+ T_INFO_ARGUMENTS = 1
43
+ T_INFO_BLOCK = 2
44
+
45
+ P_INFO_OID = 0
46
+ P_INFO_RCVR = 1
47
+ P_INFO_NAME = 2
48
+ P_INFO_ARGS = 3
49
+
50
+ class << self
51
+ # Start Redom dispatcher
52
+ # @param [Hash] opts Options
53
+ def start(opts = {})
54
+ opts = DEFAULT_OPTIONS.merge(opts)
55
+ logger = Logger.new(opts[:log])
56
+ logger.level = case opts[:log_level].downcase
57
+ when 'fatal'
58
+ Logger::FATAL
59
+ when 'error'
60
+ Logger::ERROR
61
+ when 'warn'
62
+ Logger::WARN
63
+ when 'info'
64
+ Logger::INFO
65
+ when 'debug'
66
+ Logger::DEBUG
67
+ else
68
+ Logger::FATAL
69
+ end
70
+ Utils.logger = logger
71
+ Utils.dispatcher = @@dispatcher = Dispatcher.new(opts)
72
+ end
73
+
74
+ def stop
75
+ @@dispatcher.stop
76
+ end
77
+
78
+ def on_open(ws)
79
+ @@dispatcher.on_open(ws)
80
+ end
81
+
82
+ def on_message(ws, msg)
83
+ @@dispatcher.on_message(ws, msg)
84
+ end
85
+
86
+ def on_close(ws)
87
+ @@dispatcher.on_close(ws)
88
+ end
89
+
90
+ def on_error(ws, err)
91
+ @@dispatcher.on_error(ws, err)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,96 @@
1
+ module Redom
2
+ module Connection
3
+ include Utils
4
+
5
+ # Default event handlers
6
+ def on_open; end
7
+ def on_close; end
8
+ def on_error(err); end
9
+
10
+ # Proxy of browser objects
11
+ def window
12
+ Proxy.new(self, [:window], {:backtrace => caller[0]})
13
+ end
14
+ def document
15
+ Proxy.new(self, [:document], {:backtrace => caller[0]})
16
+ end
17
+
18
+ def method_missing(name, *args, &blck)
19
+ window.method_missing(name, *args, &blck)
20
+ end
21
+
22
+ def connections(filter = self.class)
23
+ _dispatcher.connections(filter)
24
+ end
25
+
26
+ # Initialization
27
+ def _init(ws, opts)
28
+ @ws = ws
29
+ @proxies = Hash.new
30
+ @buff_size = opts[:buff_size]
31
+ self
32
+ end
33
+
34
+ def _bulk(proxy)
35
+ fid = _fid
36
+ stack = @proxies[fid] || @proxies[fid] = Hash.new
37
+ stack[proxy._info[P_INFO_OID]] = proxy
38
+ if stack.size == @buff_size
39
+ sync
40
+ end
41
+ end
42
+
43
+ def sync(obj = nil)
44
+ obj = _fid if obj == self
45
+ if Connection === obj
46
+ _dispatcher.run_task(obj, :sync, [_fid], nil)
47
+ return
48
+ end
49
+
50
+ fid = obj ? obj : _fid
51
+ stack = @proxies[fid]
52
+ return if !stack || stack.empty?
53
+
54
+ msg = []
55
+ stack.each { |k, v|
56
+ msg << v._info
57
+ }
58
+
59
+ rsp = Fiber.yield(msg)
60
+
61
+ error = nil
62
+ while result = rsp.shift
63
+ proxy = stack[result[0]]
64
+ if Array === result[1]
65
+ case result[1][0]
66
+ when TYPE_UNDEFINED
67
+ # TODO
68
+ when TYPE_PROXY
69
+ proxy._origin proxy
70
+ when TYPE_ERROR
71
+ # TODO
72
+ end
73
+ else
74
+ proxy._origin result[1]
75
+ end
76
+ end
77
+
78
+ stack.clear
79
+
80
+ on_error(error) if error
81
+ end
82
+
83
+ def _cid
84
+ @ws.__id__
85
+ end
86
+
87
+ def _fid
88
+ "F#{Fiber.current.__id__}"
89
+ end
90
+
91
+ def _send(msg)
92
+ _logger.debug "Message sent. ID='#{@ws.__id__}'\n#{msg}"
93
+ @ws.send msg
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,114 @@
1
+ module Redom
2
+ class Dispatcher
3
+ include Utils
4
+
5
+ def initialize(opts)
6
+ if find_all_connection_classes.size == 0
7
+ _logger.warn 'No Redom::Connection class defined.'
8
+ end
9
+
10
+ @opts = opts
11
+ @conns = Hash.new
12
+ @tasks = Hash.new
13
+ @pool = ThreadPool.new(@opts)
14
+ @pool.start
15
+ end
16
+
17
+ def stop
18
+ @pool.stop
19
+ end
20
+
21
+ def run_task(conn, name, args, blck)
22
+ task = Task.new(conn, @pool.worker, [name, args, blck])
23
+ @tasks[task._id] = task
24
+ task.run
25
+ end
26
+
27
+ def on_open(ws)
28
+ _logger.debug "Connection established. ID='#{ws.__id__}'"
29
+ end
30
+
31
+ def on_message(ws, msg)
32
+ _logger.debug "Message received. ID='#{ws.__id__}'\n#{msg}"
33
+ req = JSON.parse(msg)
34
+ case req[IDX_REQUEST_TYPE]
35
+ when REQ_HANDSHAKE
36
+ if cls = @conn_cls[req[IDX_CONNECTION_CLASS]]
37
+ conn = cls.new._init(ws, @opts)
38
+ @conns[ws.__id__] = conn
39
+ task = Task.new(conn, @pool.worker, [:on_open, []])
40
+ @tasks[task._id] = task
41
+ task.run
42
+ else
43
+ _logger.error "Undefined Redom::Connection class '#{req[IDX_CONNECTION_CLASS]}'."
44
+ end
45
+ when REQ_METHOD_INVOCATION
46
+ if conn = @conns[ws.__id__]
47
+ req[IDX_ARGUMENTS].map! { |arg|
48
+ if Array === arg
49
+ case arg[0]
50
+ when TYPE_PROXY
51
+ arg = Proxy.new(conn, [arg[1]])
52
+ when TYPE_ARRAY
53
+ arg = arg[1]
54
+ end
55
+ end
56
+ arg
57
+ }
58
+ task = Task.new(conn, @pool.worker, req[IDX_METHOD_NAME..-1])
59
+ @tasks[task._id] = task
60
+ task.run
61
+ else
62
+ _logger.error "Connection missing. ID='#{ws.__id__}'"
63
+ end
64
+ when REQ_PROXY_RESULT
65
+ if task = @tasks[req[IDX_TASK_ID]]
66
+ task.run req[IDX_PROXY_RESULT]
67
+ else
68
+ _logger.error "Task missing. ID='#{req[IDX_TASK_ID]}'"
69
+ end
70
+ else
71
+ _logger.error "Undefined request type '#{req[IDX_REQUEST_TYPE]}'."
72
+ end
73
+ end
74
+
75
+ def on_close(ws)
76
+ _logger.debug "Connection closed. ID='#{ws.__id__}'"
77
+ if conn = @conns[ws.__id__]
78
+ task = Task.new(conn, @pool.worker, [:on_close, []])
79
+ @tasks[task._id] = task
80
+ task.run
81
+ @conns.delete ws.__id__
82
+ end
83
+ end
84
+
85
+ def on_error(ws, err)
86
+ _logger.debug "Error occured. ID='#{ws.__id__}'"
87
+ end
88
+
89
+ def delete_task(task_id)
90
+ @tasks.delete task_id
91
+ end
92
+
93
+ def connections(filter = nil)
94
+ if filter
95
+ @conns.values.select { |conn|
96
+ filter === conn
97
+ }
98
+ else
99
+ @conns.values
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def find_all_connection_classes
106
+ @conn_cls = Hash.new
107
+ ObjectSpace.each_object(Class).select { |cls|
108
+ cls < Connection
109
+ }.each { |cls|
110
+ @conn_cls[cls.name] = cls
111
+ }
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,67 @@
1
+ module Redom
2
+ class Proxy
3
+ def initialize(conn, info, extra = {})
4
+ @conn = conn
5
+ @info = info
6
+ @info[P_INFO_OID] = __id__ unless @info[P_INFO_OID]
7
+
8
+ @solved = false
9
+ @origin = nil
10
+ end
11
+ attr_reader :origin
12
+
13
+ def method_missing(name, *args, &blck)
14
+ return @origin.send(name, *args, &block) if @solved && !@origin.is_a?(Proxy)
15
+
16
+ if blck
17
+ @conn.sync
18
+ return @origin.send(name, *args, &block)
19
+ end
20
+
21
+ proxy = Proxy.new(@conn, [nil, _info[P_INFO_OID], name, _arg(args)], {:backtrace => caller[0]})
22
+ @conn._bulk proxy
23
+ proxy
24
+ end
25
+
26
+ def sync
27
+ unless @solved
28
+ @conn.sync
29
+ end
30
+ @origin
31
+ end
32
+
33
+ def _info
34
+ @info
35
+ end
36
+
37
+ def _origin(org)
38
+ @solved = true
39
+ @origin = org
40
+ end
41
+
42
+ private
43
+
44
+ def _arg(arg)
45
+ case arg
46
+ when Proxy
47
+ [TYPE_PROXY, arg._info[P_INFO_OID]]
48
+ when Array
49
+ arg.map! { |item|
50
+ _arg(item)
51
+ }
52
+ [TYPE_ARRAY, arg]
53
+ when Hash
54
+ arg.each { |k, v|
55
+ arg[k] = _arg(v)
56
+ }
57
+ arg
58
+ when Method
59
+ [TYPE_METHOD, arg.name]
60
+ when Symbol
61
+ [TYPE_METHOD, arg.to_s]
62
+ else
63
+ arg
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,252 @@
1
+ module Redom
2
+ class << self
3
+ def runtime
4
+ <<EOS
5
+ (function() {
6
+ var
7
+ TYPE_UNDEFINED = 0,
8
+ TYPE_PROXY = 1,
9
+ TYPE_ARRAY = 2,
10
+ TYPE_ERROR = 3,
11
+ TYPE_METHOD = 4
12
+
13
+ REQ_HANDSHAKE = 0,
14
+ REQ_METHOD_INVOCATION = 1,
15
+ REQ_PROXY_RESULT = 2,
16
+
17
+ P_INFO_OID = 0,
18
+ P_INFO_RCVR = 1,
19
+ P_INFO_NAME = 2,
20
+ P_INFO_ARGS = 3;
21
+
22
+ var Redom = function(server) {
23
+ var ws = new WebSocket(server);
24
+ conn = "Connection",
25
+ refs = {
26
+ seq: 0,
27
+ hash: {},
28
+ put: function() {
29
+ var key;
30
+ if (arguments.length == 2) {
31
+ key = arguments[0];
32
+ this.hash[key] = arguments[1];
33
+ } else if (arguments.length == 1) {
34
+ key = "R" + (++this.seq);
35
+ this.hash[key] = arguments[0];
36
+ } else {
37
+ console.log("ERROR: wrong number of arguments!");
38
+ }
39
+ return key;
40
+ },
41
+ get: function(key) {
42
+ return this.hash[key];
43
+ }
44
+ },
45
+ tasks = [];
46
+
47
+ // Initialize refs
48
+ refs.put("window", window);
49
+ refs.put("document", document);
50
+ refs.put("history", history);
51
+ refs.put("navigator", navigator);
52
+ refs.put("location", location);
53
+
54
+ // Initialize WebSocket event handlers
55
+ function open(connectionClassName) {
56
+ ws.onopen = function() {
57
+ conn = connectionClassName;
58
+ ws.send(serialize([REQ_HANDSHAKE, conn]));
59
+ console.log("WebSocket opened.");
60
+ };
61
+ ws.onclose = function() {
62
+ console.log("WebSocket closed.");
63
+ };
64
+ ws.onerror = function(error) {
65
+ console.log(error);
66
+ };
67
+ ws.onmessage = function(event) {
68
+ var data = unserialize(event.data);
69
+ var tid = data[0];
70
+ var proxies = data[1];
71
+ var queue = tasks[tid];
72
+ if (!queue) {
73
+ queue = [];
74
+ tasks[tid] = queue;
75
+ }
76
+ for (var i = 0; i < proxies.length; i++) {
77
+ queue.push(proxies[i]);
78
+ }
79
+ try {
80
+ process(tid);
81
+ } catch (e) {
82
+ console.log(e);
83
+ }
84
+ };
85
+ };
86
+
87
+ // Close this WebSocket connection
88
+ function close() {
89
+ ws.close();
90
+ }
91
+
92
+ // Serialize JavaScript object into message
93
+ function serialize(data) {
94
+ return JSON.stringify(data);
95
+ };
96
+ // Unserialize message into JavaScript object
97
+ function unserialize(msg) {
98
+ return JSON.parse(msg);
99
+ };
100
+
101
+ // Restore arguments
102
+ function restore(args) {
103
+ if (args instanceof Array) {
104
+ switch(args[0]) {
105
+ case TYPE_ARRAY:
106
+ for (var i = 0; i < args[1].length; i++) {
107
+ args[1][i] = restore(args[1][i]);
108
+ }
109
+ return args[1];
110
+ break;
111
+ case TYPE_PROXY:
112
+ return refs.get(args[1]);
113
+ break;
114
+ case TYPE_METHOD:
115
+ return (function(name) {
116
+ if (window[name] && typeof(window[name]) == 'function') {
117
+ return function() {
118
+ window[name];
119
+ };
120
+ } else {
121
+ return function() {
122
+ var type, args = [];
123
+ for (var i = 0; i < arguments.length; i++) {
124
+ switch(type = typeOf(arguments[i])) {
125
+ case null:
126
+ args.push(arguments[i]);
127
+ break;
128
+ case TYPE_PROXY:
129
+ case TYPE_ARRAY:
130
+ args.push([type, refs.put(arguments[i])]);
131
+ break;
132
+ }
133
+ }
134
+ ws.send(serialize([REQ_METHOD_INVOCATION, name, args]));
135
+ }
136
+ }
137
+ })(args[1]);
138
+ break;
139
+ }
140
+ } else {
141
+ return args;
142
+ }
143
+ };
144
+
145
+ // Evaluation proxies from server
146
+ function process(tid) {
147
+ var proxy, oid, name, args, result, type,
148
+ rsps = [],
149
+ queue = tasks[tid];
150
+ while (proxy = queue.shift()) {
151
+ oid = proxy[P_INFO_OID];
152
+ rcvr = refs.get(proxy[P_INFO_RCVR]);
153
+ name = proxy[P_INFO_NAME];
154
+ args = restore(proxy[P_INFO_ARGS]);
155
+
156
+ if (rcvr) {
157
+ result = execute(rcvr, name, args);
158
+ if (result == undefined) {
159
+ rsps.push([oid, [TYPE_UNDEFINED]]);
160
+ ws.send(serialize([REQ_PROXY_RESULT, tid, rsps]));
161
+ return;
162
+ } else {
163
+ refs.put(oid, result);
164
+ switch (type = typeOf(result)) {
165
+ case null:
166
+ rsps.push([oid, result]);
167
+ break;
168
+ case TYPE_PROXY:
169
+ case TYPE_ARRAY:
170
+ rsps.push([oid, [type]]);
171
+ break;
172
+ };
173
+ }
174
+ } else {
175
+ rsps.push([oid, TYPE_ERROR, "no such object. ID='" + proxy[P_INFO_RCVR] + "'."]);
176
+ ws.send(serialize([REQ_PROXY_RESULT, tid, rsps]));
177
+ return;
178
+ }
179
+ }
180
+ ws.send(serialize([REQ_PROXY_RESULT, tid, rsps]));
181
+ };
182
+
183
+ // Type of object
184
+ function typeOf(obj) {
185
+ switch(typeof obj) {
186
+ case "string":
187
+ case "number":
188
+ case "boolean":
189
+ case "undefined":
190
+ return null;
191
+ case "object":
192
+ if (obj == null) {
193
+ return null;
194
+ } else if (obj instanceof Array) {
195
+ return TYPE_ARRAY;
196
+ } else {
197
+ return TYPE_PROXY;
198
+ }
199
+ case "function":
200
+ return TYPE_METHOD;
201
+ }
202
+ return TYPE_UNDEFINED;
203
+ }
204
+
205
+ // Method invocation
206
+ function execute(rcvr, name, args) {
207
+ var result;
208
+ if (name.match(/^(.+)=$/)) {
209
+ name = RegExp.$1;
210
+ if (name == "[]") {
211
+ result = rcvr[args[0]] = args[1];
212
+ } else {
213
+ result = rcvr[name] = args[0];
214
+ }
215
+ } else {
216
+ if (name == "[]") {
217
+ result = rcvr[args[0]];
218
+ } else {
219
+ result = rcvr[name];
220
+ if (typeof result == "function") {
221
+ result = result.apply(rcvr, args);
222
+ }
223
+ }
224
+ }
225
+ return result;
226
+ };
227
+
228
+ return {
229
+ getServer: function() {
230
+ return server;
231
+ },
232
+
233
+ getConnection: function() {
234
+ return conn;
235
+ },
236
+
237
+ open: function(connectionClassName) {
238
+ open(connectionClassName);
239
+ },
240
+
241
+ close: function() {
242
+ close();
243
+ }
244
+ };
245
+ };
246
+
247
+ window.Redom = Redom;
248
+ })();
249
+ EOS
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,52 @@
1
+ module Redom
2
+ class Task
3
+ include Utils
4
+
5
+ def initialize(conn, worker, info)
6
+ @conn = conn
7
+ @worker = worker
8
+ @info = info
9
+ @results = []
10
+ end
11
+
12
+ def run(proxy_result = nil)
13
+ @results << proxy_result if proxy_result
14
+ @worker.do_task self
15
+ end
16
+
17
+ def resume
18
+ if !@results.empty?
19
+ result = @fiber.resume(@results.shift)
20
+ else
21
+ mthd = @conn.method(@info[T_INFO_METHOD_NAME])
22
+ args = @info[T_INFO_ARGUMENTS]
23
+ blck = @info[T_INFO_BLOCK]
24
+ @fiber = Fiber.new do
25
+ if mthd
26
+ if blck
27
+ mthd.call(*args, &blck)
28
+ else
29
+ mthd.call(*args)
30
+ end
31
+ else
32
+ _logger.error "Method '#{@info[T_INFO_METHOD_NAME]}' is not defined in class '#{@conn.class}'."
33
+ @conn.method(:on_error).call("No such method '#{@info[T_INFO_METHOD_NAME]}'.")
34
+ end
35
+ @conn.sync
36
+ nil
37
+ end
38
+ result = @fiber.resume
39
+ end
40
+
41
+ if result
42
+ @conn._send [_id, result].to_json
43
+ else
44
+ _dispatcher.delete_task _id
45
+ end
46
+ end
47
+
48
+ def _id
49
+ "T#{__id__}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ module Redom
2
+ class ThreadPool
3
+ def initialize(opts)
4
+ @workers = Array.new
5
+ @idx = 0
6
+ opts[:worker].times {
7
+ @workers << Worker.new
8
+ }
9
+ end
10
+
11
+ def start
12
+ @workers.each { |worker|
13
+ worker.start
14
+ }
15
+ end
16
+
17
+ def stop
18
+ @workers.each { |worker|
19
+ worker.stop
20
+ }
21
+ end
22
+
23
+ def worker
24
+ @workers[(@idx += 1) == @workers.size ? @idx = 0 : @idx]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module Redom
2
+ module Utils
3
+ @@logger = nil
4
+
5
+ def self.logger=(logger)
6
+ @@logger = logger
7
+ end
8
+
9
+ def self.dispatcher=(dispatcher)
10
+ @@dispatcher = dispatcher
11
+ end
12
+
13
+ def _logger
14
+ @@logger
15
+ end
16
+
17
+ def _dispatcher
18
+ @@dispatcher
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Redom
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,34 @@
1
+ module Redom
2
+ class Worker
3
+ include Utils
4
+
5
+ def initialize
6
+ @queue = Queue.new
7
+ end
8
+
9
+ def start
10
+ @thread = Thread.start do
11
+ while task = @queue.pop
12
+ begin
13
+ task.resume
14
+ rescue
15
+ _logger.error "Task failed. ID='#{task.__id__}'\n"
16
+ _logger.error $!.message
17
+ $!.backtrace.each { |item|
18
+ _logger.error item
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def stop
26
+ @thread.kill
27
+ @queue.clear
28
+ end
29
+
30
+ def do_task(task)
31
+ @queue.push task
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/redom/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'redom'
5
+ s.version = Redom::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.summary = 'A distributed object based server-centric web framework'
8
+ s.description = 'A distributed object based server-centric web framework.'
9
+ s.author = ['Yi Hu', 'Sasada Koichi']
10
+ s.email = 'future.azure@gmail.com'
11
+ s.homepage = 'https://github.com/future-azure/redom'
12
+
13
+ s.require_paths = ['lib']
14
+ s.files = ['README.md', 'LICENSE', 'redom.gemspec']
15
+ s.files += ['lib/redom.rb'] + Dir['lib/redom/**/*.rb']
16
+ s.files += Dir['bin/redom']
17
+ s.executables = ['redom']
18
+
19
+ s.add_dependency 'em-websocket', '>= 0.3.5'
20
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yi Hu
9
+ - Sasada Koichi
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-12-10 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: em-websocket
17
+ requirement: &70124221424660 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 0.3.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70124221424660
26
+ description: A distributed object based server-centric web framework.
27
+ email: future.azure@gmail.com
28
+ executables:
29
+ - redom
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - LICENSE
35
+ - redom.gemspec
36
+ - lib/redom.rb
37
+ - lib/redom/connection.rb
38
+ - lib/redom/dispatcher.rb
39
+ - lib/redom/proxy.rb
40
+ - lib/redom/runtime.rb
41
+ - lib/redom/task.rb
42
+ - lib/redom/thread_pool.rb
43
+ - lib/redom/utils.rb
44
+ - lib/redom/version.rb
45
+ - lib/redom/worker.rb
46
+ - bin/redom
47
+ homepage: https://github.com/future-azure/redom
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.10
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: A distributed object based server-centric web framework
71
+ test_files: []