em-proxy 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +50 -0
- data/VERSION +1 -0
- data/examples/appserver.rb +11 -0
- data/examples/beanstalkd_interceptor.rb +43 -0
- data/examples/duplex.rb +36 -0
- data/examples/line_interceptor.rb +22 -0
- data/examples/port_forward.rb +18 -0
- data/examples/selective_forward.rb +38 -0
- data/examples/smtp_spam_filter.rb +107 -0
- data/examples/smtp_whitelist.rb +39 -0
- data/lib/em-proxy.rb +8 -0
- data/lib/em-proxy/backend.rb +46 -0
- data/lib/em-proxy/connection.rb +90 -0
- data/lib/em-proxy/proxy.rb +21 -0
- data/spec/helper.rb +6 -0
- data/spec/proxy_spec.rb +111 -0
- metadata +88 -0
data/README.rdoc
ADDED
@@ -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,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
|
data/examples/duplex.rb
ADDED
@@ -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
|
+
#
|
data/lib/em-proxy.rb
ADDED
@@ -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
|
data/spec/helper.rb
ADDED
data/spec/proxy_spec.rb
ADDED
@@ -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
|