rack-streaming-proxy 1.0.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 053ec0676586f5914af3a502686ef50783f25034
4
+ data.tar.gz: 6ee9e037cab39fec94571ff1dee67aa014f8d1cf
5
+ SHA512:
6
+ metadata.gz: 589c2c12792a03f1329b2b7fb49b7095cd3faebe0c53c8cf1e5ebf4c21c76fe055720c3ee8d727e93e4209fa295aa2f9fe72bbe6d9710ff6507d9ddefea5ba62
7
+ data.tar.gz: 25e30cb68919cbfc459b67095d35cebac93c510b9bb493ce7736eef73bc4ac70490ffc43794356cf61b9d5a08429f37e8be3d974e896c784523b3ee43c3717ad
data/.gitignore CHANGED
@@ -1,18 +1,17 @@
1
- # The list of files that should be ignored by Mr Bones.
2
- # Lines that start with '#' are comments.
3
- #
4
- # A .gitignore file can be used instead by setting it as the ignore
5
- # file in your Rakefile:
6
- #
7
- # Bones {
8
- # ignore_file '.gitignore'
9
- # }
10
- #
11
- # For a project with a C extension, the following would be a good set of
12
- # exclude patterns (uncomment them if you want to use them):
13
- # *.[oa]
14
- # *~
15
- announcement.txt
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
16
9
  coverage
17
- doc
10
+ doc/
11
+ lib/bundler/man
18
12
  pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-streaming_proxy.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009-2013 Fred Ngo, Nathan Witmer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Rack::StreamingProxy
