webricknio 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +26 -0
- data/README.md +58 -0
- data/lib/generator/USAGE +8 -0
- data/lib/generator/webricknio.rb +57 -0
- data/lib/generator/webricknio_generator.rb +15 -0
- data/lib/rack/handler/webricknio.rb +88 -0
- data/lib/webricknio.rb +237 -0
- data/lib/webricknio/accesslog.rb +157 -0
- data/lib/webricknio/block.rb +271 -0
- data/lib/webricknio/block.yaml +7 -0
- data/lib/webricknio/config.rb +125 -0
- data/lib/webricknio/httprequest.rb +559 -0
- data/lib/webricknio/httpresponse.rb +469 -0
- data/lib/webricknio/httpserver.rb +499 -0
- data/lib/webricknio/log.rb +30 -0
- data/lib/webricknio/version.rb +14 -0
- data/webricknio.gemspec +15 -0
- metadata +79 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
#
|
2
|
+
# httpserver.rb -- HTTPServer Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers, Pradeep Singh
|
5
|
+
# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers
|
7
|
+
# Copyright (c) 2012 Pradeep Singh
|
8
|
+
# All rights reserved.
|
9
|
+
|
10
|
+
require 'socket'
|
11
|
+
require 'webricknio/config'
|
12
|
+
require 'webrick/log'
|
13
|
+
require 'webricknio/log'
|
14
|
+
require 'webricknio/httprequest'
|
15
|
+
require 'webricknio/httpresponse'
|
16
|
+
require 'webrick/server'
|
17
|
+
|
18
|
+
require 'webricknio/accesslog'
|
19
|
+
|
20
|
+
require 'webricknio/block'
|
21
|
+
|
22
|
+
require 'set'
|
23
|
+
|
24
|
+
require 'java'
|
25
|
+
|
26
|
+
java_import 'java.nio.ByteBuffer'
|
27
|
+
|
28
|
+
java_import 'java.nio.channels.ServerSocketChannel'
|
29
|
+
java_import 'java.nio.channels.Selector'
|
30
|
+
java_import 'java.nio.channels.SelectionKey'
|
31
|
+
|
32
|
+
java_import 'java.net.ServerSocket'
|
33
|
+
java_import 'java.net.InetSocketAddress'
|
34
|
+
|
35
|
+
java_import 'java.util.Iterator'
|
36
|
+
|
37
|
+
module WEBrickNIO
|
38
|
+
|
39
|
+
class HTTPServer
|
40
|
+
attr_reader :status, :config, :logger, :selector
|
41
|
+
|
42
|
+
def initialize(config={}, default=Config::HTTP)
|
43
|
+
|
44
|
+
begin
|
45
|
+
file_name = "#{Rails.root}/config/webricknio.rb"
|
46
|
+
file = File.open(file_name, 'r')
|
47
|
+
hash = eval file.read
|
48
|
+
@config = default.update(hash).update(config)
|
49
|
+
rescue
|
50
|
+
@config = default.dup.update(config)
|
51
|
+
puts "custom config file not present"
|
52
|
+
end
|
53
|
+
|
54
|
+
@config[:Logger] ||= ::WEBrickNIO::Log::new @config[:LogLocation]
|
55
|
+
@logger = @config[:Logger]
|
56
|
+
@logger.level = @config[:LogLevel] if @config[:LogLevel]
|
57
|
+
|
58
|
+
@logger.info "configured properties:\n#{@config}"
|
59
|
+
|
60
|
+
@status = :Stop
|
61
|
+
|
62
|
+
poolv = WEBrickNIO::VERSION
|
63
|
+
rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
|
64
|
+
@logger.info("HTTPServerNIO #{poolv}")
|
65
|
+
@logger.info("ruby #{rubyv}")
|
66
|
+
|
67
|
+
@mount_tab = MountTable.new
|
68
|
+
|
69
|
+
unless @config[:AccessLog]
|
70
|
+
@config[:AccessLog] = [
|
71
|
+
[ $stderr, ::WEBrickNIO::AccessLog::COMMON_LOG_FORMAT ],
|
72
|
+
[ $stderr, ::WEBrickNIO::AccessLog::REFERER_LOG_FORMAT ]
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
@blocker_chain = WEBrickNIO::ChainedBlock.new
|
77
|
+
|
78
|
+
trap(:INT) do
|
79
|
+
@logger.info "SIGINT in WEBrickNIO::HTTPServer"
|
80
|
+
shutdown
|
81
|
+
end
|
82
|
+
|
83
|
+
trap(:HUP) do
|
84
|
+
@logger.info "SIGHUP in WEBrickNIO::HTTPServer"
|
85
|
+
reload
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def reload
|
90
|
+
@blocker_chain.reload
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def start(&block)
|
95
|
+
raise ::WEBrick::ServerError, "already started." if @status != :Stop
|
96
|
+
@logger.info \
|
97
|
+
"#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
|
98
|
+
|
99
|
+
@server_socket_channel = ServerSocketChannel.open
|
100
|
+
@server_socket_channel.configure_blocking false
|
101
|
+
|
102
|
+
sock_addr = InetSocketAddress.new(@config[:Port])
|
103
|
+
server_sock = @server_socket_channel.socket.bind sock_addr
|
104
|
+
@logger.info "bound socket #{@server_socket_channel.socket.object_id}"
|
105
|
+
|
106
|
+
@selector = Selector.open
|
107
|
+
@main_selector_key = @server_socket_channel.register(@selector, SelectionKey::OP_ACCEPT);
|
108
|
+
@logger.info "registered selector"
|
109
|
+
|
110
|
+
@thread_pool = java.util.concurrent.Executors.newFixedThreadPool(@config[:NumThreads])
|
111
|
+
@logger.info "creating thread pool of size #{@config[:NumThreads]}"
|
112
|
+
|
113
|
+
@status = :Running
|
114
|
+
while @status == :Running
|
115
|
+
|
116
|
+
begin
|
117
|
+
@selector.select # go ahead and block here forever
|
118
|
+
ready_keys = @selector.selected_keys
|
119
|
+
rescue Exception => ex
|
120
|
+
if @status == :Running
|
121
|
+
@logger.error "SELECTOR exception: #{ex.java_class.name}"
|
122
|
+
#ex.print_stack_trace
|
123
|
+
else
|
124
|
+
@logger.info "selector shutdown"
|
125
|
+
end
|
126
|
+
next
|
127
|
+
end
|
128
|
+
|
129
|
+
begin
|
130
|
+
iterator = ready_keys.iterator
|
131
|
+
|
132
|
+
while iterator.has_next
|
133
|
+
key = iterator.next
|
134
|
+
iterator.remove
|
135
|
+
|
136
|
+
if key.is_valid && key.is_acceptable
|
137
|
+
client_channel = @server_socket_channel.accept
|
138
|
+
client_channel.configure_blocking false
|
139
|
+
sock = client_channel.socket
|
140
|
+
remote_addr = sock.getInetAddress
|
141
|
+
@logger.info "accepted connection from: #{remote_addr.getHostAddress}"
|
142
|
+
if @blocker_chain.block_ip? remote_addr.getHostAddress
|
143
|
+
@logger.info "blocked ip: #{remote_addr.getHostAddress}"
|
144
|
+
client_channel.close
|
145
|
+
else
|
146
|
+
key2 = client_channel.register(@selector, SelectionKey::OP_READ, Attachment.new)
|
147
|
+
end
|
148
|
+
elsif key.is_valid && key.is_readable
|
149
|
+
unless key.attachment.is_locked?
|
150
|
+
key.attachment.lock
|
151
|
+
sock = key.channel.socket
|
152
|
+
sock_channel = key.channel
|
153
|
+
@thread_pool.submit key.attachment.handler.new(key, self, @config, key.attachment)
|
154
|
+
end
|
155
|
+
else
|
156
|
+
key.cancel
|
157
|
+
end
|
158
|
+
|
159
|
+
end # while iterator
|
160
|
+
|
161
|
+
rescue Exception => ex # to get rid of canceled key exception
|
162
|
+
if ex.is_a?(Exception)
|
163
|
+
@logger.error(ex)
|
164
|
+
else
|
165
|
+
message = ""
|
166
|
+
if ex.respond_to?(:java_class) && ex.respond_to?(:stack_trace)
|
167
|
+
#ex.print_stack_trace
|
168
|
+
message += ex.java_class.name + ": " unless ex.java_class.name.nil?
|
169
|
+
else
|
170
|
+
message = ex.inspect
|
171
|
+
end
|
172
|
+
@logger.error(message)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end # while Running
|
177
|
+
|
178
|
+
@logger.info "going to shutdown ..."
|
179
|
+
@logger.info "#{self.class}#start done."
|
180
|
+
@status = :Stop
|
181
|
+
shutdown if @status != :Shutdown
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Maintains state information for a socket while processing requests.
|
186
|
+
# #cleanup needs to be called after every finished processing of a request for a socket
|
187
|
+
#
|
188
|
+
|
189
|
+
class Attachment
|
190
|
+
attr_accessor :request, :response, :handler
|
191
|
+
|
192
|
+
def initialize
|
193
|
+
cleanup
|
194
|
+
end
|
195
|
+
|
196
|
+
def lock
|
197
|
+
@locked = true
|
198
|
+
end
|
199
|
+
|
200
|
+
def unlock
|
201
|
+
@locked = false
|
202
|
+
end
|
203
|
+
|
204
|
+
def is_locked?
|
205
|
+
@locked
|
206
|
+
end
|
207
|
+
|
208
|
+
def cleanup
|
209
|
+
|
210
|
+
# This is not a synchronization lock.
|
211
|
+
# It is there only to block the flood of signals generated when an event occurs.
|
212
|
+
@locked = false
|
213
|
+
|
214
|
+
# Handler that will process requests coming on this socket.
|
215
|
+
# Default is RequestHandler which assumes a regular HTTP request. In some cases, such as websockets, handler can be changed
|
216
|
+
# to some other class after initial negotiation has been completed using the default handler.
|
217
|
+
@handler = RequestHandler
|
218
|
+
|
219
|
+
@request = nil
|
220
|
+
@response = nil
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
#
|
227
|
+
# Instances of this class are submitted to the threadpool to process incoming requests.
|
228
|
+
# It will very likely process the whole request in one shot but it might postpone the request too for a slow client
|
229
|
+
#
|
230
|
+
|
231
|
+
class RequestHandler
|
232
|
+
include java.util.concurrent.Callable
|
233
|
+
|
234
|
+
def initialize(key, server, config, attachment)
|
235
|
+
@key = key
|
236
|
+
@sock_channel = @key.channel
|
237
|
+
@config = config
|
238
|
+
@server = server
|
239
|
+
@send_response = true
|
240
|
+
@socket_id = -1
|
241
|
+
@attachment = attachment
|
242
|
+
end
|
243
|
+
|
244
|
+
def call
|
245
|
+
begin
|
246
|
+
time1 = Time.now
|
247
|
+
req = @attachment.request || ::WEBrickNIO::HTTPRequest.new(@config)
|
248
|
+
res = @attachment.response || ::WEBrickNIO::HTTPResponse.new(@config)
|
249
|
+
|
250
|
+
@socket_id = @sock_channel.socket.object_id
|
251
|
+
|
252
|
+
socket = @sock_channel.socket
|
253
|
+
|
254
|
+
if req.in_progress?
|
255
|
+
@server.logger.debug "resuming request in progress"
|
256
|
+
req.resume
|
257
|
+
else
|
258
|
+
# fresh request
|
259
|
+
req.parse(@sock_channel)
|
260
|
+
end
|
261
|
+
|
262
|
+
if req.in_progress?
|
263
|
+
@server.logger.debug "request in progress"
|
264
|
+
@attachment.request = req if @attachment.request.nil?
|
265
|
+
@attachment.response = res if @attachment.response.nil?
|
266
|
+
@send_response = false
|
267
|
+
return # goes to "ensure" first
|
268
|
+
end
|
269
|
+
|
270
|
+
res.request_method = req.request_method
|
271
|
+
res.request_uri = req.request_uri
|
272
|
+
res.request_http_version = req.http_version
|
273
|
+
res.keep_alive = req.keep_alive?
|
274
|
+
|
275
|
+
if req.unparsed_uri == "*"
|
276
|
+
if req.request_method == "OPTIONS"
|
277
|
+
do_OPTIONS(req, res)
|
278
|
+
raise ::WEBrick::HTTPStatus::OK
|
279
|
+
end
|
280
|
+
raise ::WEBrick::HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
|
281
|
+
end
|
282
|
+
|
283
|
+
servlet, options, script_name, path_info = @server.search_servlet(req.path)
|
284
|
+
raise ::WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
|
285
|
+
req.script_name = script_name
|
286
|
+
req.path_info = path_info
|
287
|
+
si = servlet.get_instance(self, *options)
|
288
|
+
#@server.logger.debug(format("%s is invoked.", si.class.name))
|
289
|
+
@server.access_log(@config, req, res)
|
290
|
+
|
291
|
+
si.service(req, res)
|
292
|
+
|
293
|
+
rescue ::WEBrick::HTTPStatus::EOFError => ex
|
294
|
+
@send_response = false
|
295
|
+
@attachment.cleanup
|
296
|
+
@server.logger.debug(ex.message)
|
297
|
+
rescue ::WEBrick::HTTPStatus::Error => ex
|
298
|
+
@server.logger.error(ex)
|
299
|
+
res.set_error(ex)
|
300
|
+
rescue ::WEBrick::HTTPStatus::Status => ex
|
301
|
+
res.status = ex.code
|
302
|
+
rescue StandardError => ex
|
303
|
+
@server.logger.error(ex)
|
304
|
+
res.set_error(ex)
|
305
|
+
rescue Exception => ex
|
306
|
+
if ex.is_a?(Exception)
|
307
|
+
@server.logger.error(ex)
|
308
|
+
else
|
309
|
+
message = ""
|
310
|
+
if ex.respond_to?(:java_class) && ex.respond_to?(:stack_trace)
|
311
|
+
@server.logger.error("error")
|
312
|
+
ex.print_stack_trace
|
313
|
+
else
|
314
|
+
message = ex.inspect
|
315
|
+
end
|
316
|
+
@server.logger.error(message)
|
317
|
+
end
|
318
|
+
res.set_error("500")
|
319
|
+
ensure
|
320
|
+
begin
|
321
|
+
time3 = Time.now
|
322
|
+
@attachment.unlock
|
323
|
+
if @send_response
|
324
|
+
@attachment.cleanup
|
325
|
+
res.send_response(@sock_channel)
|
326
|
+
@server.logger.debug "time taken to send response #{Time.now - time3}"
|
327
|
+
end
|
328
|
+
if (!req.keep_alive? || !res.keep_alive? || (!@send_response && !req.in_progress?))
|
329
|
+
@server.logger.debug("closing socket. req.keep alive: #{req.keep_alive}, resp.keep alive: #{res.keep_alive}, send_response: #{@send_response}, socket id: #{@socket_id}")
|
330
|
+
@sock_channel.close
|
331
|
+
@key.cancel
|
332
|
+
end
|
333
|
+
rescue Exception => ex
|
334
|
+
if ex.is_a?(Exception)
|
335
|
+
@server.logger.error(ex)
|
336
|
+
else
|
337
|
+
message = ""
|
338
|
+
if ex.respond_to?(:java_class) && ex.respond_to?(:stack_trace)
|
339
|
+
@server.logger.error("error")
|
340
|
+
ex.print_stack_trace
|
341
|
+
else
|
342
|
+
message = ex.inspect
|
343
|
+
end
|
344
|
+
@server.logger.error(message)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
time2 = Time.now
|
348
|
+
@server.logger.info "total request time: #{time2 - time1} sec" if @send_response
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
|
354
|
+
#
|
355
|
+
# TBD
|
356
|
+
#
|
357
|
+
|
358
|
+
class WebsocketHandler
|
359
|
+
include java.util.concurrent.Callable
|
360
|
+
|
361
|
+
def initialize(key, server, config, attachment)
|
362
|
+
@key = key
|
363
|
+
@sock_channel = @key.channel
|
364
|
+
@config = config
|
365
|
+
@server = server
|
366
|
+
@send_response = true
|
367
|
+
@socket_id = -1
|
368
|
+
@attachment = attachment
|
369
|
+
end
|
370
|
+
|
371
|
+
def call
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
def stop
|
378
|
+
if @status == :Running
|
379
|
+
@status = :Shutdown
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def shutdown
|
384
|
+
stop
|
385
|
+
@server_socket_channel.close
|
386
|
+
@selector.close
|
387
|
+
@thread_pool.shutdown
|
388
|
+
@logger.debug "shutdown thread pool"
|
389
|
+
end
|
390
|
+
|
391
|
+
def [](key)
|
392
|
+
@config[key]
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
|
397
|
+
def do_OPTIONS(req, res)
|
398
|
+
res["allow"] = "GET,HEAD,POST,OPTIONS"
|
399
|
+
end
|
400
|
+
|
401
|
+
##
|
402
|
+
# Mounts +servlet+ on +dir+ passing +options+ to the servlet at creation
|
403
|
+
# time
|
404
|
+
|
405
|
+
def mount(dir, servlet, *options)
|
406
|
+
@logger.debug(sprintf("%s is mounted on %s", servlet.inspect, dir))
|
407
|
+
@mount_tab[dir] = [ servlet, options ]
|
408
|
+
end
|
409
|
+
|
410
|
+
##
|
411
|
+
# Mounts +proc+ or +block+ on +dir+ and calls it with a
|
412
|
+
# WEBrick::HTTPRequest and WEBrick::HTTPResponse
|
413
|
+
|
414
|
+
def mount_proc(dir, proc=nil, &block)
|
415
|
+
proc ||= block
|
416
|
+
raise HTTPServerError, "must pass a proc or block" unless proc
|
417
|
+
mount(dir, HTTPServlet::ProcHandler.new(proc))
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# Unmounts +dir+
|
422
|
+
|
423
|
+
def unmount(dir)
|
424
|
+
@logger.debug(sprintf("unmount %s.", dir))
|
425
|
+
@mount_tab.delete(dir)
|
426
|
+
end
|
427
|
+
alias umount unmount
|
428
|
+
|
429
|
+
##
|
430
|
+
# Finds a servlet for +path+
|
431
|
+
|
432
|
+
def search_servlet(path)
|
433
|
+
|
434
|
+
script_name, path_info = @mount_tab.scan(path)
|
435
|
+
servlet, options = @mount_tab[script_name]
|
436
|
+
if servlet
|
437
|
+
[ servlet, options, script_name, path_info ]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def access_log(config, req, res)
|
442
|
+
param = ::WEBrickNIO::AccessLog::setup_params(config, req, res)
|
443
|
+
@config[:AccessLog].each{|logger, fmt|
|
444
|
+
logger << ::WEBrickNIO::AccessLog::format(fmt+"\n", param)
|
445
|
+
}
|
446
|
+
end
|
447
|
+
|
448
|
+
class MountTable
|
449
|
+
def initialize
|
450
|
+
@tab = Hash.new
|
451
|
+
compile
|
452
|
+
end
|
453
|
+
|
454
|
+
def [](dir)
|
455
|
+
dir = normalize(dir)
|
456
|
+
@tab[dir]
|
457
|
+
end
|
458
|
+
|
459
|
+
def []=(dir, val)
|
460
|
+
dir = normalize(dir)
|
461
|
+
@tab[dir] = val
|
462
|
+
compile
|
463
|
+
val
|
464
|
+
end
|
465
|
+
|
466
|
+
def delete(dir)
|
467
|
+
dir = normalize(dir)
|
468
|
+
res = @tab.delete(dir)
|
469
|
+
compile
|
470
|
+
res
|
471
|
+
end
|
472
|
+
|
473
|
+
def scan(path)
|
474
|
+
@scanner =~ path
|
475
|
+
[ $&, $' ]
|
476
|
+
end
|
477
|
+
|
478
|
+
private
|
479
|
+
|
480
|
+
def compile
|
481
|
+
k = @tab.keys
|
482
|
+
k.sort!
|
483
|
+
k.reverse!
|
484
|
+
k.collect!{|path| Regexp.escape(path) }
|
485
|
+
@scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
|
486
|
+
end
|
487
|
+
|
488
|
+
def normalize(dir)
|
489
|
+
ret = dir ? dir.dup : ""
|
490
|
+
ret.sub!(%r|/+$|, "")
|
491
|
+
ret
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
private
|
496
|
+
MAX_URI_LENGTH = 2083
|
497
|
+
|
498
|
+
end
|
499
|
+
end
|