Class: EasyServe::TCPService
- Defined in:
- lib/easy-serve/service.rb,
lib/easy-serve/service/tunnelled.rb,
lib/easy-serve/service/accessible.rb
Overview
The scheme for referencing TCP hosts is as follows:
bind host | connect host
+------------------------------------------------------
| local remote TCP SSH tunnel
-----------+------------------------------------------------------
localhost 'localhost' X 'localhost'
0.0.0.0 'localhost' hostname(*) 'localhost'
hostname hostname hostname 'localhost'(**)
* use hostname as best guess, can override; append ".local" if
hostname not qualified
** forwarding set up to hostname[.local] instead of localhost
Constant Summary
Constants inherited from Service
Instance Attribute Summary collapse
-
#bind_host ⇒ Object
readonly
Returns the value of attribute bind_host.
-
#connect_host ⇒ Object
readonly
Returns the value of attribute connect_host.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
Attributes inherited from Service
Instance Method Summary collapse
-
#accessible(remote_host, log) ⇒ Object
Returns [service, ssh_session].
- #bump! ⇒ Object
-
#initialize(name, bind_host: nil, connect_host: nil, host: nil, port: 0) ⇒ TCPService
constructor
A new instance of TCPService.
- #serve(max_tries: 1, log: log) ⇒ Object
- #try_connect ⇒ Object
- #try_serve ⇒ Object
-
#tunnelled ⇒ Object
Returns [service, ssh_session|nil].
Methods inherited from Service
Constructor Details
#initialize(name, bind_host: nil, connect_host: nil, host: nil, port: 0) ⇒ TCPService
Returns a new instance of TCPService.
118 119 120 121 122 |
# File 'lib/easy-serve/service.rb', line 118 def initialize name, bind_host: nil, connect_host: nil, host: nil, port: 0 super name @bind_host, @connect_host, @port = bind_host, connect_host, port @host ||= EasyServe.host_name end |
Instance Attribute Details
#bind_host ⇒ Object (readonly)
Returns the value of attribute bind_host.
116 117 118 |
# File 'lib/easy-serve/service.rb', line 116 def bind_host @bind_host end |
#connect_host ⇒ Object (readonly)
Returns the value of attribute connect_host.
116 117 118 |
# File 'lib/easy-serve/service.rb', line 116 def connect_host @connect_host end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
116 117 118 |
# File 'lib/easy-serve/service.rb', line 116 def host @host end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
116 117 118 |
# File 'lib/easy-serve/service.rb', line 116 def port @port end |
Instance Method Details
#accessible(remote_host, log) ⇒ Object
Returns [service, ssh_session]. The service is modified based on self with tunneling from remote_host and ssh_session is the associated ssh pipe.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/easy-serve/service/accessible.rb', line 6 def accessible remote_host, log service_host = case bind_host when nil, "localhost", "127.0.0.1", "0.0.0.0", /\A<any>\z/i "localhost" else bind_host end fwd = "0:#{service_host}:#{port}" remote_port = nil ssh = nil tries = 10 1.times do if EasyServe.ssh_supports_dynamic_ports_forwards remote_port = Integer(`ssh -O forward -R #{fwd} #{remote_host}`) else log.warn "Unable to set up dynamic ssh port forwarding. " + "Please check if ssh -v is at least 6.0. " + "Falling back to new ssh session." code = <<-CODE require 'socket' svr = TCPServer.new "localhost", 0 # no rescue; error here is fatal puts svr.addr[1] svr.close CODE remote_port = IO.popen ["ssh", remote_host, "ruby"], "w+" do |ruby| ruby.puts code ruby.close_write Integer(ruby.gets) end cmd = [ "ssh", remote_host, "-R", "#{remote_port}:#{service_host}:#{port}", "echo ok && cat" ] ssh = IO.popen cmd, "w+" ## how to tell if port in use and retry? ssh doesn't seem to fail, ## or maybe it fails by printing a message on the remote side ssh.sync = true line = ssh.gets unless line and line.chomp == "ok" # wait for forwarding raise "Could not start ssh forwarding: #{cmd.join(" ")}" end end if remote_port == 0 log.warn "race condition in ssh selection of remote_port" tries -= 1 if tries > 0 sleep 0.1 log.info "retrying ssh selection of remote_port" redo end raise "ssh did not assign remote_port" end end # This breaks with multiple forward requests, and it would be too hard # to coordinate among all requesting processes, so let's leave the # forwarding open: #at_exit {system "ssh -O cancel -R #{fwd} #{remote_host}"} service = self.class.new name, host: host, bind_host: bind_host, connect_host: "localhost", port: remote_port return [service, ssh] end |
#bump! ⇒ Object
141 142 143 |
# File 'lib/easy-serve/service.rb', line 141 def bump! @port += 1 unless port == 0 # should not happen end |
#serve(max_tries: 1, log: log) ⇒ Object
124 125 126 127 128 129 130 131 |
# File 'lib/easy-serve/service.rb', line 124 def serve max_tries: 1, log: log super.tap do |svr| found_addr = svr.addr(false).values_at(2,1) log.debug "#{inspect} is listening at #{found_addr.join(":")}" @port = found_addr[1] @bind_host ||= found_addr[0] end end |
#try_connect ⇒ Object
133 134 135 |
# File 'lib/easy-serve/service.rb', line 133 def try_connect TCPSocket.new(connect_host, port) end |
#try_serve ⇒ Object
137 138 139 |
# File 'lib/easy-serve/service.rb', line 137 def try_serve TCPServer.new(bind_host, port || 0) # new(nil, nil) ==> error end |
#tunnelled ⇒ Object
Returns [service, ssh_session|nil]. The service is self and ssh_session is nil, unless tunneling is appropriate, in which case the returned service is the tunnelled one, and the ssh_session is the associated ssh pipe. This is for the ‘ssh -L’ type of tunneling: a process needs to connect to a cluster of remote EasyServe processes.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/easy-serve/service/tunnelled.rb', line 16 def tunnelled return [self, nil] if ["localhost", "127.0.0.1", EasyServe.host_name].include? host if ["localhost", "127.0.0.1", "0.0.0.0"].include? bind_host rhost = "localhost" else rhost = bind_host end svr = TCPServer.new "localhost", 0 # no rescue; error here is fatal lport = svr.addr[1] svr.close ## why doesn't `ssh -L 0:host:port` work? # possible alternative: ssh -f -N -o ExitOnForwardFailure: yes cmd = [ "ssh", host, "-L", "#{lport}:#{rhost}:#{port}", "echo ok && cat" ] ssh = IO.popen cmd, "w+" ## how to tell if lport in use and retry? ssh doesn't seem to fail, ## or maybe it fails by printing a message on the remote side ssh.sync = true line = ssh.gets unless line and line.chomp == "ok" # wait for forwarding raise "Could not start ssh forwarding: #{cmd.join(" ")}" end service = TCPService.new name, bind_host: bind_host, connect_host: 'localhost', host: host, port: lport return [service, ssh] end |