yup 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,5 +1,7 @@
1
1
  before_install:
2
2
  - sudo apt-get install libdb-dev
3
+ services:
4
+ - redis-server
3
5
  language: ruby
4
6
  rvm:
5
7
  - ree-1.8.7
data/Gemfile CHANGED
@@ -8,11 +8,12 @@ gem "yajl-ruby"
8
8
 
9
9
  group :development do
10
10
  gem "bdb"
11
- gem "yard", "~> 0.8.0"
12
- gem "minitest"
13
- gem "bundler", "~> 1.2"
14
- gem "jeweler", "~> 1.8.4"
15
- gem "simplecov"
16
- gem "simplecov-rcov"
17
- gem "travis-lint"
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.5)
26
- minitest (4.2.0)
27
- multi_json (1.3.7)
28
- rake (0.9.2.2)
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.4.0)
38
- hashr (>= 0.0.19)
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.3)
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
- simplecov
55
- simplecov-rcov
56
- travis-lint
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, nonpersistent queue is used. A limit (the option --watermark) at which new concurrent requests will be dropped.
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 a path to a database.
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 /var/db/yupd-errbit errbit.host.somewhere
20
+ yupd --listen localhost:8081 --status-code 201 --persistent bdb:///var/db/yupd-errbit errbit.host.somewhere
21
21
 
22
- Reconfiguration of hoptoad_notifier is very ease:
23
- HoptoadNotifier.configure do |config|
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
- == Copyright
38
+ == Credits
36
39
 
37
- Copyright (c) 2011 Denis Sukhonin. See LICENSE.txt for
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.3
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 = config[:listen_host] || 'localhost'
30
- port = config[:listen_port] || 8080
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
- require 'yup/state'
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 = Yup::State.new(dbpath, forward_to, feedback_channel)
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.close
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
- begin
2
- require 'bdb'
3
- require 'bdb/database'
4
- rescue LoadError
5
- puts "Install bdb gem to use a persistent queue."
6
- end
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
- require "timeout"
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
- module Yup
11
- class State
12
- RE_LEN = 1000
21
+ @yajl = Yajl::Parser.new(:symbolize_keys => true)
22
+ @yajl.on_parse_complete = self.method(:make_request)
23
+ end
13
24
 
14
- attr_reader :queue
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
- def self.repair_if_need(path)
17
- env = Bdb::Env.new(0)
18
- env.open(path, Bdb::DB_CREATE | Bdb::DB_INIT_TXN | Bdb::DB_RECOVER, 0)
19
- env.close()
20
- end
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
- def initialize(path, name, feedback_channel)
23
- @path = path
24
- @name = name
25
- @feedback_channel = feedback_channel
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
- @logger = Yup.logger.clone
28
- @logger.progname = "Yup::State"
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
- FileUtils.mkdir_p(@path)
31
- @env = Bdb::Env.new(0)
32
- @env = @env.open(@path, Bdb::DB_CREATE | Bdb::DB_INIT_MPOOL | Bdb::DB_INIT_CDB, 0)
33
- @queue = @env.db
34
- @queue.re_len = RE_LEN
35
- @queue.open(nil, @name, nil, Bdb::Db::QUEUE, Bdb::DB_CREATE, 0)
36
- end
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
- def push(data)
39
- @logger.debug { "Push: #{data}" }
40
- i = 0
41
- until (chunk = data.slice(i, RE_LEN)).nil?
42
- @queue.put(nil, "", chunk, Bdb::DB_APPEND)
43
- i += @queue.re_len
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
- def bpop
48
- data = @queue.get(nil, "", nil, Bdb::DB_CONSUME_WAIT)
49
- @logger.debug { "Bpoped: #{data.strip}" }
50
- data
51
- end
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
- def to_feedback(data)
54
- @logger.debug { "Push to the feedback channel: #{data.strip}" }
55
- sock = UNIXSocket.new(@feedback_channel)
56
- sock.send(data, 0)
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
- def close
61
- @queue.close(0)
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.1.3"
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-01-09"
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/test_persistence_yup.rb",
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>, [">= 0"])
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>, [">= 0"])
61
- s.add_development_dependency(%q<simplecov-rcov>, [">= 0"])
62
- s.add_development_dependency(%q<travis-lint>, [">= 0"])
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>, [">= 0"])
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>, [">= 0"])
75
- s.add_dependency(%q<simplecov-rcov>, [">= 0"])
76
- s.add_dependency(%q<travis-lint>, [">= 0"])
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>, [">= 0"])
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>, [">= 0"])
90
- s.add_dependency(%q<simplecov-rcov>, [">= 0"])
91
- s.add_dependency(%q<travis-lint>, [">= 0"])
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.1.3
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-01-09 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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/test_persistence_yup.rb
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: -4554609554866881469
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