rails-threaded-proxy 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 97a50696507952bcd8658bcecce9e784ed9f627a10c7add96e0942d0e7806e9a
4
+ data.tar.gz: 75767119d8493f4913b977e27e0d67dadfab57e6ed3cbade742d2ca5db9e4c49
5
+ SHA512:
6
+ metadata.gz: c04a9f03a0b19c139c9de59c00dfa7f49a5e6b3bdaafe1cb1db6f23b0d6ef623c60c30be5e5e08f68517915317f2764bc394bd82da36e65ef52c365641b1dc81
7
+ data.tar.gz: e6368a368ca91d818de566c6884b1840c95ff76665e18d5b8256a88b28c18048f22c52e1f771a8c5348bb9e4d02f9f91022e62c682d3cf7b4f5bd7601ec4891a
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_BIN: "bin"
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.5
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "actionpack"
6
+ gem "addressable"
7
+
8
+ group :development do
9
+ gem "bundler", "~> 2.0"
10
+ gem "jeweler", "~> 2.3.9"
11
+ gem "nokogiri", ">= 1.16.7"
12
+ gem "rdoc", "~> 6.7.0"
13
+ gem "rspec", ">= 0"
14
+ gem "webrick", ">= 0"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,164 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ actionpack (7.2.1)
5
+ actionview (= 7.2.1)
6
+ activesupport (= 7.2.1)
7
+ nokogiri (>= 1.8.5)
8
+ racc
9
+ rack (>= 2.2.4, < 3.2)
10
+ rack-session (>= 1.0.1)
11
+ rack-test (>= 0.6.3)
12
+ rails-dom-testing (~> 2.2)
13
+ rails-html-sanitizer (~> 1.6)
14
+ useragent (~> 0.16)
15
+ actionview (7.2.1)
16
+ activesupport (= 7.2.1)
17
+ builder (~> 3.1)
18
+ erubi (~> 1.11)
19
+ rails-dom-testing (~> 2.2)
20
+ rails-html-sanitizer (~> 1.6)
21
+ activesupport (7.2.1)
22
+ base64
23
+ bigdecimal
24
+ concurrent-ruby (~> 1.0, >= 1.3.1)
25
+ connection_pool (>= 2.2.5)
26
+ drb
27
+ i18n (>= 1.6, < 2)
28
+ logger (>= 1.4.2)
29
+ minitest (>= 5.1)
30
+ securerandom (>= 0.3)
31
+ tzinfo (~> 2.0, >= 2.0.5)
32
+ addressable (2.4.0)
33
+ base64 (0.2.0)
34
+ bigdecimal (3.1.8)
35
+ builder (3.3.0)
36
+ concurrent-ruby (1.3.4)
37
+ connection_pool (2.4.1)
38
+ crass (1.0.6)
39
+ descendants_tracker (0.0.4)
40
+ thread_safe (~> 0.3, >= 0.3.1)
41
+ diff-lcs (1.5.1)
42
+ drb (2.2.1)
43
+ erubi (1.13.0)
44
+ faraday (0.9.2)
45
+ multipart-post (>= 1.2, < 3)
46
+ git (1.11.0)
47
+ rchardet (~> 1.8)
48
+ github_api (0.16.0)
49
+ addressable (~> 2.4.0)
50
+ descendants_tracker (~> 0.0.4)
51
+ faraday (~> 0.8, < 0.10)
52
+ hashie (>= 3.4)
53
+ mime-types (>= 1.16, < 3.0)
54
+ oauth2 (~> 1.0)
55
+ hashie (5.0.0)
56
+ highline (3.1.1)
57
+ reline
58
+ i18n (1.14.6)
59
+ concurrent-ruby (~> 1.0)
60
+ io-console (0.7.2)
61
+ jeweler (2.3.9)
62
+ builder
63
+ bundler
64
+ git (>= 1.2.5)
65
+ github_api (~> 0.16.0)
66
+ highline (>= 1.6.15)
67
+ nokogiri (>= 1.5.10)
68
+ psych
69
+ rake
70
+ rdoc
71
+ semver2
72
+ jwt (2.9.3)
73
+ base64
74
+ logger (1.6.1)
75
+ loofah (2.22.0)
76
+ crass (~> 1.0.2)
77
+ nokogiri (>= 1.12.0)
78
+ mime-types (2.99.3)
79
+ minitest (5.25.1)
80
+ multi_json (1.15.0)
81
+ multi_xml (0.7.1)
82
+ bigdecimal (~> 3.1)
83
+ multipart-post (2.4.1)
84
+ nokogiri (1.16.7-aarch64-linux)
85
+ racc (~> 1.4)
86
+ nokogiri (1.16.7-arm-linux)
87
+ racc (~> 1.4)
88
+ nokogiri (1.16.7-arm64-darwin)
89
+ racc (~> 1.4)
90
+ nokogiri (1.16.7-x86-linux)
91
+ racc (~> 1.4)
92
+ nokogiri (1.16.7-x86_64-darwin)
93
+ racc (~> 1.4)
94
+ nokogiri (1.16.7-x86_64-linux)
95
+ racc (~> 1.4)
96
+ oauth2 (1.4.8)
97
+ faraday (>= 0.8, < 3.0)
98
+ jwt (>= 1.0, < 3.0)
99
+ multi_json (~> 1.3)
100
+ multi_xml (~> 0.5)
101
+ rack (>= 1.2, < 3)
102
+ psych (5.1.2)
103
+ stringio
104
+ racc (1.8.1)
105
+ rack (2.2.10)
106
+ rack-session (1.0.2)
107
+ rack (< 3)
108
+ rack-test (2.1.0)
109
+ rack (>= 1.3)
110
+ rails-dom-testing (2.2.0)
111
+ activesupport (>= 5.0.0)
112
+ minitest
113
+ nokogiri (>= 1.6)
114
+ rails-html-sanitizer (1.6.0)
115
+ loofah (~> 2.21)
116
+ nokogiri (~> 1.14)
117
+ rake (13.2.1)
118
+ rchardet (1.8.0)
119
+ rdoc (6.7.0)
120
+ psych (>= 4.0.0)
121
+ reline (0.5.10)
122
+ io-console (~> 0.5)
123
+ rspec (3.13.0)
124
+ rspec-core (~> 3.13.0)
125
+ rspec-expectations (~> 3.13.0)
126
+ rspec-mocks (~> 3.13.0)
127
+ rspec-core (3.13.1)
128
+ rspec-support (~> 3.13.0)
129
+ rspec-expectations (3.13.3)
130
+ diff-lcs (>= 1.2.0, < 2.0)
131
+ rspec-support (~> 3.13.0)
132
+ rspec-mocks (3.13.2)
133
+ diff-lcs (>= 1.2.0, < 2.0)
134
+ rspec-support (~> 3.13.0)
135
+ rspec-support (3.13.1)
136
+ securerandom (0.3.1)
137
+ semver2 (3.4.2)
138
+ stringio (3.1.1)
139
+ thread_safe (0.3.6)
140
+ tzinfo (2.0.6)
141
+ concurrent-ruby (~> 1.0)
142
+ useragent (0.16.10)
143
+ webrick (1.8.2)
144
+
145
+ PLATFORMS
146
+ aarch64-linux
147
+ arm-linux
148
+ arm64-darwin
149
+ x86-linux
150
+ x86_64-darwin
151
+ x86_64-linux
152
+
153
+ DEPENDENCIES
154
+ actionpack
155
+ addressable
156
+ bundler (~> 2.0)
157
+ jeweler (~> 2.3.9)
158
+ nokogiri (>= 1.16.7)
159
+ rdoc (~> 6.7.0)
160
+ rspec
161
+ webrick
162
+
163
+ BUNDLED WITH
164
+ 2.5.7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 James Hu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "rails-threaded-proxy"
18
+ gem.homepage = "http://github.com/mnutt/rails-threaded-proxy"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Threaded reverse proxy for Ruby on Rails}
21
+ gem.description = %Q{Threaded reverse proxy for Ruby on Rails}
22
+ gem.email = "michael@nuttnet.net"
23
+ gem.authors = ["Michael Nutt"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/bundle ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+ bundler_version = nil
28
+ update_index = nil
29
+ ARGV.each_with_index do |a, i|
30
+ if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
31
+ bundler_version = a
32
+ end
33
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
+ bundler_version = $1
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../Gemfile", __dir__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+ lockfile_contents = File.read(lockfile)
59
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
+ Regexp.last_match(1)
61
+ end
62
+
63
+ def bundler_requirement
64
+ @bundler_requirement ||=
65
+ env_var_version ||
66
+ cli_arg_version ||
67
+ bundler_requirement_for(lockfile_version)
68
+ end
69
+
70
+ def bundler_requirement_for(version)
71
+ return "#{Gem::Requirement.default}.a" unless version
72
+
73
+ bundler_gem_version = Gem::Version.new(version)
74
+
75
+ bundler_gem_version.approximate_recommendation
76
+ end
77
+
78
+ def load_bundler!
79
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
80
+
81
+ activate_bundler
82
+ end
83
+
84
+ def activate_bundler
85
+ gem_error = activation_error_handling do
86
+ gem "bundler", bundler_requirement
87
+ end
88
+ return if gem_error.nil?
89
+ require_error = activation_error_handling do
90
+ require "bundler/version"
91
+ end
92
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
93
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
94
+ exit 42
95
+ end
96
+
97
+ def activation_error_handling
98
+ yield
99
+ nil
100
+ rescue StandardError, LoadError => e
101
+ e
102
+ end
103
+ end
104
+
105
+ m.load_bundler!
106
+
107
+ if m.invoked_as_script?
108
+ load Gem.bin_path("bundler", "bundle")
109
+ end
data/bin/htmldiff ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'htmldiff' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("diff-lcs", "htmldiff")
data/bin/jeweler ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'jeweler' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("jeweler", "jeweler")
data/bin/ldiff ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'ldiff' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("diff-lcs", "ldiff")
data/bin/nokogiri ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'nokogiri' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("nokogiri", "nokogiri")
data/bin/racc ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'racc' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("racc", "racc")
data/bin/rackup ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rackup' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rack", "rackup")
data/bin/rake ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rake", "rake")
data/bin/rdoc ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rdoc' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rdoc", "rdoc")
data/bin/ri ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'ri' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rdoc", "ri")
data/bin/rspec ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/semver ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'semver' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("semver2", "semver")
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/threaded_proxy'
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/threaded_proxy'
@@ -0,0 +1,89 @@
1
+ require 'addressable/uri'
2
+ require 'net/http'
3
+ require_relative 'http'
4
+
5
+ module ThreadedProxy
6
+ class Client
7
+ DISALLOWED_RESPONSE_HEADERS = %w[keep-alive]
8
+
9
+ METHODS = {
10
+ 'get' => Net::HTTP::Get,
11
+ 'post' => Net::HTTP::Post,
12
+ 'put' => Net::HTTP::Put,
13
+ 'delete' => Net::HTTP::Delete,
14
+ 'head' => Net::HTTP::Head,
15
+ 'options' => Net::HTTP::Options,
16
+ 'trace' => Net::HTTP::Trace
17
+ }
18
+
19
+ DEFAULT_OPTIONS = {
20
+ headers: {},
21
+ debug: false
22
+ }
23
+
24
+ def initialize(origin_url, options={})
25
+ @origin_url = Addressable::URI.parse(origin_url)
26
+ @options = options.merge(DEFAULT_OPTIONS)
27
+ end
28
+
29
+ def log(message)
30
+ $stderr.puts message if @options[:debug]
31
+ end
32
+
33
+ def start(socket)
34
+ request_method = METHODS[(@options[:method] || 'GET').to_s.downcase]
35
+ http_request = request_method.new(@origin_url, (@options[:headers] || {}).merge('Connection' => 'close'))
36
+ if IO === @options[:body]
37
+ http_request.body_stream = @options[:body]
38
+ elsif String === @options[:body]
39
+ http_request.body = @options[:body]
40
+ @options[:headers]['Content-Length'] = @options[:body].bytesize.to_s
41
+ end
42
+
43
+ http = HTTP.new(@origin_url.host, @origin_url.port || default_port(@origin_url))
44
+ http.use_ssl = (@origin_url.scheme == 'https')
45
+ http.set_debug_output($stderr) if @options[:debug]
46
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options[:ignore_ssl_errors]
47
+
48
+ http.start do
49
+ http.request(http_request) do |client_response|
50
+ # We don't support reusing connections once we have disconnected them from rack
51
+ client_response['connection'] = 'close'
52
+
53
+ yield client_response if block_given?
54
+
55
+ # start writing response
56
+ log("Writing response status and headers")
57
+ socket.write "HTTP/1.1 #{client_response.code} #{client_response.message}\r\n"
58
+
59
+ client_response.each_header do |key, value|
60
+ socket.write "#{key}: #{value}\r\n" unless DISALLOWED_RESPONSE_HEADERS.include?(key.downcase)
61
+ end
62
+
63
+ # Done with headers
64
+ socket.write "\r\n"
65
+
66
+ # There may have been some existing data in client_response's read buffer, flush it out
67
+ # before we manually connect the raw sockets
68
+ log("Flushing existing response buffer to client")
69
+ http.flush_existing_buffer_to(socket)
70
+
71
+ # Copy the rest of the client response to the socket
72
+ log("Copying response body to client")
73
+ http.copy_to(socket)
74
+ end
75
+ end
76
+ end
77
+
78
+ def default_port(uri)
79
+ case uri.scheme
80
+ when 'http'
81
+ 80
82
+ when 'https'
83
+ 443
84
+ else
85
+ nil
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'client'
2
+
3
+ module ThreadedProxy
4
+ module Controller
5
+ def proxy_fetch(origin_url, options={})
6
+ # hijack the response so we can take it outside of the rack request/response cycle
7
+ request.env['rack.hijack'].call
8
+ socket = request.env['rack.hijack_io']
9
+
10
+ Thread.new do
11
+ if options[:body] == :rack
12
+ options[:headers] ||= {}
13
+ options[:body] = request.body_stream
14
+ if request.env['HTTP_TRANSFER_ENCODING'] == 'chunked'
15
+ options[:headers]['Transfer-Encoding'] = 'chunked'
16
+ elsif request.env['CONTENT_LENGTH']
17
+ options[:headers]['Content-Length'] = request.env['CONTENT_LENGTH'].to_s
18
+ else
19
+ raise "Cannot proxy a non-chunked POST request without content-length"
20
+ end
21
+ end
22
+
23
+ client = Client.new(origin_url, options)
24
+ client.start(socket)
25
+ rescue Errno::EPIPE
26
+ # client disconnected before request finished; not an error
27
+ ensure
28
+ socket.close unless socket.closed?
29
+ end
30
+
31
+ head :ok
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'net/http'
2
+
3
+ module ThreadedProxy
4
+ class HTTP < Net::HTTP
5
+ def flush_existing_buffer_to(dest_socket)
6
+ while (data = @socket.send(:rbuf_consume))
7
+ break if data.empty?
8
+ dest_socket.write data
9
+ end
10
+
11
+ dest_socket.flush
12
+ end
13
+
14
+ def copy_to(dest_socket)
15
+ IO.copy_stream(@socket.io, dest_socket)
16
+ end
17
+
18
+ def request(*args)
19
+ if block_given?
20
+ super { |res| yield hijack_response(res) }
21
+ else
22
+ hijack_response(super)
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ # We read the response ourselves; don't need net/http to try to read it again
29
+ def hijack_response(res)
30
+ res.instance_variable_set("@read", true)
31
+ res
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ require 'threaded_proxy/client'
2
+ require 'threaded_proxy/controller'
3
+
4
+ module ThreadedProxy
5
+ def version
6
+ File.open(File.expand_path("../../VERSION", __FILE__)).read.strip
7
+ end
8
+
9
+ extend self
10
+ end
@@ -0,0 +1,68 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: rails-threaded-proxy 0.1.0 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "rails-threaded-proxy".freeze
9
+ s.version = "0.1.0".freeze
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib".freeze]
13
+ s.authors = ["Michael Nutt".freeze]
14
+ s.date = "2024-10-14"
15
+ s.description = "Threaded reverse proxy for Ruby on Rails".freeze
16
+ s.email = "michael@nuttnet.net".freeze
17
+ s.executables = ["bundle".freeze, "htmldiff".freeze, "jeweler".freeze, "ldiff".freeze, "nokogiri".freeze, "racc".freeze, "rackup".freeze, "rake".freeze, "rdoc".freeze, "ri".freeze, "rspec".freeze, "semver".freeze]
18
+ s.extra_rdoc_files = [
19
+ "LICENSE"
20
+ ]
21
+ s.files = [
22
+ ".bundle/config",
23
+ ".rspec",
24
+ ".ruby-version",
25
+ "Gemfile",
26
+ "Gemfile.lock",
27
+ "LICENSE",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "bin/bundle",
31
+ "bin/htmldiff",
32
+ "bin/jeweler",
33
+ "bin/ldiff",
34
+ "bin/nokogiri",
35
+ "bin/racc",
36
+ "bin/rackup",
37
+ "bin/rake",
38
+ "bin/rdoc",
39
+ "bin/ri",
40
+ "bin/rspec",
41
+ "bin/semver",
42
+ "lib/rails-threaded-proxy.rb",
43
+ "lib/threaded-proxy.rb",
44
+ "lib/threaded_proxy.rb",
45
+ "lib/threaded_proxy/client.rb",
46
+ "lib/threaded_proxy/controller.rb",
47
+ "lib/threaded_proxy/http.rb",
48
+ "rails-threaded-proxy.gemspec",
49
+ "spec/spec_helper.rb",
50
+ "spec/threaded_proxy/client_spec.rb"
51
+ ]
52
+ s.homepage = "http://github.com/mnutt/rails-threaded-proxy".freeze
53
+ s.licenses = ["MIT".freeze]
54
+ s.rubygems_version = "3.5.16".freeze
55
+ s.summary = "Threaded reverse proxy for Ruby on Rails".freeze
56
+
57
+ s.specification_version = 4
58
+
59
+ s.add_runtime_dependency(%q<actionpack>.freeze, [">= 0".freeze])
60
+ s.add_runtime_dependency(%q<addressable>.freeze, [">= 0".freeze])
61
+ s.add_development_dependency(%q<bundler>.freeze, ["~> 2.0".freeze])
62
+ s.add_development_dependency(%q<jeweler>.freeze, ["~> 2.3.9".freeze])
63
+ s.add_development_dependency(%q<nokogiri>.freeze, [">= 1.16.7".freeze])
64
+ s.add_development_dependency(%q<rdoc>.freeze, ["~> 6.7.0".freeze])
65
+ s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
66
+ s.add_development_dependency(%q<webrick>.freeze, [">= 0".freeze])
67
+ end
68
+
@@ -0,0 +1,13 @@
1
+ require 'webrick'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |expectations|
5
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
6
+ end
7
+
8
+ config.mock_with :rspec do |mocks|
9
+ mocks.verify_partial_doubles = true
10
+ end
11
+
12
+ config.shared_context_metadata_behavior = :apply_to_host_groups
13
+ end
@@ -0,0 +1,60 @@
1
+ require 'rails-threaded-proxy'
2
+ require 'json'
3
+
4
+ RSpec.describe ThreadedProxy::Client do
5
+ BACKEND_PORT = 38293
6
+
7
+ before(:all) do
8
+ @backend_server = WEBrick::HTTPServer.new(Port: BACKEND_PORT,
9
+ Logger: WEBrick::Log.new("/dev/null"),
10
+ AccessLog: [])
11
+ @backend_server.mount_proc '/get' do |req, res|
12
+ raise unless req.request_method == 'GET'
13
+ res.body = "Received request: #{req.path}"
14
+ end
15
+
16
+ @backend_server.mount_proc '/post' do |req, res|
17
+ raise unless req.request_method == 'POST'
18
+ res.content_type = 'application/json'
19
+ res.body = JSON.generate(path: req.path,
20
+ headers: req.header,
21
+ body: req.body)
22
+ end
23
+
24
+ @server_thread = Thread.new { @backend_server.start }
25
+ end
26
+
27
+ after(:all) do
28
+ @backend_server.shutdown
29
+ @server_thread.kill
30
+ end
31
+
32
+ it "proxies a GET request" do
33
+ socket = StringIO.new
34
+
35
+ client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_PORT}/get")
36
+ client.start(socket)
37
+
38
+ expect(socket.string).to include("Received request: /get")
39
+ end
40
+
41
+ it "proxies a POST request with content-length" do
42
+ socket = StringIO.new
43
+
44
+ client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_PORT}/post",
45
+ method: 'post',
46
+ body: 'hello world')
47
+ client.start(socket)
48
+
49
+ status, rest = socket.string.split("\r\n", 2)
50
+ headers, body = rest.split("\r\n\r\n", 2)
51
+
52
+ parsed_body = JSON.parse(body)
53
+ parsed_headers = headers.split("\r\n").map { |h| h.split(': ', 2) }.to_h
54
+
55
+ expect(status).to eq('HTTP/1.1 200 OK')
56
+ expect(parsed_headers['content-type']).to eq('application/json')
57
+ expect(parsed_body['path']).to eq('/post')
58
+ expect(parsed_body['headers']['content-length']).to eq(['11'])
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-threaded-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Nutt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jeweler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.9
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.3.9
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.16.7
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.16.7
83
+ - !ruby/object:Gem::Dependency
84
+ name: rdoc
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 6.7.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 6.7.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webrick
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Threaded reverse proxy for Ruby on Rails
126
+ email: michael@nuttnet.net
127
+ executables:
128
+ - bundle
129
+ - htmldiff
130
+ - jeweler
131
+ - ldiff
132
+ - nokogiri
133
+ - racc
134
+ - rackup
135
+ - rake
136
+ - rdoc
137
+ - ri
138
+ - rspec
139
+ - semver
140
+ extensions: []
141
+ extra_rdoc_files:
142
+ - LICENSE
143
+ files:
144
+ - ".bundle/config"
145
+ - ".rspec"
146
+ - ".ruby-version"
147
+ - Gemfile
148
+ - Gemfile.lock
149
+ - LICENSE
150
+ - Rakefile
151
+ - VERSION
152
+ - bin/bundle
153
+ - bin/htmldiff
154
+ - bin/jeweler
155
+ - bin/ldiff
156
+ - bin/nokogiri
157
+ - bin/racc
158
+ - bin/rackup
159
+ - bin/rake
160
+ - bin/rdoc
161
+ - bin/ri
162
+ - bin/rspec
163
+ - bin/semver
164
+ - lib/rails-threaded-proxy.rb
165
+ - lib/threaded-proxy.rb
166
+ - lib/threaded_proxy.rb
167
+ - lib/threaded_proxy/client.rb
168
+ - lib/threaded_proxy/controller.rb
169
+ - lib/threaded_proxy/http.rb
170
+ - rails-threaded-proxy.gemspec
171
+ - spec/spec_helper.rb
172
+ - spec/threaded_proxy/client_spec.rb
173
+ homepage: http://github.com/mnutt/rails-threaded-proxy
174
+ licenses:
175
+ - MIT
176
+ metadata: {}
177
+ post_install_message:
178
+ rdoc_options: []
179
+ require_paths:
180
+ - lib
181
+ required_ruby_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ required_rubygems_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ requirements: []
192
+ rubygems_version: 3.5.16
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Threaded reverse proxy for Ruby on Rails
196
+ test_files: []