pry-remote-em 0.4.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.
- data/README.md +258 -0
- data/bin/pry-remote-em +29 -0
- data/lib/pry-remote-em.rb +25 -0
- data/lib/pry-remote-em/client.rb +128 -0
- data/lib/pry-remote-em/json-proto.rb +30 -0
- data/lib/pry-remote-em/server.rb +168 -0
- data/lib/pry-remote-em/version.rb +3 -0
- metadata +97 -0
data/README.md
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# What is it?
|
2
|
+
|
3
|
+
A way to start Pry remotely in EventMachine and to connect to it. This provides access to the state of the running program from anywhere.
|
4
|
+
|
5
|
+
It's based off of [Mon-Ouie's](https://github.com/Mon-Ouie) [pry-remote](https://github.com/Mon-Ouie/pry-remote) for DRb.
|
6
|
+
|
7
|
+
# Compatibility
|
8
|
+
|
9
|
+
MRI 1.9 or any other VM with support for Fibers is required.
|
10
|
+
|
11
|
+
|
12
|
+
# Installation
|
13
|
+
|
14
|
+
```bash
|
15
|
+
gem install pry-remote-em
|
16
|
+
```
|
17
|
+
|
18
|
+
# Usage
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
require 'pry-remote-em/server'
|
22
|
+
|
23
|
+
class Foo
|
24
|
+
def initialize(x, y)
|
25
|
+
binding.remote_pry_em
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
EM.run { Foo.new 10, 20 }
|
30
|
+
```
|
31
|
+
|
32
|
+
Running it will print out a message telling you Pry is waiting for a
|
33
|
+
program to connect itself to it:
|
34
|
+
|
35
|
+
[pry-remote-em] listening for connections on pryem://localhost:6462/
|
36
|
+
|
37
|
+
You can then connect to the pry session using ``pry-remote-em``:
|
38
|
+
|
39
|
+
$ pry-remote-em
|
40
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6462/
|
41
|
+
[pry-remote-em] remote is PryRemoteEm 0.1.0
|
42
|
+
[1] pry(#<Foo>)> stat
|
43
|
+
Method Information:
|
44
|
+
--
|
45
|
+
Name: initialize
|
46
|
+
Owner: Foo
|
47
|
+
Visibility: private
|
48
|
+
Type: Bound
|
49
|
+
Arity: 2
|
50
|
+
Method Signature: initialize(x, y)
|
51
|
+
Source Location: (irb):2
|
52
|
+
|
53
|
+
[2] pry(#<Foo>)> self
|
54
|
+
=> #<Foo:0x007fe66a426fa0>
|
55
|
+
|
56
|
+
[3] pry(#<Foo>)> ls
|
57
|
+
locals: _ _dir_ _ex_ _file_ _in_ _out_ _pry_ x y
|
58
|
+
[4] pry(#<Foo>)> x
|
59
|
+
=> 10
|
60
|
+
|
61
|
+
[5] pry(#<Foo>)> x = 12
|
62
|
+
=> 12
|
63
|
+
|
64
|
+
[6] pry(#<Foo>)> x
|
65
|
+
=> 12
|
66
|
+
|
67
|
+
[7] pry(#<Foo>)> exit
|
68
|
+
[pry-remote-em] session terminated
|
69
|
+
|
70
|
+
$ pry-remote-em
|
71
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6462/
|
72
|
+
[pry-remote-em] remote is PryRemoteEm 0.1.0
|
73
|
+
[1] pry(#<Foo>)> x
|
74
|
+
=> 12
|
75
|
+
|
76
|
+
[2] pry(#<Foo>)> exit
|
77
|
+
[pry-remote-em] session terminated
|
78
|
+
|
79
|
+
# Features
|
80
|
+
|
81
|
+
## TLS Encryption
|
82
|
+
|
83
|
+
When creating a server pass the :tls => true option to enable TLS. If
|
84
|
+
you pass a Hash, e.g. ``:tls => {:private_key_file => '/tmp/server.key'}`` it will be used to configure the internal TLS handler.
|
85
|
+
See [EventMachine::Connection#start_tls](http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296) for the available options.
|
86
|
+
|
87
|
+
To start the command line client in TLS mode pass it a pryems URL instead of a pryem URL.
|
88
|
+
|
89
|
+
```bash
|
90
|
+
$ bin/pry-remote-em pryems:///
|
91
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6462/
|
92
|
+
[pry-remote-em] remote is PryRemoteEm 0.2.0 pryems
|
93
|
+
[pry-remote-em] negotiating TLS
|
94
|
+
[pry-remote-em] TLS connection established
|
95
|
+
[1] pry(#<Hash>)>
|
96
|
+
```
|
97
|
+
|
98
|
+
## User Authentication
|
99
|
+
|
100
|
+
### Server
|
101
|
+
|
102
|
+
If the service is started with the :auth option it will require all
|
103
|
+
clients to authenticate on connect. The :auth option can be a Hash, proc
|
104
|
+
or any object that responds to #call.
|
105
|
+
|
106
|
+
#### Auth with a Hash
|
107
|
+
```ruby
|
108
|
+
auth_hash = {'caleb' => 'crane', 'john' => 'lowski'}
|
109
|
+
obj = {:encoding => __ENCODING__, :weather => :cloudy}
|
110
|
+
EM.run{
|
111
|
+
obj.remote_pry_em('localhost', :auto, :tls => true, :auth => auth_hash)
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
#### Auth with a lambda
|
116
|
+
```ruby
|
117
|
+
require ‘net/ldap’
|
118
|
+
ldap_anon = lambda do |user, pass|
|
119
|
+
ldap = Net::LDAP.new :host => “10.0.0.1”, :port => 389, :auth => {:method => :simple, :username => user, :password => pass}
|
120
|
+
ldap.bind
|
121
|
+
end
|
122
|
+
obj = {:encoding => __ENCODING__, :weather => :cloudy}
|
123
|
+
EM.run{
|
124
|
+
obj.remote_pry_em('localhost', :auto, :tls => true, :auth => ldap_anon)
|
125
|
+
}
|
126
|
+
```
|
127
|
+
|
128
|
+
#### Auth with an object
|
129
|
+
```ruby
|
130
|
+
class Authenticator
|
131
|
+
def initialize(db)
|
132
|
+
@db = db
|
133
|
+
end
|
134
|
+
def call(user, pass)
|
135
|
+
@db[user] && @db[user] == pass
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
obj = {:encoding => __ENCODING__, :weather => :cloudy}
|
140
|
+
EM.run{
|
141
|
+
obj.remote_pry_em('localhost', :auto, :tls => true, :auth => Authenticator.new(auth_hash))
|
142
|
+
}
|
143
|
+
```
|
144
|
+
|
145
|
+
|
146
|
+
### Client
|
147
|
+
|
148
|
+
The included command line client ``pry-remote-em`` can take a username
|
149
|
+
and/or password as part of the url argument. If either a username or
|
150
|
+
password is not supplied, but required by the server it will prompt for
|
151
|
+
them.
|
152
|
+
|
153
|
+
```shell
|
154
|
+
$ pry-remote-em pryems://localhost:6464/
|
155
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6464/
|
156
|
+
[pry-remote-em] remote is PryRemoteEm 0.4.0 pryems
|
157
|
+
[pry-remote-em] negotiating TLS
|
158
|
+
[pry-remote-em] TLS connection established
|
159
|
+
user: caleb
|
160
|
+
caleb's password: *****
|
161
|
+
[1] pry(#<Hash>)>
|
162
|
+
```
|
163
|
+
|
164
|
+
|
165
|
+
```shell
|
166
|
+
$ pry-remote-em pryems://caleb@localhost:6464
|
167
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6464/
|
168
|
+
[pry-remote-em] remote is PryRemoteEm 0.4.0 pryems
|
169
|
+
[pry-remote-em] negotiating TLS
|
170
|
+
[pry-remote-em] TLS connection established
|
171
|
+
caleb's password: *****
|
172
|
+
[1] pry(#<Hash>)> exit
|
173
|
+
```
|
174
|
+
|
175
|
+
```shell
|
176
|
+
$ pry-remote-em pryems://caleb:crane@localhost:6464
|
177
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6464/
|
178
|
+
[pry-remote-em] remote is PryRemoteEm 0.4.0 pryems
|
179
|
+
[pry-remote-em] negotiating TLS
|
180
|
+
[pry-remote-em] TLS connection established
|
181
|
+
[1] pry(#<Hash>)> exit
|
182
|
+
```
|
183
|
+
|
184
|
+
|
185
|
+
## Tab Completion
|
186
|
+
|
187
|
+
Tab completion candidates will be retrieved from the server and
|
188
|
+
presented on the client side.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
$ bin/pry-remote-em pryems:///
|
192
|
+
[pry-remote-em] client connected to pryem://127.0.0.1:6462/
|
193
|
+
[pry-remote-em] remote is PryRemoteEm 0.2.0 pryems
|
194
|
+
[1] pry(#<Hash>)> key (^TAB ^TAB)
|
195
|
+
key key? keys
|
196
|
+
[1] pry(#<Hash>)> keys
|
197
|
+
=> [:encoding]
|
198
|
+
```
|
199
|
+
|
200
|
+
## Paging
|
201
|
+
|
202
|
+
The standard Pry pager is supported through the included client.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
[1] pry(#<Hash>)> ENV
|
206
|
+
=> {"COMMAND_MODE"=>"unix2003",
|
207
|
+
"DISPLAY"=>"/tmp/launch-0EGhJW/org.x:0",
|
208
|
+
"EDITOR"=>"mvim -f --nomru -c \"au VimLeave * !open -a Terminal\"",
|
209
|
+
"GEM_HOME"=>"/Users/caleb/.rvm/gems/ruby-1.9.2-p290",
|
210
|
+
"GEM_PATH"=>
|
211
|
+
"/Users/caleb/.rvm/gems/ruby-1.9.2-p290:/Users/caleb/.rvm/gems/ruby-1.9.2-p290@global",
|
212
|
+
"GREP_COLOR"=>"1;32",
|
213
|
+
"GREP_OPTIONS"=>"--color=auto",
|
214
|
+
"HOME"=>"/Users/caleb",
|
215
|
+
"IRBRC"=>"/Users/caleb/.rvm/rubies/ruby-1.9.2-p290/.irbrc",
|
216
|
+
"LC_CTYPE"=>"",
|
217
|
+
"LOGNAME"=>"caleb",
|
218
|
+
"LSCOLORS"=>"Gxfxcxdxbxegedabagacad",
|
219
|
+
:
|
220
|
+
```
|
221
|
+
|
222
|
+
# Missing Features
|
223
|
+
|
224
|
+
- AutoDiscovery/Broker [ticket](https://github.com/simulacre/pry-remote-em/issues/11)
|
225
|
+
- HTTP Transport [ticket](https://github.com/simulacre/pry-remote-em/issues/12)
|
226
|
+
- Shell Commands [ticket](https://github.com/simulacre/pry-remote-em/issues/15)
|
227
|
+
- Vi mode editing - RbReadline doesn't support vi edit mode. I'm looking into contributing it. PryRemoteEm uses rb-readline because the STLIB version doesn't play nice with Fibers.
|
228
|
+
- Ssh key based authentication
|
229
|
+
|
230
|
+
|
231
|
+
# Issues
|
232
|
+
|
233
|
+
Please post any bug reports or feature requests on [Github](https://github.com/simulacre/pry-remote-em/issues)
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
|
238
|
+
# Copyright
|
239
|
+
|
240
|
+
Copyright (c) 2012 Caleb Crane
|
241
|
+
|
242
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
243
|
+
copy of this software and associated documentation files (the "Software"),
|
244
|
+
to deal in the Software without restriction, including without limitation
|
245
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
246
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
247
|
+
Software is furnished to do so, subject to the following conditions:
|
248
|
+
|
249
|
+
The above copyright notice and this permission notice shall be included
|
250
|
+
in all copies or substantial portions of the Software.
|
251
|
+
|
252
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
253
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
254
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
255
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
256
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
257
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
258
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/pry-remote-em
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'highline'
|
5
|
+
require 'pry-remote-em/client'
|
6
|
+
|
7
|
+
uri = ARGV[0] || "pryem://localhost:#{PryRemoteEm::DEFPORT}"
|
8
|
+
uri = URI.parse(uri)
|
9
|
+
unless %w(pryem pryems).include?(uri.scheme)
|
10
|
+
abort "only pryem URIs are currently supported\n usage: pryem(s)://127.0.0.1:#{PryRemoteEm::DEFPORT}"
|
11
|
+
end
|
12
|
+
|
13
|
+
tried = 0
|
14
|
+
auth_proc = proc do
|
15
|
+
tried += 1
|
16
|
+
user = uri.user || ($stdin.tty? ? Readline.readline("user: ") : raise("username is require for authentication"))
|
17
|
+
pass = if !uri.password.nil? && tried <= 1
|
18
|
+
uri.password
|
19
|
+
elsif $stdin.tty?
|
20
|
+
HighLine.new.ask("#{user}'s password: ") { |q| q.echo = '*'}
|
21
|
+
else
|
22
|
+
raise "password is required to authenticate"
|
23
|
+
end
|
24
|
+
[user, pass]
|
25
|
+
end
|
26
|
+
|
27
|
+
EM.run do
|
28
|
+
PryRemoteEm::Client.start(uri.host, uri.port, :auth=>auth_proc, :tls=>uri.scheme=='pryems') { |e| EM.stop }
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'openssl'
|
3
|
+
rescue LoadError
|
4
|
+
warn "OpenSSL support is not available"
|
5
|
+
end
|
6
|
+
require 'pry-remote-em/version'
|
7
|
+
require 'pry-remote-em/json-proto'
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'socket'
|
10
|
+
require 'json'
|
11
|
+
require "fiber"
|
12
|
+
|
13
|
+
module PryRemoteEm
|
14
|
+
DEFHOST = 'localhost'
|
15
|
+
DEFPORT = 6462
|
16
|
+
NEGOTIMER = 15
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class Object
|
21
|
+
def remote_pry_em(host = PryRemoteEm::DEFHOST, port = PryRemoteEm::DEFPORT, opts = {:tls => false})
|
22
|
+
opts = {:target => self}.merge(opts)
|
23
|
+
PryRemoteEm::Server.run(opts[:target], host, port, opts)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'pry-remote-em'
|
3
|
+
require 'pry/helpers/base_helpers'
|
4
|
+
#require "readline" # doesn't work with Fiber.yield
|
5
|
+
# - /Users/caleb/src/pry-remote-em/lib/pry-remote-em/client.rb:45:in `yield': fiber called across stack rewinding barrier (FiberError)
|
6
|
+
require "rb-readline" # doesn't provide vi-mode support :(
|
7
|
+
# https://github.com/luislavena/rb-readline/issues/21
|
8
|
+
# https://github.com/simulacre/rb-readline/commit/0376eb4e9526b3dc1a6512716322efcef409628d
|
9
|
+
|
10
|
+
module PryRemoteEm
|
11
|
+
module Client
|
12
|
+
include EM::Deferrable
|
13
|
+
include JsonProto
|
14
|
+
include Pry::Helpers::BaseHelpers
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def start(host = PryRemoteEm::DEFHOST, port = PryRemoteEM::DEFPORT, opts = {:tls => false})
|
18
|
+
EM.connect(host || PryRemoteEm::DEFHOST, port || PryRemoteEm::DEFPORT, PryRemoteEm::Client, opts) do |c|
|
19
|
+
c.callback { yield if block_given? }
|
20
|
+
c.errback do |e|
|
21
|
+
puts "[pry-remote-em] connection failed\n#{e}"
|
22
|
+
yield(e) if block_given?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # class << self
|
27
|
+
|
28
|
+
def initialize(opts = {})
|
29
|
+
@opts = opts
|
30
|
+
if (a = opts[:auth])
|
31
|
+
if a.respond_to?(:call)
|
32
|
+
@auth = a
|
33
|
+
else
|
34
|
+
@auth = lambda { a }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_init
|
40
|
+
return fail("connection was not established") unless get_peername
|
41
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
42
|
+
Kernel.puts "[pry-remote-em] client connected to pryem://#{ip}:#{port}/"
|
43
|
+
@nego_timer = EM::Timer.new(PryRemoteEm::NEGOTIMER) do
|
44
|
+
fail("[pry-remote-em] server didn't finish negotiation within #{PryRemoteEm::NEGOTIMER} seconds; terminating")
|
45
|
+
end
|
46
|
+
Readline.completion_proc = method(:auto_complete)
|
47
|
+
end
|
48
|
+
|
49
|
+
def auto_complete(word)
|
50
|
+
@waiting = Fiber.current
|
51
|
+
send_data({:c => word})
|
52
|
+
return Fiber.yield
|
53
|
+
end
|
54
|
+
|
55
|
+
def receive_json(j)
|
56
|
+
if j['p'] # prompt
|
57
|
+
if @negotiated && !@unbound
|
58
|
+
Fiber.new { send_data(Readline.readline(j['p'], true)) }.resume
|
59
|
+
end
|
60
|
+
|
61
|
+
elsif j['d'] # printable data
|
62
|
+
stagger_output j['d'], $stdout # Pry::Helpers::BaseHelpers
|
63
|
+
|
64
|
+
elsif j['g'] # server banner
|
65
|
+
Kernel.puts "[pry-remote-em] remote is #{j['g']}"
|
66
|
+
name, version, scheme = j['g'].split(" ", 3)
|
67
|
+
# TODO parse version and compare against a Gem style matcher
|
68
|
+
# https://github.com/simulacre/pry-remote-em/issues/21
|
69
|
+
return fail("[pry-remote-em] incompatible version #{version}") if version != PryRemoteEm::VERSION
|
70
|
+
if scheme.nil? || scheme != (reqscheme = @opts[:tls] ? 'pryems' : 'pryem')
|
71
|
+
if scheme == 'pryems' && defined?(::OpenSSL)
|
72
|
+
@opts[:tls] = true
|
73
|
+
else
|
74
|
+
return fail("[pry-remote-em] server doesn't support required scheme #{reqscheme.dump}")
|
75
|
+
end # scheme == 'pryems' && defined?(::OpenSSL)
|
76
|
+
end
|
77
|
+
@nego_timer.cancel
|
78
|
+
@negotiated = true
|
79
|
+
start_tls if @opts[:tls]
|
80
|
+
|
81
|
+
elsif j['c'] # tab completion response
|
82
|
+
@waiting, f = nil, @waiting
|
83
|
+
f.resume(j['c']) if f
|
84
|
+
|
85
|
+
elsif j.include?('a') # authentication demand
|
86
|
+
return fail j['a'] if j['a'].is_a?(String)
|
87
|
+
return authenticate if j['a'] == false
|
88
|
+
@authenticated = true if j['a'] == true
|
89
|
+
|
90
|
+
else
|
91
|
+
warn "[pry-remote-em] received unexpected data: #{j.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def authenticate
|
96
|
+
return fail("[pry-remote-em] authentication required") unless @auth
|
97
|
+
return fail("[pry-remote-em] can't authenticate before negotiation complete") unless @negotiated
|
98
|
+
user, pass = @auth.call
|
99
|
+
return fail("[pry-remote-em] expected #{@auth} to return a user and password") unless user && pass
|
100
|
+
send_data({:a => [user, pass]})
|
101
|
+
end # authenticate
|
102
|
+
|
103
|
+
def ssl_handshake_completed
|
104
|
+
Kernel.puts "[pry-remote-em] TLS connection established"
|
105
|
+
end
|
106
|
+
|
107
|
+
def start_tls
|
108
|
+
Kernel.puts "[pry-remote-em] negotiating TLS"
|
109
|
+
super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
|
110
|
+
end
|
111
|
+
|
112
|
+
def unbind
|
113
|
+
@unbound = true
|
114
|
+
Kernel.puts "[pry-remote-em] session terminated"
|
115
|
+
# prior to 1.0.0.b4 error? returns true here even when it's not
|
116
|
+
return succeed if Gem.loaded_specs["eventmachine"].version < Gem::Version.new("1.0.0.beta4")
|
117
|
+
error? ? fail : succeed
|
118
|
+
end
|
119
|
+
end # module::Client
|
120
|
+
end # module::PryRemoteEm
|
121
|
+
|
122
|
+
# Pry::Helpers::BaseHelpers#stagger_output expects Pry.pager to be defined
|
123
|
+
class Pry
|
124
|
+
class << self
|
125
|
+
attr_accessor :pager unless respond_to?(:pager)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
Pry.pager = true
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module PryRemoteEm
|
4
|
+
module JsonProto
|
5
|
+
DELIM = ']]>]]><[[<[['
|
6
|
+
|
7
|
+
def receive_data(d)
|
8
|
+
return unless d && d.length > 0
|
9
|
+
@buffer ||= "" # inlieu of a post_init
|
10
|
+
|
11
|
+
if six = d.index(DELIM)
|
12
|
+
@buffer << d[0...six]
|
13
|
+
j = JSON.load(@buffer)
|
14
|
+
@buffer.clear
|
15
|
+
receive_json(j)
|
16
|
+
receive_data(d[(six + DELIM.length)..-1])
|
17
|
+
else
|
18
|
+
@buffer << d
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive_json(j)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_data(d)
|
26
|
+
super(JSON.dump(d.is_a?(String) ? {:d => d} : d) + DELIM)
|
27
|
+
end
|
28
|
+
|
29
|
+
end # module::JsonProto
|
30
|
+
end # module::PryRemoteEm
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'pry-remote-em'
|
3
|
+
|
4
|
+
module PryRemoteEm
|
5
|
+
module Server
|
6
|
+
include JsonProto
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def run(obj, host = DEFHOST, port = DEFPORT, opts = {:tls => false})
|
10
|
+
tries = :auto == port ? 100.tap{ port = DEFPORT } : 1
|
11
|
+
# TODO raise a useful exception not RuntimeError
|
12
|
+
raise "root permission required for port below 1024 (#{port})" if port < 1024 && Process.euid != 0
|
13
|
+
begin
|
14
|
+
EM.start_server(host, port, PryRemoteEm::Server, opts) do |pre|
|
15
|
+
Fiber.new {
|
16
|
+
begin
|
17
|
+
Pry.start(obj, :input => pre, :output => pre)
|
18
|
+
ensure
|
19
|
+
pre.close_connection
|
20
|
+
end
|
21
|
+
}.resume
|
22
|
+
end
|
23
|
+
rescue => e
|
24
|
+
# EM 1.0.0.beta4's message tells us the port is in use; 0.12.10 just says, 'no acceptor'
|
25
|
+
if (e.message.include?('port is in use') || e.message.include?('no acceptor')) && tries >= 1
|
26
|
+
tries -= 1
|
27
|
+
port += 1
|
28
|
+
retry
|
29
|
+
end
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
scheme = opts[:tls] ? 'pryems' : 'pryem'
|
33
|
+
Kernel.puts "[pry-remote-em] listening for connections on #{scheme}://#{host}:#{port}/"
|
34
|
+
end # run(obj, host = DEFHOST, port = DEFPORT)
|
35
|
+
end # class << self
|
36
|
+
|
37
|
+
def initialize(opts = {:tls => false})
|
38
|
+
@after_auth = []
|
39
|
+
@opts = opts
|
40
|
+
if (a = opts[:auth])
|
41
|
+
if a.respond_to?(:call)
|
42
|
+
return error("auth handler procs must take two arguments") unless a.method(:call).arity == 2
|
43
|
+
@auth = a
|
44
|
+
else
|
45
|
+
return error("auth handler objects must respond to :call, or :[]") unless a.respond_to?(:[])
|
46
|
+
@auth = lambda {|u,p| a[u] && a[u] == p }
|
47
|
+
end
|
48
|
+
@auth_tries = 5
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def post_init
|
53
|
+
@lines = []
|
54
|
+
Pry.config.pager, @old_pager = false, Pry.config.pager
|
55
|
+
Pry.config.system, @old_system = PryRemoteEm::Server::System, Pry.config.system
|
56
|
+
@auth_required = @auth
|
57
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
58
|
+
Kernel.puts "[pry-remote-em] received client connection from #{ip}:#{port}"
|
59
|
+
send_data({:g => "PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}"})
|
60
|
+
@opts[:tls] ? start_tls : (@auth_required && send_data({:a => false}))
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_tls
|
64
|
+
Kernel.puts "[pry-remote-em] starting TLS (#{peer_ip}:#{peer_port})"
|
65
|
+
super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
|
66
|
+
end
|
67
|
+
|
68
|
+
def ssl_handshake_completed
|
69
|
+
Kernel.puts "[pry-remote-em] TLS connection established (#{peer_ip}:#{peer_port})"
|
70
|
+
send_data({:a => false}) if @auth_required
|
71
|
+
end
|
72
|
+
|
73
|
+
def peer_ip
|
74
|
+
return @peer_ip if @peer_ip
|
75
|
+
return "" if get_peername.nil?
|
76
|
+
@peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
|
77
|
+
@peer_ip
|
78
|
+
end
|
79
|
+
|
80
|
+
def peer_port
|
81
|
+
return @peer_port if @peer_port
|
82
|
+
return "" if get_peername.nil?
|
83
|
+
@peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
|
84
|
+
@peer_port
|
85
|
+
end
|
86
|
+
|
87
|
+
def receive_json(j)
|
88
|
+
return send_data({:a => false}) if @auth_required && !j['a']
|
89
|
+
|
90
|
+
if j['d'] # just normal data
|
91
|
+
@lines.push(*j['d'].split("\n"))
|
92
|
+
if @waiting
|
93
|
+
f, @waiting = @waiting, nil
|
94
|
+
f.resume(@lines.shift)
|
95
|
+
end
|
96
|
+
elsif j['c'] # tab completion request
|
97
|
+
send_data({:c => @compl_proc.call(j['c'])})
|
98
|
+
|
99
|
+
elsif j['a'] # authentication response
|
100
|
+
return send_data({:a => true}) if !@auth || !@auth_required
|
101
|
+
return send_data({:a => 'auth data must be a two element array'}) unless j['a'].is_a?(Array) && j['a'].length == 2
|
102
|
+
unless (@auth_required = !@auth.call(*j['a']))
|
103
|
+
authenticated!
|
104
|
+
else
|
105
|
+
if @auth_tries <= 0
|
106
|
+
msg = "max authentication attempts reached"
|
107
|
+
send_data({:a => msg})
|
108
|
+
Kernel.puts "[pry-remote-em] #{msg} (#{peer_ip}:#{peer_port})"
|
109
|
+
return close_connection_after_writing
|
110
|
+
end
|
111
|
+
@auth_tries -= 1
|
112
|
+
end
|
113
|
+
return send_data({:a => !@auth_required})
|
114
|
+
|
115
|
+
else
|
116
|
+
warn "received unexpected data: #{j.inspect}"
|
117
|
+
end # j['d']
|
118
|
+
end # receive_json(j)
|
119
|
+
|
120
|
+
|
121
|
+
def authenticated!
|
122
|
+
while (aa = @after_auth.shift)
|
123
|
+
send_data(aa)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def unbind
|
128
|
+
Pry.config.pager = @old_pager
|
129
|
+
Pry.config.system = @old_system
|
130
|
+
Kernel.puts "[pry-remote-em] remote session terminated (#{peer_ip}:#{peer_port})"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Methods that make Server compatible with Pry
|
134
|
+
|
135
|
+
def readline(prompt)
|
136
|
+
@auth_required ? @after_auth.push({:p => prompt}) : send_data({:p => prompt})
|
137
|
+
return @lines.shift unless @lines.empty?
|
138
|
+
@waiting = Fiber.current
|
139
|
+
return Fiber.yield
|
140
|
+
end
|
141
|
+
|
142
|
+
def print(val)
|
143
|
+
send_data({:d => val})
|
144
|
+
end
|
145
|
+
alias :write :print
|
146
|
+
|
147
|
+
def puts(data = "")
|
148
|
+
s = data.to_s
|
149
|
+
print(s[0] == "\n" ? s : s + "\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
def completion_proc=(compl)
|
153
|
+
@compl_proc = compl
|
154
|
+
end
|
155
|
+
|
156
|
+
def tty?
|
157
|
+
true # might be a very bad idea ....
|
158
|
+
end
|
159
|
+
|
160
|
+
def flush
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
System = proc do |output, cmd, _|
|
165
|
+
output.puts("shell commands are not yet supported")
|
166
|
+
end
|
167
|
+
end # module::Server
|
168
|
+
end # module::PryRemoteEm
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pry-remote-em
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Caleb Crane
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70305814043480 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70305814043480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: pry
|
27
|
+
requirement: &70305814042980 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.6
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70305814042980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rb-readline
|
38
|
+
requirement: &70305814042560 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70305814042560
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: highline
|
49
|
+
requirement: &70305814042100 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70305814042100
|
58
|
+
description: Connect to Pry remotely using EventMachine with tab-completion, paging,
|
59
|
+
user authentication and SSL support.
|
60
|
+
email: pry-remote-em@simulacre.org
|
61
|
+
executables:
|
62
|
+
- pry-remote-em
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- lib/pry-remote-em/client.rb
|
67
|
+
- lib/pry-remote-em/json-proto.rb
|
68
|
+
- lib/pry-remote-em/server.rb
|
69
|
+
- lib/pry-remote-em/version.rb
|
70
|
+
- lib/pry-remote-em.rb
|
71
|
+
- bin/pry-remote-em
|
72
|
+
- README.md
|
73
|
+
homepage: http://github.com/simulacre/pry-remote-em
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.10
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Connect to Pry remotely using EventMachine
|
97
|
+
test_files: []
|