goliath 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of goliath might be problematic. Click here for more details.

Files changed (84) hide show
  1. data/.gitignore +15 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +66 -0
  6. data/README.md +86 -0
  7. data/Rakefile +18 -0
  8. data/examples/activerecord/config/srv.rb +7 -0
  9. data/examples/activerecord/srv.rb +37 -0
  10. data/examples/async_upload.rb +34 -0
  11. data/examples/conf_test.rb +27 -0
  12. data/examples/config/conf_test.rb +12 -0
  13. data/examples/config/echo.rb +1 -0
  14. data/examples/config/http_log.rb +7 -0
  15. data/examples/config/shared.rb +5 -0
  16. data/examples/custom_server.rb +57 -0
  17. data/examples/echo.rb +37 -0
  18. data/examples/gziped.rb +40 -0
  19. data/examples/hello_world.rb +10 -0
  20. data/examples/http_log.rb +85 -0
  21. data/examples/rack_routes.rb +44 -0
  22. data/examples/stream.rb +37 -0
  23. data/examples/valid.rb +19 -0
  24. data/goliath.gemspec +41 -0
  25. data/lib/goliath.rb +38 -0
  26. data/lib/goliath/api.rb +165 -0
  27. data/lib/goliath/application.rb +90 -0
  28. data/lib/goliath/connection.rb +94 -0
  29. data/lib/goliath/constants.rb +51 -0
  30. data/lib/goliath/env.rb +92 -0
  31. data/lib/goliath/goliath.rb +49 -0
  32. data/lib/goliath/headers.rb +37 -0
  33. data/lib/goliath/http_status_codes.rb +44 -0
  34. data/lib/goliath/plugins/latency.rb +33 -0
  35. data/lib/goliath/rack/default_mime_type.rb +30 -0
  36. data/lib/goliath/rack/default_response_format.rb +33 -0
  37. data/lib/goliath/rack/formatters/html.rb +90 -0
  38. data/lib/goliath/rack/formatters/json.rb +42 -0
  39. data/lib/goliath/rack/formatters/xml.rb +90 -0
  40. data/lib/goliath/rack/heartbeat.rb +23 -0
  41. data/lib/goliath/rack/jsonp.rb +38 -0
  42. data/lib/goliath/rack/params.rb +30 -0
  43. data/lib/goliath/rack/render.rb +66 -0
  44. data/lib/goliath/rack/tracer.rb +31 -0
  45. data/lib/goliath/rack/validation/boolean_value.rb +59 -0
  46. data/lib/goliath/rack/validation/default_params.rb +46 -0
  47. data/lib/goliath/rack/validation/numeric_range.rb +59 -0
  48. data/lib/goliath/rack/validation/request_method.rb +33 -0
  49. data/lib/goliath/rack/validation/required_param.rb +54 -0
  50. data/lib/goliath/rack/validation/required_value.rb +58 -0
  51. data/lib/goliath/rack/validation_error.rb +38 -0
  52. data/lib/goliath/request.rb +199 -0
  53. data/lib/goliath/response.rb +93 -0
  54. data/lib/goliath/runner.rb +236 -0
  55. data/lib/goliath/server.rb +149 -0
  56. data/lib/goliath/test_helper.rb +118 -0
  57. data/lib/goliath/version.rb +4 -0
  58. data/spec/integration/async_request_processing.rb +23 -0
  59. data/spec/integration/echo_spec.rb +27 -0
  60. data/spec/integration/keepalive_spec.rb +28 -0
  61. data/spec/integration/pipelining_spec.rb +43 -0
  62. data/spec/integration/valid_spec.rb +24 -0
  63. data/spec/spec_helper.rb +6 -0
  64. data/spec/unit/connection_spec.rb +59 -0
  65. data/spec/unit/env_spec.rb +55 -0
  66. data/spec/unit/headers_spec.rb +53 -0
  67. data/spec/unit/rack/default_mime_type_spec.rb +34 -0
  68. data/spec/unit/rack/formatters/json_spec.rb +54 -0
  69. data/spec/unit/rack/formatters/xml_spec.rb +66 -0
  70. data/spec/unit/rack/heartbeat_spec.rb +47 -0
  71. data/spec/unit/rack/params_spec.rb +94 -0
  72. data/spec/unit/rack/render_spec.rb +87 -0
  73. data/spec/unit/rack/validation/boolean_value_spec.rb +54 -0
  74. data/spec/unit/rack/validation/default_params_spec.rb +71 -0
  75. data/spec/unit/rack/validation/numeric_range_spec.rb +96 -0
  76. data/spec/unit/rack/validation/request_method_spec.rb +47 -0
  77. data/spec/unit/rack/validation/required_param_spec.rb +92 -0
  78. data/spec/unit/rack/validation/required_value_spec.rb +99 -0
  79. data/spec/unit/rack/validation_error_spec.rb +40 -0
  80. data/spec/unit/request_spec.rb +59 -0
  81. data/spec/unit/response_spec.rb +35 -0
  82. data/spec/unit/runner_spec.rb +129 -0
  83. data/spec/unit/server_spec.rb +137 -0
  84. metadata +409 -0
