fizx-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 +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
|
+
|