fizx-proxymachine 1.3.0 → 1.4.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/Gemfile +3 -0
- data/History.txt +25 -1
- data/README.md +42 -0
- data/Rakefile +129 -37
- data/fizx-proxymachine.gemspec +77 -62
- data/lib/fizx-proxymachine.rb +3 -0
- data/lib/proxymachine.rb +30 -11
- data/lib/proxymachine/callback_server_connection.rb +1 -1
- data/lib/proxymachine/client_connection.rb +83 -52
- data/lib/proxymachine/server_connection.rb +33 -2
- data/test/configs/simple.rb +15 -1
- data/test/proxymachine_test.rb +36 -0
- data/test/test_helper.rb +2 -1
- metadata +50 -22
- data/.document +0 -5
- data/.gitignore +0 -5
- data/VERSION.yml +0 -5
- data/lib/fizx_proxymachine.rb +0 -1
data/Gemfile
ADDED
data/History.txt
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
= 1.2.4 / 2011-02-01
|
2
|
+
* Bug fixes
|
3
|
+
* Fix version number in procline
|
4
|
+
* Unrequire rubygems. Some people consider this a bug.
|
5
|
+
|
6
|
+
= 1.2.3 / 2010-02-23
|
7
|
+
* Bug fixes
|
8
|
+
* Never retry after connection is established and data is sent
|
9
|
+
|
10
|
+
= 1.2.2 / 2010-02-19
|
11
|
+
* Bug fixes
|
12
|
+
* Bring back the buffer limit
|
13
|
+
|
14
|
+
= 1.2.1 / 2010-02-11
|
15
|
+
* Bug fixes
|
16
|
+
* Don't count client closes as connection errors
|
17
|
+
|
18
|
+
= 1.2.0 / 2010-02-09
|
19
|
+
* New Features
|
20
|
+
* Connection Errors and Timeouts
|
21
|
+
* Inactivity Timeouts
|
22
|
+
* Enhancements
|
23
|
+
* Better async retry logic
|
24
|
+
|
1
25
|
= 1.1.0 / 2009-11-05
|
2
26
|
* New Features
|
3
27
|
* Add { :remote, :data, :reply } command [github.com/coderrr]
|
@@ -19,4 +43,4 @@
|
|
19
43
|
|
20
44
|
= 0.2.7 / 2009-10-12
|
21
45
|
* Minor changes
|
22
|
-
* Use a 10k buffer to prevent memory growth due to slow clients
|
46
|
+
* Use a 10k buffer to prevent memory growth due to slow clients
|
data/README.md
CHANGED
@@ -112,6 +112,48 @@ Valid return values
|
|
112
112
|
`{ :close => true }` - Close the connection.
|
113
113
|
`{ :close => String }` - Close the connection after sending the String.
|
114
114
|
|
115
|
+
Connection Errors and Timeouts
|
116
|
+
------------------------------
|
117
|
+
|
118
|
+
It's possible to register a custom callback for handling connection
|
119
|
+
errors. The callback is passed the remote when a connection is either
|
120
|
+
rejected or a connection timeout occurs:
|
121
|
+
|
122
|
+
proxy do |data|
|
123
|
+
if data =~ /your thing/
|
124
|
+
{ :remote => 'localhost:1234', :connect_timeout => 1.0 }
|
125
|
+
else
|
126
|
+
{ :noop => true }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
proxy_connect_error do |remote|
|
131
|
+
puts "error connecting to #{remote}"
|
132
|
+
end
|
133
|
+
|
134
|
+
You must provide a `:connect_timeout` value in the `proxy` return value
|
135
|
+
to enable connection timeouts. The `:connect_timeout` value is a float
|
136
|
+
representing the number of seconds to wait before a connection is
|
137
|
+
established. Hard connection rejections always trigger the callback, even
|
138
|
+
when no `:connect_timeout` is provided.
|
139
|
+
|
140
|
+
Inactivity Timeouts
|
141
|
+
-------------------
|
142
|
+
|
143
|
+
Inactivity timeouts work like connect timeouts but are triggered after
|
144
|
+
the configured amount of time elapses without receiving the first byte
|
145
|
+
of data from an already connected server:
|
146
|
+
|
147
|
+
proxy do |data|
|
148
|
+
{ :remote => 'localhost:1234', :inactivity_timeout => 10.0 }
|
149
|
+
end
|
150
|
+
|
151
|
+
proxy_inactivity_error do |remote|
|
152
|
+
puts "#{remote} did not send any data for 10 seconds"
|
153
|
+
end
|
154
|
+
|
155
|
+
If no `:inactivity_timeout` is provided, the `proxy_inactivity_error`
|
156
|
+
callback is never triggered.
|
115
157
|
|
116
158
|
Contribute
|
117
159
|
----------
|
data/Rakefile
CHANGED
@@ -1,23 +1,50 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
+
require 'date'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
19
22
|
end
|
20
23
|
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
21
48
|
require 'rake/testtask'
|
22
49
|
Rake::TestTask.new(:test) do |test|
|
23
50
|
test.libs << 'lib' << 'test'
|
@@ -25,34 +52,99 @@ Rake::TestTask.new(:test) do |test|
|
|
25
52
|
test.verbose = true
|
26
53
|
end
|
27
54
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
rescue LoadError
|
36
|
-
task :rcov do
|
37
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
-
end
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
39
61
|
end
|
40
62
|
|
41
|
-
|
42
|
-
task :default => :test
|
43
|
-
|
44
63
|
require 'rake/rdoctask'
|
45
64
|
Rake::RDocTask.new do |rdoc|
|
46
|
-
if File.exist?('VERSION.yml')
|
47
|
-
config = YAML.load(File.read('VERSION.yml'))
|
48
|
-
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
49
|
-
else
|
50
|
-
version = ""
|
51
|
-
end
|
52
|
-
|
53
65
|
rdoc.rdoc_dir = 'rdoc'
|
54
|
-
rdoc.title = "
|
66
|
+
rdoc.title = "#{name} #{version}"
|
55
67
|
rdoc.rdoc_files.include('README*')
|
56
68
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
57
69
|
end
|
58
70
|
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
#
|
86
|
+
# Packaging tasks
|
87
|
+
#
|
88
|
+
#############################################################################
|
89
|
+
|
90
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
91
|
+
task :release => :build do
|
92
|
+
unless `git branch` =~ /^\* master$/
|
93
|
+
puts "You must be on the master branch to release!"
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
97
|
+
sh "git tag v#{version}"
|
98
|
+
sh "git push origin master"
|
99
|
+
sh "git push origin v#{version}"
|
100
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Build #{gem_file} into the pkg directory"
|
104
|
+
task :build => :gemspec do
|
105
|
+
sh "mkdir -p pkg"
|
106
|
+
sh "gem build #{gemspec_file}"
|
107
|
+
sh "mv #{gem_file} pkg"
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Generate #{gemspec_file}"
|
111
|
+
task :gemspec => :validate do
|
112
|
+
# read spec file and split out manifest section
|
113
|
+
spec = File.read(gemspec_file)
|
114
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
115
|
+
|
116
|
+
# replace name version and date
|
117
|
+
replace_header(head, :name)
|
118
|
+
replace_header(head, :version)
|
119
|
+
replace_header(head, :date)
|
120
|
+
#comment this out if your rubyforge_project has a different name
|
121
|
+
replace_header(head, :rubyforge_project)
|
122
|
+
|
123
|
+
# determine file list from git ls-files
|
124
|
+
files = `git ls-files`.
|
125
|
+
split("\n").
|
126
|
+
sort.
|
127
|
+
reject { |file| file =~ /^\./ }.
|
128
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
129
|
+
map { |file| " #{file}" }.
|
130
|
+
join("\n")
|
131
|
+
|
132
|
+
# piece file back together and write
|
133
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
134
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
135
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
136
|
+
puts "Updated #{gemspec_file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
desc "Validate #{gemspec_file}"
|
140
|
+
task :validate do
|
141
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
142
|
+
# unless libfiles.empty?
|
143
|
+
# puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
144
|
+
# exit!
|
145
|
+
# end
|
146
|
+
unless Dir['VERSION*'].empty?
|
147
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
148
|
+
exit!
|
149
|
+
end
|
150
|
+
end
|
data/fizx-proxymachine.gemspec
CHANGED
@@ -1,69 +1,84 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
6
7
|
Gem::Specification.new do |s|
|
7
|
-
s.
|
8
|
-
s.version = "1.3.0"
|
9
|
-
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
9
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'fizx-proxymachine'
|
16
|
+
s.version = '1.4.0'
|
17
|
+
s.date = '2011-05-07'
|
18
|
+
s.rubyforge_project = 'fizx-proxymachine'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "ProxyMachine is a simple content aware (layer 7) TCP routing proxy."
|
23
|
+
s.description = "ProxyMachine is a simple content aware (layer 7) TCP routing proxy written in Ruby with EventMachine."
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["Tom Preston-Werner", "Kyle Maxwell"]
|
29
|
+
s.email = 'tom@mojombo.com'
|
30
|
+
s.homepage = 'http://github.com/fizx/proxymachine'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## If your gem includes any executables, list them here.
|
15
37
|
s.executables = ["proxymachine"]
|
16
|
-
s.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
s.files = [
|
21
|
-
".document",
|
22
|
-
".gitignore",
|
23
|
-
"History.txt",
|
24
|
-
"LICENSE",
|
25
|
-
"README.md",
|
26
|
-
"Rakefile",
|
27
|
-
"VERSION.yml",
|
28
|
-
"bin/proxymachine",
|
29
|
-
"examples/git.rb",
|
30
|
-
"examples/long.rb",
|
31
|
-
"examples/transparent.rb",
|
32
|
-
"fizx-proxymachine.gemspec",
|
33
|
-
"lib/fizx_proxymachine.rb",
|
34
|
-
"lib/proxymachine.rb",
|
35
|
-
"lib/proxymachine/callback_server_connection.rb",
|
36
|
-
"lib/proxymachine/client_connection.rb",
|
37
|
-
"lib/proxymachine/server_connection.rb",
|
38
|
-
"test/configs/simple.rb",
|
39
|
-
"test/proxymachine_test.rb",
|
40
|
-
"test/test_helper.rb"
|
41
|
-
]
|
42
|
-
s.homepage = %q{http://github.com/fizx/proxymachine}
|
38
|
+
s.default_executable = 'proxymachine'
|
39
|
+
|
40
|
+
## Specify any RDoc options here. You'll want to add your README and
|
41
|
+
## LICENSE files to the extra_rdoc_files list.
|
43
42
|
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
-
s.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
"test/proxymachine_test.rb",
|
50
|
-
"test/test_helper.rb",
|
51
|
-
"examples/git.rb",
|
52
|
-
"examples/long.rb",
|
53
|
-
"examples/transparent.rb"
|
54
|
-
]
|
43
|
+
s.extra_rdoc_files = %w[README.md LICENSE]
|
44
|
+
|
45
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
46
|
+
## that are needed for an end user to actually USE your code.
|
47
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
|
55
48
|
|
56
|
-
|
57
|
-
|
58
|
-
|
49
|
+
## List your development dependencies here. Development dependencies are
|
50
|
+
## those that are only needed during development
|
51
|
+
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
|
52
|
+
s.add_development_dependency(%q<shoulda>, ["~> 2.11.3"])
|
53
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
59
54
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
55
|
+
## Leave this section as-is. It will be automatically generated from the
|
56
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
57
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
58
|
+
# = MANIFEST =
|
59
|
+
s.files = %w[
|
60
|
+
Gemfile
|
61
|
+
History.txt
|
62
|
+
LICENSE
|
63
|
+
README.md
|
64
|
+
Rakefile
|
65
|
+
bin/proxymachine
|
66
|
+
examples/git.rb
|
67
|
+
examples/long.rb
|
68
|
+
examples/transparent.rb
|
69
|
+
fizx-proxymachine.gemspec
|
70
|
+
lib/fizx-proxymachine.rb
|
71
|
+
lib/proxymachine.rb
|
72
|
+
lib/proxymachine/callback_server_connection.rb
|
73
|
+
lib/proxymachine/client_connection.rb
|
74
|
+
lib/proxymachine/server_connection.rb
|
75
|
+
test/configs/simple.rb
|
76
|
+
test/proxymachine_test.rb
|
77
|
+
test/test_helper.rb
|
78
|
+
]
|
79
|
+
# = MANIFEST =
|
69
80
|
|
81
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
82
|
+
## matches what you actually use.
|
83
|
+
s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb$/ }
|
84
|
+
end
|
data/lib/proxymachine.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'yaml'
|
2
2
|
require 'eventmachine'
|
3
3
|
require 'logger'
|
4
4
|
require 'socket'
|
@@ -10,6 +10,8 @@ require 'proxymachine/callback_server_connection'
|
|
10
10
|
$logger = Logger.new(STDOUT)
|
11
11
|
|
12
12
|
class ProxyMachine
|
13
|
+
VERSION = '1.2.4'
|
14
|
+
|
13
15
|
MAX_FAST_SHUTDOWN_SECONDS = 10
|
14
16
|
|
15
17
|
def self.update_procline
|
@@ -71,12 +73,30 @@ class ProxyMachine
|
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
76
|
+
def self.set_connect_error_callback(&block)
|
77
|
+
@@connect_error_callback = block
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.connect_error_callback
|
81
|
+
@@connect_error_callback
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.set_inactivity_error_callback(&block)
|
85
|
+
@@inactivity_error_callback = block
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.inactivity_error_callback
|
89
|
+
@@inactivity_error_callback
|
90
|
+
end
|
91
|
+
|
74
92
|
def self.run(name, host, port)
|
75
93
|
@@totalcounter = 0
|
76
94
|
@@maxcounter = 0
|
77
95
|
@@counter = 0
|
78
96
|
@@name = name
|
79
97
|
@@listen = "#{host}:#{port}"
|
98
|
+
@@connect_error_callback ||= proc { |remote| }
|
99
|
+
@@inactivity_error_callback ||= proc { |remote| }
|
80
100
|
self.update_procline
|
81
101
|
EM.epoll
|
82
102
|
|
@@ -93,19 +113,18 @@ class ProxyMachine
|
|
93
113
|
end
|
94
114
|
end
|
95
115
|
end
|
96
|
-
|
97
|
-
def self.version
|
98
|
-
yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
|
99
|
-
"#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
|
100
|
-
rescue
|
101
|
-
'unknown'
|
102
|
-
end
|
103
|
-
|
104
|
-
VERSION = self.version
|
105
116
|
end
|
106
117
|
|
107
118
|
module Kernel
|
108
119
|
def proxy(&block)
|
109
120
|
ProxyMachine.set_router(block)
|
110
121
|
end
|
111
|
-
|
122
|
+
|
123
|
+
def proxy_connect_error(&block)
|
124
|
+
ProxyMachine.set_connect_error_callback(&block)
|
125
|
+
end
|
126
|
+
|
127
|
+
def proxy_inactivity_error(&block)
|
128
|
+
ProxyMachine.set_inactivity_error_callback(&block)
|
129
|
+
end
|
130
|
+
end
|
@@ -13,8 +13,8 @@ class ProxyMachine
|
|
13
13
|
@buffer ||= []
|
14
14
|
@buffer << data
|
15
15
|
if returned = @callback.call(@buffer.join(''))
|
16
|
-
proxy_incoming_to(@client_side, 10240)
|
17
16
|
@client_side.send_data returned
|
17
|
+
proxy_incoming_to(@client_side, 10240)
|
18
18
|
end
|
19
19
|
rescue => e
|
20
20
|
$logger.info e.message + e.backtrace.join("\n")
|
@@ -10,7 +10,11 @@ class ProxyMachine
|
|
10
10
|
def post_init
|
11
11
|
$logger.info "Accepted #{peer}"
|
12
12
|
@buffer = []
|
13
|
+
@remote = nil
|
13
14
|
@tries = 0
|
15
|
+
@connected = false
|
16
|
+
@connect_timeout = nil
|
17
|
+
@inactivity_timeout = nil
|
14
18
|
ProxyMachine.incr
|
15
19
|
end
|
16
20
|
|
@@ -23,76 +27,103 @@ class ProxyMachine
|
|
23
27
|
end
|
24
28
|
|
25
29
|
def receive_data(data)
|
26
|
-
if !@
|
30
|
+
if !@connected
|
27
31
|
@buffer << data
|
28
|
-
|
32
|
+
establish_remote_server if @remote.nil?
|
29
33
|
end
|
30
34
|
rescue => e
|
31
35
|
close_connection
|
32
|
-
$logger.
|
36
|
+
$logger.error "#{e.class} - #{e.message}"
|
33
37
|
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
close_connection
|
58
|
-
else
|
59
|
-
send_data(close)
|
60
|
-
close_connection_after_writing
|
61
|
-
end
|
62
|
-
elsif commands[:noop]
|
63
|
-
# do nothing
|
64
|
-
else
|
39
|
+
# Called when new data is available from the client but no remote
|
40
|
+
# server has been established. If a remote can be established, an
|
41
|
+
# attempt is made to connect and proxy to the remote server.
|
42
|
+
def establish_remote_server
|
43
|
+
fail "establish_remote_server called with remote established" if @remote
|
44
|
+
@commands = ProxyMachine.router.call(@buffer.join)
|
45
|
+
$logger.info "#{peer} #{@commands.inspect}"
|
46
|
+
close_connection unless @commands.instance_of?(Hash)
|
47
|
+
if remote = @commands[:remote]
|
48
|
+
m, host, port = *remote.match(/^(.+):(.+)$/)
|
49
|
+
@remote = [host, port]
|
50
|
+
if data = @commands[:data]
|
51
|
+
@buffer = [data]
|
52
|
+
end
|
53
|
+
if reply = @commands[:reply]
|
54
|
+
send_data(reply)
|
55
|
+
end
|
56
|
+
@connect_timeout = @commands[:connect_timeout]
|
57
|
+
@inactivity_timeout = @commands[:inactivity_timeout]
|
58
|
+
connect_to_server
|
59
|
+
elsif close = @commands[:close]
|
60
|
+
if close == true
|
65
61
|
close_connection
|
62
|
+
else
|
63
|
+
send_data(close)
|
64
|
+
close_connection_after_writing
|
66
65
|
end
|
66
|
+
elsif @commands[:noop]
|
67
|
+
# do nothing
|
68
|
+
else
|
69
|
+
close_connection
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
70
|
-
|
73
|
+
# Connect to the remote server
|
74
|
+
def connect_to_server
|
75
|
+
fail "connect_server called without remote established" if @remote.nil?
|
76
|
+
host, port = @remote
|
77
|
+
$logger.info "Establishing new connection with #{host}:#{port}"
|
78
|
+
cb = @commands[:callback]
|
79
|
+
klass = cb ? CallbackServerConnection : ServerConnection
|
71
80
|
@server_side = klass.request(host, port, self)
|
81
|
+
@server_side.callback = cb if cb
|
82
|
+
@server_side.pending_connect_timeout = @connect_timeout
|
83
|
+
@server_side.comm_inactivity_timeout = @inactivity_timeout
|
84
|
+
end
|
85
|
+
|
86
|
+
# Called by the server side immediately after the server connection was
|
87
|
+
# successfully established. Send any buffer we've accumulated and start
|
88
|
+
# raw proxying.
|
89
|
+
def server_connection_success
|
90
|
+
$logger.info "Successful connection to #{@remote.join(':')}"
|
91
|
+
@connected = true
|
92
|
+
@buffer.each { |data| @server_side.send_data(data) }
|
93
|
+
@buffer = []
|
72
94
|
proxy_incoming_to(@server_side, 10240)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
95
|
+
end
|
96
|
+
|
97
|
+
# Called by the server side when a connection could not be established,
|
98
|
+
# either due to a hard connection failure or to a connection timeout.
|
99
|
+
# Leave the client connection open and retry the server connection up to
|
100
|
+
# 10 times.
|
101
|
+
def server_connection_failed
|
102
|
+
@server_side = nil
|
103
|
+
if @connected
|
104
|
+
$logger.error "Connection with #{@remote.join(':')} was terminated prematurely."
|
105
|
+
close_connection
|
106
|
+
ProxyMachine.connect_error_callback.call(@remote.join(':'))
|
107
|
+
elsif @tries < 10
|
77
108
|
@tries += 1
|
78
|
-
$logger.
|
79
|
-
|
80
|
-
@timer = EventMachine::Timer.new(0.1) do
|
81
|
-
self.ensure_server_side_connection
|
82
|
-
end
|
109
|
+
$logger.warn "Retrying connection with #{@remote.join(':')} (##{@tries})"
|
110
|
+
EM.add_timer(0.1) { connect_to_server }
|
83
111
|
else
|
84
|
-
$logger.
|
112
|
+
$logger.error "Connect #{@remote.join(':')} failed after ten attempts."
|
113
|
+
close_connection
|
114
|
+
ProxyMachine.connect_error_callback.call(@remote.join(':'))
|
85
115
|
end
|
86
|
-
false
|
87
116
|
end
|
88
117
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
118
|
+
# Called by the server when an inactivity timeout is detected. The timeout
|
119
|
+
# argument is the configured inactivity timeout in seconds as a float; the
|
120
|
+
# elapsed argument is the amount of time that actually elapsed since
|
121
|
+
# connecting but not receiving any data.
|
122
|
+
def server_inactivity_timeout(timeout, elapsed)
|
123
|
+
$logger.error "Disconnecting #{@remote.join(':')} after #{elapsed}s of inactivity (> #{timeout.inspect})"
|
124
|
+
@server_side = nil
|
125
|
+
close_connection
|
126
|
+
ProxyMachine.inactivity_error_callback.call(@remote.join(':'))
|
96
127
|
end
|
97
128
|
|
98
129
|
def unbind
|
@@ -6,14 +6,45 @@ class ProxyMachine
|
|
6
6
|
|
7
7
|
def initialize(conn)
|
8
8
|
@client_side = conn
|
9
|
+
@connected = false
|
10
|
+
@data_received = false
|
11
|
+
@timeout = nil
|
9
12
|
end
|
10
13
|
|
11
|
-
def
|
14
|
+
def receive_data(data)
|
15
|
+
fail "receive_data called after raw proxy enabled" if @data_received
|
16
|
+
@data_received = true
|
17
|
+
@client_side.send_data(data)
|
12
18
|
proxy_incoming_to(@client_side, 10240)
|
13
19
|
end
|
14
20
|
|
21
|
+
def connection_completed
|
22
|
+
@connected = Time.now
|
23
|
+
@timeout = comm_inactivity_timeout || 0.0
|
24
|
+
@client_side.server_connection_success
|
25
|
+
end
|
26
|
+
|
15
27
|
def unbind
|
16
|
-
|
28
|
+
now = Time.now
|
29
|
+
if @client_side.error?
|
30
|
+
# the client side disconnected while we were in progress with
|
31
|
+
# the server. do nothing.
|
32
|
+
$logger.info "Client closed while server connection in progress. Dropping."
|
33
|
+
elsif !@connected
|
34
|
+
# a connection error or timeout occurred
|
35
|
+
@client_side.server_connection_failed
|
36
|
+
elsif !@data_received
|
37
|
+
if @timeout > 0.0 && (elapsed = now - @connected) >= @timeout
|
38
|
+
# EM aborted the connection due to an inactivity timeout
|
39
|
+
@client_side.server_inactivity_timeout(@timeout, elapsed)
|
40
|
+
else
|
41
|
+
# server disconnected soon after connecting without sending data
|
42
|
+
# treat this like a failed server connection
|
43
|
+
@client_side.server_connection_failed
|
44
|
+
end
|
45
|
+
else
|
46
|
+
@client_side.close_connection_after_writing
|
47
|
+
end
|
17
48
|
end
|
18
49
|
end
|
19
50
|
end
|
data/test/configs/simple.rb
CHANGED
@@ -21,7 +21,21 @@ proxy do |data|
|
|
21
21
|
{ :remote => "localhost:9980", :data => 'g2', :reply => 'g3-' }
|
22
22
|
elsif data == 'h'
|
23
23
|
{ :remote => "localhost:9980", :callback => callback }
|
24
|
+
elsif data == 'connect reject'
|
25
|
+
{ :remote => "localhost:9989" }
|
26
|
+
elsif data == 'inactivity'
|
27
|
+
{ :remote => "localhost:9980", :data => 'sleep 3', :inactivity_timeout => 1 }
|
24
28
|
else
|
25
29
|
{ :close => true }
|
26
30
|
end
|
27
|
-
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ERROR_FILE = File.expand_path('../../proxy_error', __FILE__)
|
34
|
+
|
35
|
+
proxy_connect_error do |remote|
|
36
|
+
File.open(ERROR_FILE, 'wb') { |fd| fd.write("connect error: #{remote}") }
|
37
|
+
end
|
38
|
+
|
39
|
+
proxy_inactivity_error do |remote|
|
40
|
+
File.open(ERROR_FILE, 'wb') { |fd| fd.write("activity error: #{remote}") }
|
41
|
+
end
|
data/test/proxymachine_test.rb
CHANGED
@@ -8,6 +8,14 @@ def assert_proxy(host, port, send, recv)
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class ProxymachineTest < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@proxy_error_file = "#{File.dirname(__FILE__)}/proxy_error"
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
File.unlink(@proxy_error_file) rescue nil
|
17
|
+
end
|
18
|
+
|
11
19
|
should "handle simple routing" do
|
12
20
|
assert_proxy('localhost', 9990, 'a', '9980:a')
|
13
21
|
assert_proxy('localhost', 9990, 'b', '9981:b')
|
@@ -44,4 +52,32 @@ class ProxymachineTest < Test::Unit::TestCase
|
|
44
52
|
should "execute a callback" do
|
45
53
|
assert_proxy('localhost', 9990, 'h', '9980:h:callback')
|
46
54
|
end
|
55
|
+
|
56
|
+
should "call proxy_connect_error when a connection is rejected" do
|
57
|
+
sock = TCPSocket.new('localhost', 9990)
|
58
|
+
sock.write('connect reject')
|
59
|
+
sock.flush
|
60
|
+
assert_equal "", sock.read
|
61
|
+
sock.close
|
62
|
+
assert_equal "connect error: localhost:9989", File.read(@proxy_error_file)
|
63
|
+
end
|
64
|
+
|
65
|
+
should "call proxy_inactivity_error when initial read times out" do
|
66
|
+
sock = TCPSocket.new('localhost', 9990)
|
67
|
+
sent = Time.now
|
68
|
+
sock.write('inactivity')
|
69
|
+
sock.flush
|
70
|
+
assert_equal "", sock.read
|
71
|
+
assert_operator Time.now - sent, :>=, 1.0
|
72
|
+
assert_equal "activity error: localhost:9980", File.read(@proxy_error_file)
|
73
|
+
sock.close
|
74
|
+
end
|
75
|
+
|
76
|
+
should "not consider client disconnect a server error" do
|
77
|
+
sock = TCPSocket.new('localhost', 9990)
|
78
|
+
sock.write('inactivity')
|
79
|
+
sock.close
|
80
|
+
sleep 3.1
|
81
|
+
assert !File.exist?(@proxy_error_file)
|
82
|
+
end
|
47
83
|
end
|
data/test/test_helper.rb
CHANGED
@@ -17,6 +17,7 @@ module EventMachine
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def receive_data(data)
|
20
|
+
sleep $1.to_f if data =~ /^sleep (.*)/
|
20
21
|
send_data("#{@@port}:#{data}")
|
21
22
|
close_connection_after_writing
|
22
23
|
end
|
@@ -54,4 +55,4 @@ end
|
|
54
55
|
EventMachine::Protocols::TestConnection.start('localhost', port)
|
55
56
|
end
|
56
57
|
end
|
57
|
-
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fizx-proxymachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease:
|
5
|
+
version: 1.4.0
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Tom Preston-Werner
|
@@ -10,42 +11,74 @@ autorequire:
|
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
13
|
|
13
|
-
date:
|
14
|
+
date: 2011-05-07 00:00:00 -07:00
|
14
15
|
default_executable: proxymachine
|
15
16
|
dependencies:
|
16
17
|
- !ruby/object:Gem::Dependency
|
17
18
|
name: eventmachine
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
21
22
|
requirements:
|
22
23
|
- - ">="
|
23
24
|
- !ruby/object:Gem::Version
|
24
25
|
version: 0.12.10
|
25
|
-
|
26
|
-
|
26
|
+
type: :runtime
|
27
|
+
version_requirements: *id001
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
prerelease: false
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ~>
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.8.7
|
37
|
+
type: :development
|
38
|
+
version_requirements: *id002
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: shoulda
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.11.3
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id003
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: jeweler
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 1.5.2
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id004
|
61
|
+
description: ProxyMachine is a simple content aware (layer 7) TCP routing proxy written in Ruby with EventMachine.
|
27
62
|
email: tom@mojombo.com
|
28
63
|
executables:
|
29
64
|
- proxymachine
|
30
65
|
extensions: []
|
31
66
|
|
32
67
|
extra_rdoc_files:
|
33
|
-
- LICENSE
|
34
68
|
- README.md
|
69
|
+
- LICENSE
|
35
70
|
files:
|
36
|
-
-
|
37
|
-
- .gitignore
|
71
|
+
- Gemfile
|
38
72
|
- History.txt
|
39
73
|
- LICENSE
|
40
74
|
- README.md
|
41
75
|
- Rakefile
|
42
|
-
- VERSION.yml
|
43
76
|
- bin/proxymachine
|
44
77
|
- examples/git.rb
|
45
78
|
- examples/long.rb
|
46
79
|
- examples/transparent.rb
|
47
80
|
- fizx-proxymachine.gemspec
|
48
|
-
- lib/
|
81
|
+
- lib/fizx-proxymachine.rb
|
49
82
|
- lib/proxymachine.rb
|
50
83
|
- lib/proxymachine/callback_server_connection.rb
|
51
84
|
- lib/proxymachine/client_connection.rb
|
@@ -63,28 +96,23 @@ rdoc_options:
|
|
63
96
|
require_paths:
|
64
97
|
- lib
|
65
98
|
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
66
100
|
requirements:
|
67
101
|
- - ">="
|
68
102
|
- !ruby/object:Gem::Version
|
69
103
|
version: "0"
|
70
|
-
version:
|
71
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
72
106
|
requirements:
|
73
107
|
- - ">="
|
74
108
|
- !ruby/object:Gem::Version
|
75
109
|
version: "0"
|
76
|
-
version:
|
77
110
|
requirements: []
|
78
111
|
|
79
|
-
rubyforge_project:
|
80
|
-
rubygems_version: 1.
|
112
|
+
rubyforge_project: fizx-proxymachine
|
113
|
+
rubygems_version: 1.6.2
|
81
114
|
signing_key:
|
82
|
-
specification_version:
|
115
|
+
specification_version: 2
|
83
116
|
summary: ProxyMachine is a simple content aware (layer 7) TCP routing proxy.
|
84
117
|
test_files:
|
85
|
-
- test/configs/simple.rb
|
86
118
|
- test/proxymachine_test.rb
|
87
|
-
- test/test_helper.rb
|
88
|
-
- examples/git.rb
|
89
|
-
- examples/long.rb
|
90
|
-
- examples/transparent.rb
|
data/.document
DELETED
data/VERSION.yml
DELETED
data/lib/fizx_proxymachine.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + "/proxymachine"
|