@@ -0,0 +1,93 @@
1
+ require 'goliath/headers'
2
+ require 'goliath/request'
3
+ require 'goliath/http_status_codes'
4
+ require 'time'
5
+
6
+ module Goliath
7
+ # Goliath::Response holds the information that will be sent back
8
+ # to the client.
9
+ #
10
+ # @private
11
+ class Response
12
+ # The status code to send
13
+ attr_accessor :status
14
+
15
+ # The headers to send
16
+ attr_accessor :headers
17
+
18
+ # The body to send
19
+ attr_accessor :body
20
+
21
+ SERVER = 'Server'.freeze
22
+ DATE = 'Date'.freeze
23
+
24
+ # Used to signal that a response is a streaming response
25
+ STREAMING = :goliath_stream_response
26
+
27
+ def initialize
28
+ @headers = Goliath::Headers.new
29
+ @status = 200
30
+ end
31
+
32
+ # Creates the header line for the response
33
+ #
34
+ # @return [String] The HTTP header line
35
+ def head
36
+ "HTTP/1.1 #{status} #{HTTP_STATUS_CODES[status.to_i]}\r\n"
37
+ end
38
+
39
+ # Creats the headers to be returned to the client
40
+ #
41
+ # @return [String] The HTTP headers
42
+ def headers_output
43
+ headers[SERVER] = Goliath::Request::SERVER
44
+ headers[DATE] = Time.now.httpdate
45
+
46
+ "#{headers.to_s}\r\n"
47
+ end
48
+
49
+ # Sets a set of key value pairs into the headers
50
+ #
51
+ # @param key_value_pairs [Hash] The key/value pairs to set as headers
52
+ # @return [Nil]
53
+ def headers=(key_value_pairs)
54
+ return unless key_value_pairs
55
+
56
+ key_value_pairs.each do |k, vs|
57
+ next unless vs
58
+
59
+ if vs.is_a?(String)
60
+ vs.each_line { |v| @headers[k] = v.chomp }
61
+
62
+ elsif vs.is_a?(Time)
63
+ @headers[k] = vs
64
+
65
+ else
66
+ vs.each { |v| @headers[k] = v.chomp }
67
+ end
68
+ end
69
+ end
70
+
71
+ # Used to signal that the response is closed
72
+ #
73
+ # @return [Nil]
74
+ def close
75
+ body.close if body.respond_to?(:close)
76
+ end
77
+
78
+ # Yields each portion of the response
79
+ #
80
+ # @yield [String] The header line, headers and body content
81
+ # @return [Nil]
82
+ def each
83
+ yield head
84
+ yield headers_output
85
+
86
+ if body.respond_to?(:each)
87
+ body.each { |chunk| yield chunk }
88
+ else
89
+ yield body
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,236 @@
1
+ require 'goliath/goliath'
2
+ require 'goliath/server'
3
+ require 'optparse'
4
+ require 'log4r'
5
+
6
+ module Goliath
7
+ # The Goliath::Runner is responsible for parsing any provided options, settting up the
8
+ # rack application, creating a logger, and then executing the Goliath::Server with the loaded information.
9
+ class Runner
10
+ # The address of the server @example 127.0.0.1
11
+ # @return [String] The server address
12
+ attr_accessor :address
13
+
14
+ # The port of the server @example 9000
15
+ # @return [Integer] The server port
16
+ attr_accessor :port
17
+
18
+ # Flag to determine if the server should daemonize
19
+ # @return [Boolean] True if the server should daemonize, false otherwise
20
+ attr_accessor :daemonize
21
+
22
+ # Flag to determine if the server should run in verbose mode
23
+ # @return [Boolean] True to turn on verbose mode, false otherwise
24
+ attr_accessor :verbose
25
+
26
+ # Flag to determine if the server should log to standard output
27
+ # @return [Boolean] True if the server should log to stdout, false otherwise
28
+ attr_accessor :log_stdout
29
+
30
+ # The log file for the server
31
+ # @return [String] The file the server should log too
32
+ attr_accessor :log_file
33
+
34
+ # The pid file for the server
35
+ # @return [String] The file to write the servers pid file into
36
+ attr_accessor :pid_file
37
+
38
+ # The Rack application
39
+ # @return [Object] The rack application the server will execute
40
+ attr_accessor :app
41
+
42
+ # The API application
43
+ # @return [Object] The API application the server will execute
44
+ attr_accessor :api
45
+
46
+ # The plugins the server will execute
47
+ # @return [Array] The list of plugins to be executed by the server
48
+ attr_accessor :plugins
49
+
50
+ # Any additional server options
51
+ # @return [Hash] Any options to be passed to the server
52
+ attr_accessor :app_options
53
+
54
+ # The parsed options
55
+ # @return [Hash] The options parsed by the runner
56
+ attr_reader :options
57
+
58
+ # Create a new Goliath::Runner
59
+ #
60
+ # @param argv [Array] The command line arguments
61
+ # @param api [Object | nil] The Goliath::API this runner is for, can be nil
62
+ # @return [Goliath::Runner] An initialized Goliath::Runner
63
+ def initialize(argv, api)
64
+ api.options_parser(options_parser, options) if api
65
+ options_parser.parse!(argv)
66
+
67
+ @api = api
68
+ @address = options.delete(:address)
69
+ @port = options.delete(:port)
70
+
71
+ @log_file = options.delete(:log_file)
72
+ @pid_file = options.delete(:pid_file)
73
+
74
+ @log_stdout = options.delete(:log_stdout)
75
+ @daemonize = options.delete(:daemonize)
76
+ @verbose = options.delete(:verbose)
77
+
78
+ @server_options = options
79
+ end
80
+
81
+ # Create the options parser
82
+ #
83
+ # @return [OptionParser] Creates the options parser for the runner with the default options
84
+ def options_parser
85
+ @options ||= {
86
+ :address => Goliath::Server::DEFAULT_ADDRESS,
87
+ :port => Goliath::Server::DEFAULT_PORT,
88
+
89
+ :daemonize => false,
90
+ :verbose => false,
91
+ :log_stdout => false
92
+ }
93
+
94
+ @options_parser ||= OptionParser.new do |opts|
95
+ opts.banner = "Usage: <server> [options]"
96
+
97
+ opts.separator ""
98
+ opts.separator "Server options:"
99
+
100
+ opts.on('-e', '--environment NAME', "Set the execution environment (prod, dev or test) (default: #{Goliath.env})") { |val| Goliath.env = val }
101
+
102
+ opts.on('-a', '--address HOST', "Bind to HOST address (default: #{@options[:address]})") { |addr| @options[:address] = addr }
103
+ opts.on('-p', '--port PORT', "Use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
104
+
105
+ opts.on('-l', '--log FILE', "Log to file (default: off)") { |file| @options[:log_file] = file }
106
+ opts.on('-s', '--stdout', "Log to stdout (default: #{@options[:log_stdout]})") { |v| @options[:log_stdout] = v }
107
+
108
+ opts.on('-P', '--pid FILE', "Pid file (default: off)") { |file| @options[:pid_file] = file }
109
+ opts.on('-d', '--daemonize', "Run daemonized in the background (default: #{@options[:daemonize]})") { |v| @options[:daemonize] = v }
110
+ opts.on('-v', '--verbose', "Enable verbose logging (default: #{@options[:verbose]})") { |v| @options[:verbose] = v }
111
+
112
+ opts.on('-h', '--help', 'Display help message') { show_options(opts) }
113
+ end
114
+ end
115
+
116
+ # Given a block, this will use Rack::Builder to create the application
117
+ #
118
+ # @param blk [Block] The application block to load
119
+ # @return [Object] The Rack application
120
+ def load_app(&blk)
121
+ @app = ::Rack::Builder.app(&blk)
122
+ end
123
+
124
+ # Stores the list of plugins to be used by the server
125
+ #
126
+ # @param plugins [Array] The list of plugins to use
127
+ # @return [Nil]
128
+ def load_plugins(plugins)
129
+ @plugins = plugins
130
+ end
131
+
132
+ # Create environment to run the server.
133
+ # If daemonize is set this will fork off a child and kill the runner.
134
+ #
135
+ # @return [Nil]
136
+ def run
137
+ if @daemonize
138
+ Process.fork do
139
+ Process.setsid
140
+ exit if fork
141
+
142
+ @pid_file ||= './goliath.pid'
143
+ @log_file ||= File.expand_path('goliath.log')
144
+ store_pid(Process.pid)
145
+
146
+ Dir.chdir(File.dirname(__FILE__))
147
+ File.umask(0000)
148
+
149
+ stdout_log_file = "#{File.dirname(@log_file)}/#{File.basename(@log_file)}_stdout.log"
150
+
151
+ STDIN.reopen("/dev/null")
152
+ STDOUT.reopen(stdout_log_file, "a")
153
+ STDERR.reopen(STDOUT)
154
+
155
+ run_server
156
+ end
157
+ else
158
+ run_server
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ # Output the servers options
165
+ #
166
+ # @param opts [OptionsParser] The options parser
167
+ # @return [exit] This will exit the server
168
+ def show_options(opts)
169
+ puts opts
170
+
171
+ at_exit { exit! }
172
+ exit
173
+ end
174
+
175
+ # Sets up the logging for the runner
176
+ # @return [Logger] The logger object
177
+ def setup_logger
178
+ log = Log4r::Logger.new('goliath')
179
+
180
+ log_format = Log4r::PatternFormatter.new(:pattern => "[#{Process.pid}:%l] %d :: %m")
181
+ setup_file_logger(log, log_format) if @log_file
182
+ setup_stdout_logger(log, log_format) if @log_stdout
183
+
184
+ log.level = @verbose ? Log4r::DEBUG : Log4r::INFO
185
+ log
186
+ end
187
+
188
+ # Setup file logging
189
+ #
190
+ # @param log [Logger] The logger to add file logging too
191
+ # @param log_format [Log4r::Formatter] The log format to use
192
+ # @return [Nil]
193
+ def setup_file_logger(log, log_format)
194
+ FileUtils.mkdir_p(File.dirname(@log_file))
195
+
196
+ log.add(Log4r::FileOutputter.new('fileOutput', {:filename => @log_file,
197
+ :trunc => false,
198
+ :formatter => log_format}))
199
+ end
200
+
201
+ # Setup stdout logging
202
+ #
203
+ # @param log [Logger] The logger to add stdout logging too
204
+ # @param log_format [Log4r::Formatter] The log format to use
205
+ # @return [Nil]
206
+ def setup_stdout_logger(log, log_format)
207
+ log.add(Log4r::StdoutOutputter.new('console', :formatter => log_format))
208
+ end
209
+
210
+ # Run the server
211
+ #
212
+ # @return [Nil]
213
+ def run_server
214
+ log = setup_logger
215
+
216
+ log.info("Starting server on #{@address}:#{@port} in #{Goliath.env} mode. Watch out for stones.")
217
+
218
+ server = Goliath::Server.new(@address, @port)
219
+ server.logger = log
220
+ server.app = @app
221
+ server.api = @api
222
+ server.plugins = @plugins || []
223
+ server.options = @server_options
224
+ server.start
225
+ end
226
+
227
+ # Store the servers pid into the @pid_file
228
+ #
229
+ # @param pid [Integer] The pid to store
230
+ # @return [Nil]
231
+ def store_pid(pid)
232
+ FileUtils.mkdir_p(File.dirname(@pid_file))
233
+ File.open(@pid_file, 'w') { |f| f.write(pid) }
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,149 @@
1
+ require 'em-synchrony'
2
+ require 'goliath/connection'
3
+ require 'goliath/goliath'
4
+
5
+ module Goliath
6
+ # The server is responsible for listening to the provided port and servicing the requests
7
+ #
8
+ # @private
9
+ class Server
10
+ # The address of the server @example 127.0.0.1
11
+ # @return [String] The server address
12
+ attr_accessor :address
13
+
14
+ # The port of the server @example 9000
15
+ # @return [Integer] The server port
16
+ attr_accessor :port
17
+
18
+ # The logger for the server
19
+ # @return [Logger] The logger object
20
+ attr_accessor :logger
21
+
22
+ # The Rack application
23
+ # @return [Object] The rack application the server will execute
24
+ attr_accessor :app
25
+
26
+ # The API application
27
+ # @return [Object] The API application the server will execute
28
+ attr_accessor :api
29
+
30
+ # Server status information
31
+ # @return [Hash] Server status information
32
+ attr_accessor :status
33
+
34
+ # Server configuration information
35
+ # @return [Hash] Server configuration information
36
+ attr_accessor :config
37
+
38
+ # The plugins the server will execute
39
+ # @return [Array] The list of plugins to be executed by the server
40
+ attr_accessor :plugins
41
+
42
+ # The server options
43
+ # @return [Hash] Server options
44
+ attr_accessor :options
45
+
46
+ # Default execution port
47
+ DEFAULT_PORT = 9000
48
+
49
+ # Default execution address
50
+ DEFAULT_ADDRESS = '0.0.0.0'
51
+
52
+ # Create a new Goliath::Server
53
+ #
54
+ # @param address [String] The server address (default: DEFAULT_ADDRESS)
55
+ # @param port [Integer] The server port (default: DEFAULT_PORT)
56
+ # @return [Goliath::Server] The new server object
57
+ def initialize(address = DEFAULT_ADDRESS, port = DEFAULT_PORT)
58
+ @address = address
59
+ @port = port
60
+
61
+ @status = {}
62
+ @config = {}
63
+ @plugins = []
64
+ end
65
+
66
+ # Starts the server running. This will execute the reactor, load config and plugins and
67
+ # start listening for requests
68
+ #
69
+ # @return Does not return until the server has halted.
70
+ def start
71
+ EM.synchrony do
72
+ trap("INT") { EM.stop }
73
+ trap("TERM") { EM.stop }
74
+
75
+ EM.epoll
76
+
77
+ load_config
78
+ load_plugins
79
+
80
+ EM.start_server(address, port, Goliath::Connection) do |conn|
81
+ conn.port = port
82
+ conn.app = app
83
+ conn.api = api
84
+ conn.logger = logger
85
+ conn.status = status
86
+ conn.config = config
87
+ conn.options = options
88
+ end
89
+
90
+ EM.set_effective_user("nobody") if Goliath.prod?
91
+ end
92
+ end
93
+
94
+ # Loads a configuration file
95
+ #
96
+ # @param file [String] The file to load, if not set will use the basename of $0
97
+ # @return [Nil]
98
+ def load_config(file = nil)
99
+ api_name = api.class.to_s.gsub(/(.)([A-Z])/,'\1_\2').downcase!
100
+ file ||= "#{config_dir}/#{api_name}.rb"
101
+ return unless File.exists?(file)
102
+
103
+ eval(IO.read(file))
104
+ end
105
+
106
+ # Retrieves the configuration directory for the server
107
+ #
108
+ # @return [String] THe full path to the config directory
109
+ def config_dir
110
+ if Goliath.test?
111
+ "#{File.expand_path(ENV['PWD'])}/config"
112
+ else
113
+ "#{File.expand_path(File.dirname($0))}/config"
114
+ end
115
+ end
116
+
117
+ # Import callback for configuration files
118
+ # This will trigger a call to load_config with the provided name concatenated to the config_dir
119
+ #
120
+ # @param name [String] The name of the file in config_dir to load
121
+ # @return [Nil]
122
+ def import(name)
123
+ file = "#{config_dir}/#{name}.rb"
124
+ load_config(file)
125
+ end
126
+
127
+ # The environment block handling for configuration files
128
+ #
129
+ # @param type [String|Array] The environment(s) to load the config block for
130
+ # @param blk [Block] The configuration data to load
131
+ # @return [Nil]
132
+ def environment(type, &blk)
133
+ types = [type].flatten.collect { |t| t.to_sym }
134
+ blk.call if types.include?(Goliath.env.to_sym)
135
+ end
136
+
137
+ # Executes the run method of all set plugins
138
+ #
139
+ # @return [Nil]
140
+ def load_plugins
141
+ @plugins.each do |(name, args)|
142
+ logger.info("Loading #{name.to_s}")
143
+
144
+ plugin = name.new(port, config, status, logger)
145
+ plugin.run(*args)
146
+ end
147
+ end
148
+ end
149
+ end