spider-gazelle 1.2.0 → 2.0.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 +4 -4
- data/bin/sg +1 -64
- data/lib/rack/handler/spider-gazelle.rb +17 -26
- data/lib/rack/lock_patch.rb +27 -27
- data/lib/spider-gazelle.rb +165 -16
- data/lib/spider-gazelle/gazelle.rb +151 -134
- data/lib/spider-gazelle/gazelle/app_store.rb +86 -0
- data/lib/spider-gazelle/gazelle/http1.rb +496 -0
- data/lib/spider-gazelle/gazelle/request.rb +155 -0
- data/lib/spider-gazelle/logger.rb +122 -0
- data/lib/spider-gazelle/options.rb +213 -0
- data/lib/spider-gazelle/reactor.rb +69 -0
- data/lib/spider-gazelle/signaller.rb +214 -0
- data/lib/spider-gazelle/signaller/signal_parser.rb +66 -0
- data/lib/spider-gazelle/spider.rb +305 -343
- data/lib/spider-gazelle/spider/binding.rb +80 -0
- data/lib/spider-gazelle/upgrades/websocket.rb +92 -88
- data/spec/http1_spec.rb +173 -0
- data/spec/rack_lock_spec.rb +97 -97
- data/spider-gazelle.gemspec +6 -6
- metadata +24 -17
- data/lib/spider-gazelle/app_store.rb +0 -64
- data/lib/spider-gazelle/binding.rb +0 -53
- data/lib/spider-gazelle/connection.rb +0 -371
- data/lib/spider-gazelle/const.rb +0 -206
- data/lib/spider-gazelle/request.rb +0 -103
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module SpiderGazelle
|
4
|
+
class Gazelle
|
5
|
+
module AppStore
|
6
|
+
@apps = []
|
7
|
+
@loaded = {}
|
8
|
+
@critical = Mutex.new
|
9
|
+
@logger = Logger.instance
|
10
|
+
|
11
|
+
# Load an app and assign it an ID
|
12
|
+
def self.load(rackup, options)
|
13
|
+
begin
|
14
|
+
@critical.synchronize {
|
15
|
+
return if @loaded[rackup]
|
16
|
+
|
17
|
+
app, opts = ::Rack::Builder.parse_file(rackup)
|
18
|
+
tls = configure_tls(options)
|
19
|
+
|
20
|
+
val = [app, options[:app_mode], options[:port], tls]
|
21
|
+
@apps << val
|
22
|
+
@loaded[rackup] = val
|
23
|
+
}
|
24
|
+
rescue Exception => e
|
25
|
+
# Prevent other threads from trying to load this too (might be in threaded mode)
|
26
|
+
@loaded[rackup] = true
|
27
|
+
@logger.print_error(e, "loading rackup #{rackup}")
|
28
|
+
Reactor.instance.shutdown
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add an already loaded application
|
33
|
+
def self.add(app, options)
|
34
|
+
@critical.synchronize {
|
35
|
+
obj_id = app.object_id
|
36
|
+
return if @loaded[obj_id]
|
37
|
+
|
38
|
+
id = @apps.length
|
39
|
+
tls = configure_tls(options)
|
40
|
+
|
41
|
+
val = [app, options[:app_mode], options[:port], tls]
|
42
|
+
@apps << val
|
43
|
+
@loaded[obj_id] = val
|
44
|
+
|
45
|
+
id
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Lookup an application
|
50
|
+
def self.lookup(app)
|
51
|
+
@loaded[app.to_s]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get an app using the id directly
|
55
|
+
def self.get(id)
|
56
|
+
@apps[id.to_i]
|
57
|
+
end
|
58
|
+
|
59
|
+
PROTOCOLS = ['h2'.freeze, 'http/1.1'.freeze].freeze
|
60
|
+
FALLBACK = 'http/1.1'.freeze
|
61
|
+
def self.configure_tls(opts)
|
62
|
+
return false unless opts[:tls]
|
63
|
+
|
64
|
+
tls = {
|
65
|
+
protocols: PROTOCOLS,
|
66
|
+
fallback: FALLBACK
|
67
|
+
}
|
68
|
+
tls[:verify_peer] = true if opts[:verify_peer]
|
69
|
+
tls[:ciphers] = opts[:ciphers] if opts[:ciphers]
|
70
|
+
|
71
|
+
# NOTE:: Blocking reads however only during load so it's OK
|
72
|
+
private_key = opts[:private_key]
|
73
|
+
if private_key
|
74
|
+
tls[:private_key] = ::FFI::MemoryPointer.from_string(File.read(private_key))
|
75
|
+
end
|
76
|
+
|
77
|
+
cert_chain = opts[:cert_chain]
|
78
|
+
if cert_chain
|
79
|
+
tls[:cert_chain] = ::FFI::MemoryPointer.from_string(File.read(cert_chain))
|
80
|
+
end
|
81
|
+
|
82
|
+
tls
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,496 @@
|
|
1
|
+
|
2
|
+
require 'http-parser' # C based, fast, http parser
|
3
|
+
require 'spider-gazelle/gazelle/request'
|
4
|
+
|
5
|
+
|
6
|
+
module SpiderGazelle
|
7
|
+
class Gazelle
|
8
|
+
class Http1
|
9
|
+
class Callbacks
|
10
|
+
def initialize
|
11
|
+
@parser = ::HttpParser::Parser.new self
|
12
|
+
@logger = Logger.instance
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
attr_accessor :connection
|
17
|
+
attr_reader :parser
|
18
|
+
|
19
|
+
|
20
|
+
def on_message_begin(parser)
|
21
|
+
@connection.start_parsing
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_url(parser, url)
|
25
|
+
@connection.parsing.url << url
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_header_field(parser, header)
|
29
|
+
req = @connection.parsing
|
30
|
+
req.header.frozen? ? req.header = header : req.header << header
|
31
|
+
end
|
32
|
+
|
33
|
+
DASH = '-'.freeze
|
34
|
+
UNDERSCORE = '_'.freeze
|
35
|
+
HTTP_META = 'HTTP_'.freeze
|
36
|
+
COMMA = ', '.freeze
|
37
|
+
|
38
|
+
def on_header_value(parser, value)
|
39
|
+
req = @connection.parsing
|
40
|
+
if req.header.frozen?
|
41
|
+
req.env[req.header] << value
|
42
|
+
else
|
43
|
+
header = req.header
|
44
|
+
header.upcase!
|
45
|
+
header.gsub!(DASH, UNDERSCORE)
|
46
|
+
header.prepend(HTTP_META)
|
47
|
+
header.freeze
|
48
|
+
if req.env[header]
|
49
|
+
req.env[header] << COMMA
|
50
|
+
req.env[header] << value
|
51
|
+
else
|
52
|
+
req.env[header] = value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_headers_complete(parser)
|
58
|
+
@connection.headers_complete
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_body(parser, data)
|
62
|
+
@connection.parsing.body << data
|
63
|
+
end
|
64
|
+
|
65
|
+
def on_message_complete(parser)
|
66
|
+
@connection.finished_parsing
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
Hijack = Struct.new :socket, :env
|
72
|
+
|
73
|
+
|
74
|
+
def initialize(return_method, callbacks, thread, logger)
|
75
|
+
# The HTTP parser callbacks object for this thread
|
76
|
+
@return_method = return_method
|
77
|
+
@callbacks = callbacks
|
78
|
+
@thread = thread
|
79
|
+
@logger = logger
|
80
|
+
|
81
|
+
@work = method(:work)
|
82
|
+
@work_complete = proc { |result|
|
83
|
+
request = @processing
|
84
|
+
if request.is_async && !request.hijacked
|
85
|
+
if result.is_a? Fixnum
|
86
|
+
# TODO:: setup timeout for async response
|
87
|
+
end
|
88
|
+
else
|
89
|
+
# Complete the current request
|
90
|
+
request.defer.resolve(result)
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
@async_callback = method(:async_callback)
|
95
|
+
@queue_response = method(:queue_response)
|
96
|
+
|
97
|
+
# The parser state for this instance
|
98
|
+
@state = ::HttpParser::Parser.new_instance do |inst|
|
99
|
+
inst.type = :request
|
100
|
+
end
|
101
|
+
|
102
|
+
# The request and response queues
|
103
|
+
@requests = []
|
104
|
+
@responses = []
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
attr_reader :parsing
|
109
|
+
|
110
|
+
|
111
|
+
def self.on_progress(data, socket); end
|
112
|
+
DUMMY_PROGRESS = self.method :on_progress
|
113
|
+
|
114
|
+
HTTP = 'http'.freeze
|
115
|
+
HTTPS = 'https'.freeze
|
116
|
+
|
117
|
+
def load(socket, port, app, app_mode, tls)
|
118
|
+
@socket = socket
|
119
|
+
@port = port
|
120
|
+
@app = app
|
121
|
+
@mode = app_mode
|
122
|
+
|
123
|
+
case @mode
|
124
|
+
when :thread_pool
|
125
|
+
@exec = method :exec_on_thread_pool
|
126
|
+
when :fiber_pool
|
127
|
+
# TODO:: Implement these modes
|
128
|
+
@exec = method :critical_error
|
129
|
+
when :libuv
|
130
|
+
@exec = method :critical_error
|
131
|
+
when :eventmachine
|
132
|
+
@exec = method :critical_error
|
133
|
+
when :celluloid
|
134
|
+
@exec = method :critical_error
|
135
|
+
end
|
136
|
+
|
137
|
+
@remote_ip = socket.peername[0]
|
138
|
+
@scheme = tls ? HTTPS : HTTP
|
139
|
+
|
140
|
+
set_on_close(socket)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Only close the socket we are meaning to close
|
144
|
+
def set_on_close(socket)
|
145
|
+
socket.finally { on_close if socket == @socket }
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_close
|
149
|
+
# Unlink the progress callback (prevent funny business)
|
150
|
+
@socket.progress DUMMY_PROGRESS
|
151
|
+
@socket.storage = nil
|
152
|
+
reset
|
153
|
+
@return_method.call(self)
|
154
|
+
end
|
155
|
+
alias_method :unlink, :on_close
|
156
|
+
|
157
|
+
def reset
|
158
|
+
@app = nil
|
159
|
+
@socket = nil
|
160
|
+
@remote_ip = nil
|
161
|
+
|
162
|
+
# Safe to leave these
|
163
|
+
# @port = nil
|
164
|
+
# @mode = nil
|
165
|
+
# @scheme = nil
|
166
|
+
|
167
|
+
@processing = nil
|
168
|
+
@transmitting = nil
|
169
|
+
|
170
|
+
@requests.clear
|
171
|
+
@responses.clear
|
172
|
+
@state.reset!
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse(data)
|
176
|
+
# This works as we only ever call this from a single thread
|
177
|
+
@callbacks.connection = self
|
178
|
+
parsing_error if @callbacks.parser.parse(@state, data)
|
179
|
+
end
|
180
|
+
|
181
|
+
# ----------------
|
182
|
+
# Parser Callbacks
|
183
|
+
# ----------------
|
184
|
+
def start_parsing
|
185
|
+
@parsing = Request.new @thread, @app, @port, @remote_ip, @scheme, @async_callback
|
186
|
+
end
|
187
|
+
|
188
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
189
|
+
def headers_complete
|
190
|
+
@parsing.env[REQUEST_METHOD] = @state.http_method.to_s
|
191
|
+
end
|
192
|
+
|
193
|
+
def finished_parsing
|
194
|
+
request = @parsing
|
195
|
+
@parsing = nil
|
196
|
+
|
197
|
+
if !@state.keep_alive?
|
198
|
+
request.keep_alive = false
|
199
|
+
# We don't expect any more data
|
200
|
+
@socket.stop_read
|
201
|
+
end
|
202
|
+
|
203
|
+
request.upgrade = @state.upgrade?
|
204
|
+
@requests << request
|
205
|
+
process_next unless @processing
|
206
|
+
end
|
207
|
+
|
208
|
+
# ------------------
|
209
|
+
# Request Processing
|
210
|
+
# ------------------
|
211
|
+
def process_next
|
212
|
+
@processing = @requests.shift
|
213
|
+
if @processing
|
214
|
+
@exec.call
|
215
|
+
@processing.then @queue_response
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
WORKER_ERROR = proc { |error|
|
220
|
+
@logger.print_error error, 'critical error'
|
221
|
+
Reactor.instance.shutdown
|
222
|
+
}
|
223
|
+
def exec_on_thread_pool
|
224
|
+
promise = @thread.work @work
|
225
|
+
promise.then @work_complete
|
226
|
+
promise.catch WORKER_ERROR
|
227
|
+
end
|
228
|
+
|
229
|
+
EMPTY_RESPONSE = [''.freeze].freeze
|
230
|
+
def work
|
231
|
+
begin
|
232
|
+
@processing.execute!
|
233
|
+
rescue StandardError => e
|
234
|
+
@logger.print_error e, 'framework error'
|
235
|
+
@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}"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
# ----------------
|
258
|
+
# Response Sending
|
259
|
+
# ----------------
|
260
|
+
def queue_response(result)
|
261
|
+
@responses << [@processing, result]
|
262
|
+
send_next_response unless @transmitting
|
263
|
+
|
264
|
+
# Processing will be set to nil if the array is empty
|
265
|
+
process_next
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
HEAD = 'HEAD'.freeze
|
270
|
+
ETAG = 'ETag'.freeze
|
271
|
+
HTTP_ETAG = 'HTTP_ETAG'.freeze
|
272
|
+
CONTENT_LENGTH2 = 'Content-Length'.freeze
|
273
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
|
274
|
+
CHUNKED = 'chunked'.freeze
|
275
|
+
ZERO = '0'.freeze
|
276
|
+
NOT_MODIFIED_304 = "HTTP/1.1 304 Not Modified\r\n".freeze
|
277
|
+
|
278
|
+
def send_next_response
|
279
|
+
request, result = @responses.shift
|
280
|
+
@transmitting = request
|
281
|
+
return unless request
|
282
|
+
|
283
|
+
if request.hijacked
|
284
|
+
# Unlink the management of the socket
|
285
|
+
# Then forward the raw socket to the upgrade handler
|
286
|
+
socket = @socket
|
287
|
+
unlink
|
288
|
+
request.hijacked.resolve Hijack.new(socket, request.env)
|
289
|
+
|
290
|
+
elsif @socket.closed
|
291
|
+
body = result[2]
|
292
|
+
body.close if body.respond_to?(:close)
|
293
|
+
else
|
294
|
+
status, headers, body = result
|
295
|
+
send_body = request.env[REQUEST_METHOD] != HEAD
|
296
|
+
|
297
|
+
# If a file, stream the body in a non-blocking fashion
|
298
|
+
if body.respond_to? :to_path
|
299
|
+
file = @thread.file body.to_path, File::RDONLY
|
300
|
+
|
301
|
+
# Send the body in parallel without blocking the next request in dev
|
302
|
+
# Also if this is a head request we still want the body closed
|
303
|
+
body.close if body.respond_to?(:close)
|
304
|
+
data_written = false
|
305
|
+
|
306
|
+
file.progress do
|
307
|
+
statprom = file.stat
|
308
|
+
statprom.then do |stats|
|
309
|
+
#etag = ::Digest::MD5.hexdigest "#{stats[:st_mtim][:tv_sec]}#{body.to_path}"
|
310
|
+
#if etag == request.env[HTTP_ETAG]
|
311
|
+
# header = NOT_MODIFIED_304.dup
|
312
|
+
# add_header(header, ETAG, etag)
|
313
|
+
# header << LINE_END
|
314
|
+
# @socket.write header
|
315
|
+
# return
|
316
|
+
#end
|
317
|
+
#headers[ETAG] ||= etag
|
318
|
+
|
319
|
+
if headers[CONTENT_LENGTH2]
|
320
|
+
type = :raw
|
321
|
+
else
|
322
|
+
type = :http
|
323
|
+
headers[TRANSFER_ENCODING] = CHUNKED
|
324
|
+
end
|
325
|
+
|
326
|
+
data_written = true
|
327
|
+
write_headers request.keep_alive, status, headers
|
328
|
+
|
329
|
+
if send_body
|
330
|
+
# File is open and available for reading
|
331
|
+
promise = file.send_file(@socket, type)
|
332
|
+
promise.then do
|
333
|
+
file.close
|
334
|
+
@socket.shutdown if request.keep_alive == false
|
335
|
+
end
|
336
|
+
promise.catch do |err|
|
337
|
+
@logger.warn "Error sending file: #{err}"
|
338
|
+
@socket.close
|
339
|
+
file.close
|
340
|
+
end
|
341
|
+
else
|
342
|
+
file.close
|
343
|
+
@socket.shutdown unless request.keep_alive
|
344
|
+
end
|
345
|
+
end
|
346
|
+
statprom.catch do |err|
|
347
|
+
@logger.warn "Error reading file stats: #{err}"
|
348
|
+
file.close
|
349
|
+
send_internal_error
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
file.catch do |err|
|
354
|
+
@logger.warn "Error reading file: #{err}"
|
355
|
+
|
356
|
+
if data_written
|
357
|
+
file.close
|
358
|
+
@socket.shutdown
|
359
|
+
else
|
360
|
+
send_internal_error
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Request has completed - send the next one
|
365
|
+
file.finally do
|
366
|
+
send_next_response
|
367
|
+
end
|
368
|
+
else
|
369
|
+
# Optimize the response
|
370
|
+
begin
|
371
|
+
if body.size < 2
|
372
|
+
headers[CONTENT_LENGTH2] = body.size == 1 ? body[0].bytesize : ZERO
|
373
|
+
end
|
374
|
+
rescue # just in case
|
375
|
+
end
|
376
|
+
|
377
|
+
keep_alive = request.keep_alive
|
378
|
+
|
379
|
+
if send_body
|
380
|
+
write_response request, status, headers, body
|
381
|
+
else
|
382
|
+
body.close if body.respond_to?(:close)
|
383
|
+
write_headers keep_alive, status, headers
|
384
|
+
@socket.shutdown if keep_alive == false
|
385
|
+
end
|
386
|
+
|
387
|
+
send_next_response
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
CLOSE_CHUNKED = "0\r\n\r\n".freeze
|
393
|
+
def write_response(request, status, headers, body)
|
394
|
+
keep_alive = request.keep_alive
|
395
|
+
|
396
|
+
if headers[CONTENT_LENGTH2]
|
397
|
+
headers[CONTENT_LENGTH2] = headers[CONTENT_LENGTH2].to_s
|
398
|
+
write_headers keep_alive, status, headers
|
399
|
+
|
400
|
+
# Stream the response (pass directly into @socket.write)
|
401
|
+
body.each &@socket.method(:write)
|
402
|
+
@socket.shutdown if keep_alive == false
|
403
|
+
else
|
404
|
+
headers[TRANSFER_ENCODING] = CHUNKED
|
405
|
+
write_headers keep_alive, status, headers
|
406
|
+
|
407
|
+
# Stream the response
|
408
|
+
@write_chunk ||= method :write_chunk
|
409
|
+
body.each &@write_chunk
|
410
|
+
|
411
|
+
@socket.write CLOSE_CHUNKED
|
412
|
+
@socket.shutdown if keep_alive == false
|
413
|
+
end
|
414
|
+
|
415
|
+
body.close if body.respond_to?(:close)
|
416
|
+
end
|
417
|
+
|
418
|
+
COLON_SPACE = ': '.freeze
|
419
|
+
LINE_END = "\r\n".freeze
|
420
|
+
def add_header(header, key, value)
|
421
|
+
header << key
|
422
|
+
header << COLON_SPACE
|
423
|
+
header << value
|
424
|
+
header << LINE_END
|
425
|
+
end
|
426
|
+
|
427
|
+
CONNECTION = "Connection".freeze
|
428
|
+
NEWLINE = "\n".freeze
|
429
|
+
CLOSE = "close".freeze
|
430
|
+
RACK = "rack".freeze
|
431
|
+
def write_headers(keep_alive, status, headers)
|
432
|
+
headers[CONNECTION] = CLOSE if keep_alive == false
|
433
|
+
|
434
|
+
header = "HTTP/1.1 #{status} #{fetch_code(status)}\r\n"
|
435
|
+
headers.each do |key, value|
|
436
|
+
next if key.start_with? RACK
|
437
|
+
value.to_s.split(NEWLINE).each {|val| add_header(header, key, val)}
|
438
|
+
end
|
439
|
+
header << LINE_END
|
440
|
+
@socket.write header
|
441
|
+
end
|
442
|
+
|
443
|
+
HEX_ENCODED = 16
|
444
|
+
def write_chunk(part)
|
445
|
+
chunk = part.bytesize.to_s(HEX_ENCODED) << LINE_END << part << LINE_END
|
446
|
+
@socket.write chunk
|
447
|
+
end
|
448
|
+
|
449
|
+
HTTP_STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES
|
450
|
+
HTTP_STATUS_DEFAULT = proc { 'CUSTOM'.freeze }
|
451
|
+
def fetch_code(status)
|
452
|
+
HTTP_STATUS_CODES.fetch(status, &HTTP_STATUS_DEFAULT)
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
# ----------------
|
457
|
+
# Error Management
|
458
|
+
# ----------------
|
459
|
+
def critical_error
|
460
|
+
# Kill the process
|
461
|
+
Reactor.instance.shutdown
|
462
|
+
end
|
463
|
+
|
464
|
+
def parsing_error
|
465
|
+
# Stop reading from the client
|
466
|
+
# Wait for existing requests to complete
|
467
|
+
# Send an error response for the current request
|
468
|
+
@socket.stop_read
|
469
|
+
previous = @requests[-1] || @processing
|
470
|
+
|
471
|
+
if previous
|
472
|
+
previous.finally do
|
473
|
+
send_parsing_error
|
474
|
+
end
|
475
|
+
else
|
476
|
+
send_parsing_error
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
|
481
|
+
def send_parsing_error
|
482
|
+
@logger.info "Parsing error!"
|
483
|
+
@socket.write ERROR_400_RESPONSE
|
484
|
+
@socket.shutdown
|
485
|
+
end
|
486
|
+
|
487
|
+
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
|
488
|
+
def send_internal_error
|
489
|
+
@logger.info "Internal error"
|
490
|
+
@socket.stop_read
|
491
|
+
@socket.write ERROR_500_RESPONSE
|
492
|
+
@socket.shutdown
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|