rack-streaming-proxy2 1.0.3

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