redom 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []