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
         |