spider-gazelle 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
  require 'rack' # Ruby webserver abstraction
3
5
 
4
6
  module SpiderGazelle
5
7
  class Request < ::Libuv::Q::DeferredPromise
6
- RACK_VERSION = 'rack.version'.freeze
7
- RACK_ERRORS = 'rack.errors'.freeze
8
- RACK_MULTITHREAD = "rack.multithread".freeze
9
- RACK_MULTIPROCESS = "rack.multiprocess".freeze
10
- RACK_RUN_ONCE = "rack.run_once".freeze
11
- SCRIPT_NAME = "SCRIPT_NAME".freeze
12
- EMPTY = ''.freeze
13
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
14
- HTTP_11 = "HTTP/1.1".freeze
15
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
16
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
17
- CGI_VER = "CGI/1.2".freeze
18
- SERVER = "SpiderGazelle".freeze
19
- LOCALHOST = 'localhost'.freeze
8
+ RACK_VERSION = 'rack.version'
9
+ RACK_ERRORS = 'rack.errors'
10
+ RACK_MULTITHREAD = "rack.multithread"
11
+ RACK_MULTIPROCESS = "rack.multiprocess"
12
+ RACK_RUN_ONCE = "rack.run_once"
13
+ SCRIPT_NAME = "SCRIPT_NAME"
14
+ EMPTY = ''
15
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
16
+ HTTP_11 = "HTTP/1.1"
17
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
18
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
19
+ CGI_VER = "CGI/1.2"
20
+ SERVER = "SpiderGazelle"
21
+ LOCALHOST = 'localhost'
20
22
 
21
23
 
22
24
  # TODO:: Add HTTP headers to the env and capitalise them and prefix them with HTTP_
@@ -28,7 +30,7 @@ module SpiderGazelle
28
30
  RACK_MULTIPROCESS => true, # will the app be simultaneously be invoked in a separate process?
29
31
  RACK_RUN_ONCE => false, # this isn't CGI so will always be false
30
32
 
31
- SCRIPT_NAME => ENV['SCRIPT_NAME'.freeze] || EMPTY, # The virtual path of the app base (empty if root)
33
+ SCRIPT_NAME => ENV['SCRIPT_NAME'] || EMPTY, # The virtual path of the app base (empty if root)
32
34
  SERVER_PROTOCOL => HTTP_11,
33
35
 
34
36
  GATEWAY_INTERFACE => CGI_VER,
@@ -39,17 +41,18 @@ module SpiderGazelle
39
41
  attr_reader :hijacked, :defer, :is_async
40
42
 
41
43
 
42
- SERVER_PORT = "SERVER_PORT".freeze
43
- REMOTE_ADDR = "REMOTE_ADDR".freeze
44
- RACK_URL_SCHEME = "rack.url_scheme".freeze
44
+ SERVER_PORT = "SERVER_PORT"
45
+ REMOTE_ADDR = "REMOTE_ADDR"
46
+ RACK_URL_SCHEME = "rack.url_scheme"
45
47
 
46
- def initialize(thread, app, port, remote_ip, scheme)
48
+ def initialize(thread, app, port, remote_ip, scheme, socket)
47
49
  super(thread, thread.defer)
48
50
 
51
+ @socket = socket
49
52
  @app = app
50
- @body = ''
51
- @header = ''
52
- @url = ''
53
+ @body = String.new
54
+ @header = String.new
55
+ @url = String.new
53
56
  @env = PROTO_ENV.dup
54
57
  @env[SERVER_PORT] = port
55
58
  @env[REMOTE_ADDR] = remote_ip
@@ -57,41 +60,41 @@ module SpiderGazelle
57
60
  end
58
61
 
59
62
 
