mongrel 0.3.5 → 0.3.6
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.
- data/README +4 -3
- data/Rakefile +7 -5
- data/bin/mongrel_rails +5 -4
- data/bin/mongrel_rails_service +237 -0
- data/bin/mongrel_rails_svc +194 -0
- data/doc/rdoc/classes/Mongrel.html +1 -4
- data/doc/rdoc/classes/Mongrel/Const.html +13 -3
- data/doc/rdoc/classes/Mongrel/HeaderOut.html +10 -10
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000033.html → M000028.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000034.html → M000029.html} +7 -7
- data/doc/rdoc/classes/Mongrel/HttpRequest.html +5 -5
- data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000041.html +39 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.html +36 -36
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000034.html +21 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000041.html → M000035.html} +6 -6
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000042.html → M000036.html} +5 -5
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000043.html → M000037.html} +5 -5
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000044.html → M000038.html} +6 -6
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000045.html → M000039.html} +6 -6
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000040.html +7 -8
- data/doc/rdoc/classes/Mongrel/HttpServer.html +47 -28
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000022.html +33 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000023.html +57 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000024.html +46 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000031.html → M000025.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000032.html → M000026.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000027.html +19 -0
- data/doc/rdoc/classes/Mongrel/{HttpHandler.html → StopServer.html} +4 -33
- data/doc/rdoc/classes/Mongrel/URIClassifier.html +22 -23
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000035.html → M000030.html} +0 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000036.html → M000031.html} +0 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000037.html → M000032.html} +0 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000038.html → M000033.html} +0 -0
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/README.html +9 -7
- data/doc/rdoc/files/lib/mongrel_rb.html +3 -3
- data/doc/rdoc/fr_class_index.html +1 -4
- data/doc/rdoc/fr_method_index.html +20 -37
- data/examples/mongrel_simple_ctrl.rb +92 -0
- data/examples/mongrel_simple_service.rb +116 -0
- data/examples/simpletest.rb +3 -1
- data/ext/http11/http11_parser.c +176 -263
- data/lib/mongrel.rb +56 -343
- data/lib/mongrel/cgi.rb +147 -0
- data/lib/mongrel/handlers.rb +180 -0
- metadata +28 -46
- data/doc/rdoc/classes/Mongrel/CGIWrapper.html +0 -346
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000047.html +0 -24
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000048.html +0 -48
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000049.html +0 -34
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000050.html +0 -27
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000051.html +0 -26
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000052.html +0 -18
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000053.html +0 -18
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000054.html +0 -18
- data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000055.html +0 -19
- data/doc/rdoc/classes/Mongrel/DirHandler.html +0 -283
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000022.html +0 -20
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000023.html +0 -42
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000024.html +0 -40
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000025.html +0 -31
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000026.html +0 -38
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000027.html +0 -18
- data/doc/rdoc/classes/Mongrel/Error404Handler.html +0 -171
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000056.html +0 -18
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000057.html +0 -18
- data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000039.html +0 -17
- data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000058.html +0 -31
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000046.html +0 -20
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000028.html +0 -38
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000029.html +0 -64
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000030.html +0 -23
data/lib/mongrel.rb
CHANGED
|
@@ -2,15 +2,18 @@ require 'socket'
|
|
|
2
2
|
require 'http11'
|
|
3
3
|
require 'thread'
|
|
4
4
|
require 'stringio'
|
|
5
|
-
require '
|
|
6
|
-
require '
|
|
7
|
-
|
|
5
|
+
require 'mongrel/cgi'
|
|
6
|
+
require 'mongrel/handlers'
|
|
8
7
|
|
|
9
8
|
# Mongrel module containing all of the classes (include C extensions) for running
|
|
10
9
|
# a Mongrel web server. It contains a minimalist HTTP server with just enough
|
|
11
10
|
# functionality to service web application requests fast as possible.
|
|
12
11
|
module Mongrel
|
|
13
12
|
|
|
13
|
+
# Used to stop the HttpServer via Thread.raise.
|
|
14
|
+
class StopServer < Exception
|
|
15
|
+
end
|
|
16
|
+
|
|
14
17
|
# Every standard HTTP code mapped to the appropriate message. These are
|
|
15
18
|
# used so frequently that they are placed directly in Mongrel for easy
|
|
16
19
|
# access rather than Mongrel::Const.
|
|
@@ -103,6 +106,9 @@ module Mongrel
|
|
|
103
106
|
# The port of our server as given by the HttpServer.new(host,port) call.
|
|
104
107
|
SERVER_PORT='SERVER_PORT'
|
|
105
108
|
|
|
109
|
+
# SERVER_NAME and SERVER_PORT come from this.
|
|
110
|
+
HTTP_HOST='HTTP_HOST'
|
|
111
|
+
|
|
106
112
|
# Official server protocol key in the HttpRequest parameters.
|
|
107
113
|
SERVER_PROTOCOL='SERVER_PROTOCOL'
|
|
108
114
|
# Mongrel claims to support HTTP/1.1.
|
|
@@ -112,7 +118,7 @@ module Mongrel
|
|
|
112
118
|
SERVER_SOFTWARE='SERVER_SOFTWARE'
|
|
113
119
|
|
|
114
120
|
# Current Mongrel version (used for SERVER_SOFTWARE and other response headers).
|
|
115
|
-
MONGREL_VERSION='Mongrel 0.3.
|
|
121
|
+
MONGREL_VERSION='Mongrel 0.3.6'
|
|
116
122
|
|
|
117
123
|
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
|
118
124
|
ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND"
|
|
@@ -149,7 +155,15 @@ module Mongrel
|
|
|
149
155
|
|
|
150
156
|
# fix up the CGI requirements
|
|
151
157
|
params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0
|
|
152
|
-
params[Const::CONTENT_TYPE]
|
|
158
|
+
params[Const::CONTENT_TYPE] = params[Const::HTTP_CONTENT_TYPE] if params[Const::HTTP_CONTENT_TYPE]
|
|
159
|
+
params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
|
|
160
|
+
params[Const::REMOTE_ADDR]=socket.peeraddr[3]
|
|
161
|
+
host,port = params[Const::HTTP_HOST].split(":")
|
|
162
|
+
params[Const::SERVER_NAME]=host
|
|
163
|
+
params[Const::SERVER_PORT]=port if port
|
|
164
|
+
params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
|
|
165
|
+
params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
|
|
166
|
+
|
|
153
167
|
|
|
154
168
|
# now, if the initial_body isn't long enough for the content length we have to fill it
|
|
155
169
|
# TODO: adapt for big ass stuff by writing to a temp file
|
|
@@ -271,16 +285,6 @@ module Mongrel
|
|
|
271
285
|
end
|
|
272
286
|
|
|
273
287
|
|
|
274
|
-
# You implement your application handler with this. It's very light giving
|
|
275
|
-
# just the minimum necessary for you to handle a request and shoot back
|
|
276
|
-
# a response. Look at the HttpRequest and HttpResponse objects for how
|
|
277
|
-
# to use them.
|
|
278
|
-
class HttpHandler
|
|
279
|
-
def process(request, response)
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
|
|
284
288
|
# This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
|
|
285
289
|
# make up the majority of how the server functions. It's a very simple class that just
|
|
286
290
|
# has a thread accepting connections and a simple HttpServer.process_client function
|
|
@@ -324,18 +328,13 @@ module Mongrel
|
|
|
324
328
|
@req_queue = Queue.new
|
|
325
329
|
@host = host
|
|
326
330
|
@port = port
|
|
327
|
-
@
|
|
331
|
+
@processors = []
|
|
328
332
|
@timeout = timeout
|
|
329
333
|
|
|
330
|
-
|
|
334
|
+
num_processors.times {|i|
|
|
335
|
+
@processors << Thread.new do
|
|
331
336
|
while client = @req_queue.deq
|
|
332
|
-
|
|
333
|
-
Timeout.timeout(@timeout) do
|
|
334
|
-
process_client(client)
|
|
335
|
-
end
|
|
336
|
-
rescue Timeout::Error
|
|
337
|
-
STDERR.puts "WARNING: Request took longer than #@timeout second timeout"
|
|
338
|
-
end
|
|
337
|
+
process_client(client)
|
|
339
338
|
end
|
|
340
339
|
end
|
|
341
340
|
}
|
|
@@ -361,21 +360,14 @@ module Mongrel
|
|
|
361
360
|
if handler
|
|
362
361
|
params[Const::PATH_INFO] = path_info
|
|
363
362
|
params[Const::SCRIPT_NAME] = script_name
|
|
364
|
-
params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
|
|
365
|
-
params[Const::REMOTE_ADDR]=client.peeraddr[3]
|
|
366
|
-
params[Const::SERVER_NAME]=@host
|
|
367
|
-
params[Const::SERVER_PORT]=@port
|
|
368
|
-
params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
|
|
369
|
-
params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
|
|
370
|
-
|
|
371
363
|
request = HttpRequest.new(params, data[nread ... data.length], client)
|
|
372
364
|
response = HttpResponse.new(client)
|
|
373
365
|
handler.process(request, response)
|
|
374
366
|
else
|
|
375
367
|
client.write(Const::ERROR_404_RESPONSE)
|
|
376
368
|
end
|
|
377
|
-
|
|
378
|
-
break
|
|
369
|
+
|
|
370
|
+
break #done
|
|
379
371
|
else
|
|
380
372
|
# gotta stream and read again until we can get the parser to be character safe
|
|
381
373
|
# TODO: make this more efficient since this means we're parsing a lot repeatedly
|
|
@@ -402,10 +394,33 @@ module Mongrel
|
|
|
402
394
|
def run
|
|
403
395
|
BasicSocket.do_not_reverse_lookup=true
|
|
404
396
|
@acceptor = Thread.new do
|
|
405
|
-
|
|
406
|
-
|
|
397
|
+
Thread.current[:stopped] = false
|
|
398
|
+
|
|
399
|
+
while not Thread.current[:stopped]
|
|
400
|
+
begin
|
|
401
|
+
@req_queue << @socket.accept
|
|
402
|
+
rescue StopServer
|
|
403
|
+
STDERR.puts "Server stopped. Exiting."
|
|
404
|
+
@socket.close if not @socket.closed?
|
|
405
|
+
break
|
|
406
|
+
rescue Errno::EMFILE
|
|
407
|
+
STDERR.puts "Too many open files. Try increasing ulimits."
|
|
408
|
+
sleep 0.5
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# now that processing is done we feed enough false onto the request queue to get
|
|
413
|
+
# each processor to exit and stop processing.
|
|
414
|
+
@processors.length.times { @req_queue << false }
|
|
415
|
+
|
|
416
|
+
# finally we wait until the queue is empty
|
|
417
|
+
while @req_queue.length > 0
|
|
418
|
+
STDERR.puts "Shutdown waiting for #{@req_queue.length} requests" if @req_queue.length > 0
|
|
419
|
+
sleep 1
|
|
407
420
|
end
|
|
408
421
|
end
|
|
422
|
+
|
|
423
|
+
@acceptor.priority = 1
|
|
409
424
|
end
|
|
410
425
|
|
|
411
426
|
|
|
@@ -421,318 +436,16 @@ module Mongrel
|
|
|
421
436
|
def unregister(uri)
|
|
422
437
|
@classifier.unregister(uri)
|
|
423
438
|
end
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
# The server normally returns a 404 response if a URI is requested, but it
|
|
428
|
-
# also returns a lame empty message. This lets you do a 404 response
|
|
429
|
-
# with a custom message for special URIs.
|
|
430
|
-
class Error404Handler < HttpHandler
|
|
431
|
-
|
|
432
|
-
# Sets the message to return. This is constructed once for the handler
|
|
433
|
-
# so it's pretty efficient.
|
|
434
|
-
def initialize(msg)
|
|
435
|
-
@response = Const::ERROR_404_RESPONSE + msg
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Just kicks back the standard 404 response with your special message.
|
|
439
|
-
def process(request, response)
|
|
440
|
-
response.socket.write(@response)
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
# Serves the contents of a directory. You give it the path to the root
|
|
447
|
-
# where the files are located, and it tries to find the files based on
|
|
448
|
-
# the PATH_INFO inside the directory. If the requested path is a
|
|
449
|
-
# directory then it returns a simple directory listing.
|
|
450
|
-
#
|
|
451
|
-
# It does a simple protection against going outside it's root path by
|
|
452
|
-
# converting all paths to an absolute expanded path, and then making sure
|
|
453
|
-
# that the final expanded path includes the root path. If it doesn't
|
|
454
|
-
# than it simply gives a 404.
|
|
455
|
-
class DirHandler < HttpHandler
|
|
456
|
-
MIME_TYPES = {
|
|
457
|
-
".css" => "text/css",
|
|
458
|
-
".gif" => "image/gif",
|
|
459
|
-
".htm" => "text/html",
|
|
460
|
-
".html" => "text/html",
|
|
461
|
-
".jpeg" => "image/jpeg",
|
|
462
|
-
".jpg" => "image/jpeg",
|
|
463
|
-
".js" => "text/javascript",
|
|
464
|
-
".png" => "image/png",
|
|
465
|
-
".swf" => "application/x-shockwave-flash",
|
|
466
|
-
".txt" => "text/plain"
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
attr_reader :path
|
|
471
|
-
|
|
472
|
-
# You give it the path to the directory root and an (optional)
|
|
473
|
-
def initialize(path, listing_allowed=true, index_html="index.html")
|
|
474
|
-
@path = File.expand_path(path)
|
|
475
|
-
@listing_allowed=listing_allowed
|
|
476
|
-
@index_html = index_html
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
# Checks if the given path can be served and returns the full path (or nil if not).
|
|
480
|
-
def can_serve(path_info)
|
|
481
|
-
req = File.expand_path(File.join(@path,path_info), @path)
|
|
482
|
-
|
|
483
|
-
if req.index(@path) == 0 and File.exist? req
|
|
484
|
-
# it exists and it's in the right location
|
|
485
|
-
if File.directory? req
|
|
486
|
-
# the request is for a directory
|
|
487
|
-
index = File.join(req, @index_html)
|
|
488
|
-
if File.exist? index
|
|
489
|
-
# serve the index
|
|
490
|
-
return index
|
|
491
|
-
elsif @listing_allowed
|
|
492
|
-
# serve the directory
|
|
493
|
-
req
|
|
494
|
-
else
|
|
495
|
-
# do not serve anything
|
|
496
|
-
return nil
|
|
497
|
-
end
|
|
498
|
-
else
|
|
499
|
-
# it's a file and it's there
|
|
500
|
-
return req
|
|
501
|
-
end
|
|
502
|
-
else
|
|
503
|
-
# does not exist or isn't in the right spot
|
|
504
|
-
return nil
|
|
505
|
-
end
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
# Returns a simplistic directory listing if they're enabled, otherwise a 403.
|
|
510
|
-
# Base is the base URI from the REQUEST_URI, dir is the directory to serve
|
|
511
|
-
# on the file system (comes from can_serve()), and response is the HttpResponse
|
|
512
|
-
# object to send the results on.
|
|
513
|
-
def send_dir_listing(base, dir, response)
|
|
514
|
-
# take off any trailing / so the links come out right
|
|
515
|
-
base.chop! if base[-1] == "/"[-1]
|
|
516
|
-
|
|
517
|
-
if @listing_allowed
|
|
518
|
-
response.start(200) do |head,out|
|
|
519
|
-
head['Content-Type'] = "text/html"
|
|
520
|
-
out << "<html><head><title>Directory Listing</title></head><body>"
|
|
521
|
-
Dir.entries(dir).each do |child|
|
|
522
|
-
next if child == "."
|
|
523
|
-
|
|
524
|
-
if child == ".."
|
|
525
|
-
out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
|
|
526
|
-
else
|
|
527
|
-
out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
|
|
528
|
-
end
|
|
529
|
-
end
|
|
530
|
-
out << "</body></html>"
|
|
531
|
-
end
|
|
532
|
-
else
|
|
533
|
-
response.start(403) do |head,out|
|
|
534
|
-
out.write("Directory listings not allowed")
|
|
535
|
-
end
|
|
536
|
-
end
|
|
537
|
-
end
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
# Sends the contents of a file back to the user. Not terribly efficient since it's
|
|
541
|
-
# opening and closing the file for each read.
|
|
542
|
-
def send_file(req, response)
|
|
543
|
-
response.start(200) do |head,out|
|
|
544
|
-
# set the mime type from our map based on the ending
|
|
545
|
-
dot_at = req.rindex(".")
|
|
546
|
-
if dot_at
|
|
547
|
-
ext = req[dot_at .. -1]
|
|
548
|
-
if MIME_TYPES[ext]
|
|
549
|
-
head['Content-Type'] = MIME_TYPES[ext]
|
|
550
|
-
end
|
|
551
|
-
end
|
|
552
|
-
|
|
553
|
-
open(req, "rb") do |f|
|
|
554
|
-
out.write(f.read)
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
# Process the request to either serve a file or a directory listing
|
|
561
|
-
# if allowed (based on the listing_allowed paramter to the constructor).
|
|
562
|
-
def process(request, response)
|
|
563
|
-
req = can_serve request.params['PATH_INFO']
|
|
564
|
-
if not req
|
|
565
|
-
# not found, return a 404
|
|
566
|
-
response.start(404) do |head,out|
|
|
567
|
-
out << "File not found"
|
|
568
|
-
end
|
|
569
|
-
else
|
|
570
|
-
begin
|
|
571
|
-
if File.directory? req
|
|
572
|
-
send_dir_listing(request.params["REQUEST_URI"],req, response)
|
|
573
|
-
else
|
|
574
|
-
send_file(req, response)
|
|
575
|
-
end
|
|
576
|
-
rescue => details
|
|
577
|
-
response.reset
|
|
578
|
-
response.start(403) do |head,out|
|
|
579
|
-
out << "Error accessing file: #{details}"
|
|
580
|
-
out << details.backtrace.join("\n")
|
|
581
|
-
end
|
|
582
|
-
end
|
|
583
|
-
end
|
|
584
|
-
end
|
|
585
439
|
|
|
586
|
-
#
|
|
587
|
-
#
|
|
588
|
-
def
|
|
589
|
-
|
|
440
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
|
441
|
+
# off the request queue before finally exiting.
|
|
442
|
+
def stop
|
|
443
|
+
@acceptor[:stopped] = true
|
|
444
|
+
@acceptor.raise(StopServer.new)
|
|
590
445
|
end
|
|
591
446
|
|
|
592
447
|
end
|
|
593
448
|
|
|
449
|
+
end
|
|
594
450
|
|
|
595
|
-
# The beginning of a complete wrapper around Mongrel's internal HTTP processing
|
|
596
|
-
# system but maintaining the original Ruby CGI module. Use this only as a crutch
|
|
597
|
-
# to get existing CGI based systems working. It should handle everything, but please
|
|
598
|
-
# notify me if you see special warnings. This work is still very alpha so I need
|
|
599
|
-
# testers to help work out the various corner cases.
|
|
600
|
-
class CGIWrapper < ::CGI
|
|
601
|
-
public :env_table
|
|
602
|
-
attr_reader :options
|
|
603
|
-
|
|
604
|
-
# these are stripped out of any keys passed to CGIWrapper.header function
|
|
605
|
-
REMOVED_KEYS = [ "nph","status","server","connection","type",
|
|
606
|
-
"charset","length","language","expires"]
|
|
607
|
-
|
|
608
|
-
def initialize(request, response, *args)
|
|
609
|
-
@request = request
|
|
610
|
-
@response = response
|
|
611
|
-
@args = *args
|
|
612
|
-
@input = StringIO.new(request.body)
|
|
613
|
-
@head = {}
|
|
614
|
-
@out_called = false
|
|
615
|
-
super(*args)
|
|
616
|
-
end
|
|
617
|
-
|
|
618
|
-
# The header is typically called to send back the header. In our case we
|
|
619
|
-
# collect it into a hash for later usage.
|
|
620
|
-
#
|
|
621
|
-
# nph -- Mostly ignored. It'll output the date.
|
|
622
|
-
# connection -- Completely ignored. Why is CGI doing this?
|
|
623
|
-
# length -- Ignored since Mongrel figures this out from what you write to output.
|
|
624
|
-
#
|
|
625
|
-
def header(options = "text/html")
|
|
626
|
-
|
|
627
|
-
# if they pass in a string then just write the Content-Type
|
|
628
|
-
if options.class == String
|
|
629
|
-
@head['Content-Type'] = options
|
|
630
|
-
else
|
|
631
|
-
# convert the given options into what Mongrel wants
|
|
632
|
-
@head['Content-Type'] = options['type'] || "text/html"
|
|
633
|
-
@head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
|
|
634
|
-
|
|
635
|
-
# setup date only if they use nph
|
|
636
|
-
@head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
|
|
637
|
-
|
|
638
|
-
# setup the server to use the default or what they set
|
|
639
|
-
@head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
|
|
640
|
-
|
|
641
|
-
# remaining possible options they can give
|
|
642
|
-
@head['Status'] = options['status'] if options['status']
|
|
643
|
-
@head['Content-Language'] = options['language'] if options['language']
|
|
644
|
-
@head['Expires'] = options['expires'] if options['expires']
|
|
645
|
-
|
|
646
|
-
# drop the keys we don't want anymore
|
|
647
|
-
REMOVED_KEYS.each {|k| options.delete(k) }
|
|
648
|
-
|
|
649
|
-
# finally just convert the rest raw (which puts 'cookie' directly)
|
|
650
|
-
# 'cookie' is translated later as we write the header out
|
|
651
|
-
options.each{|k,v| @head[k] = v}
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
# doing this fakes out the cgi library to think the headers are empty
|
|
655
|
-
# we then do the real headers in the out function call later
|
|
656
|
-
""
|
|
657
|
-
end
|
|
658
|
-
|
|
659
|
-
# Takes any 'cookie' setting and sends it over the Mongrel header,
|
|
660
|
-
# then removes the setting from the options. If cookie is an
|
|
661
|
-
# Array or Hash then it sends those on with .to_s, otherwise
|
|
662
|
-
# it just calls .to_s on it and hopefully your "cookie" can
|
|
663
|
-
# write itself correctly.
|
|
664
|
-
def send_cookies(to)
|
|
665
|
-
# convert the cookies based on the myriad of possible ways to set a cookie
|
|
666
|
-
if @head['cookie']
|
|
667
|
-
cookie = @head['cookie']
|
|
668
|
-
case cookie
|
|
669
|
-
when Array
|
|
670
|
-
cookie.each {|c| to['Set-Cookie'] = c.to_s }
|
|
671
|
-
when Hash
|
|
672
|
-
cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
|
|
673
|
-
else
|
|
674
|
-
to['Set-Cookie'] = options['cookie'].to_s
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
@head.delete('cookie')
|
|
678
|
-
|
|
679
|
-
# @output_cookies seems to never be used, but we'll process it just in case
|
|
680
|
-
@output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
|
|
681
|
-
end
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
# The dumb thing is people can call header or this or both and in any order.
|
|
685
|
-
# So, we just reuse header and then finalize the HttpResponse the right way.
|
|
686
|
-
# Status is taken from the various options and converted to what Mongrel needs
|
|
687
|
-
# via the CGIWrapper.status function.
|
|
688
|
-
def out(options = "text/html")
|
|
689
|
-
return if @out_called # don't do this more than once
|
|
690
|
-
|
|
691
|
-
header(options)
|
|
692
|
-
|
|
693
|
-
@response.start status do |head, out|
|
|
694
|
-
send_cookies(head)
|
|
695
|
-
|
|
696
|
-
@head.each {|k,v| head[k] = v}
|
|
697
|
-
out.write(yield || "")
|
|
698
|
-
end
|
|
699
|
-
end
|
|
700
|
-
|
|
701
|
-
# Computes the status once, but lazily so that people who call header twice
|
|
702
|
-
# don't get penalized. Because CGI insists on including the options status
|
|
703
|
-
# message in the status we have to do a bit of parsing.
|
|
704
|
-
def status
|
|
705
|
-
if not @status
|
|
706
|
-
@status = @head["Status"] || @head["status"]
|
|
707
|
-
|
|
708
|
-
if @status
|
|
709
|
-
@status[0 ... @status.index(' ')] || "200"
|
|
710
|
-
else
|
|
711
|
-
@status = "200"
|
|
712
|
-
end
|
|
713
|
-
end
|
|
714
|
-
end
|
|
715
|
-
|
|
716
|
-
# Used to wrap the normal args variable used inside CGI.
|
|
717
|
-
def args
|
|
718
|
-
@args
|
|
719
|
-
end
|
|
720
|
-
|
|
721
|
-
# Used to wrap the normal env_table variable used inside CGI.
|
|
722
|
-
def env_table
|
|
723
|
-
@request.params
|
|
724
|
-
end
|
|
725
|
-
|
|
726
|
-
# Used to wrap the normal stdinput variable used inside CGI.
|
|
727
|
-
def stdinput
|
|
728
|
-
@input
|
|
729
|
-
end
|
|
730
|
-
|
|
731
|
-
# The stdoutput should be completely bypassed but we'll drop a warning just in case
|
|
732
|
-
def stdoutput
|
|
733
|
-
STDERR.puts "WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks."
|
|
734
|
-
@response.body
|
|
735
|
-
end
|
|
736
|
-
end
|
|
737
451
|
|
|
738
|
-
end
|