em-proxy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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