60
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
61
- HTTP_CONTENT_LENGTH = "HTTP_CONTENT_LENGTH".freeze
62
- CONTENT_TYPE = "CONTENT_TYPE".freeze
63
- HTTP_CONTENT_TYPE = "HTTP_CONTENT_TYPE".freeze
64
- DEFAULT_TYPE = "text/plain".freeze
65
- REQUEST_URI= "REQUEST_URI".freeze
66
- ASCII_8BIT = "ASCII-8BIT".freeze
67
- RACK_INPUT = "rack.input".freeze
68
- PATH_INFO = "PATH_INFO".freeze
69
- REQUEST_PATH = "REQUEST_PATH".freeze
70
- QUERY_STRING = "QUERY_STRING".freeze
71
- HTTP_HOST = "HTTP_HOST".freeze
72
- COLON = ":".freeze
73
- SERVER_NAME = "SERVER_NAME".freeze
63
+ CONTENT_LENGTH = "CONTENT_LENGTH"
64
+ HTTP_CONTENT_LENGTH = "HTTP_CONTENT_LENGTH"
65
+ CONTENT_TYPE = "CONTENT_TYPE"
66
+ HTTP_CONTENT_TYPE = "HTTP_CONTENT_TYPE"
67
+ DEFAULT_TYPE = "text/plain"
68
+ REQUEST_URI= "REQUEST_URI"
69
+ ASCII_8BIT = "ASCII-8BIT"
70
+ RACK_INPUT = "rack.input"
71
+ PATH_INFO = "PATH_INFO"
72
+ REQUEST_PATH = "REQUEST_PATH"
73
+ QUERY_STRING = "QUERY_STRING"
74
+ HTTP_HOST = "HTTP_HOST"
75
+ COLON = ":"
76
+ SERVER_NAME = "SERVER_NAME"
74
77
  # Hijacking IO is supported
75
- HIJACK_P = "rack.hijack?".freeze
78
+ HIJACK_P = "rack.hijack?"
76
79
  # Callback for indicating that this socket will be hijacked
77
- HIJACK = "rack.hijack".freeze
80
+ HIJACK = "rack.hijack"
78
81
  # The object for performing IO on after hijack is called
79
- HIJACK_IO = "rack.hijack_io".freeze
80
- QUESTION_MARK = "?".freeze
82
+ HIJACK_IO = "rack.hijack_io"
83
+ QUESTION_MARK = "?"
81
84
 
82
- HTTP_UPGRADE = 'HTTP_UPGRADE'.freeze
83
- USE_HTTP2 = 'h2c'.freeze
85
+ HTTP_UPGRADE = 'HTTP_UPGRADE'
86
+ USE_HTTP2 = 'h2c'
84
87
 
85
88
 
86
89
 
87
90
 
88
91
  def execute!
89
- @env[CONTENT_LENGTH] = @env.delete(HTTP_CONTENT_LENGTH) || @body.length
92
+ @env[CONTENT_LENGTH] = @env.delete(HTTP_CONTENT_LENGTH) || @body.bytesize.to_s
90
93
  @env[CONTENT_TYPE] = @env.delete(HTTP_CONTENT_TYPE) || DEFAULT_TYPE
91
94
  @env[REQUEST_URI] = @url.freeze
92
95
 
93
96
  # For Rack::Lint on 1.9, ensure that the encoding is always for spec
94
- @body.force_encoding(ASCII_8BIT) if @body.respond_to?(:force_encoding)
97
+ @body.force_encoding(ASCII_8BIT)
95
98
  @env[RACK_INPUT] = StringIO.new @body
96
99
 
97
100
  # Break the request into its components
@@ -111,7 +114,7 @@ module SpiderGazelle
111
114
  if host = @env[HTTP_HOST]
112
115
  if colon = host.index(COLON)
113
116
  @env[SERVER_NAME] = host[0, colon]
114
- @env[SERVER_PORT] = host[colon + 1, host.bytesize].to_i
117
+ @env[SERVER_PORT] = host[colon + 1, host.bytesize]
115
118
  else
116
119
  @env[SERVER_NAME] = host
117
120
  end
@@ -119,18 +122,16 @@ module SpiderGazelle
119
122
  @env[SERVER_NAME] = LOCALHOST
120
123
  end
121
124
 
