forward-proxy 0.1.1 → 0.2.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 +5 -5
- data/.github/workflows/cli.yaml +25 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +1 -0
- data/README.md +9 -3
- data/Rakefile +19 -0
- data/exe/forward-proxy +3 -4
- data/lib/forward_proxy/errors/http_method_not_implemented.rb +5 -0
- data/lib/forward_proxy/errors/http_parse_error.rb +5 -0
- data/lib/forward_proxy/server.rb +49 -31
- data/lib/forward_proxy/thread_pool.rb +25 -23
- data/lib/forward_proxy/version.rb +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 628359d25536c4190014c9e082fff41b2adef209f04a62d8a6b217c078f3b58b
|
4
|
+
data.tar.gz: 7834932b7242fd66c120d270a922e385a6a58ab63c65e8ce2b7957da43ba5a11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c47ab1d691807a23ba779f1ae8d4feecd32300f8d840044f66a5cc2f40ccd33c095db60ca9e3f99fb3c8fd60c02ae46d5d8d07b6a4de6298bef24731fb57cb4
|
7
|
+
data.tar.gz: 609a35651496d98e4977f3691c7834efd58fe736615b3b4594e0bc61d11dad0088f11f535f6b287db85f95c1cc8ab55ea2a536447028b690d92ffd0aa3e60669
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: Command Line Interface
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
strategy:
|
10
|
+
matrix:
|
11
|
+
ruby: [ '2.3', '2.7' ]
|
12
|
+
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: ${{ matrix.ruby }}
|
18
|
+
bundler-cache: true
|
19
|
+
- run: gem install forward-proxy
|
20
|
+
- run: |
|
21
|
+
exe/forward-proxy --h
|
22
|
+
exe/forward-proxy --help
|
23
|
+
exe/forward-proxy --binding 127.0.0.1 --port 3001 --threads 2 &
|
24
|
+
sleep 1
|
25
|
+
curl --fail -x http://127.0.0.1:3001 https://google.com/
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
- Extract errors into module.
|
6
|
+
|
7
|
+
## 0.1.5
|
8
|
+
|
9
|
+
- stream http proxy requests.
|
10
|
+
- call shutdown on `Interrupt`
|
11
|
+
- remove waiting for the thread-pool to finish on shutdown and just close the server conn.
|
12
|
+
|
13
|
+
## 0.1.4
|
14
|
+
|
15
|
+
- `accept` connections via thread pool.
|
16
|
+
- catch and throw custom http parse error.
|
17
|
+
- `Server:` header added for default error 502 response.
|
18
|
+
- rescue and logs socket error `Errno::EBADF`.
|
19
|
+
|
20
|
+
## 0.1.3
|
21
|
+
|
22
|
+
- Refine CLI help text.
|
23
|
+
|
24
|
+
## 0.1.2
|
25
|
+
|
26
|
+
- Wrap ThreadPool in ForwardProxy module.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# ForwardProxy
|
2
2
|
|
3
|
-
|
3
|
+
![Gem Version][3] ![Gem][1] ![Build Status][2]
|
4
|
+
|
5
|
+
Minimal forward proxy using 150LOC and only standard libraries. Useful for development, testing, and learning.
|
4
6
|
|
5
7
|
```
|
6
8
|
$ forward-proxy --binding 0.0.0.0 --port 3182 --threads 2
|
@@ -35,9 +37,9 @@ forward-proxy
|
|
35
37
|
```
|
36
38
|
Usage: forward-proxy [options]
|
37
39
|
-p, --port=PORT Bind to specified port. Default: 9292
|
38
|
-
-b, --binding=BINDING Bind to the specified
|
40
|
+
-b, --binding=BINDING Bind to the specified ip. Default: 127.0.0.1
|
39
41
|
-t, --threads=THREADS Specify the number of client threads. Default: 32
|
40
|
-
-h, --help Prints this help
|
42
|
+
-h, --help Prints this help.
|
41
43
|
```
|
42
44
|
|
43
45
|
### Library
|
@@ -70,3 +72,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
70
72
|
## Code of Conduct
|
71
73
|
|
72
74
|
Everyone interacting in the ForwardProxy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jamesmoriarty/forward-proxy/blob/master/CODE_OF_CONDUCT.md).
|
75
|
+
|
76
|
+
[1]: https://img.shields.io/gem/dt/forward-proxy
|
77
|
+
[2]: https://github.com/jamesmoriarty/forward-proxy/workflows/Continuous%20Integration/badge.svg?branch=main
|
78
|
+
[3]: https://img.shields.io/gem/v/forward-proxy
|
data/Rakefile
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
YARD::Rake::YardocTask.new do |t|
|
6
|
+
t.files = ['lib/**/*.rb', 'README', 'CHANGELOG', 'CODE_OF_CONDUCT']
|
7
|
+
t.options = []
|
8
|
+
t.stats_options = ['--list-undoc']
|
9
|
+
end
|
3
10
|
|
4
11
|
Rake::TestTask.new(:test) do |t|
|
5
12
|
t.libs << "test"
|
@@ -7,4 +14,16 @@ Rake::TestTask.new(:test) do |t|
|
|
7
14
|
t.test_files = FileList["test/**/*_test.rb"]
|
8
15
|
end
|
9
16
|
|
17
|
+
namespace :gh do
|
18
|
+
desc "Deploy yard docs to github pages"
|
19
|
+
task :pages => :yard do
|
20
|
+
`git add -f doc`
|
21
|
+
`git commit -am "update: $(date)"`
|
22
|
+
`git subtree split --prefix doc -b gh-pages`
|
23
|
+
`git push -f origin gh-pages:gh-pages`
|
24
|
+
`git branch -D gh-pages`
|
25
|
+
`git reset head~1`
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
10
29
|
task :default => :test
|
data/exe/forward-proxy
CHANGED
@@ -7,11 +7,11 @@ options = {}
|
|
7
7
|
OptionParser.new do |parser|
|
8
8
|
parser.banner = "Usage: forward-proxy [options]"
|
9
9
|
|
10
|
-
parser.on("-pPORT", "--port=PORT", "Bind to specified port. Default: 9292"
|
10
|
+
parser.on("-pPORT", "--port=PORT", String, "Bind to specified port. Default: 9292") do |bind_port|
|
11
11
|
options[:bind_port] = bind_port
|
12
12
|
end
|
13
13
|
|
14
|
-
parser.on("-bBINDING", "--binding=BINDING", String, "Bind to the specified
|
14
|
+
parser.on("-bBINDING", "--binding=BINDING", String, "Bind to the specified ip. Default: 127.0.0.1") do |bind_address|
|
15
15
|
options[:bind_address] = bind_address
|
16
16
|
end
|
17
17
|
|
@@ -19,13 +19,12 @@ OptionParser.new do |parser|
|
|
19
19
|
options[:threads] = threads
|
20
20
|
end
|
21
21
|
|
22
|
-
parser.on("-h", "--help", "Prints this help") do
|
22
|
+
parser.on("-h", "--help", "Prints this help.") do
|
23
23
|
puts parser
|
24
24
|
exit
|
25
25
|
end
|
26
26
|
end.parse!
|
27
27
|
|
28
28
|
require_relative '../lib/forward_proxy'
|
29
|
-
|
30
29
|
server = ForwardProxy::Server.new(options)
|
31
30
|
server.start
|
data/lib/forward_proxy/server.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'forward_proxy/thread_pool'
|
2
1
|
require 'socket'
|
3
2
|
require 'webrick'
|
4
3
|
require 'net/http'
|
4
|
+
require 'forward_proxy/errors/http_method_not_implemented'
|
5
|
+
require 'forward_proxy/errors/http_parse_error'
|
6
|
+
require 'forward_proxy/thread_pool'
|
5
7
|
|
6
8
|
module ForwardProxy
|
7
|
-
class HTTPMethodNotImplemented < StandardError; end
|
8
|
-
|
9
9
|
class Server
|
10
10
|
attr_reader :bind_address, :bind_port
|
11
11
|
|
@@ -16,16 +16,14 @@ module ForwardProxy
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def start
|
19
|
-
@server = TCPServer.new(bind_address, bind_port)
|
20
|
-
|
21
19
|
thread_pool.start
|
22
20
|
|
21
|
+
@socket = TCPServer.new(bind_address, bind_port)
|
22
|
+
|
23
23
|
log("Listening #{bind_address}:#{bind_port}")
|
24
24
|
|
25
25
|
loop do
|
26
|
-
|
27
|
-
|
28
|
-
thread_pool.schedule(client) do |client_conn|
|
26
|
+
thread_pool.schedule(socket.accept) do |client_conn|
|
29
27
|
begin
|
30
28
|
req = parse_req(client_conn)
|
31
29
|
|
@@ -35,11 +33,12 @@ module ForwardProxy
|
|
35
33
|
when METHOD_CONNECT then handle_tunnel(client_conn, req)
|
36
34
|
when METHOD_GET, METHOD_POST then handle(client_conn, req)
|
37
35
|
else
|
38
|
-
raise HTTPMethodNotImplemented
|
36
|
+
raise Errors::HTTPMethodNotImplemented
|
39
37
|
end
|
40
38
|
rescue => e
|
41
39
|
client_conn.puts <<~eos.chomp
|
42
40
|
HTTP/1.1 502
|
41
|
+
Via: #{HEADER_VIA}
|
43
42
|
eos
|
44
43
|
|
45
44
|
puts e.message
|
@@ -50,24 +49,38 @@ module ForwardProxy
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
rescue Interrupt
|
52
|
+
shutdown
|
53
|
+
rescue IOError, Errno::EBADF => e
|
54
|
+
log(e.message, "ERROR")
|
53
55
|
end
|
54
56
|
|
55
57
|
def shutdown
|
56
|
-
|
57
|
-
|
58
|
+
if socket
|
59
|
+
log("Shutting down")
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
+
socket.close
|
62
|
+
end
|
61
63
|
end
|
62
64
|
|
63
65
|
private
|
64
66
|
|
65
|
-
attr_reader :
|
67
|
+
attr_reader :socket, :thread_pool
|
66
68
|
|
67
69
|
METHOD_CONNECT = "CONNECT"
|
68
70
|
METHOD_GET = "GET"
|
69
71
|
METHOD_POST = "POST"
|
70
72
|
|
73
|
+
# The following comments are from the IETF document
|
74
|
+
# "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content"
|
75
|
+
# https://tools.ietf.org/html/rfc7231#section-4.3.6
|
76
|
+
|
77
|
+
# A proxy MUST send an appropriate Via header field, as described
|
78
|
+
# below, in each message that it forwards. An HTTP-to-HTTP gateway
|
79
|
+
# MUST send an appropriate Via header field in each inbound request
|
80
|
+
# message and MAY send a Via header field in forwarded response
|
81
|
+
# messages.
|
82
|
+
HEADER_VIA = "HTTP/1.1 ForwardProxy"
|
83
|
+
|
71
84
|
def handle_tunnel(client_conn, req)
|
72
85
|
# The following comments are from the IETF document
|
73
86
|
# "Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content"
|
@@ -104,22 +117,25 @@ module ForwardProxy
|
|
104
117
|
end
|
105
118
|
|
106
119
|
def handle(client_conn, req)
|
107
|
-
|
108
|
-
http.request
|
120
|
+
Net::HTTP.start(req.host, req.port) do |http|
|
121
|
+
http.request(map_webrick_to_net_http_req(req)) do |resp|
|
122
|
+
client_conn.puts <<~eos.chomp
|
123
|
+
HTTP/1.1 #{resp.code}
|
124
|
+
Via: #{[HEADER_VIA, resp['Via']].compact.join(', ')}
|
125
|
+
#{resp.each.map { |header, value| "#{header}: #{value}" }.join("\n")}\n\n
|
126
|
+
eos
|
127
|
+
|
128
|
+
# The following comments are taken from:
|
129
|
+
# https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html#class-Net::HTTP-label-Streaming+Response+Bodies
|
130
|
+
|
131
|
+
# By default Net::HTTP reads an entire response into memory. If you are
|
132
|
+
# handling large files or wish to implement a progress bar you can
|
133
|
+
# instead stream the body directly to an IO.
|
134
|
+
resp.read_body do |chunk|
|
135
|
+
client_conn << chunk
|
136
|
+
end
|
137
|
+
end
|
109
138
|
end
|
110
|
-
|
111
|
-
# A proxy MUST send an appropriate Via header field, as described
|
112
|
-
# below, in each message that it forwards. An HTTP-to-HTTP gateway
|
113
|
-
# MUST send an appropriate Via header field in each inbound request
|
114
|
-
# message and MAY send a Via header field in forwarded response
|
115
|
-
# messages.
|
116
|
-
client_conn.puts <<~eos.chomp
|
117
|
-
HTTP/1.1 #{resp.code}
|
118
|
-
Via: #{["1.1 ForwardProxy", resp['Via']].compact.join(', ')}
|
119
|
-
#{resp.each.map { |header, value| "#{header}: #{value}" }.join("\n")}
|
120
|
-
|
121
|
-
#{resp.body}
|
122
|
-
eos
|
123
139
|
end
|
124
140
|
|
125
141
|
def map_webrick_to_net_http_req(req)
|
@@ -129,13 +145,13 @@ module ForwardProxy
|
|
129
145
|
when METHOD_GET then Net::HTTP::Get
|
130
146
|
when METHOD_POST then Net::HTTP::Post
|
131
147
|
else
|
132
|
-
raise HTTPMethodNotImplemented
|
148
|
+
raise Errors::HTTPMethodNotImplemented
|
133
149
|
end
|
134
150
|
|
135
151
|
klass.new(req.path, req_headers)
|
136
152
|
end
|
137
153
|
|
138
|
-
def transfer(
|
154
|
+
def transfer(src_conn, dest_conn)
|
139
155
|
IO.copy_stream(src_conn, dest_conn)
|
140
156
|
rescue => e
|
141
157
|
log(e.message, "WARNING")
|
@@ -145,6 +161,8 @@ module ForwardProxy
|
|
145
161
|
WEBrick::HTTPRequest.new(WEBrick::Config::HTTP).tap do |req|
|
146
162
|
req.parse(client_conn)
|
147
163
|
end
|
164
|
+
rescue => e
|
165
|
+
throw Errors::HTTPParseError.new(e.message)
|
148
166
|
end
|
149
167
|
|
150
168
|
def log(str, level = 'INFO')
|
@@ -1,34 +1,36 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module ForwardProxy
|
2
|
+
class ThreadPool
|
3
|
+
attr_reader :queue, :threads, :size
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def initialize(size)
|
6
|
+
@size = size
|
7
|
+
@queue = Queue.new
|
8
|
+
@threads = []
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
def start
|
12
|
+
size.times do
|
13
|
+
threads << Thread.new do
|
14
|
+
catch(:exit) do
|
15
|
+
loop do
|
16
|
+
job, args = queue.pop
|
17
|
+
job.call(*args)
|
18
|
+
end
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def schedule(*args, &block)
|
24
|
-
queue.push([block, args])
|
25
|
-
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
schedule { throw :exit }
|
24
|
+
def schedule(*args, &block)
|
25
|
+
queue.push([block, args])
|
30
26
|
end
|
31
27
|
|
32
|
-
|
28
|
+
def shutdown
|
29
|
+
threads.each do
|
30
|
+
schedule { throw :exit }
|
31
|
+
end
|
32
|
+
|
33
|
+
threads.each(&:join)
|
34
|
+
end
|
33
35
|
end
|
34
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forward-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Moriarty
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Forward proxy using just Ruby standard libraries.
|
14
14
|
email:
|
@@ -19,7 +19,9 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- ".github/workflows/ci.yaml"
|
22
|
+
- ".github/workflows/cli.yaml"
|
22
23
|
- ".gitignore"
|
24
|
+
- CHANGELOG.md
|
23
25
|
- CODE_OF_CONDUCT.md
|
24
26
|
- Gemfile
|
25
27
|
- LICENSE.txt
|
@@ -30,6 +32,8 @@ files:
|
|
30
32
|
- exe/forward-proxy
|
31
33
|
- forward-proxy.gemspec
|
32
34
|
- lib/forward_proxy.rb
|
35
|
+
- lib/forward_proxy/errors/http_method_not_implemented.rb
|
36
|
+
- lib/forward_proxy/errors/http_parse_error.rb
|
33
37
|
- lib/forward_proxy/server.rb
|
34
38
|
- lib/forward_proxy/thread_pool.rb
|
35
39
|
- lib/forward_proxy/version.rb
|
@@ -55,8 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
59
|
- !ruby/object:Gem::Version
|
56
60
|
version: '0'
|
57
61
|
requirements: []
|
58
|
-
|
59
|
-
rubygems_version: 2.5.2.3
|
62
|
+
rubygems_version: 3.0.3
|
60
63
|
signing_key:
|
61
64
|
specification_version: 4
|
62
65
|
summary: Forward proxy.
|