itrg-invoker 1.6.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.
- checksums.yaml +7 -0
- data/bin/invoker +7 -0
- data/lib/invoker/cli/pinger.rb +23 -0
- data/lib/invoker/cli/question.rb +15 -0
- data/lib/invoker/cli/tail.rb +34 -0
- data/lib/invoker/cli/tail_watcher.rb +34 -0
- data/lib/invoker/cli.rb +197 -0
- data/lib/invoker/command_worker.rb +64 -0
- data/lib/invoker/commander.rb +101 -0
- data/lib/invoker/daemon.rb +126 -0
- data/lib/invoker/dns_cache.rb +23 -0
- data/lib/invoker/errors.rb +17 -0
- data/lib/invoker/event/manager.rb +79 -0
- data/lib/invoker/ipc/add_command.rb +12 -0
- data/lib/invoker/ipc/add_http_command.rb +10 -0
- data/lib/invoker/ipc/base_command.rb +24 -0
- data/lib/invoker/ipc/client_handler.rb +26 -0
- data/lib/invoker/ipc/dns_check_command.rb +17 -0
- data/lib/invoker/ipc/list_command.rb +11 -0
- data/lib/invoker/ipc/message/list_response.rb +35 -0
- data/lib/invoker/ipc/message/tail_response.rb +10 -0
- data/lib/invoker/ipc/message.rb +170 -0
- data/lib/invoker/ipc/ping_command.rb +10 -0
- data/lib/invoker/ipc/reload_command.rb +12 -0
- data/lib/invoker/ipc/remove_command.rb +12 -0
- data/lib/invoker/ipc/server.rb +26 -0
- data/lib/invoker/ipc/tail_command.rb +11 -0
- data/lib/invoker/ipc/unix_client.rb +60 -0
- data/lib/invoker/ipc.rb +45 -0
- data/lib/invoker/logger.rb +13 -0
- data/lib/invoker/parsers/config.rb +184 -0
- data/lib/invoker/parsers/procfile.rb +86 -0
- data/lib/invoker/power/balancer.rb +133 -0
- data/lib/invoker/power/config.rb +77 -0
- data/lib/invoker/power/dns.rb +38 -0
- data/lib/invoker/power/http_parser.rb +68 -0
- data/lib/invoker/power/http_response.rb +81 -0
- data/lib/invoker/power/port_finder.rb +49 -0
- data/lib/invoker/power/power.rb +3 -0
- data/lib/invoker/power/powerup.rb +29 -0
- data/lib/invoker/power/setup/distro/arch.rb +15 -0
- data/lib/invoker/power/setup/distro/base.rb +80 -0
- data/lib/invoker/power/setup/distro/debian.rb +11 -0
- data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
- data/lib/invoker/power/setup/distro/redhat.rb +11 -0
- data/lib/invoker/power/setup/distro/ubuntu.rb +46 -0
- data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
- data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
- data/lib/invoker/power/setup/linux_setup.rb +97 -0
- data/lib/invoker/power/setup/osx_setup.rb +137 -0
- data/lib/invoker/power/setup.rb +93 -0
- data/lib/invoker/power/templates/400.html +40 -0
- data/lib/invoker/power/templates/404.html +40 -0
- data/lib/invoker/power/templates/503.html +40 -0
- data/lib/invoker/power/url_rewriter.rb +40 -0
- data/lib/invoker/process_manager.rb +201 -0
- data/lib/invoker/process_printer.rb +59 -0
- data/lib/invoker/reactor/reader.rb +65 -0
- data/lib/invoker/reactor.rb +37 -0
- data/lib/invoker/version.rb +47 -0
- data/lib/invoker.rb +151 -0
- data/spec/invoker/cli/pinger_spec.rb +22 -0
- data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
- data/spec/invoker/cli_spec.rb +27 -0
- data/spec/invoker/command_worker_spec.rb +45 -0
- data/spec/invoker/commander_spec.rb +152 -0
- data/spec/invoker/config_spec.rb +361 -0
- data/spec/invoker/daemon_spec.rb +34 -0
- data/spec/invoker/event/manager_spec.rb +67 -0
- data/spec/invoker/invoker_spec.rb +71 -0
- data/spec/invoker/ipc/client_handler_spec.rb +54 -0
- data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
- data/spec/invoker/ipc/message/list_response_spec.rb +24 -0
- data/spec/invoker/ipc/message_spec.rb +49 -0
- data/spec/invoker/ipc/unix_client_spec.rb +29 -0
- data/spec/invoker/power/balancer_spec.rb +53 -0
- data/spec/invoker/power/config_spec.rb +18 -0
- data/spec/invoker/power/http_parser_spec.rb +32 -0
- data/spec/invoker/power/http_response_spec.rb +34 -0
- data/spec/invoker/power/port_finder_spec.rb +16 -0
- data/spec/invoker/power/setup/linux_setup_spec.rb +166 -0
- data/spec/invoker/power/setup/osx_setup_spec.rb +105 -0
- data/spec/invoker/power/setup_spec.rb +4 -0
- data/spec/invoker/power/url_rewriter_spec.rb +69 -0
- data/spec/invoker/power/web_sockets_spec.rb +61 -0
- data/spec/invoker/process_manager_spec.rb +130 -0
- data/spec/invoker/reactor_spec.rb +6 -0
- data/spec/spec_helper.rb +43 -0
- metadata +376 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'iniparse'
|
2
|
+
|
3
|
+
module Invoker
|
4
|
+
module Parsers
|
5
|
+
class Config
|
6
|
+
PORT_REGEX = /\$PORT/
|
7
|
+
|
8
|
+
attr_accessor :processes, :power_config
|
9
|
+
attr_reader :filename
|
10
|
+
|
11
|
+
# initialize takes a port form cli and decrements it by 1 and sets the
|
12
|
+
# instance variable @port. This port value is used as the environment
|
13
|
+
# variable $PORT mentioned inside invoker.ini. When method pick_port gets
|
14
|
+
# fired it increments the value of port by 1, subsequently when pick_port
|
15
|
+
# again gets fired, for another command, it will again increment port
|
16
|
+
# value by 1, that way generating different ports for different commands.
|
17
|
+
def initialize(filename, port)
|
18
|
+
@filename = filename || autodetect_config_file
|
19
|
+
print_message_and_abort if invalid_config_file?
|
20
|
+
|
21
|
+
@port = port - 1
|
22
|
+
@processes = load_config
|
23
|
+
if Invoker.can_run_balancer?
|
24
|
+
@power_config = Invoker::Power::Config.load_config()
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def http_port
|
29
|
+
power_config && power_config.http_port
|
30
|
+
end
|
31
|
+
|
32
|
+
def dns_port
|
33
|
+
power_config && power_config.dns_port
|
34
|
+
end
|
35
|
+
|
36
|
+
def https_port
|
37
|
+
power_config && power_config.https_port
|
38
|
+
end
|
39
|
+
|
40
|
+
def tld
|
41
|
+
power_config && power_config.tld
|
42
|
+
end
|
43
|
+
|
44
|
+
def autorunnable_processes
|
45
|
+
process_to_run = processes.reject(&:disable_autorun)
|
46
|
+
process_to_run.sort_by { |process| process.index }
|
47
|
+
end
|
48
|
+
|
49
|
+
def process(label)
|
50
|
+
processes.detect { |pconfig| pconfig.label == label }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def autodetect_config_file
|
56
|
+
Dir.glob("{invoker.ini,Procfile.dev,Procfile}").first
|
57
|
+
end
|
58
|
+
|
59
|
+
def invalid_config_file?
|
60
|
+
@filename.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_config
|
64
|
+
@filename = to_global_file if is_global?
|
65
|
+
|
66
|
+
if is_ini?
|
67
|
+
process_ini
|
68
|
+
elsif is_procfile?
|
69
|
+
process_procfile
|
70
|
+
else
|
71
|
+
print_message_and_abort
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_ini
|
76
|
+
ini_content = File.read(@filename)
|
77
|
+
document = IniParse.parse(ini_content)
|
78
|
+
document.map do |section|
|
79
|
+
check_directory(section["directory"])
|
80
|
+
process_command_from_section(section)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def process_procfile
|
85
|
+
procfile = Invoker::Parsers::Procfile.new(@filename)
|
86
|
+
procfile.entries.map do |name, command|
|
87
|
+
section = { "label" => name, "command" => command }
|
88
|
+
process_command_from_section(section)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def print_message_and_abort
|
93
|
+
Invoker::Logger.puts("\n Invalid config file. Invoker requires an ini or a Procfile.".colorize(:red))
|
94
|
+
abort
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_command_from_section(section)
|
98
|
+
if supports_subdomain?(section)
|
99
|
+
port = pick_port(section)
|
100
|
+
if port
|
101
|
+
command = replace_port_in_command(section['command'], port)
|
102
|
+
section['port'], section['command'] = port, command
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
make_pconfig(section)
|
107
|
+
end
|
108
|
+
|
109
|
+
def pick_port(section)
|
110
|
+
if section['command'] =~ PORT_REGEX
|
111
|
+
@port += 1
|
112
|
+
elsif section['port']
|
113
|
+
section['port']
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def make_pconfig(section)
|
120
|
+
pconfig = {
|
121
|
+
label: section["label"] || section.key,
|
122
|
+
dir: expand_directory(section["directory"]),
|
123
|
+
cmd: section["command"]
|
124
|
+
}
|
125
|
+
pconfig['port'] = section['port'] if section['port']
|
126
|
+
pconfig['disable_autorun'] = section['disable_autorun'] if section['disable_autorun']
|
127
|
+
pconfig['index'] = section['index'].to_i if section['index']
|
128
|
+
section_index = pconfig['index'].to_i
|
129
|
+
if section_index
|
130
|
+
pconfig['index'] = section_index
|
131
|
+
else
|
132
|
+
pconfig['index'] = 0
|
133
|
+
end
|
134
|
+
|
135
|
+
sleep_duration = section['sleep'].to_i
|
136
|
+
if sleep_duration >= 0
|
137
|
+
pconfig['sleep_duration'] = sleep_duration
|
138
|
+
else
|
139
|
+
pconfig['sleep_duration'] = 0
|
140
|
+
end
|
141
|
+
|
142
|
+
OpenStruct.new(pconfig)
|
143
|
+
end
|
144
|
+
|
145
|
+
def supports_subdomain?(section)
|
146
|
+
(section['command'] =~ PORT_REGEX) || section['port']
|
147
|
+
end
|
148
|
+
|
149
|
+
def check_directory(app_dir)
|
150
|
+
if app_dir && !app_dir.empty? && !File.directory?(expand_directory(app_dir))
|
151
|
+
raise Invoker::Errors::InvalidConfig.new("Invalid directory #{app_dir}")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def expand_directory(app_dir)
|
156
|
+
File.expand_path(app_dir) if app_dir
|
157
|
+
end
|
158
|
+
|
159
|
+
def replace_port_in_command(command, port)
|
160
|
+
if command =~ PORT_REGEX
|
161
|
+
command.gsub(PORT_REGEX, port.to_s)
|
162
|
+
else
|
163
|
+
command
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def is_ini?
|
168
|
+
File.extname(@filename) == '.ini'
|
169
|
+
end
|
170
|
+
|
171
|
+
def is_procfile?
|
172
|
+
@filename =~ /Procfile/
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_global_file
|
176
|
+
File.join(Invoker::Power::Config.config_dir, "#{@filename}.ini")
|
177
|
+
end
|
178
|
+
|
179
|
+
def is_global?
|
180
|
+
@filename =~ /^\w+$/ && File.exist?(to_global_file)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Invoker
|
2
|
+
module Parsers
|
3
|
+
# rip off from foreman
|
4
|
+
class Procfile
|
5
|
+
# Initialize a Procfile
|
6
|
+
#
|
7
|
+
# @param [String] filename (nil) An optional filename to read from
|
8
|
+
#
|
9
|
+
def initialize(filename=nil)
|
10
|
+
@entries = []
|
11
|
+
load(filename) if filename
|
12
|
+
end
|
13
|
+
|
14
|
+
# Yield each +Procfile+ entry in order
|
15
|
+
#
|
16
|
+
def entries
|
17
|
+
return @entries unless block_given?
|
18
|
+
@entries.each do |(name, command)|
|
19
|
+
yield name, command
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieve a +Procfile+ command by name
|
24
|
+
#
|
25
|
+
# @param [String] name The name of the Procfile entry to retrieve
|
26
|
+
#
|
27
|
+
def [](name)
|
28
|
+
@entries.detect { |n,c| name == n }.last
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a +Procfile+ entry
|
32
|
+
#
|
33
|
+
# @param [String] name The name of the +Procfile+ entry to create
|
34
|
+
# @param [String] command The command of the +Procfile+ entry to create
|
35
|
+
#
|
36
|
+
def []=(name, command)
|
37
|
+
delete name
|
38
|
+
@entries << [name, command]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove a +Procfile+ entry
|
42
|
+
#
|
43
|
+
# @param [String] name The name of the +Procfile+ entry to remove
|
44
|
+
#
|
45
|
+
def delete(name)
|
46
|
+
@entries.reject! { |n,c| name == n }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Load a Procfile from a file
|
50
|
+
#
|
51
|
+
# @param [String] filename The filename of the +Procfile+ to load
|
52
|
+
#
|
53
|
+
def load(filename)
|
54
|
+
@entries.replace parse(filename)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Save a Procfile to a file
|
58
|
+
#
|
59
|
+
# @param [String] filename Save the +Procfile+ to this file
|
60
|
+
#
|
61
|
+
def save(filename)
|
62
|
+
File.open(filename, 'w') do |file|
|
63
|
+
file.puts self.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the +Procfile+ as a +String+
|
68
|
+
#
|
69
|
+
def to_s
|
70
|
+
@entries.map do |name, command|
|
71
|
+
[ name, command ].join(": ")
|
72
|
+
end.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parse(filename)
|
78
|
+
File.read(filename).gsub("\r\n","\n").split("\n").map do |line|
|
79
|
+
if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
|
80
|
+
[$1, $2]
|
81
|
+
end
|
82
|
+
end.compact
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'em-proxy'
|
2
|
+
require 'http-parser'
|
3
|
+
require "invoker/power/http_parser"
|
4
|
+
require "invoker/power/url_rewriter"
|
5
|
+
|
6
|
+
module Invoker
|
7
|
+
module Power
|
8
|
+
class InvokerHttpProxy < EventMachine::ProxyServer::Connection
|
9
|
+
attr_accessor :host, :ip, :port
|
10
|
+
def set_host(host, selected_backend)
|
11
|
+
self.host = host
|
12
|
+
self.ip = selected_backend[:host]
|
13
|
+
self.port = selected_backend[:port]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvokerHttpsProxy < InvokerHttpProxy
|
18
|
+
def post_init
|
19
|
+
super
|
20
|
+
start_tls(private_key_file: Invoker.private_key, cert_chain_file: Invoker.certificate)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Balancer
|
25
|
+
attr_accessor :connection, :http_parser, :session, :protocol, :upgraded_to
|
26
|
+
|
27
|
+
def self.run(options = {})
|
28
|
+
start_http_proxy(InvokerHttpProxy, 'http', options)
|
29
|
+
start_http_proxy(InvokerHttpsProxy, 'https', options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.start_http_proxy(proxy_class, protocol, options)
|
33
|
+
port = protocol == 'http' ? Invoker.config.http_port : Invoker.config.https_port
|
34
|
+
EventMachine.start_server('0.0.0.0', port,
|
35
|
+
proxy_class, options) do |connection|
|
36
|
+
balancer = Balancer.new(connection, protocol)
|
37
|
+
balancer.install_callbacks
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(connection, protocol)
|
42
|
+
@connection = connection
|
43
|
+
@protocol = protocol
|
44
|
+
@http_parser = HttpParser.new(protocol)
|
45
|
+
@session = nil
|
46
|
+
@upgraded_to = nil
|
47
|
+
@buffer = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def install_callbacks
|
51
|
+
http_parser.on_headers_complete { |headers| headers_received(headers) }
|
52
|
+
http_parser.on_message_complete { |full_message| complete_message_received(full_message) }
|
53
|
+
connection.on_data { |data| upstream_data(data) }
|
54
|
+
connection.on_response { |backend, data| backend_data(backend, data) }
|
55
|
+
connection.on_finish { |backend, name| frontend_disconnect(backend, name) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def complete_message_received(full_message)
|
59
|
+
connection.relay_to_servers(full_message)
|
60
|
+
http_parser.reset
|
61
|
+
end
|
62
|
+
|
63
|
+
def headers_received(headers)
|
64
|
+
if @session
|
65
|
+
return
|
66
|
+
end
|
67
|
+
@session = UUID.generate()
|
68
|
+
headers = headers.transform_keys(&:downcase)
|
69
|
+
|
70
|
+
if !headers['host'] || headers['host'].empty?
|
71
|
+
return_error_page(400)
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
dns_check_response = UrlRewriter.new.select_backend_config(headers['host'])
|
76
|
+
if dns_check_response && dns_check_response.port
|
77
|
+
connection.server(session, host: dns_check_response.ip, port: dns_check_response.port)
|
78
|
+
else
|
79
|
+
return_error_page(404)
|
80
|
+
http_parser.reset
|
81
|
+
connection.close_connection_after_writing
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def upstream_data(data)
|
86
|
+
if upgraded_to == "websocket"
|
87
|
+
data
|
88
|
+
else
|
89
|
+
append_for_http_parsing(data)
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def append_for_http_parsing(data)
|
95
|
+
http_parser << data
|
96
|
+
rescue HTTP::Parser::Error
|
97
|
+
http_parser.reset
|
98
|
+
connection.close_connection_after_writing
|
99
|
+
end
|
100
|
+
|
101
|
+
def backend_data(backend, data)
|
102
|
+
@backend_data = true
|
103
|
+
|
104
|
+
# check backend data for websockets connection. check for upgrade headers
|
105
|
+
# - Upgrade: websocket\r\n
|
106
|
+
if data =~ /Upgrade: websocket/
|
107
|
+
@upgraded_to = "websocket"
|
108
|
+
end
|
109
|
+
|
110
|
+
data
|
111
|
+
end
|
112
|
+
|
113
|
+
def frontend_disconnect(backend, name)
|
114
|
+
http_parser.reset
|
115
|
+
unless @backend_data
|
116
|
+
return_error_page(503)
|
117
|
+
end
|
118
|
+
@backend_data = false
|
119
|
+
connection.close_connection_after_writing if backend == session
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def return_error_page(status)
|
125
|
+
http_response = Invoker::Power::HttpResponse.new()
|
126
|
+
http_response.status = status
|
127
|
+
http_response['Content-Type'] = "text/html; charset=utf-8"
|
128
|
+
http_response.use_file_as_body(File.join(File.dirname(__FILE__), "templates/#{status}.html"))
|
129
|
+
connection.send_data(http_response.http_string)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Invoker
|
4
|
+
module Power
|
5
|
+
# Save and Load Invoker::Power config
|
6
|
+
class ConfigExists < StandardError; end
|
7
|
+
|
8
|
+
class Config
|
9
|
+
def self.has_config?
|
10
|
+
File.exist?(config_file)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create(options = {})
|
14
|
+
if has_config?
|
15
|
+
raise ConfigExists, "Config file already exists at location #{config_file}"
|
16
|
+
end
|
17
|
+
config = new(options)
|
18
|
+
config.save
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.delete
|
22
|
+
if File.exist?(config_file)
|
23
|
+
File.delete(config_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.config_file
|
28
|
+
File.join(Invoker.home, ".invoker", "config")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.config_dir
|
32
|
+
File.join(Invoker.home, ".invoker")
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options = {})
|
36
|
+
@config = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.load_config
|
40
|
+
config_hash = File.open(config_file, "r") { |fl| YAML.load(fl) }
|
41
|
+
new(config_hash)
|
42
|
+
end
|
43
|
+
|
44
|
+
def dns_port=(dns_port)
|
45
|
+
@config[:dns_port] = dns_port
|
46
|
+
end
|
47
|
+
|
48
|
+
def http_port=(http_port)
|
49
|
+
@config[:http_port] = http_port
|
50
|
+
end
|
51
|
+
|
52
|
+
def https_port=(https_port)
|
53
|
+
@config[:https_port] = https_port
|
54
|
+
end
|
55
|
+
|
56
|
+
def ipfw_rule_number=(ipfw_rule_number)
|
57
|
+
@config[:ipfw_rule_number] = ipfw_rule_number
|
58
|
+
end
|
59
|
+
|
60
|
+
def dns_port; @config[:dns_port]; end
|
61
|
+
def http_port; @config[:http_port]; end
|
62
|
+
def ipfw_rule_number; @config[:ipfw_rule_number]; end
|
63
|
+
def https_port; @config[:https_port]; end
|
64
|
+
|
65
|
+
def tld
|
66
|
+
@config[:tld] || Invoker.default_tld
|
67
|
+
end
|
68
|
+
|
69
|
+
def save
|
70
|
+
File.open(self.class.config_file, "w") do |fl|
|
71
|
+
YAML.dump(@config, fl)
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "logger"
|
2
|
+
require 'rubydns'
|
3
|
+
|
4
|
+
module Invoker
|
5
|
+
module Power
|
6
|
+
class DNS < RubyDNS::Server
|
7
|
+
def self.server_ports
|
8
|
+
[
|
9
|
+
[:udp, '127.0.0.1', Invoker.config.dns_port],
|
10
|
+
[:tcp, '127.0.0.1', Invoker.config.dns_port]
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@logger = ::Logger.new($stderr)
|
16
|
+
@logger.level = ::Logger::FATAL
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(name, resource_class, transaction)
|
20
|
+
if name_matches?(name) && resource_class_matches?(resource_class)
|
21
|
+
transaction.respond!("127.0.0.1")
|
22
|
+
else
|
23
|
+
transaction.fail!(:NXDomain)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def resource_class_matches?(resource_class)
|
30
|
+
resource_class == Resolv::DNS::Resource::IN::A
|
31
|
+
end
|
32
|
+
|
33
|
+
def name_matches?(name)
|
34
|
+
name =~ /.*\.#{Invoker.config.tld}/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Invoker
|
2
|
+
module Power
|
3
|
+
class HttpParser
|
4
|
+
attr_accessor :host, :parser, :protocol
|
5
|
+
|
6
|
+
def initialize(protocol)
|
7
|
+
@protocol = protocol
|
8
|
+
@parser = HTTP::Parser.new
|
9
|
+
@header = {}
|
10
|
+
initialize_message_content
|
11
|
+
parser.on_headers_complete { complete_headers_received }
|
12
|
+
parser.on_header_field { |field_name| @last_key = field_name }
|
13
|
+
parser.on_header_value { |field_value| header_value_received(field_value) }
|
14
|
+
|
15
|
+
parser.on_message_complete { complete_message_received }
|
16
|
+
end
|
17
|
+
|
18
|
+
# define a callback for invoking when complete header is parsed
|
19
|
+
def on_headers_complete(&block)
|
20
|
+
@on_headers_complete_callback = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def header_value_received(value)
|
24
|
+
@header[@last_key] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
# define a callback to invoke when a full http message is received
|
28
|
+
def on_message_complete(&block)
|
29
|
+
@on_message_complete_callback = block
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset
|
33
|
+
@header = {}
|
34
|
+
initialize_message_content
|
35
|
+
parser.reset
|
36
|
+
end
|
37
|
+
|
38
|
+
def << data
|
39
|
+
@full_message.write(data)
|
40
|
+
parser << data
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def complete_message_received
|
46
|
+
full_message_string = @full_message.string.dup
|
47
|
+
if full_message_string =~ /\r\n\r\n/
|
48
|
+
full_message_string.sub!(/\r\n\r\n/, "\r\nX_FORWARDED_PROTO: #{protocol}\r\n\r\n")
|
49
|
+
end
|
50
|
+
if @on_message_complete_callback
|
51
|
+
@on_message_complete_callback.call(full_message_string)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize_message_content
|
56
|
+
@full_message = StringIO.new
|
57
|
+
@full_message.set_encoding('ASCII-8BIT')
|
58
|
+
end
|
59
|
+
|
60
|
+
# gets invoker when complete header is received
|
61
|
+
def complete_headers_received
|
62
|
+
if @on_headers_complete_callback
|
63
|
+
@on_headers_complete_callback.call(@header)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Invoker
|
4
|
+
module Power
|
5
|
+
class HttpResponse
|
6
|
+
STATUS_MAPS = {
|
7
|
+
200 => "OK",
|
8
|
+
201 => "Created",
|
9
|
+
202 => "Accepted",
|
10
|
+
204 => "No Content",
|
11
|
+
205 => "Reset Content",
|
12
|
+
206 => "Partial Content",
|
13
|
+
301 => "Moved Permanently",
|
14
|
+
302 => "Found",
|
15
|
+
304 => "Not Modified",
|
16
|
+
400 => "Bad Request",
|
17
|
+
401 => "Unauthorized",
|
18
|
+
402 => "Payment Required",
|
19
|
+
403 => "Forbidden",
|
20
|
+
404 => "Not Found",
|
21
|
+
411 => "Length Required",
|
22
|
+
500 => "Internal Server Error",
|
23
|
+
501 => "Not Implemented",
|
24
|
+
502 => "Bad Gateway",
|
25
|
+
503 => "Service Unavailable",
|
26
|
+
504 => "Gateway Timeout"
|
27
|
+
}
|
28
|
+
|
29
|
+
HTTP_HEADER_FIELDS = [
|
30
|
+
'Cache-Control', 'Connection', 'Date',
|
31
|
+
'Pragma', 'Trailer', 'Transfer-Encoding',
|
32
|
+
'Accept-Ranges', 'Age', 'Etag',
|
33
|
+
'Server', 'Location', 'Allow',
|
34
|
+
'Content-Encoding', 'Content-Language', 'Content-Location',
|
35
|
+
'Content-MD5', 'Content-Range',
|
36
|
+
'Content-Type', 'Expires',
|
37
|
+
'Last-Modified', 'extension-header'
|
38
|
+
]
|
39
|
+
|
40
|
+
attr_accessor :header, :body, :status
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@header = {}
|
44
|
+
header['Server'] = "Invoker #{Invoker::VERSION}"
|
45
|
+
header['Date'] = Time.now.httpdate
|
46
|
+
@status = 200
|
47
|
+
@body = ""
|
48
|
+
end
|
49
|
+
|
50
|
+
def []=(key, value)
|
51
|
+
header[key] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
def use_file_as_body(file_name)
|
55
|
+
if file_name && File.exist?(file_name)
|
56
|
+
file_content = File.read(file_name)
|
57
|
+
self.body = file_content
|
58
|
+
else
|
59
|
+
raise Invoker::Errors::InvalidFile, "Invalid file as body"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def http_string
|
64
|
+
final_string = []
|
65
|
+
final_string << "HTTP/1.1 #{status} #{STATUS_MAPS[status]}"
|
66
|
+
|
67
|
+
if header['Transfer-Encoding'].nil? && body.empty?
|
68
|
+
header['Content-Length'] = body.length
|
69
|
+
end
|
70
|
+
|
71
|
+
HTTP_HEADER_FIELDS.each do |key|
|
72
|
+
if value = header[key]
|
73
|
+
final_string << "#{key}: #{value}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
final_string.join("\r\n") + "\r\n\r\n" + body
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|