122
- # Provide hijack options if this is an upgrade request
123
- if @upgrade == true
124
- if @env[HTTP_UPGRADE] == USE_HTTP2
125
- # TODO:: implement the upgrade process here
126
- else
127
- @env[HIJACK_P] = true
128
- @env[HIJACK] = method :hijack
129
- end
125
+ if @upgrade == true && @env[HTTP_UPGRADE] == USE_HTTP2
126
+ # TODO:: implement the upgrade process here
130
127
  end
131
128
 
129
+ # Provide hijack options
130
+ @env[HIJACK_P] = true
131
+ @env[HIJACK] = proc { @env[HIJACK_IO] = @socket }
132
+
132
133
  # Execute the request
133
- # NOTE:: Catch was overloaded by Promise so this does the
134
+ # NOTE:: Catch was overloaded by Promise so this does the trick now
134
135
  resp = ruby_catch(:async) { @app.call @env }
135
136
  if resp.nil? || resp[0] == -1
136
137
  @is_async = true
@@ -143,13 +144,5 @@ module SpiderGazelle
143
144
  end
144
145
  resp
145
146
  end
146
-
147
- protected
148
-
149
- def hijack
150
- @hijacked = @loop.defer
151
- @env.delete HIJACK # don't want to hold a reference to this request object
152
- @env[HIJACK_IO] = @hijacked.promise
153
- end
154
147
  end
155
148
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'libuv'
2
4
 
3
5
  module SpiderGazelle
@@ -19,7 +21,7 @@ module SpiderGazelle
19
21
 
20
22
 
21
23
  def initialize
22
- @thread = ::Libuv::Loop.default
24
+ @thread = ::Libuv::Reactor.default
23
25
  @level = DEFAULT_LEVEL
24
26
  @write = method(:server_write)
25
27
  end
@@ -85,17 +87,12 @@ module SpiderGazelle
85
87
  end
86
88
  end
87
89
 
88
- def print_error(e, msg = '', trace = nil)
89
- msg << ":\n" unless msg.empty?
90
- msg << "#{e.message}\n"
91
- backtrace = e.backtrace if e.respond_to?(:backtrace)
92
- if backtrace
93
- msg << "#{backtrace.join("\n")}\n"
94
- elsif trace.nil?
95
- trace = caller
96
- end
97
- msg << "Caller backtrace:\n#{trace.join("\n")}\n" if trace
98
- error(msg)
90
+ def print_error(e, msg = nil, trace = nil)
91
+ message = String.new(msg || e.message)
92
+ message << "\n#{e.message}" if msg
93
+ message << "\n#{e.backtrace.join("\n")}" if e.respond_to?(:backtrace) && e.backtrace
94
+ message << "\nCaller backtrace:\n#{trace.join("\n")}" if trace
95
+ error(message)
99
96
  end
100
97
 
101
98
  # NOTE:: should only be called on reactor thread
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  require 'optparse'
3
5
  require 'spider-gazelle/logger'
@@ -10,10 +12,9 @@ module SpiderGazelle
10
12
  port: 3000,
11
13
  verbose: false,
12
14
  tls: false,
13
- backlog: 5000,
15
+ backlog: 4096,
14
16
  rackup: "#{Dir.pwd}/config.ru",
15
17
  mode: :thread,
16
- app_mode: :thread_pool,
17
18
  isolate: true
18
19
  }.freeze
19
20
 
@@ -59,12 +60,12 @@ module SpiderGazelle
59
60
  options[:rackup] = arg
60
61
  end
61
62
 
62
- opts.on "-m", "--mode MODE", MODES, "Either process, thread or no_ipc (default: process)" do |arg|
63
- options[:mode] = arg
63
+ opts.on "-rl", "--rack-lint", "enable rack lint on all requests" do
64
+ options[:lint] = true
64
65
  end
65
66
 
66
- opts.on "-a", "--app-mode MODE", APP_MODE, "How should requests be processed (default: thread_pool)" do |arg|
67
- options[:host] = arg
67
+ opts.on "-m", "--mode MODE", MODES, "Either process, thread or no_ipc (default: thread)" do |arg|
68
+ options[:mode] = arg
68
69
  end
69
70
 
70
71
  opts.on "-b", "--backlog BACKLOG", Integer, "Number of pending connections allowed (default: 5000)" do |arg|
