explorer 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ecdc58b1c28dcdbd33d92cfab008eb4252df0fb8
4
+ data.tar.gz: 7e0f277198bb44695818fc849a4f11480767d5b1
5
+ SHA512:
6
+ metadata.gz: 3aa239b18db10c9c8573b7b6116f5da84f6647ee595a8a08a9950c01d7b7e36e91d0cb729f4d9713a3c2cde9921dcdeec5d6ab87f394cc177810205e3c4732e1
7
+ data.tar.gz: 552fdfb5179d368fe5ba55a4c905da3ed728b659621a3c5ac9fcebabf6e00bd722154c89b4640b9124430ba94997001555ed2532af3f1800f42a6f3a7674f184
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in explorer.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Tim Peters
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Explorer
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'explorer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install explorer
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/explorer/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/explore ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'explorer'
4
+
5
+ Explorer::CLI.start(ARGV)
data/data/server.crt ADDED
@@ -0,0 +1,13 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICATCCAWoCCQCBCBoE900y0TANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
3
+ VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
4
+ cyBQdHkgTHRkMB4XDTE1MDIwNjE4NDYwMFoXDTE2MDIwNjE4NDYwMFowRTELMAkG
5
+ A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
6
+ IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw4tN
7
+ oW/g0IANXIqIba2pdX1ksYRrcRmZu7eTTrkWUsBMnIpqkbY9rTsvRx62ps3N5YDi
8
+ q+s0TJG2RQSvHmkQQXXlIjiKmhseF0CptH4VIlLejzW0AWvqsXMWXaCr4smNJwsg
9
+ 3MIBUOvxMZGCHHyYD2hZM+0tOdDO1XI7nsYw+HcCAwEAATANBgkqhkiG9w0BAQUF
10
+ AAOBgQB2ilxlMO3sDnEINoZ8T1++k6iiiL1k0JOO4RzG/7rudlSBkQ+Z+tpfwjdR
11
+ kmgZq5ngr+9bK7468j1Jz715OkmAQOPxo3nabp2m0WzkOanm31mROLSWju9pqdI6
12
+ Nry1c0FhrHrS9+wL35hOCX0gBrtZ3S2vDRf6e9/HQqQkfAEJRA==
13
+ -----END CERTIFICATE-----
data/data/server.key ADDED
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXgIBAAKBgQDDi02hb+DQgA1ciohtral1fWSxhGtxGZm7t5NOuRZSwEycimqR
3
+ tj2tOy9HHramzc3lgOKr6zRMkbZFBK8eaRBBdeUiOIqaGx4XQKm0fhUiUt6PNbQB
4
+ a+qxcxZdoKviyY0nCyDcwgFQ6/ExkYIcfJgPaFkz7S050M7VcjuexjD4dwIDAQAB
5
+ AoGBALM89SONEV0MgzLYKMWf8Tyoe6vb3UAGX0wZsQTv0u53JcI3fgeiLFalcWiX
6
+ VoT/EEPWJ++94GpvBcutx3bwhtxe/chtrClkd/VrfPFjal6/mhGzEUrQPsVYmVSM
7
+ zd7k+BiQBs258murClnW0q/DY1oGMDW+qD2SKAHrmJg1IJXBAkEA7TCJr0CuSqvX
8
+ BwwlR7BYt6tk0WY7a2RmN2+9lLokUmD7DEOwMk4MrGIlQO54YrWhgdA5NUyGKFJV
9
+ zSzUmHbljQJBANMNRdA3LJ8KZbZICOu/1tNhdSJ1ZdrYgD/ZkkgBPYQQVDCkP9IC
10
+ Z7/SqqtHAd0M4qkXTGiWXL8Q2fltAUgcaxMCQFsZIFiaPBbb5yuCw7LEDV8FyiQO
11
+ 4tV9azDxjtvXnmmxa9qrCLpZdc6jp3FH5h7raculAMPyzzgfX2r9Q+4HBGUCQQCn
12
+ KJUJ5BlYYgDLTeSa7Fe3FjKTN06T9fQmHioJE8poRhKYcA8IQ9VujemVPOlR7jm7
13
+ 2XMwRx9wSWWLc3WXasK3AkEA1bb1X1OFgNfcRZRRDqKDNHrfxwYArvaW18XrvB/F
14
+ iVYkvvAmNM2+lvoFqUkWzS0aNhCpR02AvxstM/fKkDxVvA==
15
+ -----END RSA PRIVATE KEY-----
data/explorer.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'explorer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "explorer"
8
+ spec.version = Explorer::VERSION
9
+ spec.authors = ["Tim Peters"]
10
+ spec.email = ["mail@darksecond.nl"]
11
+ spec.summary = %q{A pow! replacement.}
12
+ spec.description = %q{A pow! replacement written in ruby.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency 'rubydns', '~> 1.0'
25
+ spec.add_dependency 'reel', '~> 0.5'
26
+ spec.add_dependency 'thor', '~> 0.19'
27
+ spec.add_dependency 'celluloid', '~> 0.16'
28
+ spec.add_dependency 'celluloid-io', '~> 0.16'
29
+ end
@@ -0,0 +1,31 @@
1
+ require 'pty'
2
+ require 'celluloid/io'
3
+
4
+ # TODO: Wrap slave File as well
5
+ module Celluloid
6
+ module IO
7
+ module PTY
8
+ class Master < Stream
9
+ extend Forwardable
10
+
11
+ def_delegators :@io, :read_nonblock, :write_nonblock, :close, :close_read, :close_write, :closed?
12
+
13
+ def initialize(io)
14
+ super()
15
+ @io = io
16
+ end
17
+
18
+ def to_io
19
+ @io
20
+ end
21
+ end
22
+
23
+ def self.open
24
+ m,s = ::PTY.open
25
+ cm = Master.new(m)
26
+ yield [cm,s] if block_given?
27
+ [cm,s]
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/explorer.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'explorer/version'
2
+ require 'explorer/cli'
3
+ require 'explorer/server/dns'
4
+ require 'explorer/server/http'
5
+ require 'explorer/server/https'
6
+ require 'explorer/server/ipc'
7
+ require 'explorer/servers'
8
+ require 'explorer/proxy'
9
+ require 'explorer/request_stream'
10
+ require 'explorer/ipc_client'
11
+ require 'explorer/process'
12
+ require 'explorer/process_manager'
13
+ require 'explorer/log_watcher'
14
+
15
+ module Explorer
16
+ DATADIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'data'))
17
+
18
+ def self.without_bundler
19
+ if defined?(Bundler)
20
+ Bundler.with_clean_env do
21
+ yield
22
+ end
23
+ else
24
+ yield
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ require 'thor'
2
+
3
+ module Explorer
4
+ class CLI < Thor
5
+ desc "version", "Print version of explorer"
6
+ def version
7
+ puts Explorer::VERSION
8
+ end
9
+ map %w(-v --version) => :version
10
+
11
+ desc "start CONFIG", 'Start explorer'
12
+ def start(file)
13
+ servers = Servers.new hostmap: {'test.dev' => {host: 'localhost', port: 8080}}
14
+ servers.log_watcher.add(STDOUT)
15
+ servers.run
16
+ end
17
+
18
+ desc 'map-list', 'List hostmapping'
19
+ def map_list
20
+ ipc = IPCClient.new
21
+ map = ipc.hostmap_list
22
+ puts "-------------------------------------------------------"
23
+ puts "| MAP | HOST | PORT |"
24
+ map.each do |k,v|
25
+ puts "| #{k.rjust(15)} | #{v['host'].rjust(15)} | #{v['port'].to_s.rjust(15)} |"
26
+ end
27
+ puts "-------------------------------------------------------"
28
+ end
29
+
30
+ desc 'map-add DOMAIN HOST PORT', 'Add to mapping'
31
+ def map_add(domain, host, port)
32
+ ipc = IPCClient.new
33
+ ipc.hostmap_add(domain, host, port)
34
+ puts "Added #{domain} to hostmap"
35
+ end
36
+
37
+ desc 'tail', 'Tail log'
38
+ def tail
39
+ ipc = IPCClient.new
40
+ ipc.tail
41
+ end
42
+
43
+ desc 'cmd-add LABEL CMD', 'Add command'
44
+ option :dir
45
+ def cmd_add(label, cmd)
46
+ ipc = IPCClient.new
47
+ ipc.cmd_add(label, cmd, options[:dir])
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ require 'celluloid/io'
2
+ require 'json'
3
+
4
+ module Explorer
5
+ class IPCClient
6
+ include Celluloid::IO
7
+ finalizer :shutdown
8
+
9
+ def initialize(socket_path = '/tmp/explorer_ipc')
10
+ @socket_path = socket_path
11
+ @socket = UNIXSocket.open(socket_path)
12
+ end
13
+
14
+ def hostmap_list
15
+ msg = {
16
+ command: 'map-list'
17
+ }
18
+ @socket.puts msg.to_json
19
+ JSON.parse @socket.readline
20
+ end
21
+
22
+ def hostmap_add map, host, port
23
+ msg = {
24
+ command: 'map-add',
25
+ map: map,
26
+ host: host,
27
+ port: port
28
+ }
29
+ @socket.puts msg.to_json
30
+ end
31
+
32
+ def tail(io = STDOUT)
33
+ msg = {
34
+ command: 'tail'
35
+ }
36
+ @socket.puts msg.to_json
37
+
38
+ loop do
39
+ line = @socket.readline
40
+ io.puts line
41
+ end
42
+ end
43
+
44
+ def cmd_add(label, cmd, dir=nil)
45
+ msg = {
46
+ command: 'cmd-add',
47
+ label: label,
48
+ cmd: cmd,
49
+ dir: dir,
50
+ }
51
+ @socket.puts msg.to_json
52
+ end
53
+
54
+ def shutdown
55
+ @socket.close if @socket
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ module Explorer
2
+ class LogWatcher
3
+ def initialize
4
+ @watchers = []
5
+ @mutex = Mutex.new
6
+ end
7
+
8
+ # TODO: terminate
9
+
10
+ def add(watcher)
11
+ @mutex.synchronize do
12
+ @watchers << watcher
13
+ end
14
+ end
15
+
16
+ def remove(watcher)
17
+ @mutex.synchronize do
18
+ @watcher.close
19
+ @watchers.delete watcher
20
+ end
21
+ end
22
+
23
+ def log(label, line)
24
+ @mutex.synchronize do
25
+ @watchers.each do |watcher|
26
+ begin
27
+ watcher.puts "#{label}: #{line}"
28
+ rescue
29
+ remove(watcher)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,112 @@
1
+ require 'celluloid/io/pty'
2
+ module Explorer
3
+ class Process
4
+ include Celluloid::IO
5
+ finalizer :shutdown
6
+
7
+ attr_reader :label, :command, :working_dir, :state, :status
8
+ attr_reader :pid, :pgid, :pipe, :log_watcher
9
+
10
+ # Log watcher should implement a 'log' method which accepts a label and a line of text
11
+ # Log watcher should also be thread-safe or an actor
12
+ def initialize(label, command, working_dir: ENV['PWD'], log_watcher: nil)
13
+ @label = label
14
+ @command = command
15
+ @working_dir = working_dir
16
+ @log_watcher = log_watcher
17
+ @state = :stopped
18
+ @status = nil
19
+ end
20
+
21
+ def started?
22
+ @state == :started
23
+ end
24
+
25
+ def stopped?
26
+ @state == :stopped
27
+ end
28
+
29
+ def start
30
+ return Actor.current if @state == :started
31
+
32
+ @status = nil
33
+ @pipe, slave = PTY.open
34
+ slave.raw!
35
+ @pid = spawn_process(label, command, working_dir: working_dir, pipe: slave)
36
+ @pgid = ::Process.getpgid(@pid)
37
+ slave.close
38
+
39
+ @state = :started
40
+ signal :state_changed
41
+
42
+ wait_pid
43
+ async.read_pipe
44
+
45
+ return Actor.current
46
+ end
47
+
48
+ def stop(sig='INT')
49
+ return Actor.current if @state == :stopped
50
+ begin
51
+ ::Process.kill(sig, -pgid)
52
+ wait_on_stop
53
+ rescue
54
+ end
55
+ return Actor.current
56
+ end
57
+
58
+ def wait_on_stop
59
+ while @state != :stopped
60
+ wait :state_changed
61
+ end
62
+ return Actor.current
63
+ end
64
+
65
+ private
66
+
67
+ def read_pipe
68
+ loop do
69
+ line = @pipe.readline # So we don't fill up the pipe
70
+ log_watcher.log(label, line) if log_watcher
71
+ end
72
+ rescue
73
+ end
74
+
75
+ def wait_pid
76
+ actor = Actor.current
77
+ Thread.new do
78
+ ::Process.wait(pid)
79
+ actor.send(:wait_pid_done, $?) #Run in actor thread
80
+ end
81
+ end
82
+
83
+ def wait_pid_done(status)
84
+ @status = status
85
+ @pid = nil
86
+ @pgid = nil
87
+ @state = :stopped
88
+ signal :state_changed
89
+
90
+ Logger.info "Process #{label} exited with #{status}"
91
+ end
92
+
93
+ def shutdown
94
+ stop if @state == :started
95
+ end
96
+
97
+ def spawn_process(label, command, working_dir: ENV['PWD'], pipe: :out)
98
+ env = {}
99
+ options = {
100
+ chdir: working_dir,
101
+ in: :close,
102
+ out: pipe,
103
+ err: pipe,
104
+ pgroup: true,
105
+ close_others: true,
106
+ }
107
+ Explorer.without_bundler do
108
+ spawn(env, command, options)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,57 @@
1
+ module Explorer
2
+ class ProcessManager
3
+ def initialize log_watcher = nil
4
+ @log_watcher = log_watcher
5
+ @processes = {}
6
+ end
7
+
8
+ def terminate
9
+ labels.each do |label|
10
+ remove(label)
11
+ end
12
+ end
13
+
14
+ def add(label, command, working_dir: ENV['PWD'])
15
+ #TODO $PORT replacement? (or somewhere else, perhaps)
16
+ @processes[label] = Process.new(label, command, working_dir: working_dir, log_watcher: @log_watcher)
17
+ end
18
+
19
+ def remove(label)
20
+ fail "Label is unknown" unless @processes[label]
21
+ @processes[label].terminate
22
+ @processes.delete label
23
+ end
24
+
25
+ def start(label)
26
+ fail "Label is unknown" unless @processes[label]
27
+ @processes[label].start
28
+ end
29
+
30
+ def stop(label)
31
+ fail "Label is unknown" unless @processes[label]
32
+ @processes[label].stop
33
+ end
34
+
35
+ def restart(label)
36
+ stop(label)
37
+ start(label)
38
+ end
39
+
40
+ def exists?(label)
41
+ !!@processes[label]
42
+ end
43
+
44
+ def running?(label)
45
+ fail "Label is unknown" unless @processes[label]
46
+ @processes[label].started?
47
+ end
48
+
49
+ def processes
50
+ @processes.values
51
+ end
52
+
53
+ def labels
54
+ @processes.keys
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,44 @@
1
+ require 'net/http'
2
+
3
+ module Explorer
4
+ class Proxy
5
+ def initialize(host, port)
6
+ @host = host
7
+ @port = port
8
+ end
9
+
10
+ def handle(reel_request)
11
+ Net::HTTP.new(@host, @port).start do |http|
12
+ http.request(net_request(reel_request)) do |net_response|
13
+ # Map the headers to be friendly to reel (no arrays in values)
14
+ headers = Hash[net_response.to_hash.map{|k,v| [k,v.join(", ")]}]
15
+ # Delete the connection header as otherwise there will be 2
16
+ headers.delete 'connection'
17
+ if headers['transfer-encoding'] == 'chunked'
18
+ reel_request.respond net_response.code.to_i, headers
19
+ net_response.read_body do |chunk|
20
+ reel_request << chunk
21
+ end
22
+ reel_request.finish_response
23
+ else
24
+ reel_request.respond net_response.code.to_i, headers, net_response.read_body
25
+ end
26
+ end
27
+ end
28
+ rescue Errno::ECONNREFUSED
29
+ reel_request.respond 504, 'Could not connect to upstream server'
30
+ end
31
+
32
+ private
33
+
34
+ def net_request(request)
35
+ req = Net::HTTPGenericRequest.new(request.method, true, true, request.url, request.headers)
36
+ # Only stream a body if there is a body attached
37
+ # A request that has a body _must_ have either Content-Length or Transfer-Encoding
38
+ if request.headers['content-length'] || request.headers['transfer-encoding']
39
+ req.body_stream = RequestStream.new(request)
40
+ end
41
+ req
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ module Explorer
2
+ class RequestStream
3
+ attr_reader :request
4
+ def initialize(request)
5
+ @request = request
6
+ end
7
+
8
+ def readpartial(maxlen, outbuf="")
9
+ results = request.body.readpartial(maxlen)
10
+ if results.nil?
11
+ outbuf.replace ""
12
+ raise EOFError.new("End of Stream")
13
+ else
14
+ outbuf.replace results
15
+ end
16
+ outbuf
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ require 'rubydns'
2
+
3
+ module Explorer
4
+ module Server
5
+ class DNS < RubyDNS::Server
6
+ def initialize(port = 23400)
7
+ super listen: interfaces(port)
8
+ async.run
9
+ end
10
+
11
+ def process(name, resource_class, transaction)
12
+ return transaction.fail!(:NXDomain) unless name_matches?(name)
13
+
14
+ if resource_class == Resolv::DNS::Resource::IN::A
15
+ transaction.respond!('127.0.0.1')
16
+ elsif resource_class == Resolv::DNS::Resource::IN::AAAA
17
+ transaction.respond!('::1')
18
+ else
19
+ transaction.fail!(:NXDomain)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def interfaces(port)
26
+ [
27
+ [:udp, "0.0.0.0", port],
28
+ [:tcp, "0.0.0.0", port]
29
+ ]
30
+ end
31
+
32
+ def name_matches?(name)
33
+ name =~ /.*\.dev/
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ require 'reel'
2
+
3
+ module Explorer
4
+ module Server
5
+ class HTTP < Reel::Server::HTTP
6
+ def initialize(port = 23401, map={})
7
+ @map = map
8
+
9
+ super '0.0.0.0', port, {}, &method(:on_connection)
10
+ end
11
+
12
+ def on_connection(connection)
13
+ connection.each_request do |request|
14
+ handle_request(request)
15
+ end
16
+ end
17
+
18
+ def handle_request(request)
19
+ map = @map[request.headers['Host']]
20
+ if map
21
+ Proxy.new(map[:host], map[:port]).handle(request)
22
+ else
23
+ request.respond 404, "Map not found (#{request.headers['Host']})"
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ require 'reel'
2
+
3
+ module Explorer
4
+ module Server
5
+ class HTTPS < Reel::Server::HTTPS
6
+ def initialize(port = 23402, map={})
7
+ @map = map
8
+
9
+ options = {
10
+ cert: File.read(File.join(Explorer::DATADIR, 'server.crt')),
11
+ key: File.read(File.join(Explorer::DATADIR, 'server.key'))
12
+ }
13
+ super '0.0.0.0', port, options, &method(:on_connection)
14
+ end
15
+
16
+ def on_connection(connection)
17
+ connection.each_request do |request|
18
+ handle_request(request)
19
+ end
20
+ end
21
+
22
+ def handle_request(request)
23
+ map = @map[request.headers['Host']]
24
+ if map
25
+ Proxy.new(map[:host], map[:port]).handle(request)
26
+ else
27
+ request.respond 404, "Map not found (#{request.headers['Host']}) (#{@map.inspect})"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ require 'celluloid/io'
2
+ require 'json'
3
+
4
+ module Explorer
5
+ module Server
6
+ class IPC
7
+ include Celluloid::IO
8
+ finalizer :shutdown
9
+ attr_reader :socket_path, :server
10
+
11
+ def initialize(socket_path = '/tmp/explorer_ipc', servers)
12
+ @socket_path = socket_path
13
+ @server = UNIXServer.new(socket_path)
14
+ @servers = servers
15
+ async.run
16
+ end
17
+
18
+ def shutdown
19
+ if @server
20
+ @server.close
21
+ File.delete @socket_path
22
+ end
23
+ end
24
+
25
+ def run
26
+ loop { async.handle_connection @server.accept }
27
+ end
28
+
29
+ def handle_connection(socket)
30
+ loop do
31
+ json = JSON.parse socket.readline
32
+ case json['command']
33
+ when 'map-list'
34
+ socket.puts @servers.hostmap.to_json
35
+ when 'map-add'
36
+ @servers.hostmap[json['map']] = { host: json['host'], port: json['port'].to_i }
37
+ when 'tail'
38
+ @servers.log_watcher.add(socket)
39
+ when 'cmd-add'
40
+ @servers.process_manager.add(json['label'], json['cmd'], working_dir: json['dir'] || ENV['PWD'])
41
+ @servers.process_manager.start(json['label']) #TODO Refactor out?
42
+ end
43
+ end
44
+ rescue EOFError, JSON::ParserError
45
+ ensure
46
+ socket.close
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,48 @@
1
+ require 'celluloid'
2
+
3
+ module Explorer
4
+ # TODO: Hostmap should be it's own actor; immutable; or thread-safe
5
+ class Servers
6
+ attr_reader :dns_port, :http_port, :https_port, :ipc_file
7
+ attr_reader :hostmap, :process_manager, :log_watcher
8
+
9
+ def initialize dns_port: 23400, http_port: 23401, https_port: 23402, hostmap: {}, ipc_file: '/tmp/explorer_ipc'
10
+ @dns_port = dns_port
11
+ @http_port = http_port
12
+ @https_port = https_port
13
+ @ipc_file = ipc_file
14
+ @hostmap = hostmap
15
+ @log_watcher = LogWatcher.new
16
+ @process_manager = ProcessManager.new log_watcher
17
+ end
18
+
19
+ def run
20
+ # Setup trap
21
+ read, write = IO.pipe
22
+ trap(:INT) { write.puts }
23
+
24
+ # Start servers
25
+ run!
26
+
27
+ IO.select([read]) # Wait for trap
28
+
29
+ # Cleanup
30
+ terminate
31
+ end
32
+
33
+ def terminate
34
+ @group.terminate if @group
35
+ @process_manager.terminate if @process_manager
36
+ # TODO: @log_watcher.terminate if @log_watcher
37
+ end
38
+
39
+ def run!
40
+ @group = Celluloid::SupervisionGroup.new do |group|
41
+ group.supervise_as :dns, Server::DNS, dns_port
42
+ group.supervise_as :http, Server::HTTP, http_port, hostmap
43
+ group.supervise_as :https, Server::HTTPS, https_port, hostmap
44
+ group.supervise_as :ipc, Server::IPC, ipc_file, self
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Explorer
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: explorer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Peters
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubydns
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.19'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.19'
83
+ - !ruby/object:Gem::Dependency
84
+ name: celluloid
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.16'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.16'
97
+ - !ruby/object:Gem::Dependency
98
+ name: celluloid-io
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.16'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.16'
111
+ description: A pow! replacement written in ruby.
112
+ email:
113
+ - mail@darksecond.nl
114
+ executables:
115
+ - explore
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - bin/explore
125
+ - data/server.crt
126
+ - data/server.key
127
+ - explorer.gemspec
128
+ - lib/celluloid/io/pty.rb
129
+ - lib/explorer.rb
130
+ - lib/explorer/cli.rb
131
+ - lib/explorer/ipc_client.rb
132
+ - lib/explorer/log_watcher.rb
133
+ - lib/explorer/process.rb
134
+ - lib/explorer/process_manager.rb
135
+ - lib/explorer/proxy.rb
136
+ - lib/explorer/request_stream.rb
137
+ - lib/explorer/server/dns.rb
138
+ - lib/explorer/server/http.rb
139
+ - lib/explorer/server/https.rb
140
+ - lib/explorer/server/ipc.rb
141
+ - lib/explorer/servers.rb
142
+ - lib/explorer/version.rb
143
+ homepage: ''
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.4.5
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: A pow! replacement.
167
+ test_files: []