mongrel 0.3.5 → 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|