iruby 0.2.7 → 0.5.0
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 +5 -5
- data/.github/workflows/ubuntu.yml +62 -0
- data/CHANGES +62 -0
- data/Gemfile +3 -1
- data/LICENSE +1 -1
- data/README.md +148 -27
- data/Rakefile +36 -10
- data/ci/Dockerfile.base.erb +41 -0
- data/ci/Dockerfile.main.erb +7 -0
- data/ci/requirements.txt +1 -0
- data/docker/setup.sh +15 -0
- data/docker/test.sh +7 -0
- data/iruby.gemspec +14 -18
- data/lib/iruby.rb +19 -3
- data/lib/iruby/backend.rb +22 -2
- data/lib/iruby/command.rb +76 -13
- data/lib/iruby/display.rb +69 -39
- data/lib/iruby/formatter.rb +5 -4
- data/lib/iruby/input.rb +41 -0
- data/lib/iruby/input/README.ipynb +502 -0
- data/lib/iruby/input/README.md +299 -0
- data/lib/iruby/input/autoload.rb +25 -0
- data/lib/iruby/input/builder.rb +67 -0
- data/lib/iruby/input/button.rb +47 -0
- data/lib/iruby/input/cancel.rb +32 -0
- data/lib/iruby/input/checkbox.rb +74 -0
- data/lib/iruby/input/date.rb +37 -0
- data/lib/iruby/input/field.rb +31 -0
- data/lib/iruby/input/file.rb +57 -0
- data/lib/iruby/input/form.rb +77 -0
- data/lib/iruby/input/label.rb +27 -0
- data/lib/iruby/input/multiple.rb +76 -0
- data/lib/iruby/input/popup.rb +41 -0
- data/lib/iruby/input/radio.rb +59 -0
- data/lib/iruby/input/select.rb +59 -0
- data/lib/iruby/input/textarea.rb +23 -0
- data/lib/iruby/input/widget.rb +34 -0
- data/lib/iruby/jupyter.rb +77 -0
- data/lib/iruby/kernel.rb +67 -22
- data/lib/iruby/ostream.rb +24 -8
- data/lib/iruby/session.rb +85 -67
- data/lib/iruby/session/cztop.rb +70 -0
- data/lib/iruby/session/ffi_rzmq.rb +87 -0
- data/lib/iruby/session/mixin.rb +47 -0
- data/lib/iruby/session_adapter.rb +66 -0
- data/lib/iruby/session_adapter/cztop_adapter.rb +45 -0
- data/lib/iruby/session_adapter/ffirzmq_adapter.rb +55 -0
- data/lib/iruby/session_adapter/pyzmq_adapter.rb +77 -0
- data/lib/iruby/utils.rb +5 -2
- data/lib/iruby/version.rb +1 -1
- data/run-test.sh +12 -0
- data/tasks/ci.rake +65 -0
- data/test/helper.rb +90 -0
- data/test/integration_test.rb +22 -11
- data/test/iruby/backend_test.rb +37 -0
- data/test/iruby/command_test.rb +207 -0
- data/test/iruby/jupyter_test.rb +27 -0
- data/test/iruby/mime_test.rb +32 -0
- data/test/iruby/multi_logger_test.rb +1 -2
- data/test/iruby/session_adapter/cztop_adapter_test.rb +20 -0
- data/test/iruby/session_adapter/ffirzmq_adapter_test.rb +20 -0
- data/test/iruby/session_adapter/session_adapter_test_base.rb +27 -0
- data/test/iruby/session_adapter_test.rb +91 -0
- data/test/iruby/session_test.rb +47 -0
- data/test/run-test.rb +18 -0
- metadata +130 -46
- data/.travis.yml +0 -16
- data/CONTRIBUTORS +0 -19
- data/test/test_helper.rb +0 -5
@@ -0,0 +1,59 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Select < Label
|
4
|
+
needs :options, :default
|
5
|
+
|
6
|
+
builder :select do |*args, **params|
|
7
|
+
key = :select
|
8
|
+
key, *args = args if args.first.is_a? Symbol
|
9
|
+
|
10
|
+
params[:key] = unique_key(key)
|
11
|
+
params[:options] = args
|
12
|
+
params[:default] ||= false
|
13
|
+
|
14
|
+
unless params[:options].include? params[:default]
|
15
|
+
params[:options] = [nil, *params[:options].compact]
|
16
|
+
end
|
17
|
+
|
18
|
+
add_field Select.new(**params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def widget_css
|
22
|
+
<<-CSS
|
23
|
+
.iruby-select {
|
24
|
+
min-width: 25%;
|
25
|
+
margin-left: 0 !important;
|
26
|
+
}
|
27
|
+
CSS
|
28
|
+
end
|
29
|
+
|
30
|
+
def widget_js
|
31
|
+
<<-JS
|
32
|
+
$('.iruby-select').change(function(){
|
33
|
+
$(this).data('iruby-value',
|
34
|
+
$(this).find('option:selected').text()
|
35
|
+
);
|
36
|
+
});
|
37
|
+
JS
|
38
|
+
end
|
39
|
+
|
40
|
+
def widget_html
|
41
|
+
widget_label do
|
42
|
+
div class: 'form-control' do
|
43
|
+
params = {
|
44
|
+
class: 'iruby-select',
|
45
|
+
:'data-iruby-key' => @key,
|
46
|
+
:'data-iruby-value' => @default
|
47
|
+
}
|
48
|
+
|
49
|
+
select **params do
|
50
|
+
@options.each do |o|
|
51
|
+
option o, selected: @default == o
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Textarea < Field
|
4
|
+
needs rows: 5
|
5
|
+
|
6
|
+
builder :textarea do |key='textarea', **params|
|
7
|
+
params[:key] = unique_key key
|
8
|
+
add_field Textarea.new(**params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def widget_html
|
12
|
+
widget_label do
|
13
|
+
textarea(
|
14
|
+
@default,
|
15
|
+
rows: @rows,
|
16
|
+
:'data-iruby-key' => @key,
|
17
|
+
class: 'form-control iruby-field'
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Input
|
3
|
+
class Widget < Erector::Widget
|
4
|
+
needs key: nil
|
5
|
+
|
6
|
+
def widget_js; end
|
7
|
+
def widget_css; end
|
8
|
+
def widget_html; end
|
9
|
+
def content; widget_html; end
|
10
|
+
|
11
|
+
def self.builder method, &block
|
12
|
+
Builder.instance_eval do
|
13
|
+
define_method method, &block
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def widget_join method, *args
|
18
|
+
strings = args.map do |arg|
|
19
|
+
arg.is_a?(String) ? arg : arg.send(method)
|
20
|
+
end
|
21
|
+
strings.uniq.join("\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def widget_display
|
25
|
+
IRuby.display(IRuby.html(
|
26
|
+
Erector.inline{ style raw(widget_css) }.to_html
|
27
|
+
))
|
28
|
+
|
29
|
+
IRuby.display(IRuby.html(to_html))
|
30
|
+
IRuby.display(IRuby.javascript(widget_js))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module IRuby
|
2
|
+
module Jupyter
|
3
|
+
class << self
|
4
|
+
# User's default kernelspec directory is described here:
|
5
|
+
# https://jupyter.readthedocs.io/en/latest/projects/jupyter-directories.html
|
6
|
+
def default_data_dir
|
7
|
+
case
|
8
|
+
when windows?
|
9
|
+
appdata = windows_user_appdata
|
10
|
+
if !appdata.empty?
|
11
|
+
File.join(appdata, 'jupyter')
|
12
|
+
else
|
13
|
+
jupyter_config_dir = ENV.fetch('JUPYTER_CONFIG_DIR', File.expand_path('~/.jupyter'))
|
14
|
+
File.join(jupyter_config_dir, 'data')
|
15
|
+
end
|
16
|
+
when apple?
|
17
|
+
File.expand_path('~/Library/Jupyter')
|
18
|
+
else
|
19
|
+
xdg_data_home = ENV.fetch('XDG_DATA_HOME', '')
|
20
|
+
data_home = xdg_data_home[0] ? xdg_data_home : File.expand_path('~/.local/share')
|
21
|
+
File.join(data_home, 'jupyter')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def kernelspec_dir(data_dir=nil)
|
26
|
+
data_dir ||= default_data_dir
|
27
|
+
File.join(data_dir, 'kernels')
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# returns %APPDATA%
|
33
|
+
def windows_user_appdata
|
34
|
+
require 'fiddle/import'
|
35
|
+
check_windows
|
36
|
+
path = Fiddle::Pointer.malloc(2 * 300) # uint16_t[300]
|
37
|
+
csidl_appdata = 0x001a
|
38
|
+
case call_SHGetFolderPathW(Fiddle::NULL, csidl_appdata, Fiddle::NULL, 0, path)
|
39
|
+
when 0
|
40
|
+
len = (1 ... (path.size/2)).find {|i| path[2*i, 2] == "\0\0" }
|
41
|
+
path = path.to_str(2*len).encode(Encoding::UTF_8, Encoding::UTF_16LE)
|
42
|
+
else
|
43
|
+
ENV.fetch('APPDATA', '')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call_SHGetFolderPathW(hwnd, csidl, hToken, dwFlags, pszPath)
|
48
|
+
require 'fiddle/import'
|
49
|
+
shell32 = Fiddle::Handle.new('shell32')
|
50
|
+
func = Fiddle::Function.new(
|
51
|
+
shell32['SHGetFolderPathW'],
|
52
|
+
[
|
53
|
+
Fiddle::TYPE_VOIDP,
|
54
|
+
Fiddle::TYPE_INT,
|
55
|
+
Fiddle::TYPE_VOIDP,
|
56
|
+
Fiddle::TYPE_INT,
|
57
|
+
Fiddle::TYPE_VOIDP
|
58
|
+
],
|
59
|
+
Fiddle::TYPE_INT,
|
60
|
+
Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[:stdcall])
|
61
|
+
func.(hwnd, csidl, hToken, dwFlags, pszPath)
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_windows
|
65
|
+
raise 'the current platform is not Windows' unless windows?
|
66
|
+
end
|
67
|
+
|
68
|
+
def windows?
|
69
|
+
/mingw|mswin/ =~ RUBY_PLATFORM
|
70
|
+
end
|
71
|
+
|
72
|
+
def apple?
|
73
|
+
/darwin/ =~ RUBY_PLATFORM
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/iruby/kernel.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module IRuby
|
2
2
|
class Kernel
|
3
3
|
RED = "\e[31m"
|
4
|
-
WHITE = "\e[37m"
|
5
4
|
RESET = "\e[0m"
|
6
5
|
|
7
6
|
class<< self
|
@@ -19,6 +18,8 @@ module IRuby
|
|
19
18
|
$stdout = OStream.new(@session, :stdout)
|
20
19
|
$stderr = OStream.new(@session, :stderr)
|
21
20
|
|
21
|
+
init_parent_process_poller
|
22
|
+
|
22
23
|
@execution_count = 0
|
23
24
|
@backend = create_backend
|
24
25
|
@running = true
|
@@ -40,6 +41,7 @@ module IRuby
|
|
40
41
|
|
41
42
|
def dispatch
|
42
43
|
msg = @session.recv(:reply)
|
44
|
+
IRuby.logger.debug "Kernel#dispatch: msg = #{msg}"
|
43
45
|
type = msg[:header]['msg_type']
|
44
46
|
raise "Unknown message type: #{msg.inspect}" unless type =~ /comm_|_request\Z/ && respond_to?(type)
|
45
47
|
begin
|
@@ -50,24 +52,32 @@ module IRuby
|
|
50
52
|
end
|
51
53
|
rescue Exception => e
|
52
54
|
IRuby.logger.debug "Kernel error: #{e.message}\n#{e.backtrace.join("\n")}"
|
53
|
-
@session.send(:publish, :error,
|
55
|
+
@session.send(:publish, :error, error_content(e))
|
54
56
|
end
|
55
57
|
|
56
58
|
def kernel_info_request(msg)
|
57
59
|
@session.send(:reply, :kernel_info_reply,
|
58
60
|
protocol_version: '5.0',
|
59
61
|
implementation: 'iruby',
|
60
|
-
banner: "IRuby #{IRuby::VERSION}",
|
61
62
|
implementation_version: IRuby::VERSION,
|
62
63
|
language_info: {
|
63
64
|
name: 'ruby',
|
64
65
|
version: RUBY_VERSION,
|
65
66
|
mimetype: 'application/x-ruby',
|
66
|
-
file_extension: 'rb'
|
67
|
-
}
|
67
|
+
file_extension: '.rb'
|
68
|
+
},
|
69
|
+
banner: "IRuby #{IRuby::VERSION} (with #{@session.description})",
|
70
|
+
help_links: [
|
71
|
+
{
|
72
|
+
text: "Ruby Documentation",
|
73
|
+
url: "https://ruby-doc.org/"
|
74
|
+
}
|
75
|
+
],
|
76
|
+
status: :ok)
|
68
77
|
end
|
69
78
|
|
70
79
|
def send_status(status)
|
80
|
+
IRuby.logger.debug "Send status: #{status}"
|
71
81
|
@session.send(:publish, :status, execution_state: status)
|
72
82
|
end
|
73
83
|
|
@@ -88,8 +98,10 @@ module IRuby
|
|
88
98
|
rescue SystemExit
|
89
99
|
content[:payload] << { source: :ask_exit }
|
90
100
|
rescue Exception => e
|
91
|
-
content =
|
101
|
+
content = error_content(e)
|
92
102
|
@session.send(:publish, :error, content)
|
103
|
+
content[:status] = :error
|
104
|
+
content[:execution_count] = @execution_count
|
93
105
|
end
|
94
106
|
@session.send(:reply, :execute_reply, content)
|
95
107
|
@session.send(:publish, :execute_result,
|
@@ -98,26 +110,33 @@ module IRuby
|
|
98
110
|
execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
|
99
111
|
end
|
100
112
|
|
101
|
-
def
|
102
|
-
{
|
103
|
-
|
113
|
+
def error_content(e)
|
114
|
+
rindex = e.backtrace.rindex{|line| line.start_with?(@backend.eval_path)} || -1
|
115
|
+
backtrace = SyntaxError === e && rindex == -1 ? [] : e.backtrace[0..rindex]
|
116
|
+
{ ename: e.class.to_s,
|
104
117
|
evalue: e.message,
|
105
|
-
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *
|
106
|
-
|
118
|
+
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *backtrace] }
|
119
|
+
end
|
120
|
+
|
121
|
+
def is_complete_request(msg)
|
122
|
+
# FIXME: the code completeness should be judged by using ripper or other Ruby parser
|
123
|
+
@session.send(:reply, :is_complete_reply,
|
124
|
+
status: :unknown)
|
107
125
|
end
|
108
126
|
|
109
127
|
def complete_request(msg)
|
110
128
|
# HACK for #26, only complete last line
|
111
129
|
code = msg[:content]['code']
|
112
|
-
if start = code.rindex(
|
130
|
+
if start = code.rindex(/\s|\R/)
|
113
131
|
code = code[start+1..-1]
|
114
132
|
start += 1
|
115
133
|
end
|
116
134
|
@session.send(:reply, :complete_reply,
|
117
135
|
matches: @backend.complete(code),
|
118
|
-
status: :ok,
|
119
136
|
cursor_start: start.to_i,
|
120
|
-
cursor_end: msg[:content]['cursor_pos']
|
137
|
+
cursor_end: msg[:content]['cursor_pos'],
|
138
|
+
metadata: {},
|
139
|
+
status: :ok)
|
121
140
|
end
|
122
141
|
|
123
142
|
def connect_request(msg)
|
@@ -136,14 +155,8 @@ module IRuby
|
|
136
155
|
end
|
137
156
|
|
138
157
|
def inspect_request(msg)
|
139
|
-
|
140
|
-
@session.send(:reply, :inspect_reply,
|
141
|
-
status: :ok,
|
142
|
-
data: Display.display(result),
|
143
|
-
metadata: {})
|
144
|
-
rescue Exception => e
|
145
|
-
IRuby.logger.warn "Inspection error: #{e.message}\n#{e.backtrace.join("\n")}"
|
146
|
-
@session.send(:reply, :inspect_reply, status: :error)
|
158
|
+
# not yet implemented. See (#119).
|
159
|
+
@session.send(:reply, :inspect_reply, status: :ok, found: false, data: {}, metadata: {})
|
147
160
|
end
|
148
161
|
|
149
162
|
def comm_open(msg)
|
@@ -161,5 +174,37 @@ module IRuby
|
|
161
174
|
Comm.comm[comm_id].handle_close(msg[:content]['data'])
|
162
175
|
Comm.comm.delete(comm_id)
|
163
176
|
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def init_parent_process_poller
|
181
|
+
pid = ENV.fetch('JPY_PARENT_PID', 0).to_i
|
182
|
+
return unless pid > 1
|
183
|
+
|
184
|
+
case RUBY_PLATFORM
|
185
|
+
when /mswin/, /mingw/
|
186
|
+
# TODO
|
187
|
+
else
|
188
|
+
@parent_poller = start_parent_process_pollar_unix
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def start_parent_process_pollar_unix
|
193
|
+
Thread.start do
|
194
|
+
IRuby.logger.warn("parent process poller thread started.")
|
195
|
+
loop do
|
196
|
+
begin
|
197
|
+
current_ppid = Process.ppid
|
198
|
+
if current_ppid == 1
|
199
|
+
IRuby.logger.warn("parent process appears to exited, shutting down.")
|
200
|
+
exit!(1)
|
201
|
+
end
|
202
|
+
sleep 1
|
203
|
+
rescue Errno::EINTR
|
204
|
+
# ignored
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
164
209
|
end
|
165
210
|
end
|
data/lib/iruby/ostream.rb
CHANGED
@@ -25,22 +25,38 @@ module IRuby
|
|
25
25
|
alias_method :next, :read
|
26
26
|
alias_method :readline, :read
|
27
27
|
|
28
|
-
def write(
|
29
|
-
|
30
|
-
|
31
|
-
nil
|
28
|
+
def write(*obj)
|
29
|
+
str = build_string { |sio| sio.write(*obj) }
|
30
|
+
session_send(str)
|
32
31
|
end
|
33
32
|
alias_method :<<, :write
|
34
33
|
alias_method :print, :write
|
35
34
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
def printf(format, *obj)
|
36
|
+
str = build_string { |sio| sio.printf(format, *obj) }
|
37
|
+
session_send(str)
|
38
|
+
end
|
39
|
+
|
40
|
+
def puts(*obj)
|
41
|
+
str = build_string { |sio| sio.puts(*obj) }
|
42
|
+
session_send(str)
|
40
43
|
end
|
41
44
|
|
42
45
|
def writelines(lines)
|
43
46
|
lines.each { |s| write(s) }
|
44
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_string
|
52
|
+
StringIO.open { |sio| yield(sio); sio.string }
|
53
|
+
end
|
54
|
+
|
55
|
+
def session_send(str)
|
56
|
+
raise 'I/O operation on closed file' unless @session
|
57
|
+
|
58
|
+
@session.send(:publish, :stream, name: @name, text: str)
|
59
|
+
nil
|
60
|
+
end
|
45
61
|
end
|
46
62
|
end
|
data/lib/iruby/session.rb
CHANGED
@@ -1,98 +1,116 @@
|
|
1
|
+
require 'iruby/session_adapter'
|
2
|
+
require 'iruby/session/mixin'
|
3
|
+
|
4
|
+
require 'securerandom'
|
5
|
+
|
1
6
|
module IRuby
|
2
7
|
class Session
|
3
|
-
|
8
|
+
include SessionSerialize
|
9
|
+
|
10
|
+
def initialize(config, adapter_name=nil)
|
11
|
+
@config = config
|
12
|
+
@adapter = create_session_adapter(config, adapter_name)
|
4
13
|
|
5
|
-
|
6
|
-
|
14
|
+
setup
|
15
|
+
setup_sockets
|
16
|
+
setup_heartbeat
|
17
|
+
setup_security
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
reply_socket = c.socket(:ROUTER)
|
10
|
-
reply_socket.bind(connection % config['shell_port'])
|
20
|
+
attr_reader :adapter, :config
|
11
21
|
|
12
|
-
|
13
|
-
|
22
|
+
def description
|
23
|
+
"#{@adapter.name} session adapter"
|
24
|
+
end
|
14
25
|
|
15
|
-
|
26
|
+
def setup
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup_sockets
|
30
|
+
protocol, host = config.values_at('transport', 'ip')
|
31
|
+
shell_port = config['shell_port']
|
32
|
+
iopub_port = config['iopub_port']
|
33
|
+
stdin_port = config['stdin_port']
|
34
|
+
|
35
|
+
@shell_socket, @shell_port = @adapter.make_router_socket(protocol, host, shell_port)
|
36
|
+
@iopub_socket, @iopub_port = @adapter.make_pub_socket(protocol, host, iopub_port)
|
37
|
+
@stdin_socket, @stdin_port = @adapter.make_router_socket(protocol, host, stdin_port)
|
38
|
+
|
39
|
+
@sockets = {
|
40
|
+
publish: @iopub_socket,
|
41
|
+
reply: @shell_socket,
|
42
|
+
stdin: @stdin_socket
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_heartbeat
|
47
|
+
protocol, host = config.values_at('transport', 'ip')
|
48
|
+
hb_port = config['hb_port']
|
49
|
+
@hb_socket, @hb_port = @adapter.make_rep_socket(protocol, host, hb_port)
|
50
|
+
@heartbeat_thread = Thread.start do
|
16
51
|
begin
|
17
|
-
|
18
|
-
|
19
|
-
ZMQ.proxy(hb_socket, hb_socket)
|
52
|
+
# NOTE: this loop is copied from CZTop's old session code
|
53
|
+
@adapter.heartbeat_loop(@hb_socket)
|
20
54
|
rescue Exception => e
|
21
55
|
IRuby.logger.fatal "Kernel heartbeat died: #{e.message}\n#{e.backtrace.join("\n")}"
|
22
56
|
end
|
23
57
|
end
|
58
|
+
end
|
24
59
|
|
25
|
-
|
26
|
-
@
|
27
|
-
unless config['key'].
|
28
|
-
|
29
|
-
|
60
|
+
def setup_security
|
61
|
+
@session_id = SecureRandom.uuid
|
62
|
+
unless config['key'].empty? || config['signature_scheme'].empty?
|
63
|
+
unless config['signature_scheme'] =~ /\Ahmac-/
|
64
|
+
raise "Unknown signature_scheme: #{config['signature_scheme']}"
|
65
|
+
end
|
66
|
+
digest_algorithm = config['signature_scheme'][/\Ahmac-(.*)\Z/, 1]
|
67
|
+
@hmac = OpenSSL::HMAC.new(config['key'], OpenSSL::Digest.new(digest_algorithm))
|
30
68
|
end
|
31
69
|
end
|
32
70
|
|
33
|
-
|
34
|
-
|
35
|
-
idents =
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
71
|
+
def send(socket_type, message_type, content)
|
72
|
+
sock = check_socket_type(socket_type)
|
73
|
+
idents = if socket_type == :reply && @last_recvd_msg
|
74
|
+
@last_recvd_msg[:idents]
|
75
|
+
else
|
76
|
+
message_type == :stream ? "stream.#{content[:name]}" : message_type
|
77
|
+
end
|
41
78
|
header = {
|
42
|
-
msg_type:
|
79
|
+
msg_type: message_type,
|
43
80
|
msg_id: SecureRandom.uuid,
|
44
81
|
username: 'kernel',
|
45
|
-
session: @
|
82
|
+
session: @session_id,
|
46
83
|
version: '5.0'
|
47
84
|
}
|
48
|
-
@
|
85
|
+
@adapter.send(sock, serialize(idents, header, content))
|
49
86
|
end
|
50
87
|
|
51
|
-
|
52
|
-
|
53
|
-
|
88
|
+
def recv(socket_type)
|
89
|
+
sock = check_socket_type(socket_type)
|
90
|
+
data = @adapter.recv(sock)
|
91
|
+
@last_recvd_msg = unserialize(data)
|
54
92
|
end
|
55
93
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
MultiJson.dump(@last_recvd_msg ? @last_recvd_msg[:header] : {}),
|
61
|
-
'{}',
|
62
|
-
MultiJson.dump(content || {})]
|
63
|
-
frames = ([*idents].compact.map(&:to_s) << DELIM << sign(msg)) + msg
|
64
|
-
IRuby.logger.debug "Sent #{frames.inspect}"
|
65
|
-
ZMQ::Message(*frames)
|
94
|
+
def recv_input
|
95
|
+
sock = check_socket_type(:stdin)
|
96
|
+
data = @adapter.recv(sock)
|
97
|
+
unserialize(data)[:content]["value"]
|
66
98
|
end
|
67
99
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
raise 'malformed message, must have at least #{minlen} elements' unless msg_list.length >= minlen
|
78
|
-
s, header, parent_header, metadata, content, buffers = *msg_list
|
79
|
-
raise 'Invalid signature' unless s == sign(msg_list[1..-1])
|
80
|
-
{
|
81
|
-
idents: idents,
|
82
|
-
header: MultiJson.load(header),
|
83
|
-
parent_header: MultiJson.load(parent_header),
|
84
|
-
metadata: MultiJson.load(metadata),
|
85
|
-
content: MultiJson.load(content),
|
86
|
-
buffers: buffers
|
87
|
-
}
|
100
|
+
private
|
101
|
+
|
102
|
+
def check_socket_type(socket_type)
|
103
|
+
case socket_type
|
104
|
+
when :publish, :reply, :stdin
|
105
|
+
@sockets[socket_type]
|
106
|
+
else
|
107
|
+
raise ArgumentError, "Invalid socket type #{socket_type}"
|
108
|
+
end
|
88
109
|
end
|
89
110
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
@hmac.reset
|
94
|
-
list.each {|m| @hmac.update(m) }
|
95
|
-
@hmac.hexdigest
|
111
|
+
def create_session_adapter(config, adapter_name)
|
112
|
+
adapter_class = SessionAdapter.select_adapter_class(adapter_name)
|
113
|
+
adapter_class.new(config)
|
96
114
|
end
|
97
115
|
end
|
98
116
|
end
|