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