edgar-rack 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,151 @@
1
+ require 'rack/request'
2
+ require 'rack/utils'
3
+ require 'time'
4
+
5
+ module Rack
6
+ # Rack::Response provides a convenient interface to create a Rack
7
+ # response.
8
+ #
9
+ # It allows setting of headers and cookies, and provides useful
10
+ # defaults (a OK response containing HTML).
11
+ #
12
+ # You can use Response#write to iteratively generate your response,
13
+ # but note that this is buffered by Rack::Response until you call
14
+ # +finish+. +finish+ however can take a block inside which calls to
15
+ # +write+ are syncronous with the Rack response.
16
+ #
17
+ # Your application's +call+ should end returning Response#finish.
18
+
19
+ class Response
20
+ attr_accessor :length
21
+
22
+ def initialize(body=[], status=200, header={}, &block)
23
+ @status = status.to_i
24
+ @header = Utils::HeaderHash.new("Content-Type" => "text/html").
25
+ merge(header)
26
+
27
+ @chunked = "chunked" == @header['Transfer-Encoding']
28
+ @writer = lambda { |x| @body << x }
29
+ @block = nil
30
+ @length = 0
31
+
32
+ @body = []
33
+
34
+ if body.respond_to? :to_str
35
+ write body.to_str
36
+ elsif body.respond_to?(:each)
37
+ body.each { |part|
38
+ write part.to_s
39
+ }
40
+ else
41
+ raise TypeError, "stringable or iterable required"
42
+ end
43
+
44
+ yield self if block_given?
45
+ end
46
+
47
+ attr_reader :header
48
+ attr_accessor :status, :body
49
+
50
+ def [](key)
51
+ header[key]
52
+ end
53
+
54
+ def []=(key, value)
55
+ header[key] = value
56
+ end
57
+
58
+ def set_cookie(key, value)
59
+ Utils.set_cookie_header!(header, key, value)
60
+ end
61
+
62
+ def delete_cookie(key, value={})
63
+ Utils.delete_cookie_header!(header, key, value)
64
+ end
65
+
66
+ def redirect(target, status=302)
67
+ self.status = status
68
+ self["Location"] = target
69
+ end
70
+
71
+ def finish(&block)
72
+ @block = block
73
+
74
+ if [204, 304].include?(status.to_i)
75
+ header.delete "Content-Type"
76
+ [status.to_i, header, []]
77
+ else
78
+ [status.to_i, header, self]
79
+ end
80
+ end
81
+ alias to_a finish # For *response
82
+ alias to_ary finish # For implicit-splat on Ruby 1.9.2
83
+
84
+ def each(&callback)
85
+ @body.each(&callback)
86
+ @writer = callback
87
+ @block.call(self) if @block
88
+ end
89
+
90
+ # Append to body and update Content-Length.
91
+ #
92
+ # NOTE: Do not mix #write and direct #body access!
93
+ #
94
+ def write(str)
95
+ s = str.to_s
96
+ @length += Rack::Utils.bytesize(s) unless @chunked
97
+ @writer.call s
98
+
99
+ header["Content-Length"] = @length.to_s unless @chunked
100
+ str
101
+ end
102
+
103
+ def close
104
+ body.close if body.respond_to?(:close)
105
+ end
106
+
107
+ def empty?
108
+ @block == nil && @body.empty?
109
+ end
110
+
111
+ alias headers header
112
+
113
+ module Helpers
114
+ def invalid?; @status < 100 || @status >= 600; end
115
+
116
+ def informational?; @status >= 100 && @status < 200; end
117
+ def successful?; @status >= 200 && @status < 300; end
118
+ def redirection?; @status >= 300 && @status < 400; end
119
+ def client_error?; @status >= 400 && @status < 500; end
120
+ def server_error?; @status >= 500 && @status < 600; end
121
+
122
+ def ok?; @status == 200; end
123
+ def forbidden?; @status == 403; end
124
+ def not_found?; @status == 404; end
125
+
126
+ def redirect?; [301, 302, 303, 307].include? @status; end
127
+
128
+ # Headers
129
+ attr_reader :headers, :original_headers
130
+
131
+ def include?(header)
132
+ !!headers[header]
133
+ end
134
+
135
+ def content_type
136
+ headers["Content-Type"]
137
+ end
138
+
139
+ def content_length
140
+ cl = headers["Content-Length"]
141
+ cl ? cl.to_i : cl
142
+ end
143
+
144
+ def location
145
+ headers["Location"]
146
+ end
147
+ end
148
+
149
+ include Helpers
150
+ end
151
+ end
@@ -0,0 +1,104 @@
1
+ # -*- encoding: binary -*-
2
+ require 'tempfile'
3
+ require 'rack/utils'
4
+
5
+ module Rack
6
+ # Class which can make any IO object rewindable, including non-rewindable ones. It does
7
+ # this by buffering the data into a tempfile, which is rewindable.
8
+ #
9
+ # rack.input is required to be rewindable, so if your input stream IO is non-rewindable
10
+ # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
11
+ # to easily make it rewindable.
12
+ #
13
+ # Don't forget to call #close when you're done. This frees up temporary resources that
14
+ # RewindableInput uses, though it does *not* close the original IO object.
15
+ class RewindableInput
16
+ def initialize(io)
17
+ @io = io
18
+ @rewindable_io = nil
19
+ @unlinked = false
20
+ end
21
+
22
+ def gets
23
+ make_rewindable unless @rewindable_io
24
+ @rewindable_io.gets
25
+ end
26
+
27
+ def read(*args)
28
+ make_rewindable unless @rewindable_io
29
+ @rewindable_io.read(*args)
30
+ end
31
+
32
+ def each(&block)
33
+ make_rewindable unless @rewindable_io
34
+ @rewindable_io.each(&block)
35
+ end
36
+
37
+ def rewind
38
+ make_rewindable unless @rewindable_io
39
+ @rewindable_io.rewind
40
+ end
41
+
42
+ # Closes this RewindableInput object without closing the originally
43
+ # wrapped IO oject. Cleans up any temporary resources that this RewindableInput
44
+ # has created.
45
+ #
46
+ # This method may be called multiple times. It does nothing on subsequent calls.
47
+ def close
48
+ if @rewindable_io
49
+ if @unlinked
50
+ @rewindable_io.close
51
+ else
52
+ @rewindable_io.close!
53
+ end
54
+ @rewindable_io = nil
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # Ruby's Tempfile class has a bug. Subclass it and fix it.
61
+ class Tempfile < ::Tempfile
62
+ def _close
63
+ @tmpfile.close if @tmpfile
64
+ @data[1] = nil if @data
65
+ @tmpfile = nil
66
+ end
67
+ end
68
+
69
+ def make_rewindable
70
+ # Buffer all data into a tempfile. Since this tempfile is private to this
71
+ # RewindableInput object, we chmod it so that nobody else can read or write
72
+ # it. On POSIX filesystems we also unlink the file so that it doesn't
73
+ # even have a file entry on the filesystem anymore, though we can still
74
+ # access it because we have the file handle open.
75
+ @rewindable_io = Tempfile.new('RackRewindableInput')
76
+ @rewindable_io.chmod(0000)
77
+ @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
78
+ @rewindable_io.binmode
79
+ if filesystem_has_posix_semantics?
80
+ # Use ::File.unlink as 1.9.1 Tempfile has a bug where unlink closes the file!
81
+ ::File.unlink @rewindable_io.path
82
+ raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
83
+ @unlinked = true
84
+ end
85
+
86
+ buffer = ""
87
+ while @io.read(1024 * 4, buffer)
88
+ entire_buffer_written_out = false
89
+ while !entire_buffer_written_out
90
+ written = @rewindable_io.write(buffer)
91
+ entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
92
+ if !entire_buffer_written_out
93
+ buffer.slice!(0 .. written - 1)
94
+ end
95
+ end
96
+ end
97
+ @rewindable_io.rewind
98
+ end
99
+
100
+ def filesystem_has_posix_semantics?
101
+ RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,27 @@
1
+ module Rack
2
+ # Sets an "X-Runtime" response header, indicating the response
3
+ # time of the request, in seconds
4
+ #
5
+ # You can put it right before the application to see the processing
6
+ # time, or before all the other middlewares to include time for them,
7
+ # too.
8
+ class Runtime
9
+ def initialize(app, name = nil)
10
+ @app = app
11
+ @header_name = "X-Runtime"
12
+ @header_name << "-#{name}" if name
13
+ end
14
+
15
+ def call(env)
16
+ start_time = Time.now
17
+ status, headers, body = @app.call(env)
18
+ request_time = Time.now - start_time
19
+
20
+ if !headers.has_key?(@header_name)
21
+ headers[@header_name] = "%0.6f" % request_time
22
+ end
23
+
24
+ [status, headers, body]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,139 @@
1
+ require 'rack/file'
2
+
3
+ module Rack
4
+
5
+ # = Sendfile
6
+ #
7
+ # The Sendfile middleware intercepts responses whose body is being
8
+ # served from a file and replaces it with a server specific X-Sendfile
9
+ # header. The web server is then responsible for writing the file contents
10
+ # to the client. This can dramatically reduce the amount of work required
11
+ # by the Ruby backend and takes advantage of the web server's optimized file
12
+ # delivery code.
13
+ #
14
+ # In order to take advantage of this middleware, the response body must
15
+ # respond to +to_path+ and the request must include an X-Sendfile-Type
16
+ # header. Rack::File and other components implement +to_path+ so there's
17
+ # rarely anything you need to do in your application. The X-Sendfile-Type
18
+ # header is typically set in your web servers configuration. The following
19
+ # sections attempt to document
20
+ #
21
+ # === Nginx
22
+ #
23
+ # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
24
+ # but requires parts of the filesystem to be mapped into a private URL
25
+ # hierarachy.
26
+ #
27
+ # The following example shows the Nginx configuration required to create
28
+ # a private "/files/" area, enable X-Accel-Redirect, and pass the special
29
+ # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
30
+ #
31
+ # location ~ /files/(.*) {
32
+ # internal;
33
+ # alias /var/www/$1;
34
+ # }
35
+ #
36
+ # location / {
37
+ # proxy_redirect off;
38
+ #
39
+ # proxy_set_header Host $host;
40
+ # proxy_set_header X-Real-IP $remote_addr;
41
+ # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
42
+ #
43
+ # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
44
+ # proxy_set_header X-Accel-Mapping /var/www/=/files/;
45
+ #
46
+ # proxy_pass http://127.0.0.1:8080/;
47
+ # }
48
+ #
49
+ # Note that the X-Sendfile-Type header must be set exactly as shown above. The
50
+ # X-Accel-Mapping header should specify the by the location on the file system,
51
+ # followed by an equals sign (=), followed name of the private URL pattern
52
+ # that it maps to. The middleware performs a simple substitution on the
53
+ # resulting path.
54
+ #
55
+ # See Also: http://wiki.codemongers.com/NginxXSendfile
56
+ #
57
+ # === lighttpd
58
+ #
59
+ # Lighttpd has supported some variation of the X-Sendfile header for some
60
+ # time, although only recent version support X-Sendfile in a reverse proxy
61
+ # configuration.
62
+ #
63
+ # $HTTP["host"] == "example.com" {
64
+ # proxy-core.protocol = "http"
65
+ # proxy-core.balancer = "round-robin"
66
+ # proxy-core.backends = (
67
+ # "127.0.0.1:8000",
68
+ # "127.0.0.1:8001",
69
+ # ...
70
+ # )
71
+ #
72
+ # proxy-core.allow-x-sendfile = "enable"
73
+ # proxy-core.rewrite-request = (
74
+ # "X-Sendfile-Type" => (".*" => "X-Sendfile")
75
+ # )
76
+ # }
77
+ #
78
+ # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
79
+ #
80
+ # === Apache
81
+ #
82
+ # X-Sendfile is supported under Apache 2.x using a separate module:
83
+ #
84
+ # http://tn123.ath.cx/mod_xsendfile/
85
+ #
86
+ # Once the module is compiled and installed, you can enable it using
87
+ # XSendFile config directive:
88
+ #
89
+ # RequestHeader Set X-Sendfile-Type X-Sendfile
90
+ # ProxyPassReverse / http://localhost:8001/
91
+ # XSendFile on
92
+
93
+ class Sendfile
94
+ F = ::File
95
+
96
+ def initialize(app, variation=nil)
97
+ @app = app
98
+ @variation = variation
99
+ end
100
+
101
+ def call(env)
102
+ status, headers, body = @app.call(env)
103
+ if body.respond_to?(:to_path)
104
+ case type = variation(env)
105
+ when 'X-Accel-Redirect'
106
+ path = F.expand_path(body.to_path)
107
+ if url = map_accel_path(env, path)
108
+ headers[type] = url
109
+ body = []
110
+ else
111
+ env['rack.errors'] << "X-Accel-Mapping header missing"
112
+ end
113
+ when 'X-Sendfile', 'X-Lighttpd-Send-File'
114
+ path = F.expand_path(body.to_path)
115
+ headers[type] = path
116
+ body = []
117
+ when '', nil
118
+ else
119
+ env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
120
+ end
121
+ end
122
+ [status, headers, body]
123
+ end
124
+
125
+ private
126
+ def variation(env)
127
+ @variation ||
128
+ env['sendfile.type'] ||
129
+ env['HTTP_X_SENDFILE_TYPE']
130
+ end
131
+
132
+ def map_accel_path(env, file)
133
+ if mapping = env['HTTP_X_ACCEL_MAPPING']
134
+ internal, external = mapping.split('=', 2).map{ |p| p.strip }
135
+ file.sub(/^#{internal}/i, external)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,289 @@
1
+ require 'optparse'
2
+
3
+ module Rack
4
+ class Server
5
+ class Options
6
+ def parse!(args)
7
+ options = {}
8
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
9
+ opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
10
+
11
+ opts.separator ""
12
+ opts.separator "Ruby options:"
13
+
14
+ lineno = 1
15
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
16
+ eval line, TOPLEVEL_BINDING, "-e", lineno
17
+ lineno += 1
18
+ }
19
+
20
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
21
+ options[:debug] = true
22
+ }
23
+ opts.on("-w", "--warn", "turn warnings on for your script") {
24
+ options[:warn] = true
25
+ }
26
+
27
+ opts.on("-I", "--include PATH",
28
+ "specify $LOAD_PATH (may be used more than once)") { |path|
29
+ options[:include] = path.split(":")
30
+ }
31
+
32
+ opts.on("-r", "--require LIBRARY",
33
+ "require the library, before executing your script") { |library|
34
+ options[:require] = library
35
+ }
36
+
37
+ opts.separator ""
38
+ opts.separator "Rack options:"
39
+ opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
40
+ options[:server] = s
41
+ }
42
+
43
+ opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
44
+ options[:Host] = host
45
+ }
46
+
47
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
48
+ options[:Port] = port
49
+ }
50
+
51
+ opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
52
+ options[:environment] = e
53
+ }
54
+
55
+ opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
56
+ options[:daemonize] = d ? true : false
57
+ }
58
+
59
+ opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
60
+ options[:pid] = f
61
+ }
62
+
63
+ opts.separator ""
64
+ opts.separator "Common options:"
65
+
66
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
67
+ puts opts
68
+ exit
69
+ end
70
+
71
+ opts.on_tail("--version", "Show version") do
72
+ puts "Rack #{Rack.version} (Release: #{Rack.release})"
73
+ exit
74
+ end
75
+ end
76
+
77
+ begin
78
+ opt_parser.parse! args
79
+ rescue OptionParser::InvalidOption => e
80
+ warn e.message
81
+ abort opt_parser.to_s
82
+ end
83
+
84
+ options[:config] = args.last if args.last
85
+ options
86
+ end
87
+ end
88
+
89
+ # Start a new rack server (like running rackup). This will parse ARGV and
90
+ # provide standard ARGV rackup options, defaulting to load 'config.ru'.
91
+ #
92
+ # Providing an options hash will prevent ARGV parsing and will not include
93
+ # any default options.
94
+ #
95
+ # This method can be used to very easily launch a CGI application, for
96
+ # example:
97
+ #
98
+ # Rack::Server.start(
99
+ # :app => lambda do |e|
100
+ # [200, {'Content-Type' => 'text/html'}, ['hello world']]
101
+ # end,
102
+ # :server => 'cgi'
103
+ # )
104
+ #
105
+ # Further options available here are documented on Rack::Server#initialize
106
+ def self.start(options = nil)
107
+ new(options).start
108
+ end
109
+
110
+ attr_writer :options
111
+
112
+ # Options may include:
113
+ # * :app
114
+ # a rack application to run (overrides :config)
115
+ # * :config
116
+ # a rackup configuration file path to load (.ru)
117
+ # * :environment
118
+ # this selects the middleware that will be wrapped around
119
+ # your application. Default options available are:
120
+ # - development: CommonLogger, ShowExceptions, and Lint
121
+ # - deployment: CommonLogger
122
+ # - none: no extra middleware
123
+ # note: when the server is a cgi server, CommonLogger is not included.
124
+ # * :server
125
+ # choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
126
+ # * :daemonize
127
+ # if true, the server will daemonize itself (fork, detach, etc)
128
+ # * :pid
129
+ # path to write a pid file after daemonize
130
+ # * :Host
131
+ # the host address to bind to (used by supporting Rack::Handler)
132
+ # * :Port
133
+ # the port to bind to (used by supporting Rack::Handler)
134
+ # * :AccessLog
135
+ # webrick acess log options (or supporting Rack::Handler)
136
+ # * :debug
137
+ # turn on debug output ($DEBUG = true)
138
+ # * :warn
139
+ # turn on warnings ($-w = true)
140
+ # * :include
141
+ # add given paths to $LOAD_PATH
142
+ # * :require
143
+ # require the given libraries
144
+ def initialize(options = nil)
145
+ @options = options
146
+ @app = options[:app] if options && options[:app]
147
+ end
148
+
149
+ def options
150
+ @options ||= parse_options(ARGV)
151
+ end
152
+
153
+ def default_options
154
+ {
155
+ :environment => ENV['RACK_ENV'] || "development",
156
+ :pid => nil,
157
+ :Port => 9292,
158
+ :Host => "0.0.0.0",
159
+ :AccessLog => [],
160
+ :config => "config.ru"
161
+ }
162
+ end
163
+
164
+ def app
165
+ @app ||= begin
166
+ if !::File.exist? options[:config]
167
+ abort "configuration #{options[:config]} not found"
168
+ end
169
+
170
+ app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
171
+ self.options.merge! options
172
+ app
173
+ end
174
+ end
175
+
176
+ def self.middleware
177
+ @middleware ||= begin
178
+ m = Hash.new {|h,k| h[k] = []}
179
+ m["deployment"].concat [
180
+ [Rack::ContentLength],
181
+ [Rack::Chunked],
182
+ lambda { |server|
183
+ server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
184
+ }
185
+ ]
186
+ m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
187
+ m
188
+ end
189
+ end
190
+
191
+ def middleware
192
+ self.class.middleware
193
+ end
194
+
195
+ def start
196
+ if options[:warn]
197
+ $-w = true
198
+ end
199
+
200
+ if includes = options[:include]
201
+ $LOAD_PATH.unshift(*includes)
202
+ end
203
+
204
+ if library = options[:require]
205
+ require library
206
+ end
207
+
208
+ if options[:debug]
209
+ $DEBUG = true
210
+ require 'pp'
211
+ p options[:server]
212
+ pp wrapped_app
213
+ pp app
214
+ end
215
+
216
+ # Touch the wrapped app, so that the config.ru is loaded before
217
+ # daemonization (i.e. before chdir, etc).
218
+ wrapped_app
219
+
220
+ daemonize_app if options[:daemonize]
221
+ write_pid if options[:pid]
222
+
223
+ trap(:INT) do
224
+ if server.respond_to?(:shutdown)
225
+ server.shutdown
226
+ else
227
+ exit
228
+ end
229
+ end
230
+
231
+ server.run wrapped_app, options
232
+ end
233
+
234
+ def server
235
+ @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
236
+ end
237
+
238
+ private
239
+ def parse_options(args)
240
+ options = default_options
241
+
242
+ # Don't evaluate CGI ISINDEX parameters.
243
+ # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
244
+ args.clear if ENV.include?("REQUEST_METHOD")
245
+
246
+ options.merge! opt_parser.parse! args
247
+ options[:config] = ::File.expand_path(options[:config])
248
+ ENV["RACK_ENV"] = options[:environment]
249
+ options
250
+ end
251
+
252
+ def opt_parser
253
+ Options.new
254
+ end
255
+
256
+ def build_app(app)
257
+ middleware[options[:environment]].reverse_each do |middleware|
258
+ middleware = middleware.call(self) if middleware.respond_to?(:call)
259
+ next unless middleware
260
+ klass = middleware.shift
261
+ app = klass.new(app, *middleware)
262
+ end
263
+ app
264
+ end
265
+
266
+ def wrapped_app
267
+ @wrapped_app ||= build_app app
268
+ end
269
+
270
+ def daemonize_app
271
+ if RUBY_VERSION < "1.9"
272
+ exit if fork
273
+ Process.setsid
274
+ exit if fork
275
+ Dir.chdir "/"
276
+ STDIN.reopen "/dev/null"
277
+ STDOUT.reopen "/dev/null", "a"
278
+ STDERR.reopen "/dev/null", "a"
279
+ else
280
+ Process.daemon
281
+ end
282
+ end
283
+
284
+ def write_pid
285
+ ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
286
+ at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
287
+ end
288
+ end
289
+ end