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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67d445144a447e246d05ce8b70eeb0d7d12f209f842bc0c7253e47d1aa172aa6
4
- data.tar.gz: 2fa990229d510d47e56f787f2a8901d0f837988d2d979a94fd6627ade95bf1b9
3
+ metadata.gz: 2aae2ccc6678cd9afd9e3068a996bfe1d163f0569512a9e37b958c749aeb8096
4
+ data.tar.gz: 14079d5dfc8a7319c54469d859d2cb5e055ba4fae94d80cebda8cb9968bcd076
5
5
  SHA512:
6
- metadata.gz: eb263f91b43ecea9118da5a0c8612577c56dee6f395d027b66d0f6b80aa0ded6cf0a6a6b3ffc329c24aa0d2b383713ca8d02fafd20504134fedfb62671807814
7
- data.tar.gz: cf2d19158dd8784f9f2a66a1ae82ae5529b9393e6108da7a11df9e1869bd3e8d2f9d4a56119ef207db3250ee121d7648e23f4b5e5dd3fa6a864dc28f216bb5d2
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 "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
- gem "actionpack"
6
- gem "addressable"
5
+ gem 'actionpack'
6
+ gem 'addressable'
7
7
 
8
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"
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
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2016 James Hu
3
+ Copyright (c) 2024 Michael Nutt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
- # encoding: utf-8
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
- $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
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 = "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"]
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.2.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')
@@ -1 +1,3 @@
1
- require File.dirname(__FILE__) + '/threaded_proxy'
1
+ # frozen_string_literal: true
2
+
3
+ require "#{File.dirname(__FILE__)}/threaded_proxy"
@@ -1 +1,3 @@
1
- require File.dirname(__FILE__) + '/threaded_proxy'
1
+ # frozen_string_literal: true
2
+
3
+ require "#{File.dirname(__FILE__)}/threaded_proxy"
@@ -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
- METHODS = {
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
- $stderr.puts message if @options[:debug]
55
+ warn message if @options[:debug]
31
56
  end
32
57
 
33
58
  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'))
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 String === @options[:body]
66
+ elsif @options[:body].is_a?(String)
39
67
  http_request.body = @options[:body]
40
68
  end
41
69
 
42
- http = HTTP.new(@origin_url.host, @origin_url.port || default_port(@origin_url))
43
- http.use_ssl = (@origin_url.scheme == 'https')
44
- http.set_debug_output($stderr) if @options[:debug]
45
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @options[:ignore_ssl_errors]
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
- http.start do
48
- http.request(http_request) do |client_response|
49
- # We don't support reusing connections once we have disconnected them from rack
50
- client_response['connection'] = 'close'
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
- yield client_response if block_given?
82
+ log('Writing response status and headers')
83
+ write_headers(client_response, socket)
84
+ break if socket.closed?
53
85
 
54
- # start writing response
55
- log("Writing response status and headers")
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
- client_response.each_header do |key, value|
59
- socket.write "#{key}: #{value}\r\n" unless DISALLOWED_RESPONSE_HEADERS.include?(key.downcase)
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
- # Done with headers
63
- socket.write "\r\n"
106
+ def write_headers(client_response, socket)
107
+ socket.write "HTTP/1.1 #{client_response.code} #{client_response.message}\r\n"
64
108
 
65
- # There may have been some existing data in client_response's read buffer, flush it out
66
- # before we manually connect the raw sockets
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
- # Copy the rest of the client response to the socket
71
- log("Copying response body to client")
72
- http.copy_to(socket)
73
- end
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 "Cannot proxy a non-chunked POST request without content-length"
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
@@ -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("@read", true)
33
+ res.instance_variable_set('@read', true)
31
34
  res
32
35
  end
33
36
  end
@@ -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("../../VERSION", __FILE__)).read.strip
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.2.0 ruby lib
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.2.0".freeze
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"
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'webrick'
2
4
 
3
5
  RSpec.configure do |config|
@@ -1,20 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails-threaded-proxy'
2
4
  require 'json'
3
5
 
4
- RSpec.describe ThreadedProxy::Client do
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: BACKEND_PORT,
9
- Logger: WEBrick::Log.new("/dev/null"),
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 "proxies a GET request" do
36
+ it 'proxies a GET request' do
33
37
  socket = StringIO.new
34
38
 
35
- client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_PORT}/get")
39
+ client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_STUB_PORT}/get")
36
40
  client.start(socket)
37
41
 
38
- expect(socket.string).to include("Received request: /get")
42
+ expect(socket.string).to include('Received request: /get')
39
43
  end
40
44
 
41
- it "proxies a POST request with content-length" do
45
+ it 'proxies a POST request with content-length' do
42
46
  socket = StringIO.new
43
47
 
44
- client = ThreadedProxy::Client.new("http://localhost:#{BACKEND_PORT}/post",
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.2.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-14 00:00:00.000000000 Z
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