qoobaa-rack 1.0.0.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 (116) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +18 -0
  3. data/RDOX +0 -0
  4. data/README +353 -0
  5. data/Rakefile +164 -0
  6. data/SPEC +164 -0
  7. data/bin/rackup +176 -0
  8. data/contrib/rack_logo.svg +111 -0
  9. data/example/lobster.ru +4 -0
  10. data/example/protectedlobster.rb +14 -0
  11. data/example/protectedlobster.ru +8 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +37 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +487 -0
  21. data/lib/rack/builder.rb +63 -0
  22. data/lib/rack/cascade.rb +41 -0
  23. data/lib/rack/chunked.rb +49 -0
  24. data/lib/rack/commonlogger.rb +52 -0
  25. data/lib/rack/conditionalget.rb +47 -0
  26. data/lib/rack/content_length.rb +29 -0
  27. data/lib/rack/content_type.rb +23 -0
  28. data/lib/rack/deflater.rb +96 -0
  29. data/lib/rack/directory.rb +153 -0
  30. data/lib/rack/file.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +88 -0
  34. data/lib/rack/handler/lsws.rb +60 -0
  35. data/lib/rack/handler/mongrel.rb +87 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +71 -0
  40. data/lib/rack/handler.rb +69 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +546 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +16 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +204 -0
  47. data/lib/rack/mock.rb +187 -0
  48. data/lib/rack/recursive.rb +57 -0
  49. data/lib/rack/reloader.rb +107 -0
  50. data/lib/rack/request.rb +248 -0
  51. data/lib/rack/response.rb +183 -0
  52. data/lib/rack/rewindable_input.rb +100 -0
  53. data/lib/rack/session/abstract/id.rb +142 -0
  54. data/lib/rack/session/cookie.rb +91 -0
  55. data/lib/rack/session/memcache.rb +109 -0
  56. data/lib/rack/session/pool.rb +100 -0
  57. data/lib/rack/showexceptions.rb +349 -0
  58. data/lib/rack/showstatus.rb +106 -0
  59. data/lib/rack/static.rb +38 -0
  60. data/lib/rack/urlmap.rb +55 -0
  61. data/lib/rack/utils.rb +528 -0
  62. data/lib/rack.rb +90 -0
  63. data/rack.gemspec +60 -0
  64. data/test/cgi/lighttpd.conf +20 -0
  65. data/test/cgi/test +9 -0
  66. data/test/cgi/test.fcgi +8 -0
  67. data/test/cgi/test.ru +7 -0
  68. data/test/multipart/binary +0 -0
  69. data/test/multipart/empty +10 -0
  70. data/test/multipart/file1.txt +1 -0
  71. data/test/multipart/ie +6 -0
  72. data/test/multipart/nested +10 -0
  73. data/test/multipart/none +9 -0
  74. data/test/multipart/text +10 -0
  75. data/test/spec_rack_auth_basic.rb +73 -0
  76. data/test/spec_rack_auth_digest.rb +226 -0
  77. data/test/spec_rack_auth_openid.rb +84 -0
  78. data/test/spec_rack_builder.rb +84 -0
  79. data/test/spec_rack_camping.rb +51 -0
  80. data/test/spec_rack_cascade.rb +48 -0
  81. data/test/spec_rack_cgi.rb +89 -0
  82. data/test/spec_rack_chunked.rb +62 -0
  83. data/test/spec_rack_commonlogger.rb +61 -0
  84. data/test/spec_rack_conditionalget.rb +41 -0
  85. data/test/spec_rack_content_length.rb +43 -0
  86. data/test/spec_rack_content_type.rb +30 -0
  87. data/test/spec_rack_deflater.rb +127 -0
  88. data/test/spec_rack_directory.rb +61 -0
  89. data/test/spec_rack_fastcgi.rb +89 -0
  90. data/test/spec_rack_file.rb +75 -0
  91. data/test/spec_rack_handler.rb +43 -0
  92. data/test/spec_rack_head.rb +30 -0
  93. data/test/spec_rack_lint.rb +521 -0
  94. data/test/spec_rack_lobster.rb +45 -0
  95. data/test/spec_rack_lock.rb +38 -0
  96. data/test/spec_rack_methodoverride.rb +60 -0
  97. data/test/spec_rack_mock.rb +243 -0
  98. data/test/spec_rack_mongrel.rb +189 -0
  99. data/test/spec_rack_recursive.rb +77 -0
  100. data/test/spec_rack_request.rb +504 -0
  101. data/test/spec_rack_response.rb +218 -0
  102. data/test/spec_rack_rewindable_input.rb +118 -0
  103. data/test/spec_rack_session_cookie.rb +82 -0
  104. data/test/spec_rack_session_memcache.rb +250 -0
  105. data/test/spec_rack_session_pool.rb +172 -0
  106. data/test/spec_rack_showexceptions.rb +21 -0
  107. data/test/spec_rack_showstatus.rb +72 -0
  108. data/test/spec_rack_static.rb +37 -0
  109. data/test/spec_rack_thin.rb +91 -0
  110. data/test/spec_rack_urlmap.rb +185 -0
  111. data/test/spec_rack_utils.rb +467 -0
  112. data/test/spec_rack_webrick.rb +130 -0
  113. data/test/testrequest.rb +57 -0
  114. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  115. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  116. metadata +276 -0