@@ -175,7 +176,7 @@ module SpiderGazelle
175
176
 
176
177
  # Ensure there is at least one component
177
178
  # (This will occur when no options are provided)
178
- components << '' if components.empty?
179
+ components << String.new if components.empty?
179
180
 
180
181
  # Parse the commandline options
181
182
  options = []
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'libuv'
2
4
  require 'spider-gazelle/logger'
3
5
 
@@ -9,7 +11,7 @@ module SpiderGazelle
9
11
 
10
12
 
11
13
  def initialize
12
- @thread = ::Libuv::Loop.default
14
+ @thread = ::Libuv::Reactor.default
13
15
  @logger = Logger.instance
14
16
  @running = false
15
17
  @shutdown = method(:shutdown)
@@ -21,18 +23,15 @@ module SpiderGazelle
21
23
  @thread.schedule block
22
24
  else
23
25
  @running = true
24
- @thread.run { |logger|
25
- logger.progress method(:log)
26
-
27
- # Listen for the signal to shutdown
28
- @thread.signal :INT, @shutdown
29
-
26
+ @thread.notifier method(:log)
27
+ @thread.on_program_interrupt @shutdown
28
+ @thread.run { |thread|
30
29
  block.call
31
30
  }
32
31
  end
33
32
  end
34
33
 
35
- def shutdown(_ = nil)
34
+ def shutdown
36
35
  if @shutdown_called
37
36
  @logger.warn "Shutdown called twice! Callstack:\n#{caller.join("\n")}"
38
37
  return
@@ -54,12 +53,13 @@ module SpiderGazelle
54
53
  end
55
54
 
56
55
  # This is an unhandled error on the Libuv Event loop
57
- def log(level, errorid, error)
58
- msg = ''
56
+ def log(error, context, trace = nil)
57
+ msg = String.new
59
58
  if error.respond_to?(:backtrace)
60
- msg << "unhandled exception: #{error.message} (#{level} - #{errorid})"
59
+ msg << "unhandled exception: #{error.message} (#{context})"
61
60
  backtrace = error.backtrace
62
61
  msg << "\n#{backtrace.join("\n")}" if backtrace
62
+ msg << "\n#{trace.join("\n")}" if trace
63
63
  else
64
64
  msg << "unhandled exception: #{args}"
65
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'libuv'
2
4
 
3
5
 
@@ -11,7 +13,7 @@ module SpiderGazelle
11
13
 
12
14
 
13
15
  def initialize
14
- @thread = ::Libuv::Loop.default
16
+ @thread = ::Libuv::Reactor.default
15
17
  @logger = Logger.instance
16
18
 
17
19
  # This is used to check if an instance of spider-gazelle is already running
@@ -69,7 +71,7 @@ module SpiderGazelle
69
71
  end
70
72
 
71
73
  def general_failure
72
- @pipe.write "\x02Signaller general_failure\x03".freeze
74
+ @pipe.write "\x02Signaller general_failure\x03"
73
75
  rescue
74
76
  ensure
75
77
  Reactor.instance.shutdown
@@ -91,7 +93,7 @@ module SpiderGazelle
91
93
  @is_client = true
92
94
  @is_connected = true
93
95
 
94
- @logger.verbose "Client connected to SG Master".freeze
96
+ @logger.verbose "Client connected to SG Master"
95
97
 
96
98
  require 'uv-rays/buffered_tokenizer'
97
99
  @parser = ::UV::BufferedTokenizer.new({
@@ -172,6 +174,8 @@ module SpiderGazelle
172
174
  #@logger.error "Master pipe went missing: #{reason}"
173
175
  # Server most likely exited
174
176
  # We'll shutdown here
177
+ STDERR.puts "\n\npanic! #{reason.inspect}\n\n\n"
178
+ STDERR.flush
175
179
  Reactor.instance.shutdown
176
180
  end
177
181
 
@@ -188,13 +192,13 @@ module SpiderGazelle
188
192
  case result
189
193
  when :validated
190
194
  @validated.each do |old|
191
- old.write "\x02update\x03".freeze
195
+ old.write "\x02update\x03"
192
196
  end
193
197
  @validated << client
194
198
  if @validated.length > 1
195
- client.write "\x02wait\x03".freeze
199
+ client.write "\x02wait\x03"
196
200
  else
197
- client.write "\x02ready\x03".freeze
201
+ client.write "\x02ready\x03"
198
202
  end
199
203
  @logger.verbose { "Client <0x#{client.object_id.to_s(16)}> connection was validated" }
200
204
  when :close_connection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uv-rays'
2
4
 
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spider-gazelle/spider/binding' # Holds a reference to a bound port
2
4
  require 'securerandom'
3
5
 
@@ -67,13 +69,13 @@ module SpiderGazelle
67
69
  load_promise.then do
68
70
  # Check a shutdown request didn't occur as we were loading
69
71
  if @running
70
- @logger.verbose "All gazelles running".freeze
72
+ @logger.verbose "All gazelles running"
71
73
 
72
74
  # This happends on the master thread so we don't need to check
73
75
  # for the shutdown events here
74
76
  bind_application_ports
75
77
  else
76
- @logger.warn "A shutdown event occured while loading".freeze
78
+ @logger.warn "A shutdown event occured while loading"
77
79
  perform_shutdown
78
80
  end
79
81
  end
@@ -125,7 +127,7 @@ module SpiderGazelle
125
127
  rescue
126
128
  end
127
129
 
128
- shutdown = false
130
+ @shutdown = false
129
131
  check = method(:check_credentials)
130
132
  @pipe.bind(@pipe_file) do |client|
131
133
  @logger.verbose { "Gazelle <0x#{client.object_id.to_s(16)}> connection made" }
@@ -145,8 +147,8 @@ module SpiderGazelle
145
147
  rescue
146
148
  ensure
147
149
  @gazelles.delete client
148
- if !shutdown
149
- shutdown = true
150
+ if !@shutdown
151
+ @shutdown = true
150
152
  @signaller.general_failure
151
153
  end
152
154
  end
@@ -243,7 +245,7 @@ module SpiderGazelle
243
245
 
244
246
  @threads = []
245
247
  count.times do
246
- thread = ::Libuv::Loop.new
248
+ thread = ::Libuv::Reactor.new
247
249
  @threads << thread
248
250
 
249
251
  Thread.new { load_gazelle_thread(reactor, thread, mode, options) }
@@ -266,10 +268,18 @@ module SpiderGazelle
266
268
  end
267
269
 
268
270
  def load_gazelle_thread(reactor, thread, mode, options)
269
- thread.run do |logger|
270
- # Log any unhandled errors
271
- logger.progress reactor.method(:log)
272
-
271
+ # Log any unhandled errors
272
+ thread.notifier reactor.method(:log)
273
+ # Give current requests 5 seconds to complete
274
+ thread.on_program_interrupt do
275
+ timer = thread.timer {
276
+ puts "Forcing gazelle exit"
277
+ thread.stop
278
+ }
279
+ timer.unref
280
+ timer.start(5000)
281
+ end
282
+ thread.run do |thread|
273
283
  # Start the gazelle
274
284
  ::SpiderGazelle::Gazelle.new(thread, :thread).run!(options)
275
285
  end
@@ -283,9 +293,9 @@ module SpiderGazelle
283
293
  if @running
284
294
  @thread.schedule do
285
295
  if result
286
- @logger.verbose "Gazelle process exited with exit status 0".freeze
296
+ @logger.verbose "Gazelle process exited with exit status 0"
287
297
  else
288
- @logger.error "Gazelle process exited unexpectedly".freeze
298
+ @logger.error "Gazelle process exited unexpectedly"
289
299
  end
290
300
 
291
301
  @signaller.general_failure
@@ -330,6 +340,7 @@ module SpiderGazelle
330
340
 
331
341
  def shutdown_gazelles
332
342
  @bound = false
343
+ @shutdown = true
333
344
  promises = []
334
345
 
335
346
  @iterator_source.each do |mode, gazelles|