em-proxy 0.1.1

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,50 @@
1
+ = EM-Proxy
2
+
3
+ EventMachine Proxy DSL for writing high-performance transparent / intercepting proxies in Ruby.
4
+
5
+ - Slides from RailsConf 2009: http://bit.ly/D7oWB
6
+ - GoGaRuCo notes & Slides: http://www.igvita.com/2009/04/20/ruby-proxies-for-scale-and-monitoring/
7
+
8
+ == Getting started
9
+
10
+ # install & configure gemcutter repos
11
+ gem update --system
12
+ gem install gemcutter
13
+ gem tumble
14
+
15
+ gem install em-proxy
16
+
17
+ irb:0> require 'em-proxy'
18
+
19
+ == Simple port forwarding proxy
20
+
21
+ Proxy.start(:host => "0.0.0.0", :port => 80, :debug => true) do |conn|
22
+ conn.server :srv, :host => "127.0.0.1", :port => 81
23
+
24
+ # modify / process request stream
25
+ conn.on_data do |data|
26
+ p [:on_data, data]
27
+ data
28
+ end
29
+
30
+ # modify / process response stream
31
+ conn.on_response do |backend, resp|
32
+ p [:on_response, backend, resp]
33
+ resp
34
+ end
35
+
36
+ # termination logic
37
+ conn.on_finish do |backend|
38
+ p [:on_finish, name]
39
+
40
+ # terminate connection (in duplex mode, you can terminate when prod is done)
41
+ unbind if backend == :srv
42
+ end
43
+ end
44
+
45
+ For more examples see the /examples directory.
46
+ - SMTP Spam Filtering
47
+ - Duplicating traffic
48
+ - Selective forwarding
49
+ - Beanstalkd interceptor
50
+ - etc.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,11 @@
1
+ require "rubygems"
2
+ require "rack"
3
+
4
+ app = lambda {
5
+ p [:serving, ARGV[0]]
6
+
7
+ sleep(ARGV[1].to_i)
8
+ [200, {"Content-Type" => "text/plain"}, ["hello world: #{ARGV[1]}"]]
9
+ }
10
+
11
+ Rack::Handler::Mongrel.run(app, {:Host => "0.0.0.0", :Port => ARGV[0]})
@@ -0,0 +1,43 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 11300) do |conn|
4
+ conn.server :srv, :host => "127.0.0.1", :port => 11301
5
+
6
+ # put <pri> <delay> <ttr> <bytes>\r\n
7
+ PUT_CMD = /put (\d+) (\d+) (\d+) (\d+)\r\n/
8
+
9
+ conn.on_data do |data|
10
+ if put = data.match(PUT_CMD)
11
+
12
+ # archive any job > 10 minutes away
13
+ if put[2].to_i > 600
14
+ p [:put, :archive]
15
+ # INSERT INTO ....
16
+
17
+ conn.send_data "INSERTED 9999\r\n"
18
+ data = nil
19
+ end
20
+ end
21
+
22
+ data
23
+ end
24
+
25
+ conn.on_response do |backend, resp|
26
+ p [:resp, resp]
27
+ resp
28
+ end
29
+ end
30
+
31
+ #
32
+ # beanstalkd -p 11301 -d
33
+ # ruby examples/beanstalkd_interceptor.rb
34
+ #
35
+ # irb
36
+ # >> require 'beanstalk-client'
37
+ # >> beanstalk = Beanstalk::Pool.new(['127.0.0.1'])
38
+ # >> beanstalk.put("job1")
39
+ # => 1
40
+ # >> beanstalk.put("job2")
41
+ # => 2
42
+ # >> beanstalk.put("job3", 0, 1000)
43
+ # => 9999
@@ -0,0 +1,36 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 80, :debug => true) do |conn|
4
+ @start = Time.now
5
+ @data = Hash.new("")
6
+
7
+ conn.server :test, :host => "127.0.0.1", :port => 81 # production, will render resposne
8
+ conn.server :prod, :host => "127.0.0.1", :port => 82 # testing, internal only
9
+
10
+ conn.on_data do |data|
11
+ # rewrite User-Agent
12
+ data.gsub(/User-Agent: .*?\r\n/, "User-Agent: em-proxy/0.1\r\n")
13
+ end
14
+
15
+ conn.on_response do |server, resp|
16
+ # only render response from production
17
+ @data[server] += resp
18
+ resp if server == :prod
19
+ end
20
+
21
+ conn.on_finish do |name|
22
+ p [:on_finish, name, Time.now - @start]
23
+ p @data
24
+ end
25
+ end
26
+
27
+ #
28
+ # ruby examples/appserver.rb 81
29
+ # ruby examples/appserver.rb 82
30
+ # ruby examples/line_interceptor.rb
31
+ # curl localhost
32
+ #
33
+ # > [:on_finish, 1.008561]
34
+ # > {:prod=>"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 01 May 2009 04:20:00 GMT\r\nContent-Type: text/plain\r\n\r\nhello world: 0",
35
+ # :test=>"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 01 May 2009 04:20:00 GMT\r\nContent-Type: text/plain\r\n\r\nhello world: 1"}
36
+ #
@@ -0,0 +1,22 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 80) do |conn|
4
+ conn.server :srv, :host => "127.0.0.1", :port => 81
5
+
6
+ conn.on_data do |data|
7
+ data
8
+ end
9
+
10
+ conn.on_response do |backend, resp|
11
+ # substitute all mentions of hello to 'good bye', aka intercepting proxy
12
+ resp.gsub(/hello/, 'good bye')
13
+ end
14
+ end
15
+
16
+ #
17
+ # ruby examples/appserver.rb 81
18
+ # ruby examples/line_interceptor.rb
19
+ # curl localhost
20
+ #
21
+ # > good bye world: 0
22
+ #
@@ -0,0 +1,18 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 80, :debug => true) do |conn|
4
+ conn.server :srv, :host => "127.0.0.1", :port => 81
5
+
6
+ # modify / process request stream
7
+ conn.on_data do |data|
8
+ p [:on_data, data]
9
+ data
10
+ end
11
+
12
+ # modify / process response stream
13
+ conn.on_response do |backend, resp|
14
+ p [:on_response, backend, resp]
15
+ # resp = "HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Thu, 30 Apr 2009 03:53:28 GMT\r\nContent-Type: text/plain\r\n\r\nHar!"
16
+ resp
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 80) do |conn|
4
+ @start = Time.now
5
+ @data = Hash.new("")
6
+
7
+ conn.server :prod, :host => "127.0.0.1", :port => 81 # production, will render resposne
8
+ conn.server :test, :host => "127.0.0.1", :port => 82 # testing, internal only
9
+
10
+ conn.on_data do |data|
11
+ # rewrite User-Agent
12
+ [data.gsub(/User-Agent: .*?\r\n/, 'User-Agent: em-proxy/0.1\r\n'), [:prod]]
13
+ end
14
+
15
+ conn.on_response do |server, resp|
16
+ # only render response from production
17
+ @data[server] += resp
18
+ resp if server == :prod
19
+ end
20
+
21
+ conn.on_finish do |name|
22
+ p [:on_finish, name, Time.now - @start]
23
+ unbind if name == :prod # terminate connection once prod is done
24
+
25
+ p @data if name == :done
26
+ end
27
+ end
28
+
29
+ #
30
+ # ruby examples/appserver.rb 81
31
+ # ruby examples/appserver.rb 82
32
+ # ruby examples/line_interceptor.rb
33
+ # curl localhost
34
+ #
35
+ # > [:on_finish, 1.008561]
36
+ # > {:prod=>"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 01 May 2009 04:20:00 GMT\r\nContent-Type: text/plain\r\n\r\nhello world: 0",
37
+ # :test=>"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 01 May 2009 04:20:00 GMT\r\nContent-Type: text/plain\r\n\r\nhello world: 1"}
38
+ #
@@ -0,0 +1,107 @@
1
+ require 'lib/em-proxy'
2
+ require 'em-http'
3
+ require 'yaml'
4
+ require 'net/http'
5
+
6
+ Proxy.start(:host => "0.0.0.0", :port => 2524) do |conn|
7
+ conn.server :srv, :host => "127.0.0.1", :port => 2525
8
+
9
+ RCPT_CMD = /RCPT TO:<(.*)?>\r\n/ # RCPT TO:<name@address.com>\r\n
10
+ FROM_CMD = /MAIL FROM:<(.*)?>\r\n/ # MAIL FROM:<ilya@aiderss.com>\r\n
11
+ MSG_CMD = /354 Start your message/ # 354 Start your message
12
+ MSGEND_CMD = /^.\r\n/
13
+
14
+ conn.on_data do |data|
15
+ @from = data.match(FROM_CMD)[1] if data.match(FROM_CMD)
16
+ @rcpt = data.match(RCPT_CMD)[1] if data.match(RCPT_CMD)
17
+ @done = true if data.match(MSGEND_CMD)
18
+
19
+ if @buffer
20
+ @msg += data
21
+ data = nil
22
+ end
23
+
24
+ if @done
25
+ @buffer = false
26
+ res = Net::HTTP.post_form(URI.parse('http://api.defensio.com/app/1.2/audit-comment/77ca297d7546705ee2b5136fad0dcaf8.yaml'), {
27
+ "owner-url" => "http://www.github.com/igrigorik/em-http-request",
28
+ "user-ip" => "216.16.254.254",
29
+ "article-date" => "2009/04/01",
30
+ "comment-author" => @from,
31
+ "comment-type" => "comment",
32
+ "comment-content" => @msg})
33
+
34
+ defensio = YAML.load(res.body)['defensio-result']
35
+ p [:defensio, "SPAM: #{defensio['spam']}, Spaminess: #{defensio['spaminess']}"]
36
+
37
+ if defensio['spam']
38
+ conn.send_data "550 No such user here\n"
39
+ else
40
+ data = @msg
41
+ end
42
+ end
43
+
44
+ data
45
+ end
46
+
47
+ conn.on_response do |server, resp|
48
+ p [:resp, resp]
49
+
50
+ if resp.match(MSG_CMD)
51
+ @buffer = true
52
+ @msg = ""
53
+ end
54
+
55
+ resp
56
+ end
57
+ end
58
+
59
+ # mailtrap run -p 2525 -f /tmp/mailtrap.log
60
+ # ruby examples/smtp_spam_filter.rb
61
+ #
62
+ # >> require 'net/smtp'
63
+ # >> smtp = Net::SMTP.start("localhost", 2524)
64
+ # >> smtp.send_message "Hello World!", "ilya@aiderss.com", "ilya@igvita.com"
65
+
66
+
67
+ # Protocol trace
68
+ #
69
+ # [:srv, :conn_complete]
70
+ # [:srv, "220 localhost MailTrap ready ESTMP\n"]
71
+ # [:relay_from_backend, :srv, "220 localhost MailTrap ready ESTMP\n"]
72
+ # [:resp, "220 localhost MailTrap ready ESTMP\n"]
73
+ # [:connection, "EHLO localhost.localdomain\r\n"]
74
+ # [:srv, "250-localhost offers just ONE extension my pretty"]
75
+ # [:relay_from_backend, :srv, "250-localhost offers just ONE extension my pretty"]
76
+ # [:resp, "250-localhost offers just ONE extension my pretty"]
77
+ # [:srv, "\n250 HELP\n"]
78
+ # [:relay_from_backend, :srv, "\n250 HELP\n"]
79
+ # [:resp, "\n250 HELP\n"]
80
+ # [:connection, "MAIL FROM:<ilya@aiderss.com>\r\n"]
81
+ # [:srv, "250 OK\n"]
82
+ # [:relay_from_backend, :srv, "250 OK\n"]
83
+ # [:resp, "250 OK\n"]
84
+ # [:connection, "RCPT TO:<ilya@igvita.com>\r\n"]
85
+ # [:srv, "250 OK"]
86
+ # [:relay_from_backend, :srv, "250 OK"]
87
+ # [:resp, "250 OK"]
88
+ # [:srv, "\n"]
89
+ # [:relay_from_backend, :srv, "\n"]
90
+ # [:resp, "\n"]
91
+ # [:connection, "DATA\r\n"]
92
+ # [:srv, "354 Start your message"]
93
+ # [:relay_from_backend, :srv, "354 Start your message"]
94
+ # [:resp, "354 Start your message"]
95
+ # [:srv, "\n"]
96
+ # [:relay_from_backend, :srv, "\n"]
97
+ # [:resp, "\n"]
98
+ # [:connection, "Hello World\r\n"]
99
+ # [:connection, ".\r\n"]
100
+ #
101
+ # [:defensio, "SPAM: false, Spaminess: 0.4"]
102
+ #
103
+ # [:srv, "250 OK\n"]
104
+ # [:relay_from_backend, :srv, "250 OK\n"]
105
+ # [:resp, "250 OK\n"]
106
+ #
107
+
@@ -0,0 +1,39 @@
1
+ require 'lib/em-proxy'
2
+
3
+ Proxy.start(:host => "0.0.0.0", :port => 2524) do |conn|
4
+ conn.server :srv, :host => "127.0.0.1", :port => 2525
5
+
6
+ # RCPT TO:<name@address.com>\r\n
7
+ RCPT_CMD = /RCPT TO:<(.*)?>\r\n/
8
+
9
+ conn.on_data do |data|
10
+
11
+ if rcpt = data.match(RCPT_CMD)
12
+ if rcpt[1] != "ilya@igvita.com"
13
+ conn.send_data "550 No such user here\n"
14
+ data = nil
15
+ end
16
+ end
17
+
18
+ data
19
+ end
20
+
21
+ conn.on_response do |backend, resp|
22
+ resp
23
+ end
24
+ end
25
+
26
+
27
+ # mailtrap run -p 2525 -f /tmp/mailtrap.log
28
+ # ruby examples/smtp_whitelist.rb
29
+ #
30
+ # >> require 'net/smtp'
31
+ # >> smtp = Net::SMTP.start("localhost", 2524)
32
+ # >> smtp.send_message "Hello World!", "ilya@aiderss.com", "ilya@igvita.com"
33
+ # => #<Net::SMTP::Response:0xb7dcff5c @status="250", @string="250 OK\n">
34
+ # >> smtp.finish
35
+ # => #<Net::SMTP::Response:0xb7dcc8d4 @status="221", @string="221 Seeya\n">
36
+ #
37
+ # >> smtp.send_message "Hello World!", "ilya@aiderss.com", "missing_user@igvita.com"
38
+ # => Net::SMTPFatalError: 550 No such user here
39
+ #
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require "rubygems"
4
+ require "eventmachine"
5
+
6
+ %w[ backend proxy connection ].each do |file|
7
+ require "em-proxy/#{file}"
8
+ end
@@ -0,0 +1,46 @@
1
+ module EventMachine
2
+ module ProxyServer
3
+ class Backend < EventMachine::Connection
4
+ attr_accessor :plexer, :data, :name, :debug
5
+
6
+ def initialize(debug = false)
7
+ @debug = debug
8
+ @connected = EM::DefaultDeferrable.new
9
+ @data = []
10
+ end
11
+
12
+ def connection_completed
13
+ debug [@name, :conn_complete]
14
+ @connected.succeed
15
+ end
16
+
17
+ def receive_data(data)
18
+ debug [@name, data]
19
+ @data.push data
20
+ @plexer.relay_from_backend(@name, data)
21
+ end
22
+
23
+ # Buffer data until the connection to the backend server
24
+ # is established and is ready for use
25
+ def send(data)
26
+ @connected.callback { send_data data }
27
+ end
28
+
29
+ # Notify upstream plexer that the backend server is done
30
+ # processing the request
31
+ def unbind
32
+ debug [@name, :unbind]
33
+ @plexer.unbind_backend(@name)
34
+ end
35
+
36
+ private
37
+
38
+ def debug(*data)
39
+ return unless @debug
40
+ require 'pp'
41
+ pp data
42
+ puts
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,90 @@
1
+ module EventMachine
2
+ module ProxyServer
3
+ class Connection < EventMachine::Connection
4
+ attr_accessor :debug
5
+
6
+ ##### Proxy Methods
7
+ def on_data(&blk); @on_data = blk; end
8
+ def on_response(&blk); @on_response = blk; end
9
+ def on_finish(&blk); @on_finish = blk; end
10
+
11
+ ##### EventMachine
12
+ def initialize(options)
13
+ @debug = options[:debug] || false
14
+ @servers = {}
15
+ end
16
+
17
+ def receive_data(data)
18
+ debug [:connection, data]
19
+ processed = @on_data.call(data)
20
+
21
+ if processed.is_a? Array
22
+ data, servers = *processed
23
+
24
+ # guard for "unbound" servers
25
+ servers = servers.collect {|s| @servers[s]}.compact
26
+ else
27
+ data = processed
28
+ servers ||= @servers.values.compact
29
+ end
30
+
31
+ servers.each do |s|
32
+ s.send_data data unless data.nil?
33
+ end
34
+ end
35
+
36
+ #
37
+ # initialize connections to backend servers
38
+ #
39
+ def server(name, opts)
40
+ srv = EventMachine::connect(opts[:host], opts[:port], EventMachine::ProxyServer::Backend, @debug) do |c|
41
+ c.name = name
42
+ c.plexer = self
43
+ end
44
+
45
+ @servers[name] = srv
46
+ end
47
+
48
+ #
49
+ # relay data from backend server to client
50
+ #
51
+ def relay_from_backend(name, data)
52
+ debug [:relay_from_backend, name, data]
53
+
54
+ data = @on_response.call(name, data)
55
+ send_data data unless data.nil?
56
+ end
57
+
58
+ def unbind
59
+ debug [:unbind, :connection]
60
+
61
+ # terminate any unfinished connections
62
+ @servers.values.compact.each do |s|
63
+ s.close_connection
64
+ end
65
+ end
66
+
67
+ def unbind_backend(name)
68
+ debug [:unbind_backend, name]
69
+ @servers[name] = nil
70
+
71
+ # if all connections are terminated downstream, then notify client
72
+ close_connection if @servers.values.compact.size.zero?
73
+
74
+ if @on_finish
75
+ @on_finish.call(name)
76
+ @on_finish.call(:done) if @servers.values.compact.size.zero?
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def debug(*data)
83
+ return unless @debug
84
+ require 'pp'
85
+ pp data
86
+ puts
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ class Proxy
2
+
3
+ def self.start(options, &blk)
4
+ EM.epoll
5
+ EM.run do
6
+
7
+ trap("TERM") { stop }
8
+ trap("INT") { stop }
9
+
10
+ EventMachine::start_server(options[:host], options[:port],
11
+ EventMachine::ProxyServer::Connection, options) do |c|
12
+ c.instance_eval(&blk)
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.stop
18
+ puts "Terminating ProxyServer"
19
+ EventMachine.stop
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+ require 'em-http'
5
+
6
+ require 'lib/em-proxy'
@@ -0,0 +1,111 @@
1
+ require 'spec/helper'
2
+
3
+ describe Proxy do
4
+
5
+ def failed
6
+ EventMachine.stop
7
+ fail
8
+ end
9
+
10
+ it "should recieve data on port 8080" do
11
+ EM.run do
12
+ EventMachine.add_timer(2) do
13
+ EventMachine::HttpRequest.new('http://127.0.0.1:8080/test').get({:timeout => 1})
14
+ end
15
+
16
+ Proxy.start(:host => "0.0.0.0", :port => 8080) do |conn|
17
+ conn.on_data do |data|
18
+ data.should =~ /GET \/test/
19
+ EventMachine.stop
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ it "should transparently redirect TCP traffic to google" do
26
+ EM.run do
27
+ EventMachine.add_timer(2) do
28
+ EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({:timeout => 1})
29
+ end
30
+
31
+ Proxy.start(:host => "0.0.0.0", :port => 8080) do |conn|
32
+ conn.server :goog, :host => "google.com", :port => 80
33
+ conn.on_data { |data| data }
34
+
35
+ conn.on_response do |backend, resp|
36
+ backend.should == :goog
37
+ resp.should =~ /google/
38
+ EventMachine.stop
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ it "should duplex TCP traffic to two backends google & yahoo" do
45
+ EM.run do
46
+ EventMachine.add_timer(2) do
47
+ EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({:timeout => 1})
48
+ end
49
+
50
+ Proxy.start(:host => "0.0.0.0", :port => 8080) do |conn|
51
+ conn.server :goog, :host => "google.com", :port => 80
52
+ conn.server :yhoo, :host => "yahoo.com", :port => 80
53
+ conn.on_data { |data| data }
54
+
55
+ seen = []
56
+ conn.on_response do |backend, resp|
57
+ case backend
58
+ when :goog then
59
+ resp.should =~ /google/
60
+ seen.push backend
61
+ when :yhoo
62
+ resp.should =~ /yahoo|yimg/
63
+ seen.push backend
64
+ end
65
+ seen.uniq!
66
+
67
+ EventMachine.stop if seen.size == 2
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ it "should intercept & alter response from Google" do
74
+ EM.run do
75
+ EventMachine.add_timer(2) do
76
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({:timeout => 1})
77
+ http.errback { failed }
78
+ http.callback {
79
+ http.response_header.status.should == 404
80
+ EventMachine.stop
81
+ }
82
+ end
83
+
84
+ Proxy.start(:host => "0.0.0.0", :port => 8080) do |conn|
85
+ conn.server :goog, :host => "google.com", :port => 80
86
+ conn.on_data { |data| data }
87
+ conn.on_response do |backend, data|
88
+ data.gsub(/^HTTP\/1.1 200/, 'HTTP/1.1 404')
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ it "should invoke on_finish callback when connection is terminated" do
95
+ EM.run do
96
+ EventMachine.add_timer(2) do
97
+ EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({:timeout => 1})
98
+ end
99
+
100
+ Proxy.start(:host => "0.0.0.0", :port => 8080) do |conn|
101
+ conn.server :goog, :host => "google.com", :port => 80
102
+ conn.on_data { |data| data }
103
+ conn.on_response { |backend, resp| resp }
104
+ conn.on_finish do |backend|
105
+ backend.should == :goog
106
+ EventMachine.stop
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Grigorik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-25 00:00:00 -04:00
13
+ default_executable:
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.9
24
+ version:
25
+ description: EventMachine Proxy DSL
26
+ email: ilya@igvita.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - README.rdoc
35
+ - VERSION
36
+ - examples/appserver.rb
37
+ - examples/beanstalkd_interceptor.rb
38
+ - examples/duplex.rb
39
+ - examples/line_interceptor.rb
40
+ - examples/port_forward.rb
41
+ - examples/selective_forward.rb
42
+ - examples/smtp_spam_filter.rb
43
+ - examples/smtp_whitelist.rb
44
+ - lib/em-proxy.rb
45
+ - lib/em-proxy/backend.rb
46
+ - lib/em-proxy/connection.rb
47
+ - lib/em-proxy/proxy.rb
48
+ - spec/helper.rb
49
+ - spec/proxy_spec.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/igrigorik/em-proxy
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: em-proxy
74
+ rubygems_version: 1.3.5
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: EventMachine Proxy DSL
78
+ test_files:
79
+ - spec/helper.rb
80
+ - spec/proxy_spec.rb
81
+ - examples/appserver.rb
82
+ - examples/beanstalkd_interceptor.rb
83
+ - examples/duplex.rb
84
+ - examples/line_interceptor.rb
85
+ - examples/port_forward.rb
86
+ - examples/selective_forward.rb
87
+ - examples/smtp_spam_filter.rb
88
+ - examples/smtp_whitelist.rb