fizx-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 +23 -0
- data/examples/appserver.rb +12 -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/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 +36 -0
- data/lib/em-proxy/connection.rb +75 -0
- data/lib/em-proxy/proxy.rb +20 -0
- metadata +74 -0
data/README.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
= EM-Proxy
|
2
|
+
|
3
|
+
EventMachine Proxy DSL:
|
4
|
+
- Slides from RailsConf 2009: http://bit.ly/D7oWB
|
5
|
+
|
6
|
+
== Simple port forwarding proxy
|
7
|
+
|
8
|
+
Proxy.start(:host => "0.0.0.0", :port => 80) do |conn|
|
9
|
+
conn.server :srv, :host => "127.0.0.1", :port => 81
|
10
|
+
|
11
|
+
# modify / process request stream
|
12
|
+
conn.on_data do |data|
|
13
|
+
p [:on_data, data]
|
14
|
+
data
|
15
|
+
end
|
16
|
+
|
17
|
+
# modify / process response stream
|
18
|
+
conn.on_response do |backend, resp|
|
19
|
+
p [:on_response, backend, resp]
|
20
|
+
resp
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -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) 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')
|
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) 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,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,36 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module ProxyServer
|
3
|
+
class Backend < EventMachine::Connection
|
4
|
+
attr_accessor :plexer, :data, :name
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@connected = EM::DefaultDeferrable.new
|
8
|
+
@data = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def connection_completed
|
12
|
+
p [@name, :conn_complete]
|
13
|
+
@connected.succeed
|
14
|
+
end
|
15
|
+
|
16
|
+
def receive_data(data)
|
17
|
+
p [@name, data]
|
18
|
+
@data.push data
|
19
|
+
@plexer.relay_from_backend(@name, data)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Buffer data until the connection to the backend server
|
23
|
+
# is established and is ready for use
|
24
|
+
def send(data)
|
25
|
+
@connected.callback { send_data data }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Notify upstream plexer that the backend server is done
|
29
|
+
# processing the request
|
30
|
+
def unbind
|
31
|
+
p [@name, :unbind]
|
32
|
+
@plexer.unbind_backend(@name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module ProxyServer
|
3
|
+
class Connection < EventMachine::Connection
|
4
|
+
|
5
|
+
##### Proxy Methods
|
6
|
+
def on_data(&blk); @on_data = blk; end
|
7
|
+
def on_response(&blk); @on_response = blk; end
|
8
|
+
def on_finish(&blk); @on_finish = blk; end
|
9
|
+
|
10
|
+
##### EventMachine
|
11
|
+
def initialize
|
12
|
+
@servers = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_data(data)
|
16
|
+
p [:connection, data]
|
17
|
+
processed = @on_data.call(data)
|
18
|
+
|
19
|
+
if processed.is_a? Array
|
20
|
+
data, servers = *processed
|
21
|
+
|
22
|
+
# guard for "unbound" servers
|
23
|
+
servers = servers.collect {|s| @servers[s]}.compact
|
24
|
+
else
|
25
|
+
data = processed
|
26
|
+
servers ||= @servers.values.compact
|
27
|
+
end
|
28
|
+
|
29
|
+
servers.each do |s|
|
30
|
+
s.send_data data unless data.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# initialize connections to backend servers
|
36
|
+
#
|
37
|
+
def server(name, opts)
|
38
|
+
srv = EventMachine::connect(opts[:host], opts[:port], EventMachine::ProxyServer::Backend) do |c|
|
39
|
+
c.name = name
|
40
|
+
c.plexer = self
|
41
|
+
end
|
42
|
+
|
43
|
+
@servers[name] = srv
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# relay data from backend server to client
|
48
|
+
#
|
49
|
+
def relay_from_backend(name, data)
|
50
|
+
p [:relay_from_backend, name, data]
|
51
|
+
|
52
|
+
data = @on_response.call(name, data)
|
53
|
+
send_data data unless data.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
def unbind
|
57
|
+
# terminate any unfinished connections
|
58
|
+
@servers.values.compact.each do |s|
|
59
|
+
s.close_connection_after_writing
|
60
|
+
end
|
61
|
+
|
62
|
+
close_connection_after_writing
|
63
|
+
@on_finish.call(:done) if @servers.values.compact.size.zero? if @on_finish
|
64
|
+
end
|
65
|
+
|
66
|
+
def unbind_backend(name)
|
67
|
+
p [:unbind_backend, name]
|
68
|
+
@servers[name] = nil
|
69
|
+
@on_finish.call(name) if @on_finish
|
70
|
+
close_connection_after_writing if @servers.values.compact.size.zero?
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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], EventMachine::ProxyServer::Connection) do |c|
|
11
|
+
c.instance_eval(&blk)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.stop
|
17
|
+
puts "Terminating ProxyServer"
|
18
|
+
EventMachine.stop
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fizx-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-05-07 00:00:00 -07: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.2
|
24
|
+
version:
|
25
|
+
description: EventMachine Proxy DSL
|
26
|
+
email: ilya@igvita.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- examples/appserver.rb
|
36
|
+
- examples/beanstalkd_interceptor.rb
|
37
|
+
- examples/duplex.rb
|
38
|
+
- examples/line_interceptor.rb
|
39
|
+
- examples/port_forward.rb
|
40
|
+
- examples/smtp_spam_filter.rb
|
41
|
+
- examples/smtp_whitelist.rb
|
42
|
+
- lib/em-proxy.rb
|
43
|
+
- lib/em-proxy/backend.rb
|
44
|
+
- lib/em-proxy/connection.rb
|
45
|
+
- lib/em-proxy/proxy.rb
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/igrigorik/em-proxy
|
48
|
+
licenses:
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
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: em-proxy
|
69
|
+
rubygems_version: 1.3.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: EventMachine Proxy DSL
|
73
|
+
test_files: []
|
74
|
+
|