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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/bin/invoker +7 -0
  3. data/lib/invoker/cli/pinger.rb +23 -0
  4. data/lib/invoker/cli/question.rb +15 -0
  5. data/lib/invoker/cli/tail.rb +34 -0
  6. data/lib/invoker/cli/tail_watcher.rb +34 -0
  7. data/lib/invoker/cli.rb +197 -0
  8. data/lib/invoker/command_worker.rb +64 -0
  9. data/lib/invoker/commander.rb +101 -0
  10. data/lib/invoker/daemon.rb +126 -0
  11. data/lib/invoker/dns_cache.rb +23 -0
  12. data/lib/invoker/errors.rb +17 -0
  13. data/lib/invoker/event/manager.rb +79 -0
  14. data/lib/invoker/ipc/add_command.rb +12 -0
  15. data/lib/invoker/ipc/add_http_command.rb +10 -0
  16. data/lib/invoker/ipc/base_command.rb +24 -0
  17. data/lib/invoker/ipc/client_handler.rb +26 -0
  18. data/lib/invoker/ipc/dns_check_command.rb +17 -0
  19. data/lib/invoker/ipc/list_command.rb +11 -0
  20. data/lib/invoker/ipc/message/list_response.rb +35 -0
  21. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  22. data/lib/invoker/ipc/message.rb +170 -0
  23. data/lib/invoker/ipc/ping_command.rb +10 -0
  24. data/lib/invoker/ipc/reload_command.rb +12 -0
  25. data/lib/invoker/ipc/remove_command.rb +12 -0
  26. data/lib/invoker/ipc/server.rb +26 -0
  27. data/lib/invoker/ipc/tail_command.rb +11 -0
  28. data/lib/invoker/ipc/unix_client.rb +60 -0
  29. data/lib/invoker/ipc.rb +45 -0
  30. data/lib/invoker/logger.rb +13 -0
  31. data/lib/invoker/parsers/config.rb +184 -0
  32. data/lib/invoker/parsers/procfile.rb +86 -0
  33. data/lib/invoker/power/balancer.rb +133 -0
  34. data/lib/invoker/power/config.rb +77 -0
  35. data/lib/invoker/power/dns.rb +38 -0
  36. data/lib/invoker/power/http_parser.rb +68 -0
  37. data/lib/invoker/power/http_response.rb +81 -0
  38. data/lib/invoker/power/port_finder.rb +49 -0
  39. data/lib/invoker/power/power.rb +3 -0
  40. data/lib/invoker/power/powerup.rb +29 -0
  41. data/lib/invoker/power/setup/distro/arch.rb +15 -0
  42. data/lib/invoker/power/setup/distro/base.rb +80 -0
  43. data/lib/invoker/power/setup/distro/debian.rb +11 -0
  44. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  45. data/lib/invoker/power/setup/distro/redhat.rb +11 -0
  46. data/lib/invoker/power/setup/distro/ubuntu.rb +46 -0
  47. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  48. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  49. data/lib/invoker/power/setup/linux_setup.rb +97 -0
  50. data/lib/invoker/power/setup/osx_setup.rb +137 -0
  51. data/lib/invoker/power/setup.rb +93 -0
  52. data/lib/invoker/power/templates/400.html +40 -0
  53. data/lib/invoker/power/templates/404.html +40 -0
  54. data/lib/invoker/power/templates/503.html +40 -0
  55. data/lib/invoker/power/url_rewriter.rb +40 -0
  56. data/lib/invoker/process_manager.rb +201 -0
  57. data/lib/invoker/process_printer.rb +59 -0
  58. data/lib/invoker/reactor/reader.rb +65 -0
  59. data/lib/invoker/reactor.rb +37 -0
  60. data/lib/invoker/version.rb +47 -0
  61. data/lib/invoker.rb +151 -0
  62. data/spec/invoker/cli/pinger_spec.rb +22 -0
  63. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  64. data/spec/invoker/cli_spec.rb +27 -0
  65. data/spec/invoker/command_worker_spec.rb +45 -0
  66. data/spec/invoker/commander_spec.rb +152 -0
  67. data/spec/invoker/config_spec.rb +361 -0
  68. data/spec/invoker/daemon_spec.rb +34 -0
  69. data/spec/invoker/event/manager_spec.rb +67 -0
  70. data/spec/invoker/invoker_spec.rb +71 -0
  71. data/spec/invoker/ipc/client_handler_spec.rb +54 -0
  72. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  73. data/spec/invoker/ipc/message/list_response_spec.rb +24 -0
  74. data/spec/invoker/ipc/message_spec.rb +49 -0
  75. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  76. data/spec/invoker/power/balancer_spec.rb +53 -0
  77. data/spec/invoker/power/config_spec.rb +18 -0
  78. data/spec/invoker/power/http_parser_spec.rb +32 -0
  79. data/spec/invoker/power/http_response_spec.rb +34 -0
  80. data/spec/invoker/power/port_finder_spec.rb +16 -0
  81. data/spec/invoker/power/setup/linux_setup_spec.rb +166 -0
  82. data/spec/invoker/power/setup/osx_setup_spec.rb +105 -0
  83. data/spec/invoker/power/setup_spec.rb +4 -0
  84. data/spec/invoker/power/url_rewriter_spec.rb +69 -0
  85. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  86. data/spec/invoker/process_manager_spec.rb +130 -0
  87. data/spec/invoker/reactor_spec.rb +6 -0
  88. data/spec/spec_helper.rb +43 -0
  89. 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