unicorn-fotopedia 0.99.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 (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -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 +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. metadata +283 -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,33 @@
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
+ autoload :Static, "unicorn/app/old_rails/static"
17
+
18
+ def call(env)
19
+ cgi = Unicorn::CGIWrapper.new(env)
20
+ begin
21
+ Dispatcher.dispatch(cgi,
22
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
23
+ cgi.body)
24
+ rescue => e
25
+ err = env['rack.errors']
26
+ err.write("#{e} #{e.message}\n")
27
+ e.backtrace.each { |line| err.write("#{line}\n") }
28
+ end
29
+ cgi.out # finalize the response
30
+ cgi.rack_response
31
+ end
32
+
33
+ end
@@ -0,0 +1,58 @@
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
+
25
+ # avoid allocating new strings for hash lookups
26
+ REQUEST_METHOD = 'REQUEST_METHOD'
27
+ REQUEST_URI = 'REQUEST_URI'
28
+ PATH_INFO = 'PATH_INFO'
29
+
30
+ def initialize(app)
31
+ self.app = app
32
+ self.root = "#{::RAILS_ROOT}/public"
33
+ self.file_server = ::Rack::File.new(root)
34
+ end
35
+
36
+ def call(env)
37
+ # short circuit this ASAP if serving non-file methods
38
+ FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env)
39
+
40
+ # first try the path as-is
41
+ path_info = env[PATH_INFO].chomp("/")
42
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
43
+ # File exists as-is so serve it up
44
+ env[PATH_INFO] = path_info
45
+ return file_server.call(env)
46
+ end
47
+
48
+ # then try the cached version:
49
+ path_info << ActionController::Base.page_cache_extension
50
+
51
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
52
+ env[PATH_INFO] = path_info
53
+ return file_server.call(env)
54
+ end
55
+
56
+ app.call(env) # call OldRails
57
+ end
58
+ 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