proxymachine 0.2.3

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tom Preston-Werner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,111 @@
1
+ ProxyMachine
2
+ ============
3
+
4
+ By Tom Preston-Werner (tom@mojombo.com)
5
+
6
+ WARNING: This software is alpha and should not be used in production without
7
+ extensive testing. You should not consider this project production ready until
8
+ it is released as 1.0.
9
+
10
+
11
+ Description
12
+ -----------
13
+
14
+ ProxyMachine is a simple content aware (layer 7) TCP routing proxy built on
15
+ EventMachine that lets you configure the routing logic in Ruby.
16
+
17
+ If you need to proxy connections to different backend servers depending on the
18
+ contents of the transmission, then ProxyMachine will make your life easy!
19
+
20
+ The idea here is simple. For each client connection, start receiving data
21
+ chunks and placing them into a buffer. Each time a new chunk arrives, send the
22
+ buffer to a user specified block. The block's job is to parse the buffer to
23
+ determine where the connection should proxied. If the buffer contains enough
24
+ data to make a determination, the block returns the address and port of the
25
+ correct backend server. If not, it can choose to either do nothing and wait
26
+ for more data to arrive, or close the connection. Once the block returns an
27
+ address, a connection to the backend is made, the buffer is replayed to the
28
+ backend, and the client and backend connections are hooked up to form a
29
+ straight proxy. This bidirectional proxy continues to exist until either the
30
+ client or backend close the connection.
31
+
32
+
33
+ Installation
34
+ ------------
35
+
36
+ gem install mojombo-proxymachine -s http://gems.github.com
37
+
38
+
39
+ Running
40
+ -------
41
+
42
+ Usage:
43
+ proxymachine -c <config file> [-h <host>] [-p <port>]
44
+
45
+ Options:
46
+ -c, --config CONFIG Configuration file
47
+ -h, --host HOST Hostname to bind. Default 0.0.0.0
48
+ -p, --port PORT Port to listen on. Default 5432
49
+
50
+
51
+ Example routing config file
52
+ ---------------------------
53
+
54
+ class GitRouter
55
+ # Look at the routing table and return the correct address for +name+
56
+ # Returns "<host>:<port>" e.g. "ae8f31c.example.com:9418"
57
+ def self.lookup(name)
58
+ ...
59
+ end
60
+ end
61
+
62
+ # Perform content-aware routing based on the stream data. Here, the
63
+ # header information from the Git protocol is parsed to find the
64
+ # username and a lookup routine is run on the name to find the correct
65
+ # backend server. If no match can be made yet, do nothing with the
66
+ # connection yet.
67
+ proxy do |data|
68
+ if data =~ %r{^....git-upload-pack /([\w\.\-]+)/[\w\.\-]+\000host=\w+\000}
69
+ name = $1
70
+ { :remote => GitRouter.lookup(name) }
71
+ else
72
+ { :noop => true }
73
+ end
74
+ end
75
+
76
+
77
+ Valid return values
78
+ -------------------
79
+
80
+ `{ :remote => String }` - String is the host:port of the backend server that will be proxied.
81
+ `{ :remote => String, :data => String }` - Same as above, but send the given data instead.
82
+ `{ :noop => true }` - Do nothing.
83
+ `{ :close` => true } - Close the connection.
84
+ `{ :close => String }` - Close the connection after sending the String.
85
+
86
+
87
+ Contribute
88
+ ----------
89
+
90
+ If you'd like to hack on ProxyMachine, start by forking my repo on GitHub:
91
+
92
+ http://github.com/mojombo/proxymachine
93
+
94
+ To get all of the dependencies, install the gem first. The best way to get
95
+ your changes merged back into core is as follows:
96
+
97
+ 1. Clone down your fork
98
+ 1. Create a topic branch to contain your change
99
+ 1. Hack away
100
+ 1. Add tests and make sure everything still passes by running `rake`
101
+ 1. If you are adding new functionality, document it in the README.md
102
+ 1. Do not change the version number, I will do that on my end
103
+ 1. If necessary, rebase your commits into logical chunks, without errors
104
+ 1. Push the branch up to GitHub
105
+ 1. Send me (mojombo) a pull request for your branch
106
+
107
+
108
+ Copyright
109
+ ---------
110
+
111
+ Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "proxymachine"
8
+ gem.summary = %Q{ProxyMachine is a simple content aware (layer 7) TCP routing proxy.}
9
+ gem.email = "tom@mojombo.com"
10
+ gem.homepage = "http://github.com/mojombo/proxymachine"
11
+ gem.authors = ["Tom Preston-Werner"]
12
+ gem.add_dependency('eventmachine', '>= 0.12.6')
13
+
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION.yml')
46
+ config = YAML.load(File.read('VERSION.yml'))
47
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "proxymachine #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
57
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 3
4
+ :major: 0
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'optparse'
6
+ require 'proxymachine'
7
+
8
+ begin
9
+ options = {:host => "0.0.0.0", :port => 5432}
10
+
11
+ opts = OptionParser.new do |opts|
12
+ opts.banner = <<-EOF
13
+ Usage:
14
+ proxymachine -c <config file> [-h <host>] [-p <port>]
15
+
16
+ Options:
17
+ EOF
18
+
19
+ opts.on("-cCONFIG", "--config CONFIG", "Configuration file") do |x|
20
+ options[:config] = x
21
+ end
22
+
23
+ opts.on("-hHOST", "--host HOST", "Hostname to bind. Default 0.0.0.0") do |x|
24
+ options[:host] = x
25
+ end
26
+
27
+ opts.on("-pPORT", "--port PORT", "Port to listen on. Default 5432") do |x|
28
+ options[:port] = x
29
+ end
30
+ end
31
+
32
+ opts.parse!
33
+
34
+ load(options[:config])
35
+ ProxyMachine.run(options[:host], options[:port])
36
+ rescue Exception => e
37
+ if e.instance_of?(SystemExit)
38
+ raise
39
+ else
40
+ puts 'Uncaught exception'
41
+ puts e.message
42
+ puts e.backtrace.join("\n")
43
+ end
44
+ end
@@ -0,0 +1,26 @@
1
+ # This is a config file for ProxyMachine. It pulls the username out of
2
+ # the Git stream and can proxy to different locations based on that value
3
+ # Run with `proxymachine -c examples/git.rb`
4
+
5
+ class GitRouter
6
+ # Look at the routing table and return the correct address for +name+
7
+ # Returns "<host>:<port>" e.g. "ae8f31c.example.com:9418"
8
+ def self.lookup(name)
9
+ puts "Proxying for user #{name}"
10
+ "localhost:9418"
11
+ end
12
+ end
13
+
14
+ # Perform content-aware routing based on the stream data. Here, the
15
+ # header information from the Git protocol is parsed to find the
16
+ # username and a lookup routine is run on the name to find the correct
17
+ # backend server. If no match can be made yet, do nothing with the
18
+ # connection yet.
19
+ proxy do |data|
20
+ if data =~ %r{^....git-upload-pack /([\w\.\-]+)/[\w\.\-]+\000host=(.+)\000}
21
+ name, host = $1, $2
22
+ { :remote => GitRouter.lookup(name) }
23
+ else
24
+ { :noop => true }
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ proxy do |data|
2
+ # p data
3
+ { :remote => "google.com:80" }
4
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ require 'proxymachine/client_connection'
5
+ require 'proxymachine/server_connection'
6
+
7
+ class ProxyMachine
8
+ def self.log(str)
9
+ puts str if false
10
+ end
11
+
12
+ def self.incr
13
+ @@counter ||= 0
14
+ @@counter += 1
15
+ log @@counter
16
+ end
17
+
18
+ def self.decr
19
+ @@counter ||= 0
20
+ @@counter -= 1
21
+ log @@counter
22
+ end
23
+
24
+ def self.set_router(block)
25
+ @@router = block
26
+ end
27
+
28
+ def self.router
29
+ @@router
30
+ end
31
+
32
+ def self.run(host, port)
33
+ EM.epoll
34
+
35
+ EM.run do
36
+ EventMachine::Protocols::ClientConnection.start(host, port)
37
+ end
38
+ end
39
+ end
40
+
41
+ module Kernel
42
+ def proxy(&block)
43
+ ProxyMachine.set_router(block)
44
+ end
45
+ end
@@ -0,0 +1,94 @@
1
+ module EventMachine
2
+ module Protocols
3
+ class ClientConnection < Connection
4
+ def self.start(host, port)
5
+ EM.start_server(host, port, self)
6
+ puts "Listening on #{host}:#{port}"
7
+ end
8
+
9
+ def post_init
10
+ @buffer = []
11
+ @tries = 0
12
+ ProxyMachine.incr
13
+ end
14
+
15
+ def receive_data(data)
16
+ if !@server_side
17
+ @buffer << data
18
+ ensure_server_side_connection
19
+ end
20
+ rescue => e
21
+ close_connection
22
+ puts "#{e.class} - #{e.message}"
23
+ end
24
+
25
+ def ensure_server_side_connection
26
+ @timer.cancel if @timer
27
+ unless @server_side
28
+ commands = ProxyMachine.router.call(@buffer.join)
29
+ close_connection unless commands.instance_of?(Hash)
30
+ if remote = commands[:remote]
31
+ m, host, port = *remote.match(/^(.+):(.+)$/)
32
+ if try_server_connect(host, port.to_i)
33
+ if data = commands[:data]
34
+ @buffer = [data]
35
+ end
36
+ send_and_clear_buffer
37
+ end
38
+ elsif close = commands[:close]
39
+ if close == true
40
+ close_connection
41
+ else
42
+ send_data(close)
43
+ close_connection_after_writing
44
+ end
45
+ elsif commands[:noop]
46
+ # do nothing
47
+ else
48
+ close_connection
49
+ end
50
+ end
51
+ end
52
+
53
+ def try_server_connect(host, port)
54
+ @server_side = ServerConnection.request(host, port, self)
55
+ proxy_incoming_to @server_side
56
+ if @tries > 0
57
+ puts "Successful connection."
58
+ end
59
+ true
60
+ rescue => e
61
+ if @tries < 10
62
+ @tries += 1
63
+ puts "Failed on server connect attempt #{@tries}. Trying again..."
64
+ @timer.cancel if @timer
65
+ @timer = EventMachine::Timer.new(0.1) do
66
+ self.ensure_server_side_connection
67
+ end
68
+ else
69
+ puts "Failed after ten connection attempts."
70
+ end
71
+ false
72
+ end
73
+
74
+ def send_and_clear_buffer
75
+ if !@buffer.empty?
76
+ @buffer.each do |x|
77
+ @server_side.send_data(x)
78
+ end
79
+ @buffer = []
80
+ end
81
+ end
82
+
83
+ def unbind
84
+ @server_side.close_connection_after_writing if @server_side
85
+ ProxyMachine.decr
86
+ end
87
+
88
+ # Proxy connection has been lost
89
+ def proxy_target_unbound
90
+ @server_side = nil
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,21 @@
1
+ module EventMachine
2
+ module Protocols
3
+ class ServerConnection < Connection
4
+ def self.request(host, port, client_side)
5
+ EventMachine.connect(host, port, self, client_side)
6
+ end
7
+
8
+ def initialize(conn)
9
+ @client_side = conn
10
+ end
11
+
12
+ def post_init
13
+ proxy_incoming_to @client_side
14
+ end
15
+
16
+ def unbind
17
+ @client_side.close_connection_after_writing
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,58 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{proxymachine}
5
+ s.version = "0.2.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Tom Preston-Werner"]
9
+ s.date = %q{2009-08-18}
10
+ s.default_executable = %q{proxymachine}
11
+ s.email = %q{tom@mojombo.com}
12
+ s.executables = ["proxymachine"]
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README.md"
16
+ ]
17
+ s.files = [
18
+ ".document",
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "README.md",
22
+ "Rakefile",
23
+ "VERSION.yml",
24
+ "bin/proxymachine",
25
+ "examples/git.rb",
26
+ "examples/transparent.rb",
27
+ "lib/proxymachine.rb",
28
+ "lib/proxymachine/client_connection.rb",
29
+ "lib/proxymachine/server_connection.rb",
30
+ "proxymachine.gemspec",
31
+ "test/proxymachine_test.rb",
32
+ "test/test_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/mojombo/proxymachine}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{ProxyMachine is a simple content aware (layer 7) TCP routing proxy.}
39
+ s.test_files = [
40
+ "test/proxymachine_test.rb",
41
+ "test/test_helper.rb",
42
+ "examples/git.rb",
43
+ "examples/transparent.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.6"])
52
+ else
53
+ s.add_dependency(%q<eventmachine>, [">= 0.12.6"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<eventmachine>, [">= 0.12.6"])
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class ProxymachineTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'proxymachine'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proxymachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Tom Preston-Werner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-18 00:00:00 -07:00
13
+ default_executable: proxymachine
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.12.6
24
+ version:
25
+ description:
26
+ email: tom@mojombo.com
27
+ executables:
28
+ - proxymachine
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.md
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.md
39
+ - Rakefile
40
+ - VERSION.yml
41
+ - bin/proxymachine
42
+ - examples/git.rb
43
+ - examples/transparent.rb
44
+ - lib/proxymachine.rb
45
+ - lib/proxymachine/client_connection.rb
46
+ - lib/proxymachine/server_connection.rb
47
+ - proxymachine.gemspec
48
+ - test/proxymachine_test.rb
49
+ - test/test_helper.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/mojombo/proxymachine
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --charset=UTF-8
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: ProxyMachine is a simple content aware (layer 7) TCP routing proxy.
78
+ test_files:
79
+ - test/proxymachine_test.rb
80
+ - test/test_helper.rb
81
+ - examples/git.rb
82
+ - examples/transparent.rb