mojombo-proxymachine 0.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/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.
data/README.md ADDED
@@ -0,0 +1,101 @@
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 with 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
+ GitRouter.lookup(name)
71
+ else
72
+ :noop
73
+ end
74
+ end
75
+
76
+
77
+ Contribute
78
+ ----------
79
+
80
+ If you'd like to hack on ProxyMachine, start by forking my repo on GitHub:
81
+
82
+ http://github.com/mojombo/proxymachine
83
+
84
+ To get all of the dependencies, install the gem first. The best way to get
85
+ your changes merged back into core is as follows:
86
+
87
+ 1. Clone down your fork
88
+ 1. Create a topic branch to contain your change
89
+ 1. Hack away
90
+ 1. Add tests and make sure everything still passes by running `rake`
91
+ 1. If you are adding new functionality, document it in the README.md
92
+ 1. Do not change the version number, I will do that on my end
93
+ 1. If necessary, rebase your commits into logical chunks, without errors
94
+ 1. Push the branch up to GitHub
95
+ 1. Send me (mojombo) a pull request for your branch
96
+
97
+
98
+ Copyright
99
+ ---------
100
+
101
+ Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
data/Rakefile ADDED
@@ -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{TODO}
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
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
data/bin/proxymachine ADDED
@@ -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[:host] = 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
data/examples/git.rb ADDED
@@ -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
+ GitRouter.lookup(name)
23
+ else
24
+ :noop
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ proxy do |data|
2
+ p data
3
+ "localhost:80"
4
+ end
@@ -0,0 +1,46 @@
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.kqueue
34
+ EM.epoll
35
+
36
+ EM.run do
37
+ EventMachine::Protocols::ClientConnection.start(host, port)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Kernel
43
+ def proxy(&block)
44
+ ProxyMachine.set_router(block)
45
+ end
46
+ end
@@ -0,0 +1,81 @@
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
+ else @server_side
20
+ # p data
21
+ @server_side.send_data(data)
22
+ end
23
+ rescue => e
24
+ close_connection
25
+ puts "#{e.class} - #{e.message}"
26
+ end
27
+
28
+ def ensure_server_side_connection
29
+ @timer.cancel if @timer
30
+ unless @server_side
31
+ op = ProxyMachine.router.call(@buffer.join)
32
+ if op.instance_of?(String)
33
+ m, host, port = *op.match(/^(.+):(.+)$/)
34
+ if try_server_connect(host, port.to_i)
35
+ send_and_clear_buffer
36
+ end
37
+ elsif op == :noop
38
+ # do nothing
39
+ else
40
+ close_connection
41
+ end
42
+ end
43
+ end
44
+
45
+ def try_server_connect(host, port)
46
+ @server_side = ServerConnection.request(host, port, self)
47
+ if @tries > 0
48
+ puts "Successful connection."
49
+ end
50
+ true
51
+ rescue => e
52
+ if @tries < 10
53
+ @tries += 1
54
+ puts "Failed on server connect attempt #{@tries}. Trying again..."
55
+ @timer.cancel if @timer
56
+ @timer = EventMachine::Timer.new(0.1) do
57
+ self.ensure_server_side_connection
58
+ end
59
+ else
60
+ puts "Failed after ten connection attempts."
61
+ end
62
+ false
63
+ end
64
+
65
+ def send_and_clear_buffer
66
+ if !@buffer.empty?
67
+ @buffer.each do |x|
68
+ # p x
69
+ @server_side.send_data(x)
70
+ end
71
+ @buffer = []
72
+ end
73
+ end
74
+
75
+ def unbind
76
+ @server_side.close_connection_after_writing if @server_side
77
+ ProxyMachine.decr
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,22 @@
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 receive_data(data)
13
+ # p data
14
+ @client_side.send_data(data)
15
+ end
16
+
17
+ def unbind
18
+ @client_side.close_connection_after_writing
19
+ end
20
+ end
21
+ end
22
+ 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,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mojombo-proxymachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Preston-Werner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-27 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
+ - LICENSE
36
+ - README.md
37
+ - Rakefile
38
+ - VERSION.yml
39
+ - bin/proxymachine
40
+ - examples/git.rb
41
+ - examples/transparent.rb
42
+ - lib/proxymachine.rb
43
+ - lib/proxymachine/client_connection.rb
44
+ - lib/proxymachine/server_connection.rb
45
+ - test/proxymachine_test.rb
46
+ - test/test_helper.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/mojombo/proxymachine
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.2.0
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: TODO
73
+ test_files:
74
+ - test/proxymachine_test.rb
75
+ - test/test_helper.rb
76
+ - examples/git.rb
77
+ - examples/transparent.rb