giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,150 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'unicorn'
4
+
5
+ module Unicorn::App
6
+
7
+ # This class is highly experimental (even more so than the rest of Unicorn)
8
+ # and has never run anything other than cgit.
9
+ class ExecCgi < Struct.new(:args)
10
+
11
+ CHUNK_SIZE = 16384
12
+ PASS_VARS = %w(
13
+ CONTENT_LENGTH
14
+ CONTENT_TYPE
15
+ GATEWAY_INTERFACE
16
+ AUTH_TYPE
17
+ PATH_INFO
18
+ PATH_TRANSLATED
19
+ QUERY_STRING
20
+ REMOTE_ADDR
21
+ REMOTE_HOST
22
+ REMOTE_IDENT
23
+ REMOTE_USER
24
+ REQUEST_METHOD
25
+ SERVER_NAME
26
+ SERVER_PORT
27
+ SERVER_PROTOCOL
28
+ SERVER_SOFTWARE
29
+ ).map { |x| x.freeze } # frozen strings are faster for Hash assignments
30
+
31
+ # Intializes the app, example of usage in a config.ru
32
+ # map "/cgit" do
33
+ # run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
34
+ # end
35
+ def initialize(*args)
36
+ self.args = args
37
+ first = args[0] or
38
+ raise ArgumentError, "need path to executable"
39
+ first[0] == ?/ or args[0] = ::File.expand_path(first)
40
+ File.executable?(args[0]) or
41
+ raise ArgumentError, "#{args[0]} is not executable"
42
+ end
43
+
44
+ # Calls the app
45
+ def call(env)
46
+ out, err = Unicorn::Util.tmpio, Unicorn::Util.tmpio
47
+ inp = force_file_input(env)
48
+ pid = fork { run_child(inp, out, err, env) }
49
+ inp.close
50
+ pid, status = Process.waitpid2(pid)
51
+ write_errors(env, err, status) if err.stat.size > 0
52
+ err.close
53
+
54
+ return parse_output!(out) if status.success?
55
+ out.close
56
+ [ 500, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
57
+ end
58
+
59
+ private
60
+
61
+ def run_child(inp, out, err, env)
62
+ PASS_VARS.each do |key|
63
+ val = env[key] or next
64
+ ENV[key] = val
65
+ end
66
+ ENV['SCRIPT_NAME'] = args[0]
67
+ ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
68
+ env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
69
+
70
+ a = IO.new(0).reopen(inp)
71
+ b = IO.new(1).reopen(out)
72
+ c = IO.new(2).reopen(err)
73
+ exec(*args)
74
+ end
75
+
76
+ # Extracts headers from CGI out, will change the offset of out.
77
+ # This returns a standard Rack-compatible return value:
78
+ # [ 200, HeadersHash, body ]
79
+ def parse_output!(out)
80
+ size = out.stat.size
81
+ out.sysseek(0)
82
+ head = out.sysread(CHUNK_SIZE)
83
+ offset = 2
84
+ head, body = head.split(/\n\n/, 2)
85
+ if body.nil?
86
+ head, body = head.split(/\r\n\r\n/, 2)
87
+ offset = 4
88
+ end
89
+ offset += head.length
90
+
91
+ # Allows +out+ to be used as a Rack body.
92
+ out.instance_eval { class << self; self; end }.instance_eval {
93
+ define_method(:each) { |&blk|
94
+ sysseek(offset)
95
+
96
+ # don't use a preallocated buffer for sysread since we can't
97
+ # guarantee an actual socket is consuming the yielded string
98
+ # (or if somebody is pushing to an array for eventual concatenation
99
+ begin
100
+ blk.call(sysread(CHUNK_SIZE))
101
+ rescue EOFError
102
+ break
103
+ end while true
104
+ }
105
+ }
106
+
107
+ size -= offset
108
+ prev = nil
109
+ headers = Rack::Utils::HeaderHash.new
110
+ head.split(/\r?\n/).each do |line|
111
+ case line
112
+ when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2
113
+ when /^[ \t]/ then headers[prev] << "\n#{line}" if prev
114
+ end
115
+ end
116
+ headers['Content-Length'] = size.to_s
117
+ [ 200, headers, out ]
118
+ end
119
+
120
+ # ensures rack.input is a file handle that we can redirect stdin to
121
+ def force_file_input(env)
122
+ inp = env['rack.input']
123
+ if inp.size == 0 # inp could be a StringIO or StringIO-like object
124
+ ::File.open('/dev/null', 'rb')
125
+ else
126
+ tmp = Unicorn::Util.tmpio
127
+
128
+ buf = inp.read(CHUNK_SIZE)
129
+ begin
130
+ tmp.syswrite(buf)
131
+ end while inp.read(CHUNK_SIZE, buf)
132
+ tmp.sysseek(0)
133
+ tmp
134
+ end
135
+ end
136
+
137
+ # rack.errors this may not be an IO object, so we couldn't
138
+ # just redirect the CGI executable to that earlier.
139
+ def write_errors(env, err, status)
140
+ err.seek(0)
141
+ dst = env['rack.errors']
142
+ pid = status.pid
143
+ dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n")
144
+ err.each_line { |line| dst.write("#{pid}: #{line}") }
145
+ dst.flush
146
+ end
147
+
148
+ end
149
+
150
+ end
@@ -0,0 +1,109 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+
6
+ # this class *must* be used with Rack::Chunked
7
+
8
+ module Unicorn::App
9
+ class Inetd < Struct.new(:cmd)
10
+
11
+ class CatBody < Struct.new(:errors, :err_rd, :out_rd, :pid_map)
12
+ def initialize(env, cmd)
13
+ self.errors = env['rack.errors']
14
+ in_rd, in_wr = IO.pipe
15
+ self.err_rd, err_wr = IO.pipe
16
+ self.out_rd, out_wr = IO.pipe
17
+
18
+ cmd_pid = fork {
19
+ inp, out, err = (0..2).map { |i| IO.new(i) }
20
+ inp.reopen(in_rd)
21
+ out.reopen(out_wr)
22
+ err.reopen(err_wr)
23
+ [ in_rd, in_wr, err_rd, err_wr, out_rd, out_wr ].each { |i| i.close }
24
+ exec(*cmd)
25
+ }
26
+ [ in_rd, err_wr, out_wr ].each { |io| io.close }
27
+ [ in_wr, err_rd, out_rd ].each { |io| io.binmode }
28
+ in_wr.sync = true
29
+
30
+ # Unfortunately, input here must be processed inside a seperate
31
+ # thread/process using blocking I/O since env['rack.input'] is not
32
+ # IO.select-able and attempting to make it so would trip Rack::Lint
33
+ inp_pid = fork {
34
+ input = env['rack.input']
35
+ [ err_rd, out_rd ].each { |io| io.close }
36
+
37
+ # this is dependent on input.read having readpartial semantics:
38
+ buf = input.read(16384)
39
+ begin
40
+ in_wr.write(buf)
41
+ end while input.read(16384, buf)
42
+ }
43
+ in_wr.close
44
+ self.pid_map = {
45
+ inp_pid => 'input streamer',
46
+ cmd_pid => cmd.inspect,
47
+ }
48
+ end
49
+
50
+ def each(&block)
51
+ begin
52
+ rd, = IO.select([err_rd, out_rd])
53
+ rd && rd.first or next
54
+
55
+ if rd.include?(err_rd)
56
+ begin
57
+ errors.write(err_rd.read_nonblock(16384))
58
+ rescue Errno::EINTR
59
+ rescue Errno::EAGAIN
60
+ break
61
+ end while true
62
+ end
63
+
64
+ rd.include?(out_rd) or next
65
+
66
+ begin
67
+ yield out_rd.read_nonblock(16384)
68
+ rescue Errno::EINTR
69
+ rescue Errno::EAGAIN
70
+ break
71
+ end while true
72
+ rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL
73
+ break
74
+ end while true
75
+
76
+ self
77
+ end
78
+
79
+ def close
80
+ pid_map.each { |pid, str|
81
+ begin
82
+ pid, status = Process.waitpid2(pid)
83
+ status.success? or
84
+ errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
85
+ rescue Errno::ECHILD
86
+ errors.write("Failed to reap #{str} (PID:#{pid})\n")
87
+ end
88
+ }
89
+ out_rd.close
90
+ err_rd.close
91
+ end
92
+
93
+ end
94
+
95
+ def initialize(*cmd)
96
+ self.cmd = cmd
97
+ end
98
+
99
+ def call(env)
100
+ /\A100-continue\z/i =~ env[Unicorn::Const::HTTP_EXPECT] and
101
+ return [ 100, {} , [] ]
102
+
103
+ [ 200, { 'Content-Type' => 'application/octet-stream' },
104
+ CatBody.new(env, cmd) ]
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # This code is based on the original Rails handler in Mongrel
4
+ # Copyright (c) 2005 Zed A. Shaw
5
+ # Copyright (c) 2009 Eric Wong
6
+ # You can redistribute it and/or modify it under the same terms as Ruby.
7
+ # Additional work donated by contributors. See CONTRIBUTORS for more info.
8
+ require 'unicorn/cgi_wrapper'
9
+ require 'dispatcher'
10
+
11
+ module Unicorn; module App; end; end
12
+
13
+ # Implements a handler that can run Rails.
14
+ class Unicorn::App::OldRails
15
+
16
+ def call(env)
17
+ cgi = Unicorn::CGIWrapper.new(env)
18
+ begin
19
+ Dispatcher.dispatch(cgi,
20
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
21
+ cgi.body)
22
+ rescue Object => e
23
+ err = env['rack.errors']
24
+ err.write("#{e} #{e.message}\n")
25
+ e.backtrace.each { |line| err.write("#{line}\n") }
26
+ end
27
+ cgi.out # finalize the response
28
+ cgi.rack_response
29
+ end
30
+
31
+ end
@@ -0,0 +1,60 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # This code is based on the original Rails handler in Mongrel
4
+ # Copyright (c) 2005 Zed A. Shaw
5
+ # Copyright (c) 2009 Eric Wong
6
+ # You can redistribute it and/or modify it under the same terms as Ruby.
7
+
8
+ # Static file handler for Rails < 2.3. This handler is only provided
9
+ # as a convenience for developers. Performance-minded deployments should
10
+ # use nginx (or similar) for serving static files.
11
+ #
12
+ # This supports page caching directly and will try to resolve a
13
+ # request in the following order:
14
+ #
15
+ # * If the requested exact PATH_INFO exists as a file then serve it.
16
+ # * If it exists at PATH_INFO+rest_operator+".html" exists
17
+ # then serve that.
18
+ #
19
+ # This means that if you are using page caching it will actually work
20
+ # with Unicorn and you should see a decent speed boost (but not as
21
+ # fast as if you use a static server like nginx).
22
+ class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server)
23
+ FILE_METHODS = { 'GET' => true, 'HEAD' => true }
24
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
25
+ REQUEST_URI = 'REQUEST_URI'.freeze
26
+ PATH_INFO = 'PATH_INFO'.freeze
27
+
28
+ def initialize(app)
29
+ self.app = app
30
+ self.root = "#{::RAILS_ROOT}/public"
31
+ self.file_server = ::Rack::File.new(root)
32
+ end
33
+
34
+ def call(env)
35
+ # short circuit this ASAP if serving non-file methods
36
+ FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env)
37
+
38
+ # first try the path as-is
39
+ path_info = env[PATH_INFO].chomp("/")
40
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
41
+ # File exists as-is so serve it up
42
+ env[PATH_INFO] = path_info
43
+ return file_server.call(env)
44
+ end
45
+
46
+ # then try the cached version:
47
+
48
+ # grab the semi-colon REST operator used by old versions of Rails
49
+ # this is the reason we didn't just copy the new Rails::Rack::Static
50
+ env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
51
+ path_info << "#$1#{ActionController::Base.page_cache_extension}"
52
+
53
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
54
+ env[PATH_INFO] = path_info
55
+ return file_server.call(env)
56
+ end
57
+
58
+ app.call(env) # call OldRails
59
+ end
60
+ end if defined?(Unicorn::App::OldRails)
@@ -0,0 +1,145 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # This code is based on the original CGIWrapper from Mongrel
4
+ # Copyright (c) 2005 Zed A. Shaw
5
+ # Copyright (c) 2009 Eric Wong
6
+ # You can redistribute it and/or modify it under the same terms as Ruby.
7
+ #
8
+ # Additional work donated by contributors. See CONTRIBUTORS for more info.
9
+
10
+ require 'cgi'
11
+
12
+ module Unicorn; end
13
+
14
+ # The beginning of a complete wrapper around Unicorn's internal HTTP
15
+ # processing system but maintaining the original Ruby CGI module. Use
16
+ # this only as a crutch to get existing CGI based systems working. It
17
+ # should handle everything, but please notify us if you see special
18
+ # warnings. This work is still very alpha so we need testers to help
19
+ # work out the various corner cases.
20
+ class Unicorn::CGIWrapper < ::CGI
21
+ undef_method :env_table
22
+ attr_reader :env_table
23
+ attr_reader :body
24
+
25
+ # these are stripped out of any keys passed to CGIWrapper.header function
26
+ NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
27
+ CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
28
+ CHARSET = 'charset'.freeze # this gets appended to Content-Type
29
+ COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
30
+ STATUS = 'status'.freeze # stored as @status
31
+ Status = 'Status'.freeze # code + human-readable text, Rails sets this
32
+
33
+ # some of these are common strings, but this is the only module
34
+ # using them and the reason they're not in Unicorn::Const
35
+ SET_COOKIE = 'Set-Cookie'.freeze
36
+ CONTENT_TYPE = 'Content-Type'.freeze
37
+ CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
38
+ RACK_INPUT = 'rack.input'.freeze
39
+ RACK_ERRORS = 'rack.errors'.freeze
40
+
41
+ # this maps CGI header names to HTTP header names
42
+ HEADER_MAP = {
43
+ 'status' => Status,
44
+ 'type' => CONTENT_TYPE,
45
+ 'server' => 'Server'.freeze,
46
+ 'language' => 'Content-Language'.freeze,
47
+ 'expires' => 'Expires'.freeze,
48
+ 'length' => CONTENT_LENGTH,
49
+ }
50
+
51
+ # Takes an a Rackable environment, plus any additional CGI.new
52
+ # arguments These are used internally to create a wrapper around the
53
+ # real CGI while maintaining Rack/Unicorn's view of the world. This
54
+ # this will NOT deal well with large responses that take up a lot of
55
+ # memory, but neither does the CGI nor the original CGIWrapper from
56
+ # Mongrel...
57
+ def initialize(rack_env, *args)
58
+ @env_table = rack_env
59
+ @status = nil
60
+ @head = {}
61
+ @headv = Hash.new { |hash,key| hash[key] = [] }
62
+ @body = StringIO.new("")
63
+ super(*args)
64
+ end
65
+
66
+ # finalizes the response in a way Rack applications would expect
67
+ def rack_response
68
+ # @head[CONTENT_LENGTH] ||= @body.size
69
+ @headv[SET_COOKIE].concat(@output_cookies) if @output_cookies
70
+ @headv.each_pair do |key,value|
71
+ @head[key] ||= value.join("\n") unless value.empty?
72
+ end
73
+
74
+ # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
75
+ @status ||= @head.delete(Status)
76
+
77
+ [ @status || 500, @head, [ @body.string ] ]
78
+ end
79
+
80
+ # The header is typically called to send back the header. In our case we
81
+ # collect it into a hash for later usage. This can be called multiple
82
+ # times to set different cookies.
83
+ def header(options = "text/html")
84
+ # if they pass in a string then just write the Content-Type
85
+ if String === options
86
+ @head[CONTENT_TYPE] ||= options
87
+ else
88
+ HEADER_MAP.each_pair do |from, to|
89
+ from = options.delete(from) or next
90
+ @head[to] = from.to_s
91
+ end
92
+
93
+ @head[CONTENT_TYPE] ||= "text/html"
94
+ if charset = options.delete(CHARSET)
95
+ @head[CONTENT_TYPE] << "; charset=#{charset}"
96
+ end
97
+
98
+ # lots of ways to set cookies
99
+ if cookie = options.delete(COOKIE)
100
+ set_cookies = @headv[SET_COOKIE]
101
+ case cookie
102
+ when Array
103
+ cookie.each { |c| set_cookies << c.to_s }
104
+ when Hash
105
+ cookie.each_value { |c| set_cookies << c.to_s }
106
+ else
107
+ set_cookies << cookie.to_s
108
+ end
109
+ end
110
+ @status ||= options.delete(STATUS) # all lower-case
111
+
112
+ # drop the keys we don't want anymore
113
+ options.delete(NPH)
114
+ options.delete(CONNECTION)
115
+
116
+ # finally, set the rest of the headers as-is, allowing duplicates
117
+ options.each_pair { |k,v| @headv[k] << v }
118
+ end
119
+
120
+ # doing this fakes out the cgi library to think the headers are empty
121
+ # we then do the real headers in the out function call later
122
+ ""
123
+ end
124
+
125
+ # The dumb thing is people can call header or this or both and in
126
+ # any order. So, we just reuse header and then finalize the
127
+ # HttpResponse the right way. This will have no effect if called
128
+ # the second time if the first "outputted" anything.
129
+ def out(options = "text/html")
130
+ header(options)
131
+ @body.size == 0 or return
132
+ @body << yield if block_given?
133
+ end
134
+
135
+ # Used to wrap the normal stdinput variable used inside CGI.
136
+ def stdinput
137
+ @env_table[RACK_INPUT]
138
+ end
139
+
140
+ # return a pointer to the StringIO body since it's STDOUT-like
141
+ def stdoutput
142
+ @body
143
+ end
144
+
145
+ end