rails-threaded-proxy 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +39 -0
- data/Gemfile +10 -9
- data/Gemfile.lock +24 -0
- data/LICENSE +1 -1
- data/README.md +46 -0
- data/Rakefile +10 -10
- data/VERSION +1 -1
- data/bin/rubocop +27 -0
- data/lib/rails-threaded-proxy.rb +3 -1
- data/lib/threaded-proxy.rb +3 -1
- data/lib/threaded_proxy/client.rb +80 -36
- data/lib/threaded_proxy/controller.rb +6 -6
- data/lib/threaded_proxy/http.rb +4 -1
- data/lib/threaded_proxy.rb +3 -3
- data/rails-threaded-proxy.gemspec +10 -5
- data/spec/spec_helper.rb +2 -0
- data/spec/threaded_proxy/client_spec.rb +13 -9
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aae2ccc6678cd9afd9e3068a996bfe1d163f0569512a9e37b958c749aeb8096
|
4
|
+
data.tar.gz: 14079d5dfc8a7319c54469d859d2cb5e055ba4fae94d80cebda8cb9968bcd076
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb6a536989c9b554b79ce943ed143e65a1985b7d535dac729cc7b90f01961eda02325134201e6a027d34775cafde12aa084264cce5a9c59c4c88b9b9a31b147f
|
7
|
+
data.tar.gz: a4c6056afd3971cf18cd9d2ab5e2e3739b0c76a922c655024d1e34f00069b8cbe42f376c1611c040948798b767663e1a0eb7ad7ec6753af90b4e0fc3727808f9
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# The behavior of RuboCop can be controlled via the .rubocop.yml
|
2
|
+
# configuration file. It makes it possible to enable/disable
|
3
|
+
# certain cops (checks) and to alter their behavior if they accept
|
4
|
+
# any parameters. The file can be placed either in your home
|
5
|
+
# directory or in some project directory.
|
6
|
+
#
|
7
|
+
# RuboCop will start looking for the configuration file in the directory
|
8
|
+
# where the inspected file is and continue its way up to the root directory.
|
9
|
+
#
|
10
|
+
# See https://docs.rubocop.org/rubocop/configuration
|
11
|
+
|
12
|
+
AllCops:
|
13
|
+
Exclude:
|
14
|
+
- '*.gemspec'
|
15
|
+
- 'bin/*'
|
16
|
+
|
17
|
+
Metrics/BlockLength:
|
18
|
+
Exclude:
|
19
|
+
- 'spec/**/*.rb'
|
20
|
+
|
21
|
+
Metrics/PerceivedComplexity:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Metrics/MethodLength:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/AbcSize:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/CyclomaticComplexity:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/Documentation:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Naming/FileName:
|
37
|
+
Exclude:
|
38
|
+
- 'lib/rails-threaded-proxy.rb'
|
39
|
+
- 'lib/threaded-proxy.rb'
|
data/Gemfile
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
gem
|
6
|
-
gem
|
5
|
+
gem 'actionpack'
|
6
|
+
gem 'addressable'
|
7
7
|
|
8
8
|
group :development do
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
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 'rubocop', '>= 0'
|
15
|
+
gem 'webrick', '>= 0'
|
15
16
|
end
|
data/Gemfile.lock
CHANGED
@@ -30,6 +30,7 @@ GEM
|
|
30
30
|
securerandom (>= 0.3)
|
31
31
|
tzinfo (~> 2.0, >= 2.0.5)
|
32
32
|
addressable (2.4.0)
|
33
|
+
ast (2.4.2)
|
33
34
|
base64 (0.2.0)
|
34
35
|
bigdecimal (3.1.8)
|
35
36
|
builder (3.3.0)
|
@@ -69,8 +70,10 @@ GEM
|
|
69
70
|
rake
|
70
71
|
rdoc
|
71
72
|
semver2
|
73
|
+
json (2.7.2)
|
72
74
|
jwt (2.9.3)
|
73
75
|
base64
|
76
|
+
language_server-protocol (3.17.0.3)
|
74
77
|
logger (1.6.1)
|
75
78
|
loofah (2.22.0)
|
76
79
|
crass (~> 1.0.2)
|
@@ -99,6 +102,10 @@ GEM
|
|
99
102
|
multi_json (~> 1.3)
|
100
103
|
multi_xml (~> 0.5)
|
101
104
|
rack (>= 1.2, < 3)
|
105
|
+
parallel (1.26.3)
|
106
|
+
parser (3.3.5.0)
|
107
|
+
ast (~> 2.4.1)
|
108
|
+
racc
|
102
109
|
psych (5.1.2)
|
103
110
|
stringio
|
104
111
|
racc (1.8.1)
|
@@ -114,10 +121,12 @@ GEM
|
|
114
121
|
rails-html-sanitizer (1.6.0)
|
115
122
|
loofah (~> 2.21)
|
116
123
|
nokogiri (~> 1.14)
|
124
|
+
rainbow (3.1.1)
|
117
125
|
rake (13.2.1)
|
118
126
|
rchardet (1.8.0)
|
119
127
|
rdoc (6.7.0)
|
120
128
|
psych (>= 4.0.0)
|
129
|
+
regexp_parser (2.9.2)
|
121
130
|
reline (0.5.10)
|
122
131
|
io-console (~> 0.5)
|
123
132
|
rspec (3.13.0)
|
@@ -133,12 +142,26 @@ GEM
|
|
133
142
|
diff-lcs (>= 1.2.0, < 2.0)
|
134
143
|
rspec-support (~> 3.13.0)
|
135
144
|
rspec-support (3.13.1)
|
145
|
+
rubocop (1.66.1)
|
146
|
+
json (~> 2.3)
|
147
|
+
language_server-protocol (>= 3.17.0)
|
148
|
+
parallel (~> 1.10)
|
149
|
+
parser (>= 3.3.0.2)
|
150
|
+
rainbow (>= 2.2.2, < 4.0)
|
151
|
+
regexp_parser (>= 2.4, < 3.0)
|
152
|
+
rubocop-ast (>= 1.32.2, < 2.0)
|
153
|
+
ruby-progressbar (~> 1.7)
|
154
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
155
|
+
rubocop-ast (1.32.3)
|
156
|
+
parser (>= 3.3.1.0)
|
157
|
+
ruby-progressbar (1.13.0)
|
136
158
|
securerandom (0.3.1)
|
137
159
|
semver2 (3.4.2)
|
138
160
|
stringio (3.1.1)
|
139
161
|
thread_safe (0.3.6)
|
140
162
|
tzinfo (2.0.6)
|
141
163
|
concurrent-ruby (~> 1.0)
|
164
|
+
unicode-display_width (2.6.0)
|
142
165
|
useragent (0.16.10)
|
143
166
|
webrick (1.8.2)
|
144
167
|
|
@@ -158,6 +181,7 @@ DEPENDENCIES
|
|
158
181
|
nokogiri (>= 1.16.7)
|
159
182
|
rdoc (~> 6.7.0)
|
160
183
|
rspec
|
184
|
+
rubocop
|
161
185
|
webrick
|
162
186
|
|
163
187
|
BUNDLED WITH
|
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# rails-threaded-proxy
|
2
|
+
|
3
|
+
Asynchronous high throughput reverse proxy for rails
|
4
|
+
|
5
|
+
*Warning: experimental. Use at your own risk.*
|
6
|
+
|
7
|
+
## About
|
8
|
+
|
9
|
+
Rails concurrency is often limited to running many processes, which can be memory-intensive. Even for servers that support threads, it can be difficult running dozens or hundreds of threads. But you may have backend services that are slow to respond, and/or return very large responses. It is useful to put these services behind rails for authentication, but slow responses can tie up your rails workers preventing them from serving other clients.
|
10
|
+
|
11
|
+
`rails-threaded-proxy` disconnects the proxying from the rack request/response cycle, freeing up workers to serve other clients. It does this by running the origin request in a thread. But running in a thread is not enough: we need to be able to respond to the rails request, but rack owns the socket. So it hijacks the request: rack completes immediately but dissociates from the socket. Then we're free to manage the socket ourselves. Copying between sockets, we can achieve high throughput (100MB/s+) with minimal CPU and memory overhead.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class MyController
|
17
|
+
include ThreadedProxy::Controller
|
18
|
+
|
19
|
+
def my_backend
|
20
|
+
proxy_fetch "http://backend.service/path/to/endpoint", method: :post do |config|
|
21
|
+
config.on_headers do |client_response|
|
22
|
+
# override some response headers coming from the backend
|
23
|
+
client_response['content-security-policy'] = "sandbox;"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## Requirements
|
31
|
+
|
32
|
+
Tested with Rails 7, but probably works in Rails 6+. Needs an application server that supports `rack.hijack`. (only tested on [https://puma.io/](Puma) so far)
|
33
|
+
|
34
|
+
## Caveats
|
35
|
+
|
36
|
+
* There isn't currently a way to limit concurrency. It is possible to run your server out of file descriptors, memory, etc.
|
37
|
+
* Since the proxying happens in a thread, callbacks are also run inside of the thread. Don't do anything non-threadsafe in callbacks.
|
38
|
+
* There is currently probably not sufficient error handling for edge cases. This is experimental.
|
39
|
+
|
40
|
+
## Attribution
|
41
|
+
|
42
|
+
Inspired by [https://github.com/axsuul/rails-reverse-proxy](rails-reverse-proxy), and tries to use similar API structure where possible. If you don't care about the specific benefits of `rails-threaded-proxy`, you should consider using `rails-reverse-proxy` instead.
|
43
|
+
|
44
|
+
## License
|
45
|
+
|
46
|
+
See LICENSE
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler'
|
5
5
|
begin
|
6
6
|
Bundler.setup(:default, :development)
|
7
7
|
rescue Bundler::BundlerError => e
|
8
|
-
|
9
|
-
|
8
|
+
warn e.message
|
9
|
+
warn 'Run `bundle install` to install missing gems'
|
10
10
|
exit e.status_code
|
11
11
|
end
|
12
12
|
require 'rake'
|
@@ -14,13 +14,13 @@ require 'rake'
|
|
14
14
|
require 'jeweler'
|
15
15
|
Jeweler::Tasks.new do |gem|
|
16
16
|
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
-
gem.name =
|
18
|
-
gem.homepage =
|
19
|
-
gem.license =
|
20
|
-
gem.summary = %
|
21
|
-
gem.description = %
|
22
|
-
gem.email =
|
23
|
-
gem.authors = [
|
17
|
+
gem.name = 'rails-threaded-proxy'
|
18
|
+
gem.homepage = 'http://github.com/mnutt/rails-threaded-proxy'
|
19
|
+
gem.license = 'MIT'
|
20
|
+
gem.summary = %(Threaded reverse proxy for Ruby on Rails)
|
21
|
+
gem.description = %(Threaded reverse proxy for Ruby on Rails)
|
22
|
+
gem.email = 'michael@nuttnet.net'
|
23
|
+
gem.authors = ['Michael Nutt']
|
24
24
|
# dependencies defined in Gemfile
|
25
25
|
end
|
26
26
|
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/bin/rubocop
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 'rubocop' 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('rubocop', 'rubocop')
|
data/lib/rails-threaded-proxy.rb
CHANGED
data/lib/threaded-proxy.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'addressable/uri'
|
4
|
+
require 'active_support/notifications'
|
2
5
|
require 'net/http'
|
3
6
|
require_relative 'http'
|
4
7
|
|
5
8
|
module ThreadedProxy
|
6
9
|
class Client
|
7
|
-
DISALLOWED_RESPONSE_HEADERS = %w[keep-alive]
|
10
|
+
DISALLOWED_RESPONSE_HEADERS = %w[keep-alive].freeze
|
8
11
|
|
9
|
-
|
12
|
+
HTTP_METHODS = {
|
10
13
|
'get' => Net::HTTP::Get,
|
11
14
|
'post' => Net::HTTP::Post,
|
12
15
|
'put' => Net::HTTP::Put,
|
@@ -14,64 +17,107 @@ module ThreadedProxy
|
|
14
17
|
'head' => Net::HTTP::Head,
|
15
18
|
'options' => Net::HTTP::Options,
|
16
19
|
'trace' => Net::HTTP::Trace
|
17
|
-
}
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
CALLBACK_METHODS = %i[
|
23
|
+
on_response
|
24
|
+
on_headers
|
25
|
+
on_body
|
26
|
+
on_complete
|
27
|
+
on_error
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
CALLBACK_METHODS.each do |method_name|
|
31
|
+
define_method(method_name) do |&block|
|
32
|
+
@callbacks[method_name] = block
|
33
|
+
end
|
34
|
+
end
|
18
35
|
|
19
36
|
DEFAULT_OPTIONS = {
|
20
37
|
headers: {},
|
21
|
-
debug: false
|
22
|
-
|
38
|
+
debug: false,
|
39
|
+
method: :get
|
40
|
+
}.freeze
|
23
41
|
|
24
|
-
def initialize(origin_url, options={})
|
42
|
+
def initialize(origin_url, options = {})
|
25
43
|
@origin_url = Addressable::URI.parse(origin_url)
|
26
44
|
@options = DEFAULT_OPTIONS.merge(options)
|
45
|
+
|
46
|
+
@callbacks = {}
|
47
|
+
CALLBACK_METHODS.each do |method_name|
|
48
|
+
@callbacks[method_name] = proc {}
|
49
|
+
end
|
50
|
+
|
51
|
+
yield(self) if block_given?
|
27
52
|
end
|
28
53
|
|
29
54
|
def log(message)
|
30
|
-
|
55
|
+
warn message if @options[:debug]
|
31
56
|
end
|
32
57
|
|
33
58
|
def start(socket)
|
34
|
-
request_method =
|
35
|
-
|
59
|
+
request_method = @options[:method].to_s.downcase
|
60
|
+
request_headers = @options[:headers].merge('Connection' => 'close')
|
61
|
+
|
62
|
+
request_class = HTTP_METHODS[request_method]
|
63
|
+
http_request = request_class.new(@origin_url, request_headers)
|
36
64
|
if @options[:body].respond_to?(:read)
|
37
65
|
http_request.body_stream = @options[:body]
|
38
|
-
elsif
|
66
|
+
elsif @options[:body].is_a?(String)
|
39
67
|
http_request.body = @options[:body]
|
40
68
|
end
|
41
69
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
70
|
+
ActiveSupport::Notifications.instrument('threaded_proxy.fetch', method: request_method, url: @origin_url.to_s,
|
71
|
+
headers: request_headers) do
|
72
|
+
http = HTTP.new(@origin_url.host, @origin_url.port || default_port(@origin_url))
|
73
|
+
http.use_ssl = (@origin_url.scheme == 'https')
|
74
|
+
http.set_debug_output($stderr) if @options[:debug]
|
75
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options[:ignore_ssl_errors]
|
46
76
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
77
|
+
http.start do
|
78
|
+
http.request(http_request) do |client_response|
|
79
|
+
@callbacks[:on_response].call(client_response, socket)
|
80
|
+
break if socket.closed?
|
51
81
|
|
52
|
-
|
82
|
+
log('Writing response status and headers')
|
83
|
+
write_headers(client_response, socket)
|
84
|
+
break if socket.closed?
|
53
85
|
|
54
|
-
|
55
|
-
|
56
|
-
socket.write "HTTP/1.1 #{client_response.code} #{client_response.message}\r\n"
|
86
|
+
@callbacks[:on_body].call(client_response, socket)
|
87
|
+
break if socket.closed?
|
57
88
|
|
58
|
-
|
59
|
-
|
89
|
+
# There may have been some existing data in client_response's read buffer, flush it out
|
90
|
+
# before we manually connect the raw sockets
|
91
|
+
log('Flushing existing response buffer to client')
|
92
|
+
http.flush_existing_buffer_to(socket)
|
93
|
+
|
94
|
+
# Copy the rest of the client response to the socket
|
95
|
+
log('Copying response body to client')
|
96
|
+
http.copy_to(socket)
|
97
|
+
|
98
|
+
@callbacks[:on_complete].call(client_response)
|
60
99
|
end
|
100
|
+
rescue StandardError => e
|
101
|
+
@callbacks[:on_error].call(e) or raise
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
61
105
|
|
62
|
-
|
63
|
-
|
106
|
+
def write_headers(client_response, socket)
|
107
|
+
socket.write "HTTP/1.1 #{client_response.code} #{client_response.message}\r\n"
|
64
108
|
|
65
|
-
|
66
|
-
|
67
|
-
log("Flushing existing response buffer to client")
|
68
|
-
http.flush_existing_buffer_to(socket)
|
109
|
+
# We don't support reusing connections once we have disconnected them from rack
|
110
|
+
client_response['connection'] = 'close'
|
69
111
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
112
|
+
@callbacks[:on_headers].call(client_response, socket)
|
113
|
+
return if socket.closed?
|
114
|
+
|
115
|
+
client_response.each_header do |key, value|
|
116
|
+
socket.write "#{key}: #{value}\r\n" unless DISALLOWED_RESPONSE_HEADERS.include?(key.downcase)
|
74
117
|
end
|
118
|
+
|
119
|
+
# Done with headers
|
120
|
+
socket.write "\r\n"
|
75
121
|
end
|
76
122
|
|
77
123
|
def default_port(uri)
|
@@ -80,8 +126,6 @@ module ThreadedProxy
|
|
80
126
|
80
|
81
127
|
when 'https'
|
82
128
|
443
|
83
|
-
else
|
84
|
-
nil
|
85
129
|
end
|
86
130
|
end
|
87
131
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'client'
|
2
4
|
|
3
5
|
module ThreadedProxy
|
4
6
|
module Controller
|
5
|
-
def proxy_fetch(origin_url, options={})
|
7
|
+
def proxy_fetch(origin_url, options = {}, &block)
|
6
8
|
# hijack the response so we can take it outside of the rack request/response cycle
|
7
9
|
request.env['rack.hijack'].call
|
8
10
|
socket = request.env['rack.hijack_io']
|
@@ -17,15 +19,13 @@ module ThreadedProxy
|
|
17
19
|
elsif request.env['CONTENT_LENGTH']
|
18
20
|
options[:headers]['content-length'] = request.env['CONTENT_LENGTH'].to_s
|
19
21
|
else
|
20
|
-
raise
|
22
|
+
raise 'Cannot proxy a non-chunked POST request without content-length'
|
21
23
|
end
|
22
24
|
|
23
|
-
if request.env['CONTENT_TYPE']
|
24
|
-
options[:headers]['Content-Type'] = request.env['CONTENT_TYPE']
|
25
|
-
end
|
25
|
+
options[:headers]['Content-Type'] = request.env['CONTENT_TYPE'] if request.env['CONTENT_TYPE']
|
26
26
|
end
|
27
27
|
|
28
|
-
client = Client.new(origin_url, options)
|
28
|
+
client = Client.new(origin_url, options, &block)
|
29
29
|
client.start(socket)
|
30
30
|
rescue Errno::EPIPE
|
31
31
|
# client disconnected before request finished; not an error
|
data/lib/threaded_proxy/http.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'net/http'
|
2
4
|
|
3
5
|
module ThreadedProxy
|
@@ -5,6 +7,7 @@ module ThreadedProxy
|
|
5
7
|
def flush_existing_buffer_to(dest_socket)
|
6
8
|
while (data = @socket.send(:rbuf_consume))
|
7
9
|
break if data.empty?
|
10
|
+
|
8
11
|
dest_socket.write data
|
9
12
|
end
|
10
13
|
|
@@ -27,7 +30,7 @@ module ThreadedProxy
|
|
27
30
|
|
28
31
|
# We read the response ourselves; don't need net/http to try to read it again
|
29
32
|
def hijack_response(res)
|
30
|
-
res.instance_variable_set(
|
33
|
+
res.instance_variable_set('@read', true)
|
31
34
|
res
|
32
35
|
end
|
33
36
|
end
|
data/lib/threaded_proxy.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'threaded_proxy/client'
|
2
4
|
require 'threaded_proxy/controller'
|
3
5
|
|
4
6
|
module ThreadedProxy
|
5
7
|
def version
|
6
|
-
File.open(File.expand_path(
|
8
|
+
File.open(File.expand_path('../VERSION', __dir__)).read.strip
|
7
9
|
end
|
8
|
-
|
9
|
-
extend self
|
10
10
|
end
|
@@ -2,29 +2,32 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: rails-threaded-proxy 0.
|
5
|
+
# stub: rails-threaded-proxy 0.4.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rails-threaded-proxy".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.4.0".freeze
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Michael Nutt".freeze]
|
14
|
-
s.date = "2024-10-
|
14
|
+
s.date = "2024-10-15"
|
15
15
|
s.description = "Threaded reverse proxy for Ruby on Rails".freeze
|
16
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]
|
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, "rubocop".freeze, "semver".freeze]
|
18
18
|
s.extra_rdoc_files = [
|
19
|
-
"LICENSE"
|
19
|
+
"LICENSE",
|
20
|
+
"README.md"
|
20
21
|
]
|
21
22
|
s.files = [
|
22
23
|
".bundle/config",
|
23
24
|
".rspec",
|
25
|
+
".rubocop.yml",
|
24
26
|
".ruby-version",
|
25
27
|
"Gemfile",
|
26
28
|
"Gemfile.lock",
|
27
29
|
"LICENSE",
|
30
|
+
"README.md",
|
28
31
|
"Rakefile",
|
29
32
|
"VERSION",
|
30
33
|
"bin/bundle",
|
@@ -38,6 +41,7 @@ Gem::Specification.new do |s|
|
|
38
41
|
"bin/rdoc",
|
39
42
|
"bin/ri",
|
40
43
|
"bin/rspec",
|
44
|
+
"bin/rubocop",
|
41
45
|
"bin/semver",
|
42
46
|
"lib/rails-threaded-proxy.rb",
|
43
47
|
"lib/threaded-proxy.rb",
|
@@ -63,6 +67,7 @@ Gem::Specification.new do |s|
|
|
63
67
|
s.add_development_dependency(%q<nokogiri>.freeze, [">= 1.16.7".freeze])
|
64
68
|
s.add_development_dependency(%q<rdoc>.freeze, ["~> 6.7.0".freeze])
|
65
69
|
s.add_development_dependency(%q<rspec>.freeze, [">= 0".freeze])
|
70
|
+
s.add_development_dependency(%q<rubocop>.freeze, [">= 0".freeze])
|
66
71
|
s.add_development_dependency(%q<webrick>.freeze, [">= 0".freeze])
|
67
72
|
end
|
68
73
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails-threaded-proxy'
|
2
4
|
require 'json'
|
3
5
|
|
4
|
-
|
5
|
-
BACKEND_PORT = 38293
|
6
|
+
BACKEND_STUB_PORT = 38_293
|
6
7
|
|
8
|
+
RSpec.describe ThreadedProxy::Client do
|
7
9
|
before(:all) do
|
8
|
-
@backend_server = WEBrick::HTTPServer.new(Port:
|
9
|
-
Logger: WEBrick::Log.new(
|
10
|
+
@backend_server = WEBrick::HTTPServer.new(Port: BACKEND_STUB_PORT,
|
11
|
+
Logger: WEBrick::Log.new('/dev/null'),
|
10
12
|
AccessLog: [])
|
11
13
|
@backend_server.mount_proc '/get' do |req, res|
|
12
14
|
raise unless req.request_method == 'GET'
|
15
|
+
|
13
16
|
res.body = "Received request: #{req.path}"
|
14
17
|
end
|
15
18
|
|
16
19
|
@backend_server.mount_proc '/post' do |req, res|
|
17
20
|
raise unless req.request_method == 'POST'
|
21
|
+
|
18
22
|
res.content_type = 'application/json'
|
19
23
|
res.body = JSON.generate(path: req.path,
|
20
24
|
headers: req.header,
|
@@ -29,19 +33,19 @@ RSpec.describe ThreadedProxy::Client do
|
|
29
33
|
@server_thread.kill
|
30
34
|
end
|
31
35
|
|
32
|
-
it
|
36
|
+
it 'proxies a GET request' do
|
33
37
|
socket = StringIO.new
|
34
38
|
|
35
|
-
client = ThreadedProxy::Client.new("http://localhost:#{
|
39
|
+
client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_STUB_PORT}/get")
|
36
40
|
client.start(socket)
|
37
41
|
|
38
|
-
expect(socket.string).to include(
|
42
|
+
expect(socket.string).to include('Received request: /get')
|
39
43
|
end
|
40
44
|
|
41
|
-
it
|
45
|
+
it 'proxies a POST request with content-length' do
|
42
46
|
socket = StringIO.new
|
43
47
|
|
44
|
-
client = ThreadedProxy::Client.new("http://localhost:#{
|
48
|
+
client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_STUB_PORT}/post",
|
45
49
|
method: 'post',
|
46
50
|
body: 'hello world')
|
47
51
|
client.start(socket)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-threaded-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Nutt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
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'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: webrick
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,17 +150,21 @@ executables:
|
|
136
150
|
- rdoc
|
137
151
|
- ri
|
138
152
|
- rspec
|
153
|
+
- rubocop
|
139
154
|
- semver
|
140
155
|
extensions: []
|
141
156
|
extra_rdoc_files:
|
142
157
|
- LICENSE
|
158
|
+
- README.md
|
143
159
|
files:
|
144
160
|
- ".bundle/config"
|
145
161
|
- ".rspec"
|
162
|
+
- ".rubocop.yml"
|
146
163
|
- ".ruby-version"
|
147
164
|
- Gemfile
|
148
165
|
- Gemfile.lock
|
149
166
|
- LICENSE
|
167
|
+
- README.md
|
150
168
|
- Rakefile
|
151
169
|
- VERSION
|
152
170
|
- bin/bundle
|
@@ -160,6 +178,7 @@ files:
|
|
160
178
|
- bin/rdoc
|
161
179
|
- bin/ri
|
162
180
|
- bin/rspec
|
181
|
+
- bin/rubocop
|
163
182
|
- bin/semver
|
164
183
|
- lib/rails-threaded-proxy.rb
|
165
184
|
- lib/threaded-proxy.rb
|