2
+
3
+ A transparent streaming proxy to be used as rack middleware.
4
+
5
+ * Streams the response from the downstream server to minimize memory usage
6
+ * Handles chunked encoding if used
7
+ * Proxies GET/PUT/POST/DELETE, XHR, and cookies
8
+
9
+ Now updated to be compatible with Rails 3 and 4, and fixes major concurrency issues that were present in 1.0.
10
+
11
+ Use Rack::StreamingProxy when you need to have the response streamed back to the client, for example when handling large file requests that could be proxied directly but need to be authenticated against the rest of your middleware stack.
12
+
13
+ Note that this will not work well with EventMachine. EM buffers the entire rack response before sending it to the client. When testing, try Unicorn or Passenger rather than the EM-based Thin (See [discussion](http://groups.google.com/group/thin-ruby/browse_thread/thread/4762f8f851b965f6)).
14
+
15
+ A simple streamer app has been included for testing and development.
16
+
17
+ ## Usage
18
+
19
+ To use inside a Rails app, add a `config/initializers/streaming_proxy.rb` initialization file, and place in it:
20
+
21
+ ```ruby
22
+ require 'rack/streaming_proxy'
23
+
24
+ YourRailsApp::Application.configure do
25
+ config.streaming_proxy.logger = Rails.logger # stdout by default
26
+ config.streaming_proxy.log_verbosity = Rails.env.production? ? :low : :high # :low or :high, :low by default
27
+ config.streaming_proxy.num_retries_on_5xx = 5 # 0 by default
28
+ config.streaming_proxy.raise_on_5xx = true # false by default
29
+
30
+ # Will be inserted at the end of the middleware stack by default.
31
+ config.middleware.use Rack::StreamingProxy::Proxy do |request|
32
+
33
+ # Inside the request block, return the full URI to redirect the request to,
34
+ # or nil/false if the request should continue on down the middleware stack.
35
+ if request.path.start_with?('/search')
36
+ "http://www.some-other-service.com/search?#{request.query}"
37
+ end
38
+ end
39
+ end
40
+ ```
41
+
42
+ To use as a Rack app:
43
+
44
+ ```ruby
45
+ require 'rack/streaming_proxy'
46
+
47
+ use Rack::StreamingProxy::Proxy do |request|
48
+ if request.path.start_with?('/proxy')
49
+ # You probably want to get rid of the '/proxy' in the path, when requesting from the destination.
50
+ proxy_path = request.path.sub %r{^/proxy}, ''
51
+ "http://www.another-server.com#{proxy_path}"
52
+ end
53
+ end
54
+ ```
55
+
56
+ ## Installation
57
+
58
+ Add this line to your application's Gemfile:
59
+
60
+ gem 'rack-streaming-proxy'
61
+
62
+ And then execute:
63
+
64
+ $ bundle
65
+
66
+ Or install it yourself as:
67
+
68
+ $ gem install rack-streaming-proxy
69
+
70
+ ## Requirements
71
+
72
+ * Ruby = 1.9.3
73
+ * rack >= 1.4
74
+ * servolux ~> 0.10
75
+
76
+ These requirements (other than Ruby) will be automatically installed via Bundler.
77
+
78
+ This gem has not been tested with versions lower than those indicated.
79
+
80
+ This gem works with Ubuntu 10.04. It has not been tested with later versions of Ubuntu or other Linuxes, but it should work just fine. It has not been tested with OS X but should work as well. However, I doubt it will work on any version of Windows, as it does process-based stuff. You are most welcome to try it and report back.
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork it
85
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
86
+ 3. Implement your changes, and make sure to add tests!
87
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 5. Push to the branch (`git push origin my-new-feature`)
89
+ 6. Create new Pull Request
90
+
91
+ ## Thanks To
92
+
93
+ * [Nathan Witmer](http://github.com/zerowidth) for the 1.0 implementation of [Rack::StreamingProxy](http://github.com/zerowidth/rack-streaming-proxy)
94
+ * [Tom Lea](http://github.com/cwninja) for [Rack::Proxy](http://gist.github.com/207938), which inspired Rack::StreamingProxy.
95
+ * [Tim Pease](http://github.com/TwP) for [Servolux](https://github.com/Twp/servolux)
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  rack-streaming-proxy
2
2
  by Nathan Witmer <nwitmer@gmail.com>
3
- http://github.com/aniero/rack-streaming-proxy
3
+ http://github.com/zerowidth/rack-streaming-proxy
4
4
 
5
5
  == DESCRIPTION:
6
6
 
data/Rakefile CHANGED
@@ -1,28 +1 @@
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
-
1
+ require "bundler/gem_tasks"
data/dev/client.rb ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ # cf. https://gist.github.com/sonots/7751554
4
+
5
+ require 'net/http'
6
+ require 'uri'
7
+
8
+ # unicorn spec/app.ru -p 4321
9
+ # unicorn spec/proxy.ru -p 4322
10
+ PORT = ARGV[0] || 8080
11
+
12
+ http = Net::HTTP.new "localhost", PORT
13
+ request = Net::HTTP::Get.new "/slow_stream"
14
+ #request['Transfer-Encoding'] = 'chunked'
15
+ request['Connection'] = 'keep-alive'
16
+ http.request(request){|response|
17
+ puts "content-length: #{response.content_length}"
18
+ body = []
19
+ response.read_body{|x|
20
+ body << Time.now
21
+ puts "read_block: #{body.length}, #{x.size}byte(s)"
22
+ }
23
+ puts body
24
+ }
@@ -1,97 +1,3 @@
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
- return app.call(env) unless uri = request_uri.call(req)
71
- begin # only want to catch proxy errors, not app errors
72
- proxy = ProxyRequest.new(req, uri)
73
- [proxy.status, proxy.headers, proxy]
74
- rescue => e
75
- msg = "Proxy error when proxying to #{uri}: #{e.class}: #{e.message}"
76
- env["rack.errors"].puts msg
77
- env["rack.errors"].puts e.backtrace.map { |l| "\t" + l }
78
- env["rack.errors"].flush
79
- raise Error, msg
80
- end
81
- end
82
-
83
- protected
84
-
85
- attr_reader :request_uri, :app
86
-
87
- end
88
-
89
- end
90
-
91
- require "rack"
92
- require "servolux"
93
- require "net/http"
94
- require "uri"
95
-
96
- Rack::StreamingProxy.require_all_libs_relative_to(__FILE__)
97
-
1
+ require 'rack/streaming_proxy/version'
2
+ require 'rack/streaming_proxy/proxy'
3
+ require 'rack/streaming_proxy/railtie' if defined? ::Rails::Railtie
@@ -0,0 +1,5 @@
1
+ module Rack::StreamingProxy
2
+ class Error < RuntimeError; end
3
+ class UnknownError < Error; end
4
+ class HttpServerError < Error; end
5
+ end
@@ -0,0 +1,115 @@
1
+ require 'rack'
2
+ require 'logger'
3
+ require 'rack/streaming_proxy/session'
4
+ require 'rack/streaming_proxy/request'
5
+ require 'rack/streaming_proxy/response'
6
+
7
+ class Rack::StreamingProxy::Proxy
8
+
9
+ class << self
10
+ attr_accessor :logger, :log_verbosity, :num_retries_on_5xx, :raise_on_5xx
11
+
12
+ def set_default_configuration
13
+ # Logs to stdout by default unless configured with another logger via Railtie.
14
+ @logger ||= Logger.new(STDOUT)
15
+
16
+ # At :low verbosity by default -- will not output :debug level messages.
17
+ # :high verbosity outputs :debug level messages.
18
+ # This is independent of the Logger's log_level, as set in Rails, for example,
19
+ # although the Logger's level can override this setting.
20
+ @log_verbosity ||= :low
21
+
22
+ # No retries are performed by default.
23
+ @num_retries_on_5xx ||= 0
24
+
25
+ # If the proxy cannot recover from 5xx's through retries (see num_retries_on_5xx),
26
+ # then it by default passes through the content from the destination
27
+ # e.g. the Apache error page. If you want an exception to be raised instead so
28
+ # you can handle it yourself (i.e. display your own error page), set raise_on_5xx to true.
29
+ @raise_on_5xx ||= false
30
+ end
31
+
32
+ def log(level, message)
33
+ unless log_verbosity == :low && level == :debug
34
+ @logger.send level, "[Rack::StreamingProxy] #{message}"
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ # The block provided to the initializer is given a Rack::Request
41
+ # and should return:
42
+ #
43
+ # * nil/false to skip the proxy and continue down the stack
44
+ # * a complete uri (with query string if applicable) to proxy to
45
+ #
46
+ # Example:
47
+ #
48
+ # use Rack::StreamingProxy::Proxy do |req|
49
+ # if req.path.start_with?('/search')
50
+ # "http://some_other_service/search?#{req.query}"
51
+ # end
52
+ # end
53
+ #
54
+ # Most headers, request body, and HTTP method are preserved.
55
+ #
56
+ def initialize(app, &block)
57
+ self.class.set_default_configuration
58
+ @app = app
59
+ @block = block
60
+ end
61
+
62
+ def call(env)
63
+ current_request = Rack::Request.new(env)
64
+
65
+ # Decide whether this request should be proxied.
66
+ if destination_uri = @block.call(current_request)
67
+ self.class.log :info, "Starting proxy request to: #{destination_uri}"
68
+
69
+ request = Rack::StreamingProxy::Request.new(destination_uri, current_request)
70
+ begin
71
+ response = Rack::StreamingProxy::Session.new(request).start
72
+ rescue Exception => e # Rescuing only for the purpose of logging to rack.errors
73
+ log_rack_error(env, e)
74
+ raise e
75
+ end
76
+
77
+ # Notify client http version to the instance of Response class.
78
+ response.client_http_version = env['HTTP_VERSION'].sub(/HTTP\//, '') if env.has_key?('HTTP_VERSION')
79
+ # Ideally, both a Content-Length header field and a Transfer-Encoding
80
+ # header field are not expected to be present from servers which
81
+ # are compliant with RFC2616. However, irresponsible servers may send
82
+ # both to rack-streaming-proxy.
83
+ # RFC2616 says if a message is received with both a Transfer-Encoding
84
+ # header field and a Content-Length header field, the latter MUST be
85
+ # ignored. So I deleted a Content-Length header here.
86
+ #
87
+ # Though there is a case that rack-streaming-proxy deletes both a
88
+ # Content-Length and a Transfer-Encoding, a client can acknowledge the
89
+ # end of body by closing the connection when the entire response has
90
+ # been sent without a Content-Length header. So a Content-Length header
91
+ # does not have to be required here in our understaing.
92
+ response.headers.delete('Content-Length') if response.headers.has_key?('Transfer-Encoding')
93
+ if env.has_key?('HTTP_VERSION') && env['HTTP_VERSION'] < 'HTTP/1.1'
94
+ # Be compliant with RFC2146
95
+ response.headers.delete('Transfer-Encoding')
96
+ end
97
+
98
+ self.class.log :info, "Finishing proxy request to: #{destination_uri}"
99
+ [response.status, response.headers, response]
100
+
101
+ # Continue down the middleware stack if the request is not to be proxied.
102
+ else
103
+ @app.call(env)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def log_rack_error(env, e)
110
+ env['rack.errors'].puts e.message
111
+ env['rack.errors'].puts e.backtrace #.collect { |line| "\t" + line }
112
+ env['rack.errors'].flush
113
+ end
114
+
115
+ end