Module: Portless::Health
- Defined in:
- lib/portless/health.rb
Overview
"Is our proxy on this port?" — every proxied response carries the X-Portless-Rb header, so a HEAD probe distinguishes our proxy from any other process holding 443/80/1355. The default proxy is HTTPS, so we try a TLS handshake first (no noisy plaintext-to-TLS errors), then plain HTTP. Mirrors portless's isProxyRunning + discoverState.
Constant Summary collapse
- REQUEST =
"HEAD / HTTP/1.0\r\nHost: rb-portless.localhost\r\n\r\n"
Class Method Summary collapse
-
.discover_port ⇒ Object
The port our proxy is currently on, if any: an explicit marker file, else a probe of the usual suspects.
- .marker?(response) ⇒ Boolean
-
.probe(port, timeout) ⇒ Object
The raw probe response when it's our proxy answering, else nil.
- .probe_plain(port, timeout) ⇒ Object
- .probe_tls(port, timeout) ⇒ Object
- .proxy_running?(port, timeout: 1.0) ⇒ Boolean
-
.proxy_version(port, timeout: 1.0) ⇒ Object
The running proxy's version, read from the health header it stamps on every response.
- .read_port_file ⇒ Object
-
.version_from(response) ⇒ Object
Proxies since 0.4.0 stamp their version as the header value; older ones stamped a bare "1" — report those as "0.0.0" so any release compares newer.
Class Method Details
.discover_port ⇒ Object
The port our proxy is currently on, if any: an explicit marker file, else a probe of the usual suspects.
74 75 76 77 78 79 80 81 82 |
# File 'lib/portless/health.rb', line 74 def discover_port from_file = read_port_file return from_file if from_file && proxy_running?(from_file) candidates = [ Integer(ENV["PORTLESS_PORT"], exception: false), Constants::HTTPS_PORT, Constants::HTTP_PORT, Constants::FALLBACK_PROXY_PORT ].compact candidates.each { |port| return port if proxy_running?(port) } nil end |
.marker?(response) ⇒ Boolean
70 |
# File 'lib/portless/health.rb', line 70 def marker?(response) = response.to_s.downcase.include?(Constants::HEALTH_HEADER) |
.probe(port, timeout) ⇒ Object
The raw probe response when it's our proxy answering, else nil.
38 39 40 41 |
# File 'lib/portless/health.rb', line 38 def probe(port, timeout) response = probe_tls(port, timeout) || probe_plain(port, timeout) marker?(response) ? response : nil end |
.probe_plain(port, timeout) ⇒ Object
60 61 62 63 64 65 66 67 68 |
# File 'lib/portless/health.rb', line 60 def probe_plain(port, timeout) Socket.tcp("127.0.0.1", port, connect_timeout: timeout) do |sock| sock.write(REQUEST) sock.close_write Timeout.timeout(timeout) { sock.read(4096) } end rescue StandardError nil end |
.probe_tls(port, timeout) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/portless/health.rb', line 43 def probe_tls(port, timeout) socket = Socket.tcp("127.0.0.1", port, connect_timeout: timeout) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE ssl = OpenSSL::SSL::SSLSocket.new(socket, ctx) ssl.sync_close = true ssl.connect ssl.write(REQUEST) # Read timeout too — a port that accepts but never answers must not hang us. Timeout.timeout(timeout) { ssl.read(4096) } rescue StandardError nil ensure ssl&.close socket&.close unless ssl end |
.proxy_running?(port, timeout: 1.0) ⇒ Boolean
18 19 20 |
# File 'lib/portless/health.rb', line 18 def proxy_running?(port, timeout: 1.0) !probe(port, timeout).nil? end |
.proxy_version(port, timeout: 1.0) ⇒ Object
The running proxy's version, read from the health header it stamps on every response. Nil when no proxy answers on the port.
24 25 26 |
# File 'lib/portless/health.rb', line 24 def proxy_version(port, timeout: 1.0) version_from(probe(port, timeout)) end |
.read_port_file ⇒ Object
84 85 86 87 88 |
# File 'lib/portless/health.rb', line 84 def read_port_file Integer(File.read(State.proxy_port_file).strip, exception: false) rescue StandardError nil end |
.version_from(response) ⇒ Object
Proxies since 0.4.0 stamp their version as the header value; older ones stamped a bare "1" — report those as "0.0.0" so any release compares newer.
30 31 32 33 34 35 |
# File 'lib/portless/health.rb', line 30 def version_from(response) value = response.to_s[/^#{Regexp.escape(Constants::HEALTH_HEADER)}:\s*(\S+)/i, 1] return nil unless value value.match?(/\A\d+\.\d+/) ? value : "0.0.0" end |