rack-streaming-proxy2 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2009-11-14
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
@@ -0,0 +1,78 @@
1
+ rack-streaming-proxy
2
+ by Nathan Witmer <nwitmer@gmail.com>
3
+ http://github.com/aniero/rack-streaming-proxy
4
+
5
+ == DESCRIPTION:
6
+
7
+ Streaming proxy for Rack, the rainbows to Rack::Proxy's unicorn.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ Provides a transparent streaming proxy to be used as rack middleware.
12
+
13
+ * Streams the response from the downstream server to minimize memory usage
14
+ * Handles chunked encoding if used
15
+ * Proxies GET/PUT/POST/DELETE, XHR, and cookies
16
+
17
+ Use this when you need to have the response streamed back to the client,
18
+ for example when handling large file requests that could be proxied
19
+ directly but need to be authenticated against the rest of your middleware
20
+ stack.
21
+
22
+ Note that this will not work well with EventMachine. EM buffers the entire
23
+ rack response before sending it to the client. When testing, try
24
+ mongrel (via rackup) or passenger, rather than the EM-based thin. See
25
+ http://groups.google.com/group/thin-ruby/browse_thread/thread/4762f8f851b965f6
26
+ for more discussion.
27
+
28
+ I've included a simple streamer app for testing and development.
29
+
30
+ Thanks to:
31
+
32
+ * Tom Lea (cwninja) for Rack::Proxy (http://gist.github.com/207938)
33
+ * Tim Pease for bones, servolux, &c
34
+
35
+ == SYNOPSIS:
36
+
37
+ require "rack/streaming_proxy"
38
+
39
+ use Rack::StreamingProxy do |request|
40
+ # inside the request block, return the full URI to redirect the request to,
41
+ # or nil/false if the request should continue on down the middleware stack.
42
+ if request.path.start_with?("/proxy")
43
+ "http://another_server#{request.path}"
44
+ end
45
+ end
46
+
47
+ == REQUIREMENTS:
48
+
49
+ * servolux (gem install servolux)
50
+
51
+ == INSTALL:
52
+
53
+ * sudo gem install rack-streaming-proxy --source http://gemcutter.org
54
+
55
+ == LICENSE:
56
+
57
+ (The MIT License)
58
+
59
+ Copyright (c) 2009 Nathan Witmer
60
+
61
+ Permission is hereby granted, free of charge, to any person obtaining
62
+ a copy of this software and associated documentation files (the
63
+ 'Software'), to deal in the Software without restriction, including
64
+ without limitation the rights to use, copy, modify, merge, publish,
65
+ distribute, sublicense, and/or sell copies of the Software, and to
66
+ permit persons to whom the Software is furnished to do so, subject to
67
+ the following conditions:
68
+
69
+ The above copyright notice and this permission notice shall be
70
+ included in all copies or substantial portions of the Software.
71
+
72
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
73
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
74
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
75
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
76
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
77
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
78
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'bones'
3
+ rescue LoadError
4
+ abort '### Please install the "bones" gem ###'
5
+ end
6
+
7
+ ensure_in_path 'lib'
8
+ require 'rack/streaming_proxy'
9
+
10
+ task :default => 'spec:specdoc'
11
+ task 'gem:release' => 'spec:specdoc'
12
+
13
+ Bones {
14
+ name 'rack-streaming-proxy'
15
+ authors 'Nathan Witmer'
16
+ email 'nwitmer@gmail.com'
17
+ url 'http://github.com/aniero/rack-streaming-proxy'
18
+ version Rack::StreamingProxy::VERSION
19
+ ignore_file '.gitignore'
20
+ depend_on "rack", :version => ">= 1.0"
21
+ depend_on "servolux", :version => "~> 0.8.1"
22
+ depend_on "rack-test", :version => "~> 0.5.1", :development => true
23
+ spec {
24
+ opts ["--colour", "--loadby mtime", "--reverse", "--diff unified"]
25
+ }
26
+ enable_sudo
27
+ }
28
+
@@ -0,0 +1,13 @@
1
+ require File.expand_path(
2
+ File.join(File.dirname(__FILE__), %w[.. lib rack streaming_proxy]))
3
+
4
+ use Rack::Reloader, 1
5
+ # use Rack::CommonLogger # rackup already has commonlogger loaded
6
+ use Rack::Lint
7
+ use Rack::StreamingProxy do |req|
8
+ url = "http://localhost:4000#{req.path}"
9
+ url << "?#{req.query_string}" unless req.query_string.empty?
10
+ url
11
+ end
12
+
13
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, ""] }
@@ -0,0 +1,54 @@
1
+ class Streamer
2
+ include Rack::Utils
3
+
4
+ def call(env)
5
+ req = Rack::Request.new(env)
6
+ headers = {"Content-Type" => "text/plain"}
7
+
8
+ @chunked = req.path.start_with?("/chunked")
9
+
10
+ if count = req.path.match(/(\d+)$/)
11
+ count = count[0].to_i
12
+ else
13
+ count = 100
14
+ end
15
+ @strings = count.times.collect {|n| "~~~~~ #{n} ~~~~~\n" }
16
+
17
+ if chunked?
18
+ headers["Transfer-Encoding"] = "chunked"
19
+ else
20
+ headers["Content-Length"] = @strings.inject(0) {|sum, s| sum += bytesize(s)}.to_s
21
+ end
22
+
23
+ [200, headers, self.dup]
24
+ end
25
+
26
+ def each
27
+ term = "\r\n"
28
+ @strings.each do |chunk|
29
+ if chunked?
30
+ size = bytesize(chunk)
31
+ yield [size.to_s(16), term, chunk, term].join
32
+ else
33
+ yield chunk
34
+ end
35
+ sleep 0.05
36
+ end
37
+ yield ["0", term, "", term].join if chunked?
38
+ end
39
+
40
+ protected
41
+
42
+ def chunked?
43
+ @chunked
44
+ end
45
+ end
46
+
47
+ # use Rack::CommonLogger # rackup already has commonlogger loaded
48
+ use Rack::Lint
49
+
50
+ # GET /
51
+ # GET /10
52
+ # GET /chunked
53
+ # GET /chunked/10
54
+ run Streamer.new
@@ -0,0 +1,107 @@
1
+ module Rack
2
+ class StreamingProxy
3
+
4
+ class Error < StandardError; end
5
+
6
+ # :stopdoc:
7
+ VERSION = '1.0.3'
8
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
9
+ PATH = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "..")) + ::File::SEPARATOR
10
+ # :startdoc:
11
+
12
+ # Returns the version string for the library.
13
+ #
14
+ def self.version
15
+ VERSION
16
+ end
17
+
18
+ # Returns the library path for the module. If any arguments are given,
19
+ # they will be joined to the end of the libray path using
20
+ # <tt>File.join</tt>.
21
+ #
22
+ def self.libpath( *args )
23
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
24
+ end
25
+
26
+ # Returns the lpath for the module. If any arguments are given,
27
+ # they will be joined to the end of the path using
28
+ # <tt>File.join</tt>.
29
+ #
30
+ def self.path( *args )
31
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
32
+ end
33
+
34
+ # Utility method used to require all files ending in .rb that lie in the
35
+ # directory below this file that has the same name as the filename passed
36
+ # in. Optionally, a specific _directory_ name can be passed in such that
37
+ # the _filename_ does not have to be equivalent to the directory.
38
+ #
39
+ def self.require_all_libs_relative_to( fname, dir = nil )
40
+ dir ||= ::File.basename(fname, '.*')
41
+ search_me = ::File.expand_path(
42
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
43
+
44
+ Dir.glob(search_me).sort.each {|rb| require rb}
45
+ end
46
+
47
+ # The block provided to the initializer is given a Rack::Request
48
+ # and should return:
49
+ #
50
+ # * nil/false to skip the proxy and continue down the stack
51
+ # * a complete uri (with query string if applicable) to proxy to
52
+ #
53
+ # E.g.
54
+ #
55
+ # use Rack::StreamingProxy do |req|
56
+ # if req.path.start_with?("/search")
57
+ # "http://some_other_service/search?#{req.query}"
58
+ # end
59
+ # end
60
+ #
61
+ # Most headers, request body, and HTTP method are preserved.
62
+ #
63
+ def initialize(app, &block)
64
+ @request_uri = block
65
+ @app = app
66
+ end
67
+
68
+ def call(env)
69
+ req = Rack::Request.new(env)
70
+ unless uri = request_uri.call(req)
71
+ code, headers, body = app.call(env)
72
+ unless headers['X-Accel-Redirect']
73
+ return [code, headers, body]
74
+ else
75
+ proxy_env = env.merge("PATH_INFO" => headers['X-Accel-Redirect'])
76
+ unless uri = request_uri.call(Rack::Request.new(proxy_env))
77
+ raise "Could not proxy #{headers['X-Accel-Redirect']}: Path does not map to any uri"
78
+ end
79
+ end
80
+ end
81
+ begin # only want to catch proxy errors, not app errors
82
+ proxy = ProxyRequest.new(req, uri)
83
+ [proxy.status, proxy.headers, proxy]
84
+ rescue => e
85
+ msg = "Proxy error when proxying to #{uri}: #{e.class}: #{e.message}"
86
+ env["rack.errors"].puts msg
87
+ env["rack.errors"].puts e.backtrace.map { |l| "\t" + l }
88
+ env["rack.errors"].flush
89
+ raise Error, msg
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ attr_reader :request_uri, :app
96
+
97
+ end
98
+
99
+ end
100
+
101
+ require "rack"
102
+ require "servolux"
103
+ require "net/https"
104
+ require "uri"
105
+
106
+ Rack::StreamingProxy.require_all_libs_relative_to(__FILE__)
107
+
@@ -0,0 +1,98 @@
1
+ class Rack::StreamingProxy
2
+ class ProxyRequest
3
+ include Rack::Utils
4
+
5
+ attr_reader :status, :headers
6
+
7
+ def initialize(request, uri)
8
+ uri = URI.parse(uri)
9
+
10
+ method = request.request_method.downcase
11
+ method[0..0] = method[0..0].upcase
12
+
13
+ proxy_request = Net::HTTP.const_get(method).new("#{uri.path}#{"?" if uri.query}#{uri.query}")
14
+
15
+ if proxy_request.request_body_permitted? and request.body
16
+ proxy_request.body_stream = request.body
17
+ proxy_request.content_length = request.content_length if request.content_length
18
+ proxy_request.content_type = request.content_type if request.content_type
19
+ end
20
+
21
+ %w(Accept Accept-Encoding Accept-Charset
22
+ X-Requested-With Referer User-Agent Cookie
23
+ Authorization
24
+ ).each do |header|
25
+ key = "HTTP_#{header.upcase.gsub('-', '_')}"
26
+ proxy_request[header] = request.env[key] if request.env[key]
27
+ end
28
+ proxy_request["X-Forwarded-For"] =
29
+ (request.env["X-Forwarded-For"].to_s.split(/, +/) + [request.env["REMOTE_ADDR"]]).join(", ")
30
+
31
+ @piper = Servolux::Piper.new 'r', :timeout => 30
32
+
33
+ @piper.child do
34
+ http_req = Net::HTTP.new(uri.host, uri.port)
35
+ http_req.use_ssl = uri.is_a?(URI::HTTPS)
36
+ http_req.start do |http|
37
+ http.request(proxy_request) do |response|
38
+ # at this point the headers and status are available, but the body
39
+ # has not yet been read. start reading it and putting it in the parent's pipe.
40
+ response_headers = {}
41
+ response.each_header {|k,v| response_headers[k] = v}
42
+ @piper.puts response.code.to_i
43
+ @piper.puts response_headers
44
+
45
+ response.read_body do |chunk|
46
+ @piper.puts chunk
47
+ end
48
+ @piper.puts :done
49
+ end
50
+ end
51
+ end
52
+
53
+ @piper.parent do
54
+ # wait for the status and headers to come back from the child
55
+ @status = read_from_child
56
+ @headers = HeaderHash.new(read_from_child)
57
+ end
58
+ rescue => e
59
+ if @piper
60
+ @piper.parent { raise }
61
+ @piper.child { @piper.puts e }
62
+ else
63
+ raise
64
+ end
65
+ ensure
66
+ # child needs to exit, always.
67
+ @piper.child { exit!(0) } if @piper
68
+ end
69
+
70
+ def each
71
+ chunked = @headers["Transfer-Encoding"] == "chunked"
72
+ term = "\r\n"
73
+
74
+ while chunk = read_from_child
75
+ break if chunk == :done
76
+ if chunked
77
+ size = bytesize(chunk)
78
+ next if size == 0
79
+ yield [size.to_s(16), term, chunk, term].join
80
+ else
81
+ yield chunk
82
+ end
83
+ end
84
+
85
+ yield ["0", term, "", term].join if chunked
86
+ end
87
+
88
+
89
+ protected
90
+
91
+ def read_from_child
92
+ val = @piper.gets
93
+ raise val if val.kind_of?(Exception)
94
+ val
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,55 @@
1
+ require "yaml"
2
+
3
+ class Streamer
4
+ include Rack::Utils
5
+
6
+ def initialize(sleep=0.05)
7
+ @sleep = sleep
8
+ @strings = 5.times.collect {|n| "~~~~~ #{n} ~~~~~\n" }
9
+ end
10
+
11
+ def call(env)
12
+ req = Rack::Request.new(env)
13
+ headers = {"Content-Type" => "text/plain"}
14
+ headers["Transfer-Encoding"] = "chunked"
15
+ [200, headers, self.dup]
16
+ end
17
+
18
+ def each
19
+ term = "\r\n"
20
+ @strings.each do |chunk|
21
+ size = bytesize(chunk)
22
+ yield [size.to_s(16), term, chunk, term].join
23
+ sleep @sleep
24
+ end
25
+ yield ["0", term, "", term].join
26
+ end
27
+ end
28
+
29
+ # if no content-length is provided and the response isn't streamed,
30
+ # make sure the headers get a content length.
31
+ use Rack::ContentLength
32
+
33
+ map "/" do
34
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, "ALL GOOD"] }
35
+ end
36
+
37
+ map "/stream" do
38
+ run Streamer.new
39
+ end
40
+
41
+ map "/slow_stream" do
42
+ run Streamer.new(0.5)
43
+ end
44
+
45
+ map "/env" do
46
+ run lambda { |env|
47
+ req = Rack::Request.new(env)
48
+ req.POST # modifies env inplace to include "rack.request.form_vars" key
49
+ [200, {"Content-Type" => "application/x-yaml"}, [env.to_yaml]] }
50
+ end
51
+
52
+ map "/boom" do
53
+ run lambda { |env| [500, {"Content-Type" => "text/plain"}, "kaboom!"] }
54
+ end
55
+
@@ -0,0 +1,9 @@
1
+ require File.expand_path(
2
+ File.join(File.dirname(__FILE__), %w[.. lib rack streaming_proxy]))
3
+
4
+ use Rack::Lint
5
+ # use Rack::CommonLogger
6
+ use Rack::StreamingProxy do |req|
7
+ "http://localhost:4321#{req.path}"
8
+ end
9
+ run lambda { |env| [200, {}, "should never get here..."]}
@@ -0,0 +1,8 @@
1
+ require File.expand_path(
2
+ File.join(File.dirname(__FILE__), %w[.. lib rack streaming_proxy]))
3
+
4
+ require "rack/test"
5
+
6
+ Spec::Runner.configure do |config|
7
+ end
8
+
@@ -0,0 +1,159 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe Rack::StreamingProxy do
4
+ include Rack::Test::Methods
5
+
6
+ APP_PORT = 4321 # hardcoded in proxy.ru as well!
7
+ PROXY_PORT = 4322
8
+
9
+ def app
10
+ @app ||= Rack::Builder.new do
11
+ use Rack::Lint
12
+ use Rack::StreamingProxy do |req|
13
+ unless req.path.start_with?("/not_proxied")
14
+ url = "http://localhost:#{APP_PORT}#{req.path}"
15
+ url << "?#{req.query_string}" unless req.query_string.empty?
16
+ # STDERR.puts "PROXYING to #{url}"
17
+ url
18
+ end
19
+ end
20
+ run lambda { |env|
21
+ raise "app error" if env["PATH_INFO"] =~ /boom/
22
+ [200, {"Content-Type" => "text/plain"}, "not proxied"]
23
+ }
24
+ end
25
+ end
26
+
27
+ before(:all) do
28
+ app_path = Rack::StreamingProxy.path("spec", "app.ru")
29
+ @app_server = Servolux::Child.new(
30
+ # :command => "thin -R #{app_path} -p #{APP_PORT} start", # buffers!
31
+ :command => "rackup #{app_path} -p #{APP_PORT}",
32
+ :timeout => 30, # all specs should take <30 sec to run
33
+ :suspend => 0.25
34
+ )
35
+ puts "----- starting app server -----"
36
+ @app_server.start
37
+ sleep 2 # give it a sec
38
+ puts "----- started app server -----"
39
+ end
40
+
41
+ after(:all) do
42
+ puts "----- shutting down app server -----"
43
+ @app_server.stop
44
+ @app_server.wait
45
+ puts "----- app server is stopped -----"
46
+ end
47
+
48
+ def with_proxy_server
49
+ proxy_path = Rack::StreamingProxy.path("spec", "proxy.ru")
50
+ @proxy_server = Servolux::Child.new(
51
+ :command => "rackup #{proxy_path} -p #{PROXY_PORT}",
52
+ :timeout => 10,
53
+ :suspend => 0.25
54
+ )
55
+ puts "----- starting proxy server -----"
56
+ @proxy_server.start
57
+ sleep 2
58
+ puts "----- started proxy server -----"
59
+ yield
60
+ ensure
61
+ puts "----- shutting down proxy server -----"
62
+ @proxy_server.stop
63
+ @proxy_server.wait
64
+ puts "----- proxy server is stopped -----"
65
+ end
66
+
67
+ it "passes through to the rest of the stack if block returns false" do
68
+ get "/not_proxied"
69
+ last_response.should be_ok
70
+ last_response.body.should == "not proxied"
71
+ end
72
+
73
+ it "proxies a request back to the app server" do
74
+ get "/"
75
+ last_response.should be_ok
76
+ last_response.body.should == "ALL GOOD"
77
+ end
78
+
79
+ it "uses chunked encoding when the app server send data that way" do
80
+ get "/stream"
81
+ last_response.should be_ok
82
+ last_response.headers["Transfer-Encoding"].should == "chunked"
83
+ last_response.body.should =~ /^e\r\n~~~~~ 0 ~~~~~\n\r\n/
84
+ end
85
+
86
+ # this is the most critical spec: it makes sure things are actually streamed, not buffered
87
+ it "streams data from the app server to the client" do
88
+ @app = Rack::Builder.new do
89
+ use Rack::Lint
90
+ run lambda { |env|
91
+ body = []
92
+ Net::HTTP.start("localhost", PROXY_PORT) do |http|
93
+ http.request_get("/slow_stream") do |response|
94
+ response.read_body do |chunk|
95
+ body << "#{Time.now.to_i}\n"
96
+ end
97
+ end
98
+ end
99
+ [200, {"Content-Type" => "text/plain"}, body]
100
+ }
101
+ end
102
+
103
+ with_proxy_server do
104
+ get "/"
105
+ last_response.should be_ok
106
+ times = last_response.body.split("\n").map {|l| l.to_i}
107
+ unless (times.last - times.first) >= 2
108
+ violated "expected receive time of first chunk to be at least " +
109
+ "two seconds before the last chunk"
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ it "handles POST, PUT, and DELETE methods" do
116
+ post "/env"
117
+ last_response.should be_ok
118
+ last_response.body.should =~ /REQUEST_METHOD: POST/
119
+ put "/env"
120
+ last_response.should be_ok
121
+ last_response.body.should =~ /REQUEST_METHOD: PUT/
122
+ delete "/env"
123
+ last_response.should be_ok
124
+ last_response.body.should =~ /REQUEST_METHOD: DELETE/
125
+ end
126
+
127
+ it "sets a X-Forwarded-For header" do
128
+ post "/env"
129
+ last_response.should =~ /HTTP_X_FORWARDED_FOR: 127.0.0.1/
130
+ end
131
+
132
+ it "preserves the post body" do
133
+ post "/env", "foo" => "bar"
134
+ last_response.body.should =~ /rack.request.form_vars: foo=bar/
135
+ end
136
+
137
+ it "raises a Rack::Proxy::StreamingProxy error when something goes wrong" do
138
+ Rack::StreamingProxy::ProxyRequest.should_receive(:new).and_raise(RuntimeError.new("kaboom"))
139
+ lambda { get "/" }.should raise_error(Rack::StreamingProxy::Error, /proxy error.*kaboom/i)
140
+ end
141
+
142
+ it "does not raise a Rack::Proxy error if the app itself raises something" do
143
+ lambda { get "/not_proxied/boom" }.should raise_error(RuntimeError, /app error/)
144
+ end
145
+
146
+ it "preserves cookies" do
147
+ set_cookie "foo"
148
+ post "/env"
149
+ YAML.load(last_response.body)["HTTP_COOKIE"].should == "foo"
150
+ end
151
+
152
+ it "preserves authentication info" do
153
+ basic_authorize "admin", "secret"
154
+ post "/env"
155
+ YAML.load(last_response.body)["HTTP_AUTHORIZATION"].should == "Basic YWRtaW46c2VjcmV0\n"
156
+ end
157
+
158
+ end
159
+
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-streaming-proxy2
3
+ version: !ruby/object:Gem::Version
4
+ hash: 17
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 3
10
+ version: 1.0.3
11
+ platform: ruby
12
+ authors:
13
+ - Nathan Witmer, gr2m
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-15 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 15
30
+ segments:
31
+ - 1
32
+ - 0
33
+ version: "1.0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: servolux
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 61
45
+ segments:
46
+ - 0
47
+ - 8
48
+ - 1
49
+ version: 0.8.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rack-test
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 9
61
+ segments:
62
+ - 0
63
+ - 5
64
+ - 1
65
+ version: 0.5.1
66
+ type: :development
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: bones
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 17
77
+ segments:
78
+ - 3
79
+ - 5
80
+ - 1
81
+ version: 3.5.1
82
+ type: :development
83
+ version_requirements: *id004
84
+ description: Streaming proxy for Rack, the rainbows to Rack::Proxy's unicorn. With SSL Support
85
+ email: nwitmer@gmail.com
86
+ executables: []
87
+
88
+ extensions: []
89
+
90
+ extra_rdoc_files:
91
+ - History.txt
92
+ - README.txt
93
+ files:
94
+ - History.txt
95
+ - README.txt
96
+ - Rakefile
97
+ - dev/proxy.ru
98
+ - dev/streamer.ru
99
+ - lib/rack/streaming_proxy.rb
100
+ - lib/rack/streaming_proxy/proxy_request.rb
101
+ - spec/app.ru
102
+ - spec/proxy.ru
103
+ - spec/spec_helper.rb
104
+ - spec/streaming_proxy_spec.rb
105
+ has_rdoc: true
106
+ homepage: https://github.com/gr2m/rack-streaming-proxy
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options:
111
+ - --main
112
+ - README.txt
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ hash: 3
121
+ segments:
122
+ - 0
123
+ version: "0"
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ hash: 3
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ requirements: []
134
+
135
+ rubyforge_project: rack-streaming-proxy2
136
+ rubygems_version: 1.3.7
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: Streaming proxy for Rack, the rainbows to Rack::Proxy's unicorn With SSL Support
140
+ test_files: []
141
+