spider-gazelle 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d572d56238befc1bb320a35caa722a5b5862de98
4
- data.tar.gz: 543e26733659f208fd6f2cbb8c634a2082b3a01b
3
+ metadata.gz: ce0882fd3ec8b1ae9319cff47eb1ed6504b18c2e
4
+ data.tar.gz: 9ac4f207d5fb67c773ae7775d42f61b1dfc8a309
5
5
  SHA512:
6
- metadata.gz: 053e8b2128d068779d21d5ffd8f312265895b61d7da0ccceb8dea262c2fb6026dbcd3bf4f41bdc4f7809a0d7a6bc3baf4a56be58a42a31d05e33cad4b7af1eb6
7
- data.tar.gz: 8c8a6ee3c20692c3173b2a0240818223cf4cd9c967f007db3fde7e6186f273234c81e7419906b787325c5e39ff0b6e6ae3e858e0625ad9f13de7ca8b3256638f
6
+ metadata.gz: 0cc6c0b91aaf2dd1299267df06682ea445f5d2b5811a41f83cdb708f5032805513dff7c7872371f982106154e7d5dcbe7f08d76c4506a5064b3fdc5f044cae19
7
+ data.tar.gz: cfd13c84de17f19f91832a9e29caef019c329cabf6b29de0255512f512b66ba2a44e0128598153c92e80ce3498687f9b8eddd7cc6c6fc810dda763c62393c3f8
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # spider-gazelle
2
2
 
3
- [<img src="https://codeclimate.com/github/cotag/spider-gazelle.png" />](https://codeclimate.com/github/cotag/spider-gazelle)
3
+ [<img src="https://codeclimate.com/github/cotag/spider-gazelle/badges/gpa.svg" />](https://codeclimate.com/github/cotag/spider-gazelle)
4
+ [![Build Status](https://travis-ci.org/cotag/spider-gazelle.svg?branch=master)](https://travis-ci.org/cotag/spider-gazelle)
4
5
 
5
6
 
6
7
  A fast, parallel and concurrent web server for ruby
@@ -28,7 +29,7 @@ Look out! Here comes the Spidergazelle!
28
29
 
29
30
  ## Options
30
31
 
31
- For other command line options look at [the source](/bin/sg)
32
+ For other command line options look at [the source](/lib/spider-gazelle/options.rb)
32
33
 
33
34
 
34
35
  ## Community support
data/bin/sg CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'spider-gazelle/version'
4
+ puts "Launching spider-gazelle v#{::SpiderGazelle::VERSION}"
3
5
  require 'spider-gazelle'
4
6
  SpiderGazelle::LaunchControl.instance.exec(ARGV)
@@ -79,10 +79,9 @@ module SpiderGazelle
79
79
  @logger = logger
80
80
 
81
81
  @work = method(:work)
82
- @work_complete = proc { |result|
83
- request = @processing
82
+ @work_complete = proc { |request, result|
84
83
  if request.is_async && !request.hijacked
85
- if result.is_a? Fixnum
84
+ if result.is_a?(Fixnum) && !request.defer.resolved?
86
85
  # TODO:: setup timeout for async response
87
86
  end
88
87
  else
@@ -91,7 +90,6 @@ module SpiderGazelle
91
90
  end
92
91
  }
93
92
 
94
- @async_callback = method(:async_callback)
95
93
  @queue_response = method(:queue_response)
96
94
 
97
95
  # The parser state for this instance
@@ -182,7 +180,7 @@ module SpiderGazelle
182
180
  # Parser Callbacks
183
181
  # ----------------
184
182
  def start_parsing
185
- @parsing = Request.new @thread, @app, @port, @remote_ip, @scheme, @async_callback
183
+ @parsing = Request.new @thread, @app, @port, @remote_ip, @scheme
186
184
  end
187
185
 
188
186
  REQUEST_METHOD = 'REQUEST_METHOD'.freeze
@@ -190,6 +188,7 @@ module SpiderGazelle
190
188
  @parsing.env[REQUEST_METHOD] = @state.http_method.to_s
191
189
  end
192
190
 
191
+ ASYNC = "async.callback".freeze
193
192
  def finished_parsing
194
193
  request = @parsing
195
194
  @parsing = nil
@@ -200,6 +199,12 @@ module SpiderGazelle
200
199
  @socket.stop_read
201
200
  end
202
201
 
202
+ # Process the async request in the same way as Mizuno
203
+ # See: http://polycrystal.org/2012/04/15/asynchronous_responses_in_rack.html
204
+ # Process a response that was marked as async.
205
+ request.env[ASYNC] = proc { |data|
206
+ @thread.schedule { request.defer.resolve(data) }
207
+ }
203
208
  request.upgrade = @state.upgrade?
204
209
  @requests << request
205
210
  process_next unless @processing
@@ -228,28 +233,13 @@ module SpiderGazelle
228
233
 
229
234
  EMPTY_RESPONSE = [''.freeze].freeze
230
235
  def work
236
+ request = @processing
231
237
  begin
232
- @processing.execute!
238
+ [request, request.execute!]
233
239
  rescue StandardError => e
234
240
  @logger.print_error e, 'framework error'
235
241
  @processing.keep_alive = false
236
- [500, {}, EMPTY_RESPONSE]
237
- end
238
- end
239
-
240
- # Process the async request in the same way as Mizuno
241
- # See: http://polycrystal.org/2012/04/15/asynchronous_responses_in_rack.html
242
- def async_callback(data)
243
- @thread.schedule { callback(data) }
244
- end
245
-
246
- # Process a response that was marked as async. Save the data if the request hasn't responded yet
247
- def callback(data)
248
- request = @processing
249
- if request && request.is_async
250
- request.defer.resolve(data)
251
- else
252
- @logger.warn "Received async callback and there are no pending requests. Data was:\n#{data}"
242
+ [request, [500, {}, EMPTY_RESPONSE]]
253
243
  end
254
244
  end
255
245
 
@@ -477,14 +467,15 @@ module SpiderGazelle
477
467
  end
478
468
  end
479
469
 
480
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
470
+ ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n".freeze
481
471
  def send_parsing_error
482
472
  @logger.info "Parsing error!"
473
+ @socket.stop_read
483
474
  @socket.write ERROR_400_RESPONSE
484
475
  @socket.shutdown
485
476
  end
486
477
 
487
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
478
+ ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\nContent-Length: 0\r\n\r\n".freeze
488
479
  def send_internal_error
489
480
  @logger.info "Internal error"
490
481
  @socket.stop_read
@@ -42,9 +42,8 @@ module SpiderGazelle
42
42
  SERVER_PORT = "SERVER_PORT".freeze
43
43
  REMOTE_ADDR = "REMOTE_ADDR".freeze
44
44
  RACK_URL_SCHEME = "rack.url_scheme".freeze
45
- ASYNC = "async.callback".freeze
46
45
 
47
- def initialize(thread, app, port, remote_ip, scheme, async_callback)
46
+ def initialize(thread, app, port, remote_ip, scheme)
48
47
  super(thread, thread.defer)
49
48
 
50
49
  @app = app
@@ -55,7 +54,6 @@ module SpiderGazelle
55
54
  @env[SERVER_PORT] = port
56
55
  @env[REMOTE_ADDR] = remote_ip
57
56
  @env[RACK_URL_SCHEME] = scheme
58
- @env[ASYNC] = async_callback
59
57
  end
60
58
 
61
59
 
@@ -85,6 +83,8 @@ module SpiderGazelle
85
83
  USE_HTTP2 = 'h2c'.freeze
86
84
 
87
85
 
86
+
87
+
88
88
  def execute!
89
89
  @env[CONTENT_LENGTH] = @env.delete(HTTP_CONTENT_LENGTH) || @body.length
90
90
  @env[CONTENT_TYPE] = @env.delete(HTTP_CONTENT_TYPE) || DEFAULT_TYPE
@@ -160,6 +160,9 @@ module SpiderGazelle
160
160
 
161
161
  # isolation and process mode don't mix
162
162
  options[:isolate] = false if options[:mode] == :process
163
+
164
+ # Force no_ipc mode on Windows (sockets over pipes are not working in threaded mode)
165
+ options[:mode] = :no_ipc if ::FFI::Platform.windows? && options[:mode] == :thread
163
166
  end
164
167
 
165
168
  options
@@ -0,0 +1,5 @@
1
+
2
+ module SpiderGazelle
3
+ VERSION = '2.0.2'.freeze
4
+ EXEC_NAME = 'sg'.freeze
5
+ end
@@ -2,6 +2,7 @@ require 'thread'
2
2
  require 'singleton'
3
3
 
4
4
 
5
+ require 'spider-gazelle/version'
5
6
  require 'spider-gazelle/options'
6
7
  require 'spider-gazelle/logger'
7
8
  require 'spider-gazelle/reactor'
@@ -9,8 +10,6 @@ require 'spider-gazelle/signaller'
9
10
 
10
11
 
11
12
  module SpiderGazelle
12
- VERSION = '2.0.1'.freeze
13
- EXEC_NAME = 'sg'.freeze
14
13
  INTERNAL_PIPE_BACKLOG = 128
15
14
 
16
15
  # Signaller is used to communicate:
data/spec/http1_spec.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'spider-gazelle'
1
2
  require 'spider-gazelle/gazelle/http1'
2
3
 
3
4
 
@@ -100,6 +101,53 @@ describe ::SpiderGazelle::Gazelle::Http1 do
100
101
  ])
101
102
  end
102
103
 
104
+ it "should fill out the environment properly", http1: true do
105
+ app = lambda do |env|
106
+ expect(env['REQUEST_URI']).to eq('/?test=ing')
107
+ expect(env['REQUEST_PATH']).to eq('/')
108
+ expect(env['QUERY_STRING']).to eq('test=ing')
109
+ expect(env['SERVER_NAME']).to eq('spider.gazelle.net')
110
+ expect(env['SERVER_PORT']).to eq(3000)
111
+ expect(env['REMOTE_ADDR']).to eq('127.0.0.1')
112
+ expect(env['rack.url_scheme']).to eq('http')
113
+
114
+ body = 'Hello, World!'
115
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
116
+ end
117
+
118
+ @loop.run {
119
+ @http1.load(@socket, @port, app, @app_mode, @tls)
120
+ @http1.parse("GET /?test=ing HTTP/1.1\r\nHost: spider.gazelle.net:3000\r\nConnection: Close\r\n\r\n")
121
+ }
122
+
123
+ expect(@shutdown_called).to be == 1
124
+ expect(@close_called).to be == 0
125
+ end
126
+
127
+ it "should respond with a chunked response", http1: true do
128
+ app = lambda do |env|
129
+ body = ['Hello', ',', ' World!']
130
+ [200, {'Content-Type' => 'text/plain'}, body]
131
+ end
132
+ writes = []
133
+
134
+ @loop.run {
135
+ @http1.load(@socket, @port, app, @app_mode, @tls)
136
+ @http1.parse("GET / HTTP/1.1\r\nConnection: Close\r\n\r\n")
137
+
138
+ @socket.write_cb = proc { |data|
139
+ writes << data
140
+ }
141
+ }
142
+
143
+ expect(@shutdown_called).to be == 1
144
+ expect(@close_called).to be == 0
145
+ expect(writes).to eq([
146
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n",
147
+ "5\r\nHello\r\n", "1\r\n,\r\n", "7\r\n World!\r\n", "0\r\n\r\n"
148
+ ])
149
+ end
150
+
103
151
  it "should process a single request and keep the connection open", http1: true do
104
152
  app = lambda do |env|
105
153
  body = 'Hello, World!'
@@ -129,33 +177,162 @@ describe ::SpiderGazelle::Gazelle::Http1 do
129
177
  ])
130
178
  end
131
179
 
132
- it "should fill out the environment properly", http1: true do
180
+ it "should process pipelined requests in order", http1: true do
181
+ current = 0
182
+ order = []
133
183
  app = lambda do |env|
134
- expect(env['REQUEST_URI']).to eq('/?test=ing')
135
- expect(env['REQUEST_PATH']).to eq('/')
136
- expect(env['QUERY_STRING']).to eq('test=ing')
137
- expect(env['SERVER_NAME']).to eq('spider.gazelle.net')
138
- expect(env['SERVER_PORT']).to eq(3000)
139
- expect(env['REMOTE_ADDR']).to eq('127.0.0.1')
140
- expect(env['rack.url_scheme']).to eq('http')
184
+ case env['REQUEST_PATH']
185
+ when '/1'
186
+ order << 1
187
+ current = 1
188
+ body = 'Hello, World!'
189
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
190
+ when '/2'
191
+ order << 2
192
+ current = 2
193
+ body = ['Hello,', ' World!']
194
+ [200, {'Content-Type' => 'text/plain'}, body]
195
+ when '/3'
196
+ order << 3
197
+ current = 3
198
+ body = 'Happiness'
199
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
200
+ when '/4'
201
+ order << 4
202
+ current = 4
203
+ body = 'is a warm gun'
204
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
205
+ end
206
+ end
141
207
 
142
- body = 'Hello, World!'
143
- [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
208
+ writes = []
209
+ @loop.run {
210
+ @http1.load(@socket, @port, app, @app_mode, @tls)
211
+ @http1.parse("GET /1 HTTP/1.1\r\n\r\nGET /2 HTTP/1.1\r\n\r\nGET /3 HTTP/1.1\r\n\r\n")
212
+ @http1.parse("GET /4 HTTP/1.1\r\nConnection: Close\r\n\r\n")
213
+
214
+ @socket.write_cb = proc { |data|
215
+ order << current
216
+ writes << data
217
+ }
218
+ }
219
+
220
+ expect(@shutdown_called).to be == 1
221
+ expect(@close_called).to be == 0
222
+ expect(order).to eq([1,1,1, 2,2,2,2,2, 3,3,3, 4,4,4])
223
+ expect(writes).to eq([
224
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\n",
225
+ "Hello, World!",
226
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n",
227
+ "6\r\nHello,\r\n", "7\r\n World!\r\n", "0\r\n\r\n",
228
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\n",
229
+ "Happiness",
230
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nConnection: close\r\n\r\n",
231
+ "is a warm gun"
232
+ ])
233
+ end
234
+
235
+ it "should process a single async request and close the connection", http1: true do
236
+ app = lambda do |env|
237
+ expect(env['SERVER_PORT']).to eq(80)
238
+
239
+ Thread.new do
240
+ body = 'Hello, World!'
241
+ env['async.callback'].call [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
242
+ end
243
+
244
+ throw :async
144
245
  end
246
+ writes = []
145
247
 
146
248
  @loop.run {
147
249
  @http1.load(@socket, @port, app, @app_mode, @tls)
148
- @http1.parse("GET /?test=ing HTTP/1.1\r\nHost: spider.gazelle.net:3000\r\nConnection: Close\r\n\r\n")
250
+ @http1.parse("GET / HTTP/1.1\r\nConnection: Close\r\n\r\n")
251
+
252
+ @socket.write_cb = proc { |data|
253
+ writes << data
254
+ }
149
255
  }
150
256
 
151
257
  expect(@shutdown_called).to be == 1
152
258
  expect(@close_called).to be == 0
259
+ expect(writes).to eq([
260
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nConnection: close\r\n\r\n",
261
+ "Hello, World!"
262
+ ])
153
263
  end
154
264
 
155
- it "should respond with a chunked response", http1: true do
265
+ it "should process pipelined async requests in order", http1: true do
266
+ current = 0
267
+ order = []
156
268
  app = lambda do |env|
157
- body = ['Hello', ',', ' World!']
158
- [200, {'Content-Type' => 'text/plain'}, body]
269
+ Thread.new do
270
+ env['async.callback'].call case env['REQUEST_PATH']
271
+ when '/1'
272
+ order << 1
273
+ current = 1
274
+ body = 'Hello, World!'
275
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
276
+ when '/2'
277
+ order << 2
278
+ current = 2
279
+ body = ['Hello,', ' World!']
280
+ [200, {'Content-Type' => 'text/plain'}, body]
281
+ when '/3'
282
+ order << 3
283
+ current = 3
284
+ body = 'Happiness'
285
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
286
+ when '/4'
287
+ order << 4
288
+ current = 4
289
+ body = 'is a warm gun'
290
+ [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
291
+ end
292
+ end
293
+
294
+ throw :async
295
+ end
296
+
297
+ writes = []
298
+ @loop.run {
299
+ @http1.load(@socket, @port, app, @app_mode, @tls)
300
+ @http1.parse("GET /1 HTTP/1.1\r\n\r\nGET /2 HTTP/1.1\r\n\r\nGET /3 HTTP/1.1\r\n\r\n")
301
+ @http1.parse("GET /4 HTTP/1.1\r\nConnection: Close\r\n\r\n")
302
+
303
+ @socket.write_cb = proc { |data|
304
+ order << current
305
+ writes << data
306
+ }
307
+ }
308
+
309
+ expect(@shutdown_called).to be == 1
310
+ expect(@close_called).to be == 0
311
+ expect(order).to eq([1,1,1, 2,2,2,2,2, 3,3,3, 4,4,4])
312
+ expect(writes).to eq([
313
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\n",
314
+ "Hello, World!",
315
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n",
316
+ "6\r\nHello,\r\n", "7\r\n World!\r\n", "0\r\n\r\n",
317
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 9\r\n\r\n",
318
+ "Happiness",
319
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nConnection: close\r\n\r\n",
320
+ "is a warm gun"
321
+ ])
322
+ end
323
+
324
+ it "should process a single async request and not suffer from race conditions", http1: true do
325
+ app = lambda do |env|
326
+ expect(env['SERVER_PORT']).to eq(80)
327
+
328
+ Thread.new do
329
+ body = 'Hello, World!'
330
+ env['async.callback'].call [200, {'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s}, [body]]
331
+ end
332
+
333
+ sleep 0.5
334
+
335
+ throw :async
159
336
  end
160
337
  writes = []
161
338
 
@@ -170,6 +347,12 @@ describe ::SpiderGazelle::Gazelle::Http1 do
170
347
 
171
348
  expect(@shutdown_called).to be == 1
172
349
  expect(@close_called).to be == 0
173
- expect(writes).to eq(["HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n", "5\r\nHello\r\n", "1\r\n,\r\n", "7\r\n World!\r\n", "0\r\n\r\n"])
350
+ expect(writes).to eq([
351
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nConnection: close\r\n\r\n",
352
+ "Hello, World!"
353
+ ])
354
+
355
+ # Allow the worker thread to complete so we exit cleanly
356
+ sleep 1
174
357
  end
175
358
  end
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
- require 'spider-gazelle'
4
+ require 'spider-gazelle/version'
5
5
  version = SpiderGazelle::VERSION
6
6
 
7
7
  Gem::Specification.new do |s|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spider-gazelle
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen von Takach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-23 00:00:00.000000000 Z
11
+ date: 2015-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -167,6 +167,7 @@ files:
167
167
  - lib/spider-gazelle/spider.rb
168
168
  - lib/spider-gazelle/spider/binding.rb
169
169
  - lib/spider-gazelle/upgrades/websocket.rb
170
+ - lib/spider-gazelle/version.rb
170
171
  - spec/http1_spec.rb
171
172
  - spec/rack_lock_spec.rb
172
173
  - spider-gazelle.gemspec