explorer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []