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.
- 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
|