yup 0.1.3 → 0.2.0
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/.travis.yml +2 -0
- data/Gemfile +8 -7
- data/Gemfile.lock +16 -12
- data/README.rdoc +11 -8
- data/VERSION +1 -1
- data/lib/yup.rb +52 -7
- data/lib/yup/request_forwarder.rb +0 -128
- data/lib/yup/state.rb +100 -49
- data/lib/yup/state/bdb.rb +99 -0
- data/lib/yup/state/redis.rb +52 -0
- data/test/helper.rb +62 -1
- data/test/test_stateful_yup_with_bdb.rb +47 -0
- data/test/test_stateful_yup_with_redis.rb +41 -0
- data/yup.gemspec +21 -15
- metadata +39 -20
- data/test/test_persistence_yup.rb +0 -105
    
        data/.travis.yml
    CHANGED
    
    
    
        data/Gemfile
    CHANGED
    
    | @@ -8,11 +8,12 @@ gem "yajl-ruby" | |
| 8 8 |  | 
| 9 9 | 
             
            group :development do
         | 
| 10 10 | 
             
              gem "bdb"
         | 
| 11 | 
            -
              gem " | 
| 12 | 
            -
              gem " | 
| 13 | 
            -
              gem " | 
| 14 | 
            -
              gem " | 
| 15 | 
            -
              gem " | 
| 16 | 
            -
              gem "simplecov | 
| 17 | 
            -
              gem " | 
| 11 | 
            +
              gem "redis-namespace"
         | 
| 12 | 
            +
              gem "yard",           "~> 0.8.0"
         | 
| 13 | 
            +
              gem "minitest",       "~> 4.5"
         | 
| 14 | 
            +
              gem "bundler",        "~> 1.2"
         | 
| 15 | 
            +
              gem "jeweler",        "~> 1.8.4"
         | 
| 16 | 
            +
              gem "simplecov",      "~> 0.7.1"
         | 
| 17 | 
            +
              gem "simplecov-rcov", "~> 0.2.3"
         | 
| 18 | 
            +
              gem "travis-lint",    "~> 1.4"
         | 
| 18 19 | 
             
            end
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -22,23 +22,26 @@ GEM | |
| 22 22 | 
             
                  git (>= 1.2.5)
         | 
| 23 23 | 
             
                  rake
         | 
| 24 24 | 
             
                  rdoc
         | 
| 25 | 
            -
                json (1.7. | 
| 26 | 
            -
                minitest (4. | 
| 27 | 
            -
                multi_json (1. | 
| 28 | 
            -
                rake (0. | 
| 29 | 
            -
                rdoc (3.12)
         | 
| 25 | 
            +
                json (1.7.6)
         | 
| 26 | 
            +
                minitest (4.5.0)
         | 
| 27 | 
            +
                multi_json (1.5.0)
         | 
| 28 | 
            +
                rake (10.0.3)
         | 
| 29 | 
            +
                rdoc (3.12.1)
         | 
| 30 30 | 
             
                  json (~> 1.4)
         | 
| 31 | 
            +
                redis (3.0.2)
         | 
| 32 | 
            +
                redis-namespace (1.2.1)
         | 
| 33 | 
            +
                  redis (~> 3.0.0)
         | 
| 31 34 | 
             
                simplecov (0.7.1)
         | 
| 32 35 | 
             
                  multi_json (~> 1.0)
         | 
| 33 36 | 
             
                  simplecov-html (~> 0.7.1)
         | 
| 34 37 | 
             
                simplecov-html (0.7.1)
         | 
| 35 38 | 
             
                simplecov-rcov (0.2.3)
         | 
| 36 39 | 
             
                  simplecov (>= 0.4.1)
         | 
| 37 | 
            -
                travis-lint (1. | 
| 38 | 
            -
                  hashr ( | 
| 40 | 
            +
                travis-lint (1.6.0)
         | 
| 41 | 
            +
                  hashr (~> 0.0.22)
         | 
| 39 42 | 
             
                tuple (0.1.2)
         | 
| 40 43 | 
             
                yajl-ruby (1.1.0)
         | 
| 41 | 
            -
                yard (0.8. | 
| 44 | 
            +
                yard (0.8.4.1)
         | 
| 42 45 |  | 
| 43 46 | 
             
            PLATFORMS
         | 
| 44 47 | 
             
              ruby
         | 
| @@ -50,10 +53,11 @@ DEPENDENCIES | |
| 50 53 | 
             
              eventmachine
         | 
| 51 54 | 
             
              http_parser.rb
         | 
| 52 55 | 
             
              jeweler (~> 1.8.4)
         | 
| 53 | 
            -
              minitest
         | 
| 54 | 
            -
               | 
| 55 | 
            -
              simplecov | 
| 56 | 
            -
               | 
| 56 | 
            +
              minitest (~> 4.5)
         | 
| 57 | 
            +
              redis-namespace
         | 
| 58 | 
            +
              simplecov (~> 0.7.1)
         | 
| 59 | 
            +
              simplecov-rcov (~> 0.2.3)
         | 
| 60 | 
            +
              travis-lint (~> 1.4)
         | 
| 57 61 | 
             
              tuple
         | 
| 58 62 | 
             
              yajl-ruby
         | 
| 59 63 | 
             
              yard (~> 0.8.0)
         | 
    
        data/README.rdoc
    CHANGED
    
    | @@ -9,18 +9,21 @@ This is the small daemon to forward HTTP requests when response is known or unim | |
| 9 9 | 
             
            When a http request is arrived the yup daemon (yupd), it answers 200 OK (customizable). Then the yupd forwards the http request to the specified host and retries if a timeout error was happend.
         | 
| 10 10 |  | 
| 11 11 | 
             
            == Non-persistent queue
         | 
| 12 | 
            -
            By default,  | 
| 12 | 
            +
            By default, no persistence is used and forwarded requests is not serialized. A limit (the option --watermark) at which new concurrent requests will be dropped.
         | 
| 13 13 |  | 
| 14 14 | 
             
            == Persistent queue
         | 
| 15 | 
            -
            If you want use persistent queue you need to specify the option --persistent with  | 
| 15 | 
            +
            If you want use persistent queue you need to specify the option --persistent with uri.
         | 
| 16 16 |  | 
| 17 17 | 
             
            == One of use cases
         | 
| 18 18 |  | 
| 19 19 | 
             
            For example we can have a rails app which send exceptions to an Errbit by the gem airbrake. We know the errbit can be not available by network issues or some else reasons, but we do not want to lose exceptions. To resolve this problem we can start yupd on the same host with the rails app:
         | 
| 20 | 
            -
                yupd --listen localhost:8081 --status-code 201 --persistent  | 
| 20 | 
            +
                yupd --listen localhost:8081 --status-code 201 --persistent bdb:///var/db/yupd-errbit errbit.host.somewhere
         | 
| 21 21 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
                 | 
| 22 | 
            +
            Or if you have Redis:
         | 
| 23 | 
            +
                yupd --listen localhost:8081 --status-code 201 --persistent redis://localhost/yupd-errbit errbit.host.somewhere
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            Reconfiguration of airbrake gem is very ease:
         | 
| 26 | 
            +
                Airbrake.configure do |config|
         | 
| 24 27 | 
             
                  config.host    = "localhost"  # yupd host
         | 
| 25 28 | 
             
                  config.port    = 8081         # yupd port
         | 
| 26 29 | 
             
                  config.api_key = "api_key_for_your_app"
         | 
| @@ -32,8 +35,8 @@ Now problem of availability errbit is assigned to the yupd. | |
| 32 35 |  | 
| 33 36 | 
             
            Feel free to contribute.
         | 
| 34 37 |  | 
| 35 | 
            -
            ==  | 
| 38 | 
            +
            == Credits
         | 
| 36 39 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
            further details.
         | 
| 40 | 
            +
            Yup is maintained and funded by {Denis Sukhonin}[mailto:d.sukhonin@gmail.com].
         | 
| 39 41 |  | 
| 42 | 
            +
            Thank you to all {the contributors}[https://github.com/neglectedvalue/yup/contributors]!
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.2.0
         | 
    
        data/lib/yup.rb
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            require 'uri'
         | 
| 1 2 | 
             
            require 'rubygems'
         | 
| 2 3 | 
             
            require 'eventmachine'
         | 
| 3 4 | 
             
            require 'logger'
         | 
| @@ -7,6 +8,7 @@ require 'tmpdir' | |
| 7 8 | 
             
            require 'yup/version'
         | 
| 8 9 | 
             
            require 'yup/request_forwarder'
         | 
| 9 10 | 
             
            require 'yup/request_handler'
         | 
| 11 | 
            +
            require 'yup/state'
         | 
| 10 12 |  | 
| 11 13 | 
             
            module Yup
         | 
| 12 14 | 
             
              @@resend_delay = 60.0
         | 
| @@ -26,8 +28,8 @@ module Yup | |
| 26 28 | 
             
              def self.retry_unless_2xx=(bool); @@retry_unless_2xx = bool end
         | 
| 27 29 |  | 
| 28 30 | 
             
              def self.run(config)
         | 
| 29 | 
            -
                host | 
| 30 | 
            -
                port | 
| 31 | 
            +
                host        = config[:listen_host] || 'localhost'
         | 
| 32 | 
            +
                port        = config[:listen_port] || 8080
         | 
| 31 33 | 
             
                status_code = config[:status_code] || 200
         | 
| 32 34 | 
             
                forward_to  = config[:forward_to]
         | 
| 33 35 | 
             
                timeout     = config[:timeout] || 60
         | 
| @@ -39,7 +41,18 @@ module Yup | |
| 39 41 | 
             
              end
         | 
| 40 42 |  | 
| 41 43 | 
             
              def self.run_with_state(config)
         | 
| 42 | 
            -
                 | 
| 44 | 
            +
                case State.state_type(config[:persistent])
         | 
| 45 | 
            +
                when :bdb
         | 
| 46 | 
            +
                  self.run_with_bdb(config)
         | 
| 47 | 
            +
                when :redis
         | 
| 48 | 
            +
                  self.run_with_redis(config)
         | 
| 49 | 
            +
                else
         | 
| 50 | 
            +
                  abort "Unknown scheme of persistent queue."
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              def self.run_with_bdb(config)
         | 
| 55 | 
            +
                require 'yup/state/bdb'
         | 
| 43 56 |  | 
| 44 57 | 
             
                host        = config[:listen_host] || 'localhost'
         | 
| 45 58 | 
             
                port        = config[:listen_port] || 8080
         | 
| @@ -48,17 +61,17 @@ module Yup | |
| 48 61 | 
             
                dbpath      = config[:persistent]
         | 
| 49 62 | 
             
                timeout     = config[:timeout] || 60
         | 
| 50 63 | 
             
                feedback_channel = File.join(Dir.tmpdir, "yupd-#{$$}-feedback")
         | 
| 51 | 
            -
                state            =  | 
| 64 | 
            +
                state            = State::BDB.new(dbpath, forward_to, feedback_channel)
         | 
| 52 65 |  | 
| 53 66 | 
             
                pid = Process.fork do
         | 
| 54 | 
            -
                  State::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 67 | 
            +
                  State::BDB::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 55 68 | 
             
                end
         | 
| 56 69 |  | 
| 57 70 | 
             
                if pid
         | 
| 58 71 | 
             
                  db_closer = proc do
         | 
| 59 72 | 
             
                    Yup.logger.info { "Terminating consumer #{$$}" }
         | 
| 60 73 | 
             
                    Process.kill("KILL", pid)
         | 
| 61 | 
            -
                    state. | 
| 74 | 
            +
                    state.dispose()
         | 
| 62 75 | 
             
                    exit 0
         | 
| 63 76 | 
             
                  end
         | 
| 64 77 | 
             
                  Signal.trap("TERM", &db_closer)
         | 
| @@ -66,11 +79,43 @@ module Yup | |
| 66 79 | 
             
                end
         | 
| 67 80 |  | 
| 68 81 | 
             
                EM.run do
         | 
| 69 | 
            -
                  EM.start_unix_domain_server(feedback_channel, State::FeedbackHandler, state)
         | 
| 82 | 
            +
                  EM.start_unix_domain_server(feedback_channel, State::BDB::FeedbackHandler, state)
         | 
| 70 83 | 
             
                  logger.info { "Feedback through #{feedback_channel}" }
         | 
| 71 84 |  | 
| 72 85 | 
             
                  EM.start_server(host, port, RequestHandler, forward_to, status_code, state, timeout)
         | 
| 73 86 | 
             
                  logger.info { "Listening on #{host}:#{port}" }
         | 
| 74 87 | 
             
                end
         | 
| 75 88 | 
             
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def self.run_with_redis(config)
         | 
| 91 | 
            +
                require 'yup/state/redis'
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                host        = config[:listen_host] || 'localhost'
         | 
| 94 | 
            +
                port        = config[:listen_port] || 8080
         | 
| 95 | 
            +
                status_code = config[:status_code] || 200
         | 
| 96 | 
            +
                forward_to  = config[:forward_to]
         | 
| 97 | 
            +
                dbpath      = config[:persistent]
         | 
| 98 | 
            +
                timeout     = config[:timeout] || 60
         | 
| 99 | 
            +
                state       = State::Redis.new(dbpath, forward_to)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                pid = Process.fork do
         | 
| 102 | 
            +
                  State::Redis::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                if pid
         | 
| 106 | 
            +
                  db_closer = proc do
         | 
| 107 | 
            +
                    Yup.logger.info { "Terminating consumer #{$$}" }
         | 
| 108 | 
            +
                    Process.kill("KILL", pid)
         | 
| 109 | 
            +
                    state.dispose()
         | 
| 110 | 
            +
                    exit 0
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                  Signal.trap("TERM", &db_closer)
         | 
| 113 | 
            +
                  Signal.trap("INT", &db_closer)
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                EM.run do
         | 
| 117 | 
            +
                  EM.start_server(host, port, RequestHandler, forward_to, status_code, state, timeout)
         | 
| 118 | 
            +
                  logger.info { "Listening on #{host}:#{port}" }
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
              end
         | 
| 76 121 | 
             
            end
         | 
| @@ -67,132 +67,4 @@ module Yup | |
| 67 67 | 
             
                  end
         | 
| 68 68 | 
             
                end
         | 
| 69 69 | 
             
              end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
              class State
         | 
| 72 | 
            -
                class RequestForwarder
         | 
| 73 | 
            -
                  def initialize(state, forward_to, timeout)
         | 
| 74 | 
            -
                    @state      = state
         | 
| 75 | 
            -
                    @forward_to = forward_to
         | 
| 76 | 
            -
                    @timeout    = timeout
         | 
| 77 | 
            -
                    @logger     = Yup.logger.clone
         | 
| 78 | 
            -
                    @logger.progname = "Yup::State::RequestForwarder"
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                    @yajl = Yajl::Parser.new(:symbolize_keys => true)
         | 
| 81 | 
            -
                    @yajl.on_parse_complete = self.method(:make_request)
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  def run_loop
         | 
| 85 | 
            -
                    loop do
         | 
| 86 | 
            -
                      data = @state.bpop
         | 
| 87 | 
            -
                      begin
         | 
| 88 | 
            -
                        @yajl << data
         | 
| 89 | 
            -
                      rescue Yajl::ParseError
         | 
| 90 | 
            -
                        @logger.error { "Error while parsing \"#{data}\"" }
         | 
| 91 | 
            -
                      end
         | 
| 92 | 
            -
                    end
         | 
| 93 | 
            -
                  end
         | 
| 94 | 
            -
             | 
| 95 | 
            -
                  def make_request(req)
         | 
| 96 | 
            -
                    begin
         | 
| 97 | 
            -
                      @http_method, @request_url, headers, body = req
         | 
| 98 | 
            -
                      headers = Hash[*headers.to_a.flatten.map(&:to_s)]
         | 
| 99 | 
            -
                      headers["Host"]       = @forward_to
         | 
| 100 | 
            -
                      headers["Connection"] = "Close"
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                      req = "#{@http_method.upcase} #{@request_url} HTTP/1.1\r\n"
         | 
| 103 | 
            -
                      headers.each do |k, v|
         | 
| 104 | 
            -
                        req << "#{k}: #{v}\r\n"
         | 
| 105 | 
            -
                      end
         | 
| 106 | 
            -
                      req << "\r\n"
         | 
| 107 | 
            -
                      req << body if !body.empty?
         | 
| 108 | 
            -
                      raw_response = send_data(req.to_s, @forward_to)
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                      response_body = ""
         | 
| 111 | 
            -
                      http = Http::Parser.new()
         | 
| 112 | 
            -
                      http.on_body = proc do |chunk|
         | 
| 113 | 
            -
                        response_body << chunk
         | 
| 114 | 
            -
                      end
         | 
| 115 | 
            -
                      http << raw_response
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                      if http.status_code && http.status_code / 100 == 2
         | 
| 118 | 
            -
                        log_response(raw_response, response_body, http)
         | 
| 119 | 
            -
                        @logger.info "Success"
         | 
| 120 | 
            -
                      else
         | 
| 121 | 
            -
                        log_response(raw_response, response_body, http)
         | 
| 122 | 
            -
                        if Yup.retry_unless_2xx
         | 
| 123 | 
            -
                          @logger.info "Fail: got status code #{http.status_code}; will retry after #{Yup.resend_delay} seconds"
         | 
| 124 | 
            -
                          @state.to_feedback(Yajl::Encoder.encode([@http_method.downcase, @request_url, headers, body]))
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                          sleep Yup.resend_delay
         | 
| 127 | 
            -
                        else
         | 
| 128 | 
            -
                          @logger.info "Fail; will not retry"
         | 
| 129 | 
            -
                        end
         | 
| 130 | 
            -
                      end
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                    rescue Exception, Timeout::Error => e
         | 
| 133 | 
            -
                      log_response(raw_response, response_body, http)
         | 
| 134 | 
            -
                      @logger.info "Error: #{e.class}: #{e.message}; will retry after #{Yup.resend_delay} seconds"
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                      @state.to_feedback(Yajl::Encoder.encode([@http_method.downcase, @request_url, headers, body]))
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                      sleep Yup.resend_delay
         | 
| 139 | 
            -
                    end
         | 
| 140 | 
            -
                  end
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                private
         | 
| 143 | 
            -
                  def log_response(raw_response, body, http)
         | 
| 144 | 
            -
                    @logger.info { "HTTP request: #{@http_method.upcase} #{@request_url} HTTP/1.1" }
         | 
| 145 | 
            -
                    if raw_response && !raw_response.empty?
         | 
| 146 | 
            -
                      @logger.info  { "HTTP response: #{raw_response.lines.first.chomp}" }
         | 
| 147 | 
            -
                      @logger.debug { "HTTP response headers" + (http.headers.empty? ? " is empty" : "\n" + http.headers.inspect) }
         | 
| 148 | 
            -
                      @logger.debug { "HTTP response body"    + (body.empty? ? " is empty" : "\n" + body.inspect) }
         | 
| 149 | 
            -
                    end
         | 
| 150 | 
            -
                  end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                  def send_data(data, host)
         | 
| 153 | 
            -
                    host, port = host.split(":")
         | 
| 154 | 
            -
                    addr = Socket.getaddrinfo(host, nil)
         | 
| 155 | 
            -
                    sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
         | 
| 156 | 
            -
             | 
| 157 | 
            -
                    secs   = Integer(@timeout)
         | 
| 158 | 
            -
                    usecs  = Integer((@timeout - secs) * 1_000_000)
         | 
| 159 | 
            -
                    optval = [secs, usecs].pack("l_2")
         | 
| 160 | 
            -
                    sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
         | 
| 161 | 
            -
                    sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval)
         | 
| 162 | 
            -
             | 
| 163 | 
            -
                    resp = Timeout::timeout(@timeout) do
         | 
| 164 | 
            -
                      sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
         | 
| 165 | 
            -
                      sock.write(data)
         | 
| 166 | 
            -
                      sock.read()
         | 
| 167 | 
            -
                    end
         | 
| 168 | 
            -
                    return resp
         | 
| 169 | 
            -
                  ensure
         | 
| 170 | 
            -
                    sock.close()
         | 
| 171 | 
            -
                  end
         | 
| 172 | 
            -
                end
         | 
| 173 | 
            -
             | 
| 174 | 
            -
                class FeedbackHandler < EM::Connection
         | 
| 175 | 
            -
                  def initialize(state)
         | 
| 176 | 
            -
                    @state      = state
         | 
| 177 | 
            -
             | 
| 178 | 
            -
                    @yajl   = Yajl::Parser.new(:symbolize_keys => true)
         | 
| 179 | 
            -
                    @yajl.on_parse_complete = method(:on_message)
         | 
| 180 | 
            -
             | 
| 181 | 
            -
                    @logger = Yup.logger.clone
         | 
| 182 | 
            -
                    @logger.progname = "Yup::State::FeedbackHandler"
         | 
| 183 | 
            -
                  end
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                  def receive_data(data)
         | 
| 186 | 
            -
                    begin
         | 
| 187 | 
            -
                      @yajl << data
         | 
| 188 | 
            -
                    rescue Yajl::ParseError
         | 
| 189 | 
            -
                      @logger.error { "Error while parsing \"#{data}\"" }
         | 
| 190 | 
            -
                    end
         | 
| 191 | 
            -
                  end
         | 
| 192 | 
            -
             | 
| 193 | 
            -
                  def on_message(req)
         | 
| 194 | 
            -
                    @state.push(Yajl::Encoder.encode(req))
         | 
| 195 | 
            -
                  end
         | 
| 196 | 
            -
                end
         | 
| 197 | 
            -
              end
         | 
| 198 70 | 
             
            end
         | 
    
        data/lib/yup/state.rb
    CHANGED
    
    | @@ -1,64 +1,115 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
               | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 1 | 
            +
            module Yup
         | 
| 2 | 
            +
              module State
         | 
| 3 | 
            +
                def self.queue_type(str)
         | 
| 4 | 
            +
                  uri = URI.parse(str)
         | 
| 5 | 
            +
                  case uri.scheme
         | 
| 6 | 
            +
                  when "bdb"
         | 
| 7 | 
            +
                    :bdb
         | 
| 8 | 
            +
                  when "redis"
         | 
| 9 | 
            +
                    :redis
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 7 12 |  | 
| 8 | 
            -
             | 
| 13 | 
            +
                class RequestForwarder
         | 
| 14 | 
            +
                  def initialize(state, forward_to, timeout)
         | 
| 15 | 
            +
                    @state      = state
         | 
| 16 | 
            +
                    @forward_to = forward_to
         | 
| 17 | 
            +
                    @timeout    = timeout
         | 
| 18 | 
            +
                    @logger     = Yup.logger.clone
         | 
| 19 | 
            +
                    @logger.progname = "Yup::State::RequestForwarder"
         | 
| 9 20 |  | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 21 | 
            +
                    @yajl = Yajl::Parser.new(:symbolize_keys => true)
         | 
| 22 | 
            +
                    @yajl.on_parse_complete = self.method(:make_request)
         | 
| 23 | 
            +
                  end
         | 
| 13 24 |  | 
| 14 | 
            -
             | 
| 25 | 
            +
                  def run_loop
         | 
| 26 | 
            +
                    loop do
         | 
| 27 | 
            +
                      data = @state.bpop
         | 
| 28 | 
            +
                      begin
         | 
| 29 | 
            +
                        @yajl << data
         | 
| 30 | 
            +
                      rescue Yajl::ParseError
         | 
| 31 | 
            +
                        @logger.error { "Error while parsing \"#{data}\"" }
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 15 35 |  | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 36 | 
            +
                  def make_request(req)
         | 
| 37 | 
            +
                    begin
         | 
| 38 | 
            +
                      @http_method, @request_url, headers, body = req
         | 
| 39 | 
            +
                      headers = Hash[*headers.to_a.flatten.map(&:to_s)]
         | 
| 40 | 
            +
                      headers["Host"]       = @forward_to
         | 
| 41 | 
            +
                      headers["Connection"] = "Close"
         | 
| 21 42 |  | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 43 | 
            +
                      req = "#{@http_method.upcase} #{@request_url} HTTP/1.1\r\n"
         | 
| 44 | 
            +
                      headers.each do |k, v|
         | 
| 45 | 
            +
                        req << "#{k}: #{v}\r\n"
         | 
| 46 | 
            +
                      end
         | 
| 47 | 
            +
                      req << "\r\n"
         | 
| 48 | 
            +
                      req << body if !body.empty?
         | 
| 49 | 
            +
                      raw_response = send_data(req.to_s, @forward_to)
         | 
| 26 50 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 51 | 
            +
                      response_body = ""
         | 
| 52 | 
            +
                      http = Http::Parser.new()
         | 
| 53 | 
            +
                      http.on_body = proc do |chunk|
         | 
| 54 | 
            +
                        response_body << chunk
         | 
| 55 | 
            +
                      end
         | 
| 56 | 
            +
                      http << raw_response
         | 
| 29 57 |  | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 58 | 
            +
                      if http.status_code && http.status_code / 100 == 2
         | 
| 59 | 
            +
                        log_response(raw_response, response_body, http)
         | 
| 60 | 
            +
                        @logger.info "Success"
         | 
| 61 | 
            +
                      else
         | 
| 62 | 
            +
                        log_response(raw_response, response_body, http)
         | 
| 63 | 
            +
                        if Yup.retry_unless_2xx
         | 
| 64 | 
            +
                          @logger.info "Fail: got status code #{http.status_code}; will retry after #{Yup.resend_delay} seconds"
         | 
| 65 | 
            +
                          @state.pushback(Yajl::Encoder.encode([@http_method.downcase, @request_url, headers, body]))
         | 
| 37 66 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 67 | 
            +
                          sleep Yup.resend_delay
         | 
| 68 | 
            +
                        else
         | 
| 69 | 
            +
                          @logger.info "Fail; will not retry"
         | 
| 70 | 
            +
                        end
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    rescue Exception, Timeout::Error => e
         | 
| 74 | 
            +
                      log_response(raw_response, response_body, http)
         | 
| 75 | 
            +
                      @logger.info "Error: #{e.class}: #{e.message}; will retry after #{Yup.resend_delay} seconds"
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      @state.pushback(Yajl::Encoder.encode([@http_method.downcase, @request_url, headers, body]))
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                      sleep Yup.resend_delay
         | 
| 80 | 
            +
                    end
         | 
| 44 81 | 
             
                  end
         | 
| 45 | 
            -
                end
         | 
| 46 82 |  | 
| 47 | 
            -
                 | 
| 48 | 
            -
                   | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 83 | 
            +
                private
         | 
| 84 | 
            +
                  def log_response(raw_response, body, http)
         | 
| 85 | 
            +
                    @logger.info { "HTTP request: #{@http_method.upcase} #{@request_url} HTTP/1.1" }
         | 
| 86 | 
            +
                    if raw_response && !raw_response.empty?
         | 
| 87 | 
            +
                      @logger.info  { "HTTP response: #{raw_response.lines.first.chomp}" }
         | 
| 88 | 
            +
                      @logger.debug { "HTTP response headers" + (http.headers.empty? ? " is empty" : "\n" + http.headers.inspect) }
         | 
| 89 | 
            +
                      @logger.debug { "HTTP response body"    + (body.empty? ? " is empty" : "\n" + body.inspect) }
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
                  end
         | 
| 52 92 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
                  sock.close
         | 
| 58 | 
            -
                end
         | 
| 93 | 
            +
                  def send_data(data, host)
         | 
| 94 | 
            +
                    host, port = host.split(":")
         | 
| 95 | 
            +
                    addr = Socket.getaddrinfo(host, nil)
         | 
| 96 | 
            +
                    sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
         | 
| 59 97 |  | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 98 | 
            +
                    secs   = Integer(@timeout)
         | 
| 99 | 
            +
                    usecs  = Integer((@timeout - secs) * 1_000_000)
         | 
| 100 | 
            +
                    optval = [secs, usecs].pack("l_2")
         | 
| 101 | 
            +
                    sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
         | 
| 102 | 
            +
                    sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval)
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    resp = Timeout::timeout(@timeout) do
         | 
| 105 | 
            +
                      sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
         | 
| 106 | 
            +
                      sock.write(data)
         | 
| 107 | 
            +
                      sock.read()
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                    return resp
         | 
| 110 | 
            +
                  ensure
         | 
| 111 | 
            +
                    sock.close()
         | 
| 112 | 
            +
                  end
         | 
| 62 113 | 
             
                end
         | 
| 63 114 | 
             
              end
         | 
| 64 115 | 
             
            end
         | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              require 'bdb'
         | 
| 3 | 
            +
              require 'bdb/database'
         | 
| 4 | 
            +
            rescue LoadError
         | 
| 5 | 
            +
              abort "Install bdb gem to use a persistent queue."
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require "timeout"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module Yup
         | 
| 11 | 
            +
              module State
         | 
| 12 | 
            +
                class BDB
         | 
| 13 | 
            +
                  RE_LEN = 1000
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  attr_reader :queue
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def self.repair_if_need(path)
         | 
| 18 | 
            +
                    env = Bdb::Env.new(0)
         | 
| 19 | 
            +
                    env.open(path, Bdb::DB_CREATE | Bdb::DB_INIT_TXN | Bdb::DB_RECOVER, 0)
         | 
| 20 | 
            +
                    env.close()
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def initialize(uri, forward_to, feedback_channel)
         | 
| 24 | 
            +
                    @uri  = URI.parse(uri)
         | 
| 25 | 
            +
                    @path = @uri.path
         | 
| 26 | 
            +
                    @name = forward_to
         | 
| 27 | 
            +
                    @feedback_channel = feedback_channel
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    @logger = Yup.logger.clone
         | 
| 30 | 
            +
                    @logger.progname = "Yup::State::BDB"
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    FileUtils.mkdir_p(@path)
         | 
| 33 | 
            +
                    @env   = Bdb::Env.new(0)
         | 
| 34 | 
            +
                    @env   = @env.open(@path, Bdb::DB_CREATE | Bdb::DB_INIT_MPOOL | Bdb::DB_INIT_CDB, 0)
         | 
| 35 | 
            +
                    @queue = @env.db
         | 
| 36 | 
            +
                    @queue.re_len = RE_LEN
         | 
| 37 | 
            +
                    @queue.open(nil, @name, nil, Bdb::Db::QUEUE, Bdb::DB_CREATE, 0)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def push(data)
         | 
| 41 | 
            +
                    @logger.debug { "Push: #{data}" }
         | 
| 42 | 
            +
                    i = 0
         | 
| 43 | 
            +
                    until (chunk = data.slice(i, RE_LEN)).nil?
         | 
| 44 | 
            +
                      @queue.put(nil, "", chunk, Bdb::DB_APPEND)
         | 
| 45 | 
            +
                      i += @queue.re_len
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def bpop
         | 
| 50 | 
            +
                    data = @queue.get(nil, "", nil, Bdb::DB_CONSUME_WAIT)
         | 
| 51 | 
            +
                    @logger.debug { "Bpoped: #{data.strip}" }
         | 
| 52 | 
            +
                    data
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def pushback(data)
         | 
| 56 | 
            +
                    @logger.debug { "Push to the feedback channel: #{data.strip}" }
         | 
| 57 | 
            +
                    sock = UNIXSocket.new(@feedback_channel)
         | 
| 58 | 
            +
                    sock.send(data, 0)
         | 
| 59 | 
            +
                    sock.close
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def dispose
         | 
| 63 | 
            +
                    @queue.close(0)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  class RequestForwarder < ::Yup::State::RequestForwarder
         | 
| 67 | 
            +
                    def initialize(*args)
         | 
| 68 | 
            +
                      super
         | 
| 69 | 
            +
                      @logger = Yup.logger.clone
         | 
| 70 | 
            +
                      @logger.progname = "Yup::State::BDB::RequestForwarder"
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  class FeedbackHandler < EM::Connection
         | 
| 75 | 
            +
                    def initialize(state)
         | 
| 76 | 
            +
                      @state = state
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      @yajl = Yajl::Parser.new(:symbolize_keys => true)
         | 
| 79 | 
            +
                      @yajl.on_parse_complete = method(:on_message)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      @logger = Yup.logger.clone
         | 
| 82 | 
            +
                      @logger.progname = "Yup::State::BDB::FeedbackHandler"
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def receive_data(data)
         | 
| 86 | 
            +
                      begin
         | 
| 87 | 
            +
                        @yajl << data
         | 
| 88 | 
            +
                      rescue Yajl::ParseError
         | 
| 89 | 
            +
                        @logger.error { "Error while parsing \"#{data}\"" }
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    def on_message(req)
         | 
| 94 | 
            +
                      @state.push(Yajl::Encoder.encode(req))
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
            end
         | 
| @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              require 'redis'
         | 
| 3 | 
            +
              require 'redis-namespace'
         | 
| 4 | 
            +
            rescue LoadError
         | 
| 5 | 
            +
              abort "Install redis-namespace gem to use a persistent queue."
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Yup
         | 
| 9 | 
            +
              module State
         | 
| 10 | 
            +
                class Redis
         | 
| 11 | 
            +
                  def initialize(uri, forward_to)
         | 
| 12 | 
            +
                    @uri = URI.parse(uri)
         | 
| 13 | 
            +
                    @ns  = @uri.path[1..-1]
         | 
| 14 | 
            +
                    @ns  = "yup-#{VERSION}" if @ns.empty?
         | 
| 15 | 
            +
                    @ns << ":#{forward_to}"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    @logger = Yup.logger.clone
         | 
| 18 | 
            +
                    @logger.progname = "Yup::State::Redis"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    @redis_backend = ::Redis.new(:host => @uri.host, :port => @uri.port)
         | 
| 21 | 
            +
                    @redis         = ::Redis::Namespace.new(@ns, :redis => @redis_backend)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def push(data)
         | 
| 25 | 
            +
                    @logger.debug { "Push: #{data}" }
         | 
| 26 | 
            +
                    @redis.lpush("requests", data)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def pushback(data)
         | 
| 30 | 
            +
                    @logger.debug { "Push back: #{data}" }
         | 
| 31 | 
            +
                    @redis.lpush("requests", data)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def bpop
         | 
| 35 | 
            +
                    _, data = @redis.brpop("requests", :timeout => 0)
         | 
| 36 | 
            +
                    @logger.debug { "Bpoped: #{data.strip}" }
         | 
| 37 | 
            +
                    data
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def dispose
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  class RequestForwarder < ::Yup::State::RequestForwarder
         | 
| 44 | 
            +
                    def initialize(*args)
         | 
| 45 | 
            +
                      super
         | 
| 46 | 
            +
                      @logger = Yup.logger.clone
         | 
| 47 | 
            +
                      @logger.progname = "Yup::State::Redis::RequestForwarder"
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
    
        data/test/helper.rb
    CHANGED
    
    | @@ -28,7 +28,68 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) | |
| 28 28 | 
             
            $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 29 29 | 
             
            require 'yup'
         | 
| 30 30 |  | 
| 31 | 
            -
            class MiniTest::Unit::TestCase
         | 
| 31 | 
            +
            class YupTestCase < MiniTest::Unit::TestCase
         | 
| 32 | 
            +
              def after_setup
         | 
| 33 | 
            +
                Service.attempts = 0
         | 
| 34 | 
            +
                super
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              class Service < EM::Connection
         | 
| 38 | 
            +
                @@attempts = 0
         | 
| 39 | 
            +
                def self.attempts;     @@attempts end
         | 
| 40 | 
            +
                def self.attempts=(n); @@attempts = n end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def post_init
         | 
| 43 | 
            +
                  @parser = Http::Parser.new(self)
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def receive_data(data)
         | 
| 47 | 
            +
                  @parser << data
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def on_message_complete
         | 
| 51 | 
            +
                  $service_parser = @parser
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  case @@attempts
         | 
| 54 | 
            +
                  when 0
         | 
| 55 | 
            +
                  when 1
         | 
| 56 | 
            +
                    send_data "HTTP/1.1 400 Bad Request\r\nServer: test\r\n\r\n"
         | 
| 57 | 
            +
                    close_connection_after_writing
         | 
| 58 | 
            +
                  when 2
         | 
| 59 | 
            +
                    send_data "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n"
         | 
| 60 | 
            +
                    close_connection_after_writing
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  @@attempts += 1
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def unbind
         | 
| 67 | 
            +
                  if @@attempts > 2
         | 
| 68 | 
            +
                    EM.add_timer(1) do
         | 
| 69 | 
            +
                      Process.kill("KILL", $pid)
         | 
| 70 | 
            +
                      EM.stop_event_loop()
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              module Client
         | 
| 77 | 
            +
                def connection_completed
         | 
| 78 | 
            +
                  send_data("GET /foo HTTP/1.0\r\n\r\n")
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def post_init
         | 
| 82 | 
            +
                  @parser = Http::Parser.new(self)
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def receive_data(data)
         | 
| 86 | 
            +
                  @parser << data
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def on_message_complete
         | 
| 90 | 
            +
                  $client_parser = @parser
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 32 93 | 
             
            end
         | 
| 33 94 |  | 
| 34 95 | 
             
            MiniTest::Unit.autorun
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require 'helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'tmpdir'
         | 
| 4 | 
            +
            require 'fileutils'
         | 
| 5 | 
            +
            require 'yup/state/bdb'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class TestStatefulYupWithBDB < YupTestCase
         | 
| 8 | 
            +
              class RequestHandlerMock < Yup::RequestHandler
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def test_request_handler
         | 
| 12 | 
            +
                Service.attempts = 0
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                dbpath           = Dir.mktmpdir("yupd-db")
         | 
| 15 | 
            +
                uri              = "bdb://#{dbpath}"
         | 
| 16 | 
            +
                feedback_channel = File.join(Dir.tmpdir, "yupd-#{$$}-feedback")
         | 
| 17 | 
            +
                forward_to       = "127.0.0.1:26785"
         | 
| 18 | 
            +
                status_code      = 200
         | 
| 19 | 
            +
                state            = Yup::State::BDB.new(uri, forward_to, feedback_channel)
         | 
| 20 | 
            +
                timeout          = 1
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                Yup.resend_delay     = 1
         | 
| 23 | 
            +
                Yup.retry_unless_2xx = true
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                $pid = Process.fork do
         | 
| 26 | 
            +
                  Yup::State::BDB::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                EM.run {
         | 
| 30 | 
            +
                  EM.start_server("127.0.0.1", 26785, Service)
         | 
| 31 | 
            +
                  EM.start_unix_domain_server(feedback_channel, Yup::State::BDB::FeedbackHandler, state)
         | 
| 32 | 
            +
                  EM.start_server("127.0.0.1", 26784, RequestHandlerMock, forward_to, status_code, state, timeout)
         | 
| 33 | 
            +
                  EM.connect("127.0.0.1", 26784, Client)
         | 
| 34 | 
            +
                }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                assert       $client_parser
         | 
| 37 | 
            +
                assert_equal 200,    $client_parser.status_code
         | 
| 38 | 
            +
                assert_equal "yupd", $client_parser.headers["Server"]
         | 
| 39 | 
            +
                assert       $service_parser
         | 
| 40 | 
            +
                assert_equal "/foo", $service_parser.request_url
         | 
| 41 | 
            +
                assert_equal 3,      Service.attempts
         | 
| 42 | 
            +
              ensure
         | 
| 43 | 
            +
                Process.kill("KILL", $pid) if $pid
         | 
| 44 | 
            +
                state.dispose() if state
         | 
| 45 | 
            +
                FileUtils.remove_entry_secure(dbpath) if dbpath
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'tmpdir'
         | 
| 4 | 
            +
            require 'fileutils'
         | 
| 5 | 
            +
            require 'yup/state/redis'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class TestStatefulYupWithRedis < YupTestCase
         | 
| 8 | 
            +
              class RequestHandlerMock < Yup::RequestHandler
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def test_request_handler
         | 
| 12 | 
            +
                uri         = "redis://localhost/yup-testing-#{Time.now.to_f}"
         | 
| 13 | 
            +
                forward_to  = "127.0.0.1:26785"
         | 
| 14 | 
            +
                status_code = 200
         | 
| 15 | 
            +
                state       = Yup::State::Redis.new(uri, forward_to)
         | 
| 16 | 
            +
                timeout     = 1
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                Yup.resend_delay     = 1
         | 
| 19 | 
            +
                Yup.retry_unless_2xx = true
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                $pid = Process.fork do
         | 
| 22 | 
            +
                  Yup::State::Redis::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                EM.run {
         | 
| 26 | 
            +
                  EM.start_server("127.0.0.1", 26785, Service)
         | 
| 27 | 
            +
                  EM.start_server("127.0.0.1", 26784, RequestHandlerMock, forward_to, status_code, state, timeout)
         | 
| 28 | 
            +
                  EM.connect("127.0.0.1", 26784, Client)
         | 
| 29 | 
            +
                }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                assert       $client_parser
         | 
| 32 | 
            +
                assert_equal 200,    $client_parser.status_code
         | 
| 33 | 
            +
                assert_equal "yupd", $client_parser.headers["Server"]
         | 
| 34 | 
            +
                assert       $service_parser
         | 
| 35 | 
            +
                assert_equal "/foo", $service_parser.request_url
         | 
| 36 | 
            +
                assert_equal 3,      Service.attempts
         | 
| 37 | 
            +
              ensure
         | 
| 38 | 
            +
                Process.kill("KILL", $pid) if $pid
         | 
| 39 | 
            +
                state.dispose() if state
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
    
        data/yup.gemspec
    CHANGED
    
    | @@ -5,11 +5,11 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| 7 7 | 
             
              s.name = "yup"
         | 
| 8 | 
            -
              s.version = "0. | 
| 8 | 
            +
              s.version = "0.2.0"
         | 
| 9 9 |  | 
| 10 10 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 11 | 
             
              s.authors = ["Denis Sukhonin"]
         | 
| 12 | 
            -
              s.date = "2013- | 
| 12 | 
            +
              s.date = "2013-02-08"
         | 
| 13 13 | 
             
              s.description = "Just answers 200 (or specified) to a client and asynchronously forwards HTTP request to a configured host"
         | 
| 14 14 | 
             
              s.email = "d.sukhonin@gmail.com"
         | 
| 15 15 | 
             
              s.executables = ["yupd"]
         | 
| @@ -31,9 +31,12 @@ Gem::Specification.new do |s| | |
| 31 31 | 
             
                "lib/yup/request_forwarder.rb",
         | 
| 32 32 | 
             
                "lib/yup/request_handler.rb",
         | 
| 33 33 | 
             
                "lib/yup/state.rb",
         | 
| 34 | 
            +
                "lib/yup/state/bdb.rb",
         | 
| 35 | 
            +
                "lib/yup/state/redis.rb",
         | 
| 34 36 | 
             
                "lib/yup/version.rb",
         | 
| 35 37 | 
             
                "test/helper.rb",
         | 
| 36 | 
            -
                "test/ | 
| 38 | 
            +
                "test/test_stateful_yup_with_bdb.rb",
         | 
| 39 | 
            +
                "test/test_stateful_yup_with_redis.rb",
         | 
| 37 40 | 
             
                "test/test_yup.rb",
         | 
| 38 41 | 
             
                "yup.gemspec"
         | 
| 39 42 | 
             
              ]
         | 
| @@ -53,13 +56,14 @@ Gem::Specification.new do |s| | |
| 53 56 | 
             
                  s.add_runtime_dependency(%q<tuple>, [">= 0"])
         | 
| 54 57 | 
             
                  s.add_runtime_dependency(%q<yajl-ruby>, [">= 0"])
         | 
| 55 58 | 
             
                  s.add_development_dependency(%q<bdb>, [">= 0"])
         | 
| 59 | 
            +
                  s.add_development_dependency(%q<redis-namespace>, [">= 0"])
         | 
| 56 60 | 
             
                  s.add_development_dependency(%q<yard>, ["~> 0.8.0"])
         | 
| 57 | 
            -
                  s.add_development_dependency(%q<minitest>, [" | 
| 61 | 
            +
                  s.add_development_dependency(%q<minitest>, ["~> 4.5"])
         | 
| 58 62 | 
             
                  s.add_development_dependency(%q<bundler>, ["~> 1.2"])
         | 
| 59 63 | 
             
                  s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
         | 
| 60 | 
            -
                  s.add_development_dependency(%q<simplecov>, [" | 
| 61 | 
            -
                  s.add_development_dependency(%q<simplecov-rcov>, [" | 
| 62 | 
            -
                  s.add_development_dependency(%q<travis-lint>, [" | 
| 64 | 
            +
                  s.add_development_dependency(%q<simplecov>, ["~> 0.7.1"])
         | 
| 65 | 
            +
                  s.add_development_dependency(%q<simplecov-rcov>, ["~> 0.2.3"])
         | 
| 66 | 
            +
                  s.add_development_dependency(%q<travis-lint>, ["~> 1.4"])
         | 
| 63 67 | 
             
                else
         | 
| 64 68 | 
             
                  s.add_dependency(%q<eventmachine>, [">= 0"])
         | 
| 65 69 | 
             
                  s.add_dependency(%q<em-http-request>, [">= 0"])
         | 
| @@ -67,13 +71,14 @@ Gem::Specification.new do |s| | |
| 67 71 | 
             
                  s.add_dependency(%q<tuple>, [">= 0"])
         | 
| 68 72 | 
             
                  s.add_dependency(%q<yajl-ruby>, [">= 0"])
         | 
| 69 73 | 
             
                  s.add_dependency(%q<bdb>, [">= 0"])
         | 
| 74 | 
            +
                  s.add_dependency(%q<redis-namespace>, [">= 0"])
         | 
| 70 75 | 
             
                  s.add_dependency(%q<yard>, ["~> 0.8.0"])
         | 
| 71 | 
            -
                  s.add_dependency(%q<minitest>, [" | 
| 76 | 
            +
                  s.add_dependency(%q<minitest>, ["~> 4.5"])
         | 
| 72 77 | 
             
                  s.add_dependency(%q<bundler>, ["~> 1.2"])
         | 
| 73 78 | 
             
                  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
         | 
| 74 | 
            -
                  s.add_dependency(%q<simplecov>, [" | 
| 75 | 
            -
                  s.add_dependency(%q<simplecov-rcov>, [" | 
| 76 | 
            -
                  s.add_dependency(%q<travis-lint>, [" | 
| 79 | 
            +
                  s.add_dependency(%q<simplecov>, ["~> 0.7.1"])
         | 
| 80 | 
            +
                  s.add_dependency(%q<simplecov-rcov>, ["~> 0.2.3"])
         | 
| 81 | 
            +
                  s.add_dependency(%q<travis-lint>, ["~> 1.4"])
         | 
| 77 82 | 
             
                end
         | 
| 78 83 | 
             
              else
         | 
| 79 84 | 
             
                s.add_dependency(%q<eventmachine>, [">= 0"])
         | 
| @@ -82,13 +87,14 @@ Gem::Specification.new do |s| | |
| 82 87 | 
             
                s.add_dependency(%q<tuple>, [">= 0"])
         | 
| 83 88 | 
             
                s.add_dependency(%q<yajl-ruby>, [">= 0"])
         | 
| 84 89 | 
             
                s.add_dependency(%q<bdb>, [">= 0"])
         | 
| 90 | 
            +
                s.add_dependency(%q<redis-namespace>, [">= 0"])
         | 
| 85 91 | 
             
                s.add_dependency(%q<yard>, ["~> 0.8.0"])
         | 
| 86 | 
            -
                s.add_dependency(%q<minitest>, [" | 
| 92 | 
            +
                s.add_dependency(%q<minitest>, ["~> 4.5"])
         | 
| 87 93 | 
             
                s.add_dependency(%q<bundler>, ["~> 1.2"])
         | 
| 88 94 | 
             
                s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
         | 
| 89 | 
            -
                s.add_dependency(%q<simplecov>, [" | 
| 90 | 
            -
                s.add_dependency(%q<simplecov-rcov>, [" | 
| 91 | 
            -
                s.add_dependency(%q<travis-lint>, [" | 
| 95 | 
            +
                s.add_dependency(%q<simplecov>, ["~> 0.7.1"])
         | 
| 96 | 
            +
                s.add_dependency(%q<simplecov-rcov>, ["~> 0.2.3"])
         | 
| 97 | 
            +
                s.add_dependency(%q<travis-lint>, ["~> 1.4"])
         | 
| 92 98 | 
             
              end
         | 
| 93 99 | 
             
            end
         | 
| 94 100 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: yup
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2013- | 
| 12 | 
            +
            date: 2013-02-08 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: eventmachine
         | 
| @@ -107,6 +107,22 @@ dependencies: | |
| 107 107 | 
             
                - - ! '>='
         | 
| 108 108 | 
             
                  - !ruby/object:Gem::Version
         | 
| 109 109 | 
             
                    version: '0'
         | 
| 110 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 111 | 
            +
              name: redis-namespace
         | 
| 112 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 113 | 
            +
                none: false
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ! '>='
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                none: false
         | 
| 122 | 
            +
                requirements:
         | 
| 123 | 
            +
                - - ! '>='
         | 
| 124 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                    version: '0'
         | 
| 110 126 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 111 127 | 
             
              name: yard
         | 
| 112 128 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -128,17 +144,17 @@ dependencies: | |
| 128 144 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 129 145 | 
             
                none: false
         | 
| 130 146 | 
             
                requirements:
         | 
| 131 | 
            -
                - -  | 
| 147 | 
            +
                - - ~>
         | 
| 132 148 | 
             
                  - !ruby/object:Gem::Version
         | 
| 133 | 
            -
                    version: ' | 
| 149 | 
            +
                    version: '4.5'
         | 
| 134 150 | 
             
              type: :development
         | 
| 135 151 | 
             
              prerelease: false
         | 
| 136 152 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 137 153 | 
             
                none: false
         | 
| 138 154 | 
             
                requirements:
         | 
| 139 | 
            -
                - -  | 
| 155 | 
            +
                - - ~>
         | 
| 140 156 | 
             
                  - !ruby/object:Gem::Version
         | 
| 141 | 
            -
                    version: ' | 
| 157 | 
            +
                    version: '4.5'
         | 
| 142 158 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 143 159 | 
             
              name: bundler
         | 
| 144 160 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -176,49 +192,49 @@ dependencies: | |
| 176 192 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 177 193 | 
             
                none: false
         | 
| 178 194 | 
             
                requirements:
         | 
| 179 | 
            -
                - -  | 
| 195 | 
            +
                - - ~>
         | 
| 180 196 | 
             
                  - !ruby/object:Gem::Version
         | 
| 181 | 
            -
                    version:  | 
| 197 | 
            +
                    version: 0.7.1
         | 
| 182 198 | 
             
              type: :development
         | 
| 183 199 | 
             
              prerelease: false
         | 
| 184 200 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 185 201 | 
             
                none: false
         | 
| 186 202 | 
             
                requirements:
         | 
| 187 | 
            -
                - -  | 
| 203 | 
            +
                - - ~>
         | 
| 188 204 | 
             
                  - !ruby/object:Gem::Version
         | 
| 189 | 
            -
                    version:  | 
| 205 | 
            +
                    version: 0.7.1
         | 
| 190 206 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 191 207 | 
             
              name: simplecov-rcov
         | 
| 192 208 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 193 209 | 
             
                none: false
         | 
| 194 210 | 
             
                requirements:
         | 
| 195 | 
            -
                - -  | 
| 211 | 
            +
                - - ~>
         | 
| 196 212 | 
             
                  - !ruby/object:Gem::Version
         | 
| 197 | 
            -
                    version:  | 
| 213 | 
            +
                    version: 0.2.3
         | 
| 198 214 | 
             
              type: :development
         | 
| 199 215 | 
             
              prerelease: false
         | 
| 200 216 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 201 217 | 
             
                none: false
         | 
| 202 218 | 
             
                requirements:
         | 
| 203 | 
            -
                - -  | 
| 219 | 
            +
                - - ~>
         | 
| 204 220 | 
             
                  - !ruby/object:Gem::Version
         | 
| 205 | 
            -
                    version:  | 
| 221 | 
            +
                    version: 0.2.3
         | 
| 206 222 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 207 223 | 
             
              name: travis-lint
         | 
| 208 224 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 209 225 | 
             
                none: false
         | 
| 210 226 | 
             
                requirements:
         | 
| 211 | 
            -
                - -  | 
| 227 | 
            +
                - - ~>
         | 
| 212 228 | 
             
                  - !ruby/object:Gem::Version
         | 
| 213 | 
            -
                    version: ' | 
| 229 | 
            +
                    version: '1.4'
         | 
| 214 230 | 
             
              type: :development
         | 
| 215 231 | 
             
              prerelease: false
         | 
| 216 232 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 217 233 | 
             
                none: false
         | 
| 218 234 | 
             
                requirements:
         | 
| 219 | 
            -
                - -  | 
| 235 | 
            +
                - - ~>
         | 
| 220 236 | 
             
                  - !ruby/object:Gem::Version
         | 
| 221 | 
            -
                    version: ' | 
| 237 | 
            +
                    version: '1.4'
         | 
| 222 238 | 
             
            description: Just answers 200 (or specified) to a client and asynchronously forwards
         | 
| 223 239 | 
             
              HTTP request to a configured host
         | 
| 224 240 | 
             
            email: d.sukhonin@gmail.com
         | 
| @@ -242,9 +258,12 @@ files: | |
| 242 258 | 
             
            - lib/yup/request_forwarder.rb
         | 
| 243 259 | 
             
            - lib/yup/request_handler.rb
         | 
| 244 260 | 
             
            - lib/yup/state.rb
         | 
| 261 | 
            +
            - lib/yup/state/bdb.rb
         | 
| 262 | 
            +
            - lib/yup/state/redis.rb
         | 
| 245 263 | 
             
            - lib/yup/version.rb
         | 
| 246 264 | 
             
            - test/helper.rb
         | 
| 247 | 
            -
            - test/ | 
| 265 | 
            +
            - test/test_stateful_yup_with_bdb.rb
         | 
| 266 | 
            +
            - test/test_stateful_yup_with_redis.rb
         | 
| 248 267 | 
             
            - test/test_yup.rb
         | 
| 249 268 | 
             
            - yup.gemspec
         | 
| 250 269 | 
             
            homepage: http://github.com/neglectedvalue/yup
         | 
| @@ -262,7 +281,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 262 281 | 
             
                  version: '0'
         | 
| 263 282 | 
             
                  segments:
         | 
| 264 283 | 
             
                  - 0
         | 
| 265 | 
            -
                  hash: - | 
| 284 | 
            +
                  hash: -3031791047917054277
         | 
| 266 285 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 267 286 | 
             
              none: false
         | 
| 268 287 | 
             
              requirements:
         | 
| @@ -1,105 +0,0 @@ | |
| 1 | 
            -
            require 'helper'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'tmpdir'
         | 
| 4 | 
            -
            require 'fileutils'
         | 
| 5 | 
            -
            require 'yup/state'
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            class Yup::State::FeedbackHandler
         | 
| 8 | 
            -
              alias :on_message_original :on_message
         | 
| 9 | 
            -
              def on_message(req)
         | 
| 10 | 
            -
                on_message_original(req)
         | 
| 11 | 
            -
                $attempts += 1
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
            end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
            class TestPersistenceYup < MiniTest::Unit::TestCase
         | 
| 16 | 
            -
              class RequestHandlerMock < Yup::RequestHandler
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              class Service < EM::Connection
         | 
| 20 | 
            -
                def post_init
         | 
| 21 | 
            -
                  @parser = Http::Parser.new(self)
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                def receive_data(data)
         | 
| 25 | 
            -
                  @parser << data
         | 
| 26 | 
            -
                end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                def on_message_complete
         | 
| 29 | 
            -
                  $service_parser = @parser
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  case $attempts
         | 
| 32 | 
            -
                  when 0
         | 
| 33 | 
            -
                  when 1
         | 
| 34 | 
            -
                    send_data "HTTP/1.1 400 Bad Request\r\nServer: test\r\n\r\n"
         | 
| 35 | 
            -
                    close_connection_after_writing
         | 
| 36 | 
            -
                  when 2
         | 
| 37 | 
            -
                    send_data "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n"
         | 
| 38 | 
            -
                    close_connection_after_writing
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                def unbind
         | 
| 43 | 
            -
                  if $attempts >= 2
         | 
| 44 | 
            -
                    EM.add_timer(1) do
         | 
| 45 | 
            -
                      Process.kill("KILL", $pid)
         | 
| 46 | 
            -
                      EM.stop_event_loop()
         | 
| 47 | 
            -
                    end
         | 
| 48 | 
            -
                  end
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
              end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
              module Client
         | 
| 53 | 
            -
                def connection_completed
         | 
| 54 | 
            -
                  send_data("GET /foo HTTP/1.0\r\n\r\n")
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                def post_init
         | 
| 58 | 
            -
                  @parser = Http::Parser.new(self)
         | 
| 59 | 
            -
                end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                def receive_data(data)
         | 
| 62 | 
            -
                  @parser << data
         | 
| 63 | 
            -
                end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                def on_message_complete
         | 
| 66 | 
            -
                  $client_parser = @parser
         | 
| 67 | 
            -
                end
         | 
| 68 | 
            -
              end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
              def test_request_handler
         | 
| 71 | 
            -
                $attempts = 0
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                dbpath           = Dir.mktmpdir("yupd-db")
         | 
| 74 | 
            -
                feedback_channel = File.join(Dir.tmpdir, "yupd-#{$$}-feedback")
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                forward_to  = "127.0.0.1:26785"
         | 
| 77 | 
            -
                status_code = 200
         | 
| 78 | 
            -
                state       = Yup::State.new(dbpath, forward_to, feedback_channel)
         | 
| 79 | 
            -
                timeout     = 1
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                Yup.resend_delay     = 1
         | 
| 82 | 
            -
                Yup.retry_unless_2xx = true
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                $pid = Process.fork do
         | 
| 85 | 
            -
                  Yup::State::RequestForwarder.new(state, forward_to, timeout).run_loop
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                EM.run {
         | 
| 89 | 
            -
                  EM.start_server("127.0.0.1", 26785, Service)
         | 
| 90 | 
            -
                  EM.start_unix_domain_server(feedback_channel, Yup::State::FeedbackHandler, state)
         | 
| 91 | 
            -
                  EM.start_server("127.0.0.1", 26784, RequestHandlerMock, forward_to, status_code, state, timeout)
         | 
| 92 | 
            -
                  EM.connect("127.0.0.1", 26784, Client)
         | 
| 93 | 
            -
                }
         | 
| 94 | 
            -
             | 
| 95 | 
            -
                assert       $client_parser
         | 
| 96 | 
            -
                assert_equal 200,    $client_parser.status_code
         | 
| 97 | 
            -
                assert_equal "yupd", $client_parser.headers["Server"]
         | 
| 98 | 
            -
                assert       $service_parser
         | 
| 99 | 
            -
                assert_equal "/foo", $service_parser.request_url
         | 
| 100 | 
            -
              ensure
         | 
| 101 | 
            -
                Process.kill("KILL", $pid) if $pid
         | 
| 102 | 
            -
                state.close if state
         | 
| 103 | 
            -
                FileUtils.remove_entry_secure(dbpath) if dbpath
         | 
| 104 | 
            -
              end
         | 
| 105 | 
            -
            end
         |