@@ -0,0 +1,47 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+
5
+ # Middleware that enables conditional GET using If-None-Match and
6
+ # If-Modified-Since. The application should set either or both of the
7
+ # Last-Modified or Etag response headers according to RFC 2616. When
8
+ # either of the conditions is met, the response body is set to be zero
9
+ # length and the response status is set to 304 Not Modified.
10
+ #
11
+ # Applications that defer response body generation until the body's each
12
+ # message is received will avoid response body generation completely when
13
+ # a conditional GET matches.
14
+ #
15
+ # Adapted from Michael Klishin's Merb implementation:
16
+ # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
17
+ class ConditionalGet
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def call(env)
23
+ return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])
24
+
25
+ status, headers, body = @app.call(env)
26
+ headers = Utils::HeaderHash.new(headers)
27
+ if etag_matches?(env, headers) || modified_since?(env, headers)
28
+ status = 304
29
+ headers.delete('Content-Type')
30
+ headers.delete('Content-Length')
31
+ body = []
32
+ end
33
+ [status, headers, body]
34
+ end
35
+
36
+ private
37
+ def etag_matches?(env, headers)
38
+ etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
39
+ end
40
+
41
+ def modified_since?(env, headers)
42
+ last_modified = headers['Last-Modified'] and
43
+ last_modified == env['HTTP_IF_MODIFIED_SINCE']
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,29 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+ # Sets the Content-Length header on responses with fixed-length bodies.
5
+ class ContentLength
6
+ include Rack::Utils
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ status, headers, body = @app.call(env)
14
+ headers = HeaderHash.new(headers)
15
+
16
+ if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
17
+ !headers['Content-Length'] &&
18
+ !headers['Transfer-Encoding'] &&
19
+ (body.respond_to?(:to_ary) || body.respond_to?(:to_str))
20
+
21
+ body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
22
+ length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
23
+ headers['Content-Length'] = length.to_s
24
+ end
25
+
26
+ [status, headers, body]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack
4
+
5
+ # Sets the Content-Type header on responses which don't have one.
6
+ #
7
+ # Builder Usage:
8
+ # use Rack::ContentType, "text/plain"
9
+ #
10
+ # When no content type argument is provided, "text/html" is assumed.
11
+ class ContentType
12
+ def initialize(app, content_type = "text/html")
13
+ @app, @content_type = app, content_type
14
+ end
15
+
16
+ def call(env)
17
+ status, headers, body = @app.call(env)
18
+ headers = Utils::HeaderHash.new(headers)
19
+ headers['Content-Type'] ||= @content_type
20
+ [status, headers.to_hash, body]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ require "zlib"
2
+ require "stringio"
3
+ require "time" # for Time.httpdate
4
+ require 'rack/utils'
5
+
6
+ module Rack
7
+ class Deflater
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ status, headers, body = @app.call(env)
14
+ headers = Utils::HeaderHash.new(headers)
15
+
16
+ # Skip compressing empty entity body responses and responses with
17
+ # no-transform set.
18
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
19
+ headers['Cache-Control'].to_s =~ /\bno-transform\b/
20
+ return [status, headers, body]
21
+ end
22
+
23
+ request = Request.new(env)
24
+
25
+ encoding = Utils.select_best_encoding(%w(gzip deflate identity),
26
+ request.accept_encoding)
27
+
28
+ # Set the Vary HTTP header.
29
+ vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
30
+ unless vary.include?("*") || vary.include?("Accept-Encoding")
31
+ headers["Vary"] = vary.push("Accept-Encoding").join(",")
32
+ end
33
+
34
+ case encoding
35
+ when "gzip"
36
+ headers['Content-Encoding'] = "gzip"
37
+ headers.delete('Content-Length')
38
+ mtime = headers.key?("Last-Modified") ?
39
+ Time.httpdate(headers["Last-Modified"]) : Time.now
40
+ [status, headers, GzipStream.new(body, mtime)]
41
+ when "deflate"
42
+ headers['Content-Encoding'] = "deflate"
43
+ headers.delete('Content-Length')
44
+ [status, headers, DeflateStream.new(body)]
45
+ when "identity"
46
+ [status, headers, body]
47
+ when nil
48
+ message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
49
+ [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
50
+ end
51
+ end
52
+
53
+ class GzipStream
54
+ def initialize(body, mtime)
55
+ @body = body
56
+ @mtime = mtime
57
+ end
58
+
59
+ def each(&block)
60
+ @writer = block
61
+ gzip =::Zlib::GzipWriter.new(self)
62
+ gzip.mtime = @mtime
63
+ @body.each { |part| gzip << part }
64
+ @body.close if @body.respond_to?(:close)
65
+ gzip.close
66
+ @writer = nil
67
+ end
68
+
69
+ def write(data)
70
+ @writer.call(data)
71
+ end
72
+ end
73
+
74
+ class DeflateStream
75
+ DEFLATE_ARGS = [
76
+ Zlib::DEFAULT_COMPRESSION,
77
+ # drop the zlib header which causes both Safari and IE to choke
78
+ -Zlib::MAX_WBITS,
79
+ Zlib::DEF_MEM_LEVEL,
80
+ Zlib::DEFAULT_STRATEGY
81
+ ]
82
+
83
+ def initialize(body)
84
+ @body = body
85
+ end
86
+
87
+ def each
88
+ deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
89
+ @body.each { |part| yield deflater.deflate(part) }
90
+ @body.close if @body.respond_to?(:close)
91
+ yield deflater.finish
92
+ nil
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,153 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ module Rack
6
+ # Rack::Directory serves entries below the +root+ given, according to the
7
+ # path info of the Rack request. If a directory is found, the file's contents
8
+ # will be presented in an html based index. If a file is found, the env will
9
+ # be passed to the specified +app+.
10
+ #
11
+ # If +app+ is not specified, a Rack::File of the same +root+ will be used.
12
+
13
+ class Directory
14
+ DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
15
+ DIR_PAGE = <<-PAGE
16
+ <html><head>
17
+ <title>%s</title>
18
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
19
+ <style type='text/css'>
20
+ table { width:100%%; }
21
+ .name { text-align:left; }
22
+ .size, .mtime { text-align:right; }
23
+ .type { width:11em; }
24
+ .mtime { width:15em; }
25
+ </style>
26
+ </head><body>
27
+ <h1>%s</h1>
28
+ <hr />
29
+ <table>
30
+ <tr>
31
+ <th class='name'>Name</th>
32
+ <th class='size'>Size</th>
33
+ <th class='type'>Type</th>
34
+ <th class='mtime'>Last Modified</th>
35
+ </tr>
36
+ %s
37
+ </table>
38
+ <hr />
39
+ </body></html>
40
+ PAGE
41
+
42
+ attr_reader :files
43
+ attr_accessor :root, :path
44
+
45
+ def initialize(root, app=nil)
46
+ @root = F.expand_path(root)
47
+ @app = app || Rack::File.new(@root)
48
+ end
49
+
50
+ def call(env)
51
+ dup._call(env)
52
+ end
53
+
54
+ F = ::File
55
+
56
+ def _call(env)
57
+ @env = env
58
+ @script_name = env['SCRIPT_NAME']
59
+ @path_info = Utils.unescape(env['PATH_INFO'])
60
+
61
+ if forbidden = check_forbidden
62
+ forbidden
63
+ else
64
+ @path = F.join(@root, @path_info)
65
+ list_path
66
+ end
67
+ end
68
+
69
+ def check_forbidden
70
+ return unless @path_info.include? ".."
71
+
72
+ body = "Forbidden\n"
73
+ size = Rack::Utils.bytesize(body)
74
+ return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
75
+ end
76
+
77
+ def list_directory
78
+ @files = [['../','Parent Directory','','','']]
79
+ glob = F.join(@path, '*')
80
+
81
+ Dir[glob].sort.each do |node|
82
+ stat = stat(node)
83
+ next unless stat
84
+ basename = F.basename(node)
85
+ ext = F.extname(node)
86
+
87
+ url = F.join(@script_name, @path_info, basename)
88
+ size = stat.size
89
+ type = stat.directory? ? 'directory' : Mime.mime_type(ext)
90
+ size = stat.directory? ? '-' : filesize_format(size)
91
+ mtime = stat.mtime.httpdate
92
+ url << '/' if stat.directory?
93
+ basename << '/' if stat.directory?
94
+
95
+ @files << [ url, basename, size, type, mtime ]
96
+ end
97
+
98
+ return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
99
+ end
100
+
101
+ def stat(node, max = 10)
102
+ F.stat(node)
103
+ rescue Errno::ENOENT, Errno::ELOOP
104
+ return nil
105
+ end
106
+
107
+ # TODO: add correct response if not readable, not sure if 404 is the best
108
+ # option
109
+ def list_path
110
+ @stat = F.stat(@path)
111
+
112
+ if @stat.readable?
113
+ return @app.call(@env) if @stat.file?
114
+ return list_directory if @stat.directory?
115
+ else
116
+ raise Errno::ENOENT, 'No such file or directory'
117
+ end
118
+
119
+ rescue Errno::ENOENT, Errno::ELOOP
120
+ return entity_not_found
121
+ end
122
+
123
+ def entity_not_found
124
+ body = "Entity not found: #{@path_info}\n"
125
+ size = Rack::Utils.bytesize(body)
126
+ return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
127
+ end
128
+
129
+ def each
130
+ show_path = @path.sub(/^#{@root}/,'')
131
+ files = @files.map{|f| DIR_FILE % f }*"\n"
132
+ page = DIR_PAGE % [ show_path, show_path , files ]
133
+ page.each_line{|l| yield l }
134
+ end
135
+
136
+ # Stolen from Ramaze
137
+
138
+ FILESIZE_FORMAT = [
139
+ ['%.1fT', 1 << 40],
140
+ ['%.1fG', 1 << 30],
141
+ ['%.1fM', 1 << 20],
142
+ ['%.1fK', 1 << 10],
143
+ ]
144
+
145
+ def filesize_format(int)
146
+ FILESIZE_FORMAT.each do |format, size|
147
+ return format % (int.to_f / size) if int >= size
148
+ end
149
+
150
+ int.to_s + 'B'
151
+ end
152
+ end
153
+ end
data/lib/rack/file.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'time'
2
+ require 'rack/utils'
3
+ require 'rack/mime'
4
+
5
+ module Rack
6
+ # Rack::File serves files below the +root+ given, according to the
7
+ # path info of the Rack request.
8
+ #
9
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
10
+ # like sendfile on the +path+.
11
+
12
+ class File
13
+ attr_accessor :root
14
+ attr_accessor :path
15
+
16
+ alias :to_path :path
17
+
18
+ def initialize(root)
19
+ @root = root
20
+ end
21
+
22
+ def call(env)
23
+ dup._call(env)
24
+ end
25
+
26
+ F = ::File
27
+
28
+ def _call(env)
29
+ @path_info = Utils.unescape(env["PATH_INFO"])
30
+ return forbidden if @path_info.include? ".."
31
+
32
+ @path = F.join(@root, @path_info)
33
+
34
+ begin
35
+ if F.file?(@path) && F.readable?(@path)
36
+ serving
37
+ else
38
+ raise Errno::EPERM
39
+ end
40
+ rescue SystemCallError
41
+ not_found
42
+ end
43
+ end
44
+
45
+ def forbidden
46
+ body = "Forbidden\n"
47
+ [403, {"Content-Type" => "text/plain",
48
+ "Content-Length" => body.size.to_s},
49
+ [body]]
50
+ end
51
+
52
+ # NOTE:
53
+ # We check via File::size? whether this file provides size info
54
+ # via stat (e.g. /proc files often don't), otherwise we have to
55
+ # figure it out by reading the whole file into memory. And while
56
+ # we're at it we also use this as body then.
57
+
58
+ def serving
59
+ if size = F.size?(@path)
60
+ body = self
61
+ else
62
+ body = [F.read(@path)]
63
+ size = Utils.bytesize(body.first)
64
+ end
65
+
66
+ [200, {
67
+ "Last-Modified" => F.mtime(@path).httpdate,
68
+ "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
69
+ "Content-Length" => size.to_s
70
+ }, body]
71
+ end
72
+
73
+ def not_found
74
+ body = "File not found: #{@path_info}\n"
75
+ [404, {"Content-Type" => "text/plain",
76
+ "Content-Length" => body.size.to_s},
77
+ [body]]
78
+ end
79
+
80
+ def each
81
+ F.open(@path, "rb") { |file|
82
+ while part = file.read(8192)
83
+ yield part
84
+ end
85
+ }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,61 @@
1
+ require 'rack/content_length'
2
+
3
+ module Rack
4
+ module Handler
5
+ class CGI
6
+ def self.run(app, options=nil)
7
+ serve app
8
+ end
9
+
10
+ def self.serve(app)
11
+ app = ContentLength.new(app)
12
+
13
+ env = ENV.to_hash
14
+ env.delete "HTTP_CONTENT_LENGTH"
15
+
16
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
17
+
18
+ env.update({"rack.version" => [1,0],
19
+ "rack.input" => $stdin,
20
+ "rack.errors" => $stderr,
21
+
22
+ "rack.multithread" => false,
23
+ "rack.multiprocess" => true,
24
+ "rack.run_once" => true,
25
+
26
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
27
+ })
28
+
29
+ env["QUERY_STRING"] ||= ""
30
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
31
+ env["REQUEST_PATH"] ||= "/"
32
+
33
+ status, headers, body = app.call(env)
34
+ begin
35
+ send_headers status, headers
36
+ send_body body
37
+ ensure
38
+ body.close if body.respond_to? :close
39
+ end
40
+ end
41
+
42
+ def self.send_headers(status, headers)
43
+ STDOUT.print "Status: #{status}\r\n"
44
+ headers.each { |k, vs|
45
+ vs.split("\n").each { |v|
46
+ STDOUT.print "#{k}: #{v}\r\n"
47
+ }
48
+ }
49
+ STDOUT.print "\r\n"
50
+ STDOUT.flush
51
+ end
52
+
53
+ def self.send_body(body)
54
+ body.each { |part|
55
+ STDOUT.print part
56
+ STDOUT.flush
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ require 'swiftcore/evented_mongrel'
2
+
3
+ module Rack
4
+ module Handler
5
+ class EventedMongrel < Handler::Mongrel
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,88 @@
1
+ require 'fcgi'
2
+ require 'socket'
3
+ require 'rack/content_length'
4
+ require 'rack/rewindable_input'
5
+
6
+ class FCGI::Stream
7
+ alias _rack_read_without_buffer read
8
+
9
+ def read(n, buffer=nil)
10
+ buf = _rack_read_without_buffer n
11
+ buffer.replace(buf.to_s) if buffer
12
+ buf
13
+ end
14
+ end
15
+
16
+ module Rack
17
+ module Handler
18
+ class FastCGI
19
+ def self.run(app, options={})
20
+ file = options[:File] and STDIN.reopen(UNIXServer.new(file))
21
+ port = options[:Port] and STDIN.reopen(TCPServer.new(port))
22
+ FCGI.each { |request|
23
+ serve request, app
24
+ }
25
+ end
26
+
27
+ def self.serve(request, app)
28
+ app = Rack::ContentLength.new(app)
29
+
30
+ env = request.env
31
+ env.delete "HTTP_CONTENT_LENGTH"
32
+
33
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
34
+
35
+ rack_input = RewindableInput.new(request.in)
36
+
37
+ env.update({"rack.version" => [1,0],
38
+ "rack.input" => rack_input,
39
+ "rack.errors" => request.err,
40
+
41
+ "rack.multithread" => false,
42
+ "rack.multiprocess" => true,
43
+ "rack.run_once" => false,
44
+
45
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
46
+ })
47
+
48
+ env["QUERY_STRING"] ||= ""
49
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
50
+ env["REQUEST_PATH"] ||= "/"
51
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
52
+ env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
53
+ env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
54
+
55
+ begin
56
+ status, headers, body = app.call(env)
57
+ begin
58
+ send_headers request.out, status, headers
59
+ send_body request.out, body
60
+ ensure
61
+ body.close if body.respond_to? :close
62
+ end
63
+ ensure
64
+ rack_input.close
65
+ request.finish
66
+ end
67
+ end
68
+
69
+ def self.send_headers(out, status, headers)
70
+ out.print "Status: #{status}\r\n"
71
+ headers.each { |k, vs|
72
+ vs.split("\n").each { |v|
73
+ out.print "#{k}: #{v}\r\n"
74
+ }
75
+ }
76
+ out.print "\r\n"
77
+ out.flush
78
+ end
79
+
80
+ def self.send_body(out, body)
81
+ body.each { |part|
82
+ out.print part
83
+ out.flush
84
+ }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,60 @@
1
+ require 'lsapi'
2
+ require 'rack/content_length'
3
+
4
+ module Rack
5
+ module Handler
6
+ class LSWS
7
+ def self.run(app, options=nil)
8
+ while LSAPI.accept != nil
9
+ serve app
10
+ end
11
+ end
12
+ def self.serve(app)
13
+ app = Rack::ContentLength.new(app)
14
+
15
+ env = ENV.to_hash
16
+ env.delete "HTTP_CONTENT_LENGTH"
17
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
18
+
19
+ rack_input = RewindableInput.new($stdin.read.to_s)
20
+
21
+ env.update(
22
+ "rack.version" => [1,0],
23
+ "rack.input" => rack_input,
24
+ "rack.errors" => $stderr,
25
+ "rack.multithread" => false,
26
+ "rack.multiprocess" => true,
27
+ "rack.run_once" => false,
28
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
29
+ )
30
+
31
+ env["QUERY_STRING"] ||= ""
32
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
33
+ env["REQUEST_PATH"] ||= "/"
34
+ status, headers, body = app.call(env)
35
+ begin
36
+ send_headers status, headers
37
+ send_body body
38
+ ensure
39
+ body.close if body.respond_to? :close
40
+ end
41
+ end
42
+ def self.send_headers(status, headers)
43
+ print "Status: #{status}\r\n"
44
+ headers.each { |k, vs|
45
+ vs.split("\n").each { |v|
46
+ print "#{k}: #{v}\r\n"
47
+ }
48
+ }
49
+ print "\r\n"
50
+ STDOUT.flush
51
+ end
52
+ def self.send_body(body)
53
+ body.each { |part|
54
+ print part
55
+ STDOUT.flush
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end