nodo 1.5.5 → 1.6.2
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.
- checksums.yaml +4 -4
- data/README.md +114 -11
- data/lib/nodo/constant.rb +1 -1
- data/lib/nodo/core.rb +99 -38
- data/lib/nodo/dependency.rb +10 -1
- data/lib/nodo/errors.rb +21 -6
- data/lib/nodo/nodo.js +36 -17
- data/lib/nodo/railtie.rb +2 -1
- data/lib/nodo/version.rb +1 -1
- data/lib/nodo.rb +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95d8c9823753373c6be5ad0c493e7f14346950b611d4ca0d90c5a96bcabfa5e6
|
4
|
+
data.tar.gz: 3ad4b37694f4ef33cfadad03794ce7b4bf2a9fd42dc4e0abb319ec981e529b33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e0004a84541cb4edd4cc398250fe17ee5623aed13f1bbb76b31eee531867906cffd87c5e0f71b50c0c72fdae4f26e57d24f654fdd2623bee90b20c60f3ff0b4
|
7
|
+
data.tar.gz: 539b75f4a8283f4db872db614158403129651f4b0811d9e7f9ef5dfa1f506bedcf238fa0b93896fe776ae936bbc8eba9cbd44ea86fa8f80b87142e5d69cfe957
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
[](http://badge.fury.io/rb/nodo)
|
1
|
+
[](http://badge.fury.io/rb/nodo)
|
2
|
+
[](https://github.com/mtgrosser/nodo/actions/workflows/build.yml)
|
2
3
|
|
3
4
|
# Nōdo – call Node.js from Ruby
|
4
5
|
|
@@ -107,13 +108,7 @@ To set a custom path:
|
|
107
108
|
Nodo.modules_root = 'path/to/node_modules'
|
108
109
|
```
|
109
110
|
|
110
|
-
|
111
|
-
To use the Rails 6 default of putting `node_modules` to `RAILS_ROOT`:
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
# config/initializers/nodo.rb
|
115
|
-
Nodo.modules_root = Rails.root.join('node_modules')
|
116
|
-
```
|
111
|
+
Also see: [Clean your Rails root](#Clean-your-Rails-root)
|
117
112
|
|
118
113
|
### Defining JS constants
|
119
114
|
|
@@ -175,8 +170,17 @@ end
|
|
175
170
|
|
176
171
|
### Limiting function execution time
|
177
172
|
|
178
|
-
The default timeout for a single JS function call is 60 seconds
|
179
|
-
|
173
|
+
The default timeout for a single JS function call is 60 seconds and can be
|
174
|
+
set globally:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
Nodo.timeout = 5
|
178
|
+
```
|
179
|
+
|
180
|
+
If the execution of a single function call exceeds the timeout, `Nodo::TimeoutError`
|
181
|
+
is raised.
|
182
|
+
|
183
|
+
The timeout can also be set on a per-function basis:
|
180
184
|
|
181
185
|
```ruby
|
182
186
|
class Foo < Nodo::Core
|
@@ -185,6 +189,105 @@ class Foo < Nodo::Core
|
|
185
189
|
JS
|
186
190
|
end
|
187
191
|
|
188
|
-
|
192
|
+
Foo.new.sleep(2)
|
189
193
|
=> Nodo::TimeoutError raised
|
190
194
|
```
|
195
|
+
|
196
|
+
### Logging
|
197
|
+
|
198
|
+
By default, JS errors will be logged to `STDOUT`.
|
199
|
+
|
200
|
+
To set a custom logger:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
Nodo.logger = Logger.new('nodo.log')
|
204
|
+
```
|
205
|
+
|
206
|
+
In Rails applications, `Rails.logger` will automatically be set.
|
207
|
+
|
208
|
+
|
209
|
+
### Debugging
|
210
|
+
|
211
|
+
To get verbose debug output, set
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
Nodo.debug = true
|
215
|
+
```
|
216
|
+
|
217
|
+
before instantiating any worker instances. The debug mode will be active during
|
218
|
+
the current process run.
|
219
|
+
|
220
|
+
To print a debug message from JS code:
|
221
|
+
|
222
|
+
```js
|
223
|
+
nodo.debug("Debug message");
|
224
|
+
```
|
225
|
+
|
226
|
+
### Evaluation
|
227
|
+
|
228
|
+
While `Nodo` is mainly function-based, it is possible to evaluate JS code in the
|
229
|
+
context of the defined object.
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
foo = Foo.new.evaluate("3 + 5")
|
233
|
+
=> 8
|
234
|
+
```
|
235
|
+
|
236
|
+
Evaluated code can access functions, required dependencies and constants:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
class Foo < Nodo::Core
|
240
|
+
const :BAR, 'bar'
|
241
|
+
require :uuid
|
242
|
+
function :hello, code: '() => "world"'
|
243
|
+
end
|
244
|
+
|
245
|
+
foo = Foo.new
|
246
|
+
|
247
|
+
foo.evaluate('BAR')
|
248
|
+
=> "bar"
|
249
|
+
|
250
|
+
foo.evaluate('uuid.v4()')
|
251
|
+
=> "f258bef3-0d6f-4566-ad39-d8dec973ef6b"
|
252
|
+
|
253
|
+
foo.evaluate('hello()')
|
254
|
+
=> "world"
|
255
|
+
```
|
256
|
+
|
257
|
+
Variables defined by evaluation are local to the current instance:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
one = Foo.new
|
261
|
+
one.evaluate('a = 1')
|
262
|
+
two = Foo.new
|
263
|
+
two.evaluate('a = 2')
|
264
|
+
one.evaluate('a') => 1
|
265
|
+
two.evaluate('a') => 2
|
266
|
+
```
|
267
|
+
|
268
|
+
⚠️ Evaluation comes with the usual caveats:
|
269
|
+
|
270
|
+
- Avoid modifying any of your predefined identifiers. Remember that in JS,
|
271
|
+
as in Ruby, constants are not necessarily constant.
|
272
|
+
- Never evaluate any code which includes un-checked user data. The Node.js process
|
273
|
+
has full read/write access to your filesystem! 💥
|
274
|
+
|
275
|
+
|
276
|
+
## Clean your Rails root
|
277
|
+
|
278
|
+
For Rails applications, Nodo enables you to move `node_modules`, `package.json` and
|
279
|
+
`yarn.lock` into your application's `vendor` folder by setting the `NODE_PATH` in
|
280
|
+
an initializer:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
# config/initializers/nodo.rb
|
284
|
+
Nodo.modules_root = Rails.root.join('vendor', 'node_modules')
|
285
|
+
```
|
286
|
+
|
287
|
+
The rationale for this is NPM modules being external vendor dependencies, which
|
288
|
+
should not clutter the application root directory.
|
289
|
+
|
290
|
+
With this new default, all `yarn` operations should be done after `cd`ing to `vendor`.
|
291
|
+
|
292
|
+
This repo provides an [adapted version](https://github.com/mtgrosser/nodo/blob/master/install/yarn.rake)
|
293
|
+
of the `yarn:install` rake task which will automatically take care of the vendored module location.
|
data/lib/nodo/constant.rb
CHANGED
data/lib/nodo/core.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Nodo
|
2
2
|
class Core
|
3
3
|
SOCKET_NAME = 'nodo.sock'
|
4
|
-
DEFINE_METHOD = '__nodo_define_class__'
|
5
|
-
|
4
|
+
DEFINE_METHOD = '__nodo_define_class__'.freeze
|
5
|
+
EVALUATE_METHOD = '__nodo_evaluate__'.freeze
|
6
|
+
GC_METHOD = '__nodo_gc__'.freeze
|
7
|
+
INTERNAL_METHODS = [DEFINE_METHOD, EVALUATE_METHOD, GC_METHOD].freeze
|
8
|
+
LAUNCH_TIMEOUT = 5
|
6
9
|
ARRAY_CLASS_ATTRIBUTES = %i[dependencies constants scripts].freeze
|
7
10
|
HASH_CLASS_ATTRIBUTES = %i[functions].freeze
|
8
11
|
CLASS_ATTRIBUTES = (ARRAY_CLASS_ATTRIBUTES + HASH_CLASS_ATTRIBUTES).freeze
|
@@ -10,6 +13,7 @@ module Nodo
|
|
10
13
|
@@node_pid = nil
|
11
14
|
@@tmpdir = nil
|
12
15
|
@@mutex = Mutex.new
|
16
|
+
@@exiting = nil
|
13
17
|
|
14
18
|
class << self
|
15
19
|
extend Forwardable
|
@@ -21,15 +25,11 @@ module Nodo
|
|
21
25
|
subclass.send "#{attr}=", send(attr).dup
|
22
26
|
end
|
23
27
|
end
|
24
|
-
|
28
|
+
|
25
29
|
def instance
|
26
30
|
@instance ||= new
|
27
31
|
end
|
28
32
|
|
29
|
-
def class_function(*methods)
|
30
|
-
singleton_class.def_delegators(:instance, *methods)
|
31
|
-
end
|
32
|
-
|
33
33
|
def class_defined?
|
34
34
|
!!class_defined
|
35
35
|
end
|
@@ -42,6 +42,7 @@ module Nodo
|
|
42
42
|
define_method "#{attr}=" do |value|
|
43
43
|
instance_variable_set :"@#{attr}", value
|
44
44
|
end
|
45
|
+
protected "#{attr}="
|
45
46
|
end
|
46
47
|
|
47
48
|
ARRAY_CLASS_ATTRIBUTES.each do |attr|
|
@@ -55,28 +56,6 @@ module Nodo
|
|
55
56
|
instance_variable_get(:"@#{attr}") || instance_variable_set(:"@#{attr}", {})
|
56
57
|
end
|
57
58
|
end
|
58
|
-
|
59
|
-
def require(*mods)
|
60
|
-
deps = mods.last.is_a?(Hash) ? mods.pop : {}
|
61
|
-
mods = mods.map { |m| [m, m] }.to_h
|
62
|
-
self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) }
|
63
|
-
end
|
64
|
-
|
65
|
-
def function(name, _code = nil, timeout: 60, code: nil)
|
66
|
-
raise ArgumentError, "reserved method name #{name.inspect}" if Nodo::Core.method_defined?(name)
|
67
|
-
code = (code ||= _code).strip
|
68
|
-
raise ArgumentError, 'function code is required' if '' == code
|
69
|
-
self.functions = functions.merge(name => Function.new(name, _code || code, caller.first, timeout))
|
70
|
-
define_method(name) { |*args| call_js_method(name, args) }
|
71
|
-
end
|
72
|
-
|
73
|
-
def const(name, value)
|
74
|
-
self.constants = constants + [Constant.new(name, value)]
|
75
|
-
end
|
76
|
-
|
77
|
-
def script(code)
|
78
|
-
self.scripts = scripts + [Script.new(code)]
|
79
|
-
end
|
80
59
|
|
81
60
|
def generate_core_code
|
82
61
|
<<~JS
|
@@ -94,7 +73,7 @@ module Nodo
|
|
94
73
|
nodo.core.close(() => { process.exit(0) });
|
95
74
|
};
|
96
75
|
|
97
|
-
process.on('SIGINT', shutdown);
|
76
|
+
// process.on('SIGINT', shutdown);
|
98
77
|
process.on('SIGTERM', shutdown);
|
99
78
|
|
100
79
|
nodo.core.run(socket);
|
@@ -104,8 +83,7 @@ module Nodo
|
|
104
83
|
def generate_class_code
|
105
84
|
<<~JS
|
106
85
|
(() => {
|
107
|
-
const
|
108
|
-
const __nodo_klass__ = {};
|
86
|
+
const __nodo_klass__ = { nodo: global.nodo };
|
109
87
|
#{dependencies.map(&:to_js).join}
|
110
88
|
#{constants.map(&:to_js).join}
|
111
89
|
#{functions.values.map(&:to_js).join}
|
@@ -115,14 +93,57 @@ module Nodo
|
|
115
93
|
JS
|
116
94
|
end
|
117
95
|
|
96
|
+
protected
|
97
|
+
|
98
|
+
def finalize_context(context_id)
|
99
|
+
proc do
|
100
|
+
if not @@exiting and core = Nodo::Core.instance
|
101
|
+
core.send(:call_js_method, GC_METHOD, context_id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
118
106
|
private
|
119
107
|
|
108
|
+
def require(*mods)
|
109
|
+
deps = mods.last.is_a?(Hash) ? mods.pop : {}
|
110
|
+
mods = mods.map { |m| [m, m] }.to_h
|
111
|
+
self.dependencies = dependencies + mods.merge(deps).map { |name, package| Dependency.new(name, package) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def function(name, _code = nil, timeout: Nodo.timeout, code: nil)
|
115
|
+
raise ArgumentError, "reserved method name #{name.inspect}" if reserved_method_name?(name)
|
116
|
+
code = (code ||= _code).strip
|
117
|
+
raise ArgumentError, 'function code is required' if '' == code
|
118
|
+
loc = caller_locations(1, 1)[0]
|
119
|
+
source_location = "#{loc.path}:#{loc.lineno}: in `#{name}'"
|
120
|
+
self.functions = functions.merge(name => Function.new(name, _code || code, source_location, timeout))
|
121
|
+
define_method(name) { |*args| call_js_method(name, args) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def class_function(*methods)
|
125
|
+
singleton_class.def_delegators(:instance, *methods)
|
126
|
+
end
|
127
|
+
|
128
|
+
def const(name, value)
|
129
|
+
self.constants = constants + [Constant.new(name, value)]
|
130
|
+
end
|
131
|
+
|
132
|
+
def script(code)
|
133
|
+
self.scripts = scripts + [Script.new(code)]
|
134
|
+
end
|
135
|
+
|
120
136
|
def nodo_js
|
121
137
|
Pathname.new(__FILE__).dirname.join('nodo.js').to_s.to_json
|
122
138
|
end
|
139
|
+
|
140
|
+
def reserved_method_name?(name)
|
141
|
+
Nodo::Core.method_defined?(name, false) || Nodo::Core.private_method_defined?(name, false) || name.to_s == DEFINE_METHOD
|
142
|
+
end
|
123
143
|
end
|
124
144
|
|
125
145
|
def initialize
|
146
|
+
raise ClassError, :new if self.class == Nodo::Core
|
126
147
|
@@mutex.synchronize do
|
127
148
|
ensure_process_is_spawned
|
128
149
|
wait_for_socket
|
@@ -130,6 +151,13 @@ module Nodo
|
|
130
151
|
end
|
131
152
|
end
|
132
153
|
|
154
|
+
def evaluate(code)
|
155
|
+
ensure_context_is_defined
|
156
|
+
call_js_method(EVALUATE_METHOD, code)
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
133
161
|
def node_pid
|
134
162
|
@@node_pid
|
135
163
|
end
|
@@ -146,6 +174,17 @@ module Nodo
|
|
146
174
|
self.class.clsid
|
147
175
|
end
|
148
176
|
|
177
|
+
def context_defined?
|
178
|
+
@context_defined
|
179
|
+
end
|
180
|
+
|
181
|
+
def log_exception(e)
|
182
|
+
return unless logger = Nodo.logger
|
183
|
+
message = "\n#{e.class} (#{e.message})"
|
184
|
+
message << ":\n\n#{e.backtrace.join("\n")}" if e.backtrace
|
185
|
+
logger.error message
|
186
|
+
end
|
187
|
+
|
149
188
|
def ensure_process_is_spawned
|
150
189
|
return if node_pid
|
151
190
|
spawn_process
|
@@ -157,11 +196,22 @@ module Nodo
|
|
157
196
|
self.class.class_defined = true
|
158
197
|
end
|
159
198
|
|
199
|
+
def ensure_context_is_defined
|
200
|
+
return if context_defined?
|
201
|
+
@@mutex.synchronize do
|
202
|
+
call_js_method(EVALUATE_METHOD, '')
|
203
|
+
ObjectSpace.define_finalizer(self, self.class.send(:finalize_context, self.object_id))
|
204
|
+
@context_defined = true
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
160
208
|
def spawn_process
|
161
209
|
@@tmpdir = Pathname.new(Dir.mktmpdir('nodo'))
|
162
210
|
env = Nodo.env.merge('NODE_PATH' => Nodo.modules_root.to_s)
|
211
|
+
env['NODO_DEBUG'] = '1' if Nodo.debug
|
163
212
|
@@node_pid = Process.spawn(env, Nodo.binary, '-e', self.class.generate_core_code, '--', socket_path.to_s, err: :out)
|
164
213
|
at_exit do
|
214
|
+
@@exiting = true
|
165
215
|
Process.kill(:SIGTERM, node_pid) rescue Errno::ECHILD
|
166
216
|
Process.wait(node_pid) rescue Errno::ECHILD
|
167
217
|
FileUtils.remove_entry(tmpdir) if File.directory?(tmpdir)
|
@@ -171,7 +221,7 @@ module Nodo
|
|
171
221
|
def wait_for_socket
|
172
222
|
start = Time.now
|
173
223
|
socket = nil
|
174
|
-
while Time.now - start <
|
224
|
+
while Time.now - start < LAUNCH_TIMEOUT
|
175
225
|
begin
|
176
226
|
break if socket = UNIXSocket.new(socket_path)
|
177
227
|
rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::ENOTDIR
|
@@ -184,10 +234,16 @@ module Nodo
|
|
184
234
|
|
185
235
|
def call_js_method(method, args)
|
186
236
|
raise CallError, 'Node process not ready' unless node_pid
|
187
|
-
raise CallError, "Class #{clsid} not defined" unless self.class.class_defined? || method
|
237
|
+
raise CallError, "Class #{clsid} not defined" unless self.class.class_defined? || INTERNAL_METHODS.include?(method)
|
188
238
|
function = self.class.functions[method]
|
189
|
-
raise NameError, "undefined function `#{method}' for #{self.class}" unless function || method
|
190
|
-
|
239
|
+
raise NameError, "undefined function `#{method}' for #{self.class}" unless function || INTERNAL_METHODS.include?(method)
|
240
|
+
context_id = case method
|
241
|
+
when DEFINE_METHOD then 0
|
242
|
+
when GC_METHOD then args.first
|
243
|
+
else
|
244
|
+
object_id
|
245
|
+
end
|
246
|
+
request = Net::HTTP::Post.new("/#{clsid}/#{context_id}/#{method}", 'Content-Type': 'application/json')
|
191
247
|
request.body = JSON.dump(args)
|
192
248
|
client = Client.new("unix://#{socket_path}")
|
193
249
|
client.read_timeout = function.timeout if function
|
@@ -207,9 +263,14 @@ module Nodo
|
|
207
263
|
def handle_error(response, function)
|
208
264
|
if response.body
|
209
265
|
result = parse_response(response)
|
210
|
-
|
266
|
+
error = if result.is_a?(Hash) && result['error'].is_a?(Hash)
|
267
|
+
attrs = result['error']
|
268
|
+
(attrs['nodo_dependency'] ? DependencyError : JavaScriptError).new(attrs, function)
|
269
|
+
end
|
211
270
|
end
|
212
|
-
|
271
|
+
error ||= CallError.new("Node returned #{response.code}")
|
272
|
+
log_exception(error)
|
273
|
+
raise error
|
213
274
|
end
|
214
275
|
|
215
276
|
def parse_response(response)
|
data/lib/nodo/dependency.rb
CHANGED
@@ -7,7 +7,16 @@ module Nodo
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_js
|
10
|
-
|
10
|
+
<<~JS
|
11
|
+
const #{name} = __nodo_klass__.#{name} = (() => {
|
12
|
+
try {
|
13
|
+
return require(#{package.to_json});
|
14
|
+
} catch(e) {
|
15
|
+
e.nodo_dependency = #{package.to_json};
|
16
|
+
throw e;
|
17
|
+
}
|
18
|
+
})();
|
19
|
+
JS
|
11
20
|
end
|
12
21
|
end
|
13
22
|
end
|
data/lib/nodo/errors.rb
CHANGED
@@ -2,6 +2,11 @@ module Nodo
|
|
2
2
|
class Error < StandardError; end
|
3
3
|
class TimeoutError < Error; end
|
4
4
|
class CallError < Error; end
|
5
|
+
class ClassError < Error
|
6
|
+
def initialize(method = nil)
|
7
|
+
super("Cannot call method `#{method}' on Nodo::Core, use subclass instead")
|
8
|
+
end
|
9
|
+
end
|
5
10
|
|
6
11
|
class JavaScriptError < Error
|
7
12
|
attr_reader :attributes
|
@@ -37,13 +42,23 @@ module Nodo
|
|
37
42
|
end
|
38
43
|
|
39
44
|
def generate_message
|
40
|
-
message = "#{attributes['message'] || 'Unknown error'}"
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
message = "#{attributes['message'] || attributes['name'] || 'Unknown error'}"
|
46
|
+
message << format_location(attributes['loc'])
|
47
|
+
end
|
48
|
+
|
49
|
+
def format_location(loc)
|
50
|
+
return '' unless loc
|
51
|
+
loc.inject(' in') { |s, (key, value)| s << " #{key}: #{value}" }
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
48
|
-
class DependencyError < JavaScriptError
|
55
|
+
class DependencyError < JavaScriptError
|
56
|
+
private
|
57
|
+
|
58
|
+
def generate_message
|
59
|
+
message = "#{attributes['message'] || attributes['name'] || 'Dependency error'}\n"
|
60
|
+
message << "The specified dependency '#{attributes['nodo_dependency']}' could not be loaded. "
|
61
|
+
message << "Run 'yarn add #{attributes['nodo_dependency']}' to install it.\n"
|
62
|
+
end
|
63
|
+
end
|
49
64
|
end
|
data/lib/nodo/nodo.js
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
module.exports = (function() {
|
2
2
|
const DEFINE_METHOD = '__nodo_define_class__';
|
3
|
+
const EVALUATE_METHOD = '__nodo_evaluate__';
|
4
|
+
const GC_METHOD = '__nodo_gc__';
|
5
|
+
const DEBUG = process.env.NODO_DEBUG;
|
3
6
|
|
4
7
|
const vm = require('vm');
|
5
8
|
const http = require('http');
|
@@ -9,6 +12,7 @@ module.exports = (function() {
|
|
9
12
|
|
10
13
|
let server, closing;
|
11
14
|
const classes = {};
|
15
|
+
const contexts = {};
|
12
16
|
|
13
17
|
function render_error(e) {
|
14
18
|
let errInfo = {};
|
@@ -24,7 +28,7 @@ module.exports = (function() {
|
|
24
28
|
function respond_with_error(res, code, name) {
|
25
29
|
res.statusCode = code;
|
26
30
|
const rendered = render_error(name);
|
27
|
-
|
31
|
+
debug(`Error ${code} ${rendered}`);
|
28
32
|
res.end(rendered, 'utf8');
|
29
33
|
}
|
30
34
|
|
@@ -35,34 +39,41 @@ module.exports = (function() {
|
|
35
39
|
if (start) {
|
36
40
|
timing = ` in ${(performance.now() - start).toFixed(2)}ms`;
|
37
41
|
}
|
38
|
-
|
42
|
+
debug(`Completed 200 OK${timing}\n`);
|
39
43
|
}
|
40
44
|
|
41
|
-
function
|
42
|
-
|
43
|
-
|
45
|
+
function debug(message) {
|
46
|
+
if (DEBUG) {
|
47
|
+
// fs.appendFileSync('log/nodo.log', `${message}\n`);
|
48
|
+
console.log(`[Nodo] ${message}`);
|
49
|
+
}
|
44
50
|
}
|
45
51
|
|
46
52
|
const core = {
|
47
53
|
run: (socket) => {
|
48
|
-
|
54
|
+
debug('Starting up...');
|
49
55
|
server = http.createServer((req, res) => {
|
50
56
|
const start = performance.now();
|
51
57
|
|
52
58
|
res.setHeader('Content-Type', 'application/json');
|
53
|
-
|
59
|
+
debug(`${req.method} ${req.url}`);
|
54
60
|
|
55
61
|
if (req.method !== 'POST' || !req.url.startsWith('/')) {
|
56
62
|
return respond_with_error(res, 405, 'Method Not Allowed');
|
57
63
|
}
|
58
64
|
|
59
65
|
const url = req.url.substring(1);
|
60
|
-
const [class_name, method] = url.split('/');
|
61
|
-
let klass;
|
66
|
+
const [class_name, object_id, method] = url.split('/');
|
67
|
+
let klass, context;
|
62
68
|
|
63
69
|
if (classes.hasOwnProperty(class_name)) {
|
64
70
|
klass = classes[class_name];
|
65
|
-
if (
|
71
|
+
if (EVALUATE_METHOD == method) {
|
72
|
+
if (!contexts.hasOwnProperty(object_id)) {
|
73
|
+
contexts[object_id] = vm.createContext({ require: require, ...klass });
|
74
|
+
}
|
75
|
+
context = contexts[object_id];
|
76
|
+
} else if (!klass.hasOwnProperty(method) && !GC_METHOD == method) {
|
66
77
|
return respond_with_error(res, 404, `Method ${class_name}#${method} not found`);
|
67
78
|
}
|
68
79
|
} else if (DEFINE_METHOD != method) {
|
@@ -84,13 +95,21 @@ module.exports = (function() {
|
|
84
95
|
|
85
96
|
try {
|
86
97
|
if (DEFINE_METHOD == method) {
|
87
|
-
|
88
|
-
classes[class_name] = new_class;
|
98
|
+
classes[class_name] = vm.runInThisContext(input, class_name);
|
89
99
|
respond_with_data(res, class_name, start);
|
100
|
+
} else if (EVALUATE_METHOD == method) {
|
101
|
+
Promise.resolve(vm.runInNewContext(input, context)).then((result) => {
|
102
|
+
respond_with_data(res, result, start);
|
103
|
+
}).catch((error) => {
|
104
|
+
respond_with_error(res, 500, error);
|
105
|
+
});
|
106
|
+
} else if (GC_METHOD == method) {
|
107
|
+
delete contexts[object_id];
|
108
|
+
respond_with_data(res, true, start);
|
90
109
|
} else {
|
91
|
-
Promise.resolve(klass[method].apply(null, input)).then(
|
110
|
+
Promise.resolve(klass[method].apply(null, input)).then((result) => {
|
92
111
|
respond_with_data(res, result, start);
|
93
|
-
}).catch(
|
112
|
+
}).catch((error) => {
|
94
113
|
respond_with_error(res, 500, error);
|
95
114
|
});
|
96
115
|
}
|
@@ -103,12 +122,12 @@ module.exports = (function() {
|
|
103
122
|
|
104
123
|
//server.maxConnections = 64;
|
105
124
|
server.listen(socket, () => {
|
106
|
-
|
125
|
+
debug(`server ready, listening on ${socket} (max connections: ${server.maxConnections})`);
|
107
126
|
});
|
108
127
|
},
|
109
128
|
|
110
129
|
close: (finalizer) => {
|
111
|
-
|
130
|
+
debug("Shutting down");
|
112
131
|
if (!closing) {
|
113
132
|
closing = true;
|
114
133
|
server.close(finalizer);
|
@@ -116,5 +135,5 @@ module.exports = (function() {
|
|
116
135
|
}
|
117
136
|
};
|
118
137
|
|
119
|
-
return { core: core,
|
138
|
+
return { core: core, debug: debug };
|
120
139
|
})();
|
data/lib/nodo/railtie.rb
CHANGED
data/lib/nodo/version.rb
CHANGED
data/lib/nodo.rb
CHANGED
@@ -3,16 +3,20 @@ require 'json'
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'tmpdir'
|
5
5
|
require 'tempfile'
|
6
|
+
require 'logger'
|
6
7
|
require 'socket'
|
7
8
|
require 'forwardable'
|
8
9
|
|
9
10
|
module Nodo
|
10
11
|
class << self
|
11
|
-
attr_accessor :modules_root, :env, :binary
|
12
|
+
attr_accessor :modules_root, :env, :binary, :logger, :debug, :timeout
|
12
13
|
end
|
13
14
|
self.modules_root = './node_modules'
|
14
15
|
self.env = {}
|
15
16
|
self.binary = 'node'
|
17
|
+
self.logger = Logger.new(STDOUT)
|
18
|
+
self.debug = false
|
19
|
+
self.timeout = 60
|
16
20
|
end
|
17
21
|
|
18
22
|
require_relative 'nodo/version'
|