edgar-rack 1.2.1

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.
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