proxymachine 1.0.0 → 1.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.
- data/History.txt +9 -0
- data/README.md +19 -2
- data/Rakefile +1 -1
- data/VERSION.yml +2 -2
- data/lib/proxymachine.rb +2 -1
- data/lib/proxymachine/client_connection.rb +82 -81
- data/lib/proxymachine/server_connection.rb +13 -15
- data/proxymachine.gemspec +5 -5
- data/test/configs/simple.rb +2 -0
- data/test/proxymachine_test.rb +4 -0
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
= 1.1.0 / 2009-11-05
|
2
|
+
* New Features
|
3
|
+
* Add { :remote, :data, :reply } command [github.com/coderrr]
|
4
|
+
* Minor Changes
|
5
|
+
* Namespace connection classes under ProxyMachine instead of EM [github.com/cmelbye]
|
6
|
+
* Require socket [github.com/cmelbye]
|
7
|
+
* Up EM dep to 0.12.10
|
8
|
+
* Add SOCKS4 Proxy example [github.com/coderrr]
|
9
|
+
|
1
10
|
= 1.0.0 / 2009-10-19
|
2
11
|
* No changes. Production ready!
|
3
12
|
|
data/README.md
CHANGED
@@ -26,7 +26,9 @@ backend connections are hooked up to form a transparent proxy. This
|
|
26
26
|
bidirectional proxy continues to exist until either the client or backend
|
27
27
|
close the connection.
|
28
28
|
|
29
|
-
ProxyMachine was developed for GitHub's federated architecture and is
|
29
|
+
ProxyMachine was developed for GitHub's federated architecture and is
|
30
|
+
successfully used in production to proxy millions of requests every day. The
|
31
|
+
performance and memory profile have both proven to be excellent.
|
30
32
|
|
31
33
|
|
32
34
|
Installation
|
@@ -86,11 +88,26 @@ Example routing config file
|
|
86
88
|
end
|
87
89
|
|
88
90
|
|
91
|
+
Example SOCKS4 Proxy in 7 Lines
|
92
|
+
-------------------------------
|
93
|
+
|
94
|
+
proxy do |data|
|
95
|
+
return if data.size < 9
|
96
|
+
v, c, port, o1, o2, o3, o4, user = data.unpack("CCnC4a*")
|
97
|
+
return { :close => "\0\x5b\0\0\0\0\0\0" } if v != 4 or c != 1
|
98
|
+
return if ! idx = user.index("\0")
|
99
|
+
{ :remote => "#{[o1,o2,o3,o4]*'.'}:#{port}",
|
100
|
+
:reply => "\0\x5a\0\0\0\0\0\0",
|
101
|
+
:data => data[idx+9..-1] }
|
102
|
+
end
|
103
|
+
|
104
|
+
|
89
105
|
Valid return values
|
90
106
|
-------------------
|
91
107
|
|
92
108
|
`{ :remote => String }` - String is the host:port of the backend server that will be proxied.
|
93
109
|
`{ :remote => String, :data => String }` - Same as above, but send the given data instead.
|
110
|
+
`{ :remote => String, :data => String, :reply => String}` - Same as above, but reply with given data back to the client
|
94
111
|
`{ :noop => true }` - Do nothing.
|
95
112
|
`{ :close => true }` - Close the connection.
|
96
113
|
`{ :close => String }` - Close the connection after sending the String.
|
@@ -120,4 +137,4 @@ your changes merged back into core is as follows:
|
|
120
137
|
Copyright
|
121
138
|
---------
|
122
139
|
|
123
|
-
Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
|
140
|
+
Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ begin
|
|
9
9
|
gem.email = "tom@mojombo.com"
|
10
10
|
gem.homepage = "http://github.com/mojombo/proxymachine"
|
11
11
|
gem.authors = ["Tom Preston-Werner"]
|
12
|
-
gem.add_dependency('eventmachine', '>= 0.12.
|
12
|
+
gem.add_dependency('eventmachine', '>= 0.12.10')
|
13
13
|
|
14
14
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
15
|
end
|
data/VERSION.yml
CHANGED
data/lib/proxymachine.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'eventmachine'
|
3
3
|
require 'logger'
|
4
|
+
require 'socket'
|
4
5
|
|
5
6
|
require 'proxymachine/client_connection'
|
6
7
|
require 'proxymachine/server_connection'
|
@@ -79,7 +80,7 @@ class ProxyMachine
|
|
79
80
|
EM.epoll
|
80
81
|
|
81
82
|
EM.run do
|
82
|
-
|
83
|
+
ProxyMachine::ClientConnection.start(host, port)
|
83
84
|
trap('QUIT') do
|
84
85
|
self.graceful_shutdown('QUIT')
|
85
86
|
end
|
@@ -1,104 +1,105 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
1
|
+
class ProxyMachine
|
2
|
+
class ClientConnection < EventMachine::Connection
|
3
|
+
def self.start(host, port)
|
4
|
+
$server = EM.start_server(host, port, self)
|
5
|
+
LOGGER.info "Listening on #{host}:#{port}"
|
6
|
+
LOGGER.info "Send QUIT to quit after waiting for all connections to finish."
|
7
|
+
LOGGER.info "Send TERM or INT to quit after waiting for up to 10 seconds for connections to finish."
|
8
|
+
end
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def post_init
|
11
|
+
LOGGER.info "Accepted #{peer}"
|
12
|
+
@buffer = []
|
13
|
+
@tries = 0
|
14
|
+
ProxyMachine.incr
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
17
|
+
def peer
|
18
|
+
@peer ||=
|
19
|
+
begin
|
20
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
21
|
+
"#{ip}:#{port}"
|
24
22
|
end
|
23
|
+
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
rescue => e
|
32
|
-
close_connection
|
33
|
-
LOGGER.info "#{e.class} - #{e.message}"
|
25
|
+
def receive_data(data)
|
26
|
+
if !@server_side
|
27
|
+
@buffer << data
|
28
|
+
ensure_server_side_connection
|
34
29
|
end
|
30
|
+
rescue => e
|
31
|
+
close_connection
|
32
|
+
LOGGER.info "#{e.class} - #{e.message}"
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
send_and_clear_buffer
|
35
|
+
def ensure_server_side_connection
|
36
|
+
@timer.cancel if @timer
|
37
|
+
unless @server_side
|
38
|
+
commands = ProxyMachine.router.call(@buffer.join)
|
39
|
+
LOGGER.info "#{peer} #{commands.inspect}"
|
40
|
+
close_connection unless commands.instance_of?(Hash)
|
41
|
+
if remote = commands[:remote]
|
42
|
+
m, host, port = *remote.match(/^(.+):(.+)$/)
|
43
|
+
if try_server_connect(host, port.to_i)
|
44
|
+
if data = commands[:data]
|
45
|
+
@buffer = [data]
|
49
46
|
end
|
50
|
-
|
51
|
-
|
52
|
-
close_connection
|
53
|
-
else
|
54
|
-
send_data(close)
|
55
|
-
close_connection_after_writing
|
47
|
+
if reply = commands[:reply]
|
48
|
+
send_data(reply)
|
56
49
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
50
|
+
send_and_clear_buffer
|
51
|
+
end
|
52
|
+
elsif close = commands[:close]
|
53
|
+
if close == true
|
60
54
|
close_connection
|
55
|
+
else
|
56
|
+
send_data(close)
|
57
|
+
close_connection_after_writing
|
61
58
|
end
|
59
|
+
elsif commands[:noop]
|
60
|
+
# do nothing
|
61
|
+
else
|
62
|
+
close_connection
|
62
63
|
end
|
63
64
|
end
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
else
|
79
|
-
LOGGER.info "Failed after ten connection attempts."
|
67
|
+
def try_server_connect(host, port)
|
68
|
+
@server_side = ServerConnection.request(host, port, self)
|
69
|
+
proxy_incoming_to(@server_side, 10240)
|
70
|
+
LOGGER.info "Successful connection to #{host}:#{port}."
|
71
|
+
true
|
72
|
+
rescue => e
|
73
|
+
if @tries < 10
|
74
|
+
@tries += 1
|
75
|
+
LOGGER.info "Failed on server connect attempt #{@tries}. Trying again..."
|
76
|
+
@timer.cancel if @timer
|
77
|
+
@timer = EventMachine::Timer.new(0.1) do
|
78
|
+
self.ensure_server_side_connection
|
80
79
|
end
|
81
|
-
|
80
|
+
else
|
81
|
+
LOGGER.info "Failed after ten connection attempts."
|
82
82
|
end
|
83
|
+
false
|
84
|
+
end
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
89
|
-
@buffer = []
|
86
|
+
def send_and_clear_buffer
|
87
|
+
if !@buffer.empty?
|
88
|
+
@buffer.each do |x|
|
89
|
+
@server_side.send_data(x)
|
90
90
|
end
|
91
|
+
@buffer = []
|
91
92
|
end
|
93
|
+
end
|
92
94
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
95
|
+
def unbind
|
96
|
+
@server_side.close_connection_after_writing if @server_side
|
97
|
+
ProxyMachine.decr
|
98
|
+
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
100
|
+
# Proxy connection has been lost
|
101
|
+
def proxy_target_unbound
|
102
|
+
@server_side = nil
|
102
103
|
end
|
103
104
|
end
|
104
105
|
end
|
@@ -1,21 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
end
|
1
|
+
class ProxyMachine
|
2
|
+
class ServerConnection < EventMachine::Connection
|
3
|
+
def self.request(host, port, client_side)
|
4
|
+
EventMachine.connect(host, port, self, client_side)
|
5
|
+
end
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def initialize(conn)
|
8
|
+
@client_side = conn
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def post_init
|
12
|
+
proxy_incoming_to(@client_side, 10240)
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
15
|
+
def unbind
|
16
|
+
@client_side.close_connection_after_writing
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
data/proxymachine.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{proxymachine}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tom Preston-Werner"]
|
12
|
-
s.date = %q{2009-
|
12
|
+
s.date = %q{2009-11-05}
|
13
13
|
s.default_executable = %q{proxymachine}
|
14
14
|
s.email = %q{tom@mojombo.com}
|
15
15
|
s.executables = ["proxymachine"]
|
@@ -56,11 +56,11 @@ Gem::Specification.new do |s|
|
|
56
56
|
s.specification_version = 3
|
57
57
|
|
58
58
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
59
|
-
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.
|
59
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
60
60
|
else
|
61
|
-
s.add_dependency(%q<eventmachine>, [">= 0.12.
|
61
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
62
62
|
end
|
63
63
|
else
|
64
|
-
s.add_dependency(%q<eventmachine>, [">= 0.12.
|
64
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
|
65
65
|
end
|
66
66
|
end
|
data/test/configs/simple.rb
CHANGED
data/test/proxymachine_test.rb
CHANGED
@@ -28,6 +28,10 @@ class ProxymachineTest < Test::Unit::TestCase
|
|
28
28
|
assert_proxy('localhost', 9990, 'd', 'ddd')
|
29
29
|
end
|
30
30
|
|
31
|
+
should "handle data plus reply" do
|
32
|
+
assert_proxy('localhost', 9990, 'g', 'g3-9980:g2')
|
33
|
+
end
|
34
|
+
|
31
35
|
should "handle noop" do
|
32
36
|
sock = TCPSocket.new('localhost', 9990)
|
33
37
|
sock.write('e' * 2048)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: proxymachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Preston-Werner
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-05 00:00:00 -08:00
|
13
13
|
default_executable: proxymachine
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.12.
|
23
|
+
version: 0.12.10
|
24
24
|
version:
|
25
25
|
description:
|
26
26
|
email: tom@mojombo.com
|