yup 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,21 @@
1
+ before_install:
2
+ - sudo apt-get install libdb-dev
3
+ language: ruby
4
+ rvm:
5
+ - ree-1.8.7
6
+ - 1.8.7
7
+ - 1.9.2
8
+ - 1.9.3
9
+ - jruby-18mode
10
+ - jruby-19mode
11
+ - rbx-18mode
12
+ - rbx-19mode
13
+ notifications:
14
+ recipients:
15
+ - d.sukhonin@gmail.com
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: jruby-18mode
19
+ - rvm: jruby-19mode
20
+ - rvm: rbx-18mode
21
+ - rvm: rbx-19mode
data/Gemfile CHANGED
@@ -2,16 +2,17 @@ source "http://rubygems.org"
2
2
 
3
3
  gem "eventmachine"
4
4
  gem "em-http-request"
5
- gem "http_request.rb"
6
5
  gem "http_parser.rb"
7
6
  gem "tuple"
8
7
  gem "yajl-ruby"
9
8
 
10
9
  group :development do
11
10
  gem "bdb"
12
- gem "yard", "~> 0.6.0"
13
- gem "minitest", ">= 0"
14
- gem "bundler", "~> 1.0.0"
15
- gem "jeweler", "~> 1.5.2"
16
- gem "rcov", ">= 0"
11
+ gem "yard", "~> 0.6.0"
12
+ gem "minitest", ">= 0"
13
+ gem "bundler", "~> 1.0"
14
+ gem "jeweler", "~> 1.8.4"
15
+ gem "simplecov", ">= 0"
16
+ gem "simplecov-rcov", ">= 0"
17
+ gem "travis-lint"
17
18
  end
data/Gemfile.lock CHANGED
@@ -1,27 +1,43 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.2.6)
4
+ addressable (2.3.2)
5
5
  bdb (0.2.6.5)
6
6
  tuple (>= 0.1.2)
7
- em-http-request (0.3.0)
8
- addressable (>= 2.0.0)
9
- escape_utils
10
- eventmachine (>= 0.12.9)
11
- escape_utils (0.2.3)
12
- eventmachine (0.12.10)
7
+ cookiejar (0.3.0)
8
+ em-http-request (1.0.3)
9
+ addressable (>= 2.2.3)
10
+ cookiejar
11
+ em-socksify
12
+ eventmachine (>= 1.0.0.beta.4)
13
+ http_parser.rb (>= 0.5.3)
14
+ em-socksify (0.2.1)
15
+ eventmachine (>= 1.0.0.beta.4)
16
+ eventmachine (1.0.0)
13
17
  git (1.2.5)
14
- http_parser.rb (0.5.1)
15
- http_request.rb (1.1.11)
16
- jeweler (1.5.2)
17
- bundler (~> 1.0.0)
18
+ hashr (0.0.22)
19
+ http_parser.rb (0.5.3)
20
+ jeweler (1.8.4)
21
+ bundler (~> 1.0)
18
22
  git (>= 1.2.5)
19
23
  rake
20
- minitest (2.5.0)
21
- rake (0.9.2)
22
- rcov (0.9.10)
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)
30
+ json (~> 1.4)
31
+ simplecov (0.7.1)
32
+ multi_json (~> 1.0)
33
+ simplecov-html (~> 0.7.1)
34
+ simplecov-html (0.7.1)
35
+ simplecov-rcov (0.2.3)
36
+ simplecov (>= 0.4.1)
37
+ travis-lint (1.4.0)
38
+ hashr (>= 0.0.19)
23
39
  tuple (0.1.2)
24
- yajl-ruby (0.8.3)
40
+ yajl-ruby (1.1.0)
25
41
  yard (0.6.8)
26
42
 
27
43
  PLATFORMS
@@ -29,14 +45,15 @@ PLATFORMS
29
45
 
30
46
  DEPENDENCIES
31
47
  bdb
32
- bundler (~> 1.0.0)
48
+ bundler (~> 1.0)
33
49
  em-http-request
34
50
  eventmachine
35
51
  http_parser.rb
36
- http_request.rb
37
- jeweler (~> 1.5.2)
52
+ jeweler (~> 1.8.4)
38
53
  minitest
39
- rcov
54
+ simplecov
55
+ simplecov-rcov
56
+ travis-lint
40
57
  tuple
41
58
  yajl-ruby
42
59
  yard (~> 0.6.0)
data/README.rdoc CHANGED
@@ -1,18 +1,21 @@
1
1
  = yup daemon
2
2
 
3
- This is the small daemon for transparent asynchronous delegating HTTP requests when response is known or unimportant.
3
+ {<img src="https://secure.travis-ci.org/neglectedvalue/yup.png" alt="Build Status" />}[http://travis-ci.org/neglectedvalue/yup]
4
+ {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/neglectedvalue/yup]
4
5
 
5
- When a http request is arrived the yup daemon (yupd) answers to the client configured answer (by default 200 OK). Then the yupd forwards the http request to the specified host and retries if a timeout error was happend.
6
+ This is the small daemon to forward HTTP requests when response is known or unimportant.
7
+
8
+ 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.
6
9
 
7
10
  == Non-persistent queue
8
- By default, nonpersistent queue is used. A limit (the option --watermark) at which new requests will be dropped.
11
+ By default, nonpersistent queue is used. A limit (the option --watermark) at which new concurrent requests will be dropped.
9
12
 
10
13
  == Persistent queue
11
14
  If you want use persistent queue you need to specify the option --persistent with a path to a database.
12
15
 
13
16
  == One of use cases
14
17
 
15
- For example we can have a rails app which send exceptions to an errbit by the gem hoptoad_notifier. We know the errbit can be not available by network issues or some else reasons, but we do not want lose exceptions. To resolve this problem we can start yupd on the same host with the rails app:
18
+ 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:
16
19
  yupd --listen localhost:8081 --status-code 201 --persistent /var/db/yupd-errbit errbit.host.somewhere
17
20
 
18
21
  Reconfiguration of hoptoad_notifier is very ease:
@@ -22,25 +25,11 @@ Reconfiguration of hoptoad_notifier is very ease:
22
25
  config.api_key = "api_key_for_your_app"
23
26
  end
24
27
 
25
- Now problem of availability of errbit is assigned to the yupd.
26
-
27
- == Roadmap to 0.1
28
-
29
- * More better documentation
30
- * Daemonize
31
- * Preforking
32
- * A configurable map of different delegating rules
33
- * Tests...
28
+ Now problem of availability errbit is assigned to the yupd.
34
29
 
35
30
  == Contributing to yup
36
31
 
37
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
38
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
39
- * Fork the project
40
- * Start a feature/bugfix branch
41
- * Commit and push until you are happy with your contribution
42
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
43
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
32
+ Feel free to contribute.
44
33
 
45
34
  == Copyright
46
35
 
data/Rakefile CHANGED
@@ -15,14 +15,10 @@ Jeweler::Tasks.new do |gem|
15
15
  gem.name = "yup"
16
16
  gem.homepage = "http://github.com/neglectedvalue/yup"
17
17
  gem.license = "MIT"
18
- gem.summary = "Asynchronous HTTP delegate"
18
+ gem.summary = "HTTP forwarder"
19
19
  gem.description = "Just answers 200 (or specified) to a client and asynchronously forwards HTTP request to a configured host"
20
20
  gem.email = "d.sukhonin@gmail.com"
21
21
  gem.authors = ["Denis Sukhonin"]
22
- # Include your dependencies below. Runtime dependencies are required when using your gem,
23
- # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
- # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
- # gem.add_development_dependency 'rspec', '> 1.2.3'
26
22
  end
27
23
  Jeweler::RubygemsDotOrgTasks.new
28
24
 
@@ -33,13 +29,6 @@ Rake::TestTask.new(:test) do |test|
33
29
  test.verbose = true
34
30
  end
35
31
 
36
- require 'rcov/rcovtask'
37
- Rcov::RcovTask.new do |test|
38
- test.libs << 'test'
39
- test.pattern = 'test/**/test_*.rb'
40
- test.verbose = true
41
- end
42
-
43
32
  task :default => :test
44
33
 
45
34
  require 'yard'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.1.0
data/bin/yupd CHANGED
@@ -8,18 +8,20 @@ require 'yup'
8
8
 
9
9
  def usage
10
10
  puts <<-EOF
11
+ yup #{Yup::VERSION}
12
+
11
13
  Usage: #{$0} [OPTION] ... FORWARD_TO_HOST
12
14
 
13
15
  Options:
14
16
  -h, --help Show help
15
17
  --listen <host:port>, -l Listen on an address (default localhost:8080)
16
18
  --status-code <code> Send status code to a client on request (default 200)
17
- --resend-delay <seconds> Resend failed requests in seconds (default 5.0)
19
+ --resend-delay <seconds> Resend failed requests in seconds (default 60.0)
18
20
  --watermark <number> Maximum of concurrent requests (default 100)
19
21
  --loglevel <level> Logging severity (default fatal).
20
22
  Available log levels: fatal, error, warn, info, debug.
21
23
  --persistent <path> Use persistent queue.
22
- --timeout <seconds> Timeout for answer from FORWARD_TO_HOST
24
+ --timeout <seconds> Timeout for answer from FORWARD_TO_HOST (default 60.0)
23
25
 
24
26
  Examples:
25
27
  yupd --listen 0.0.0.0:8081 --status-code 201 errbit.host.somewhere
@@ -70,7 +72,7 @@ opts.each do |opt, arg|
70
72
  when '--persistent'
71
73
  config[:persistent] = arg || "/tmp/queue"
72
74
  when '--timeout'
73
- config[:timeout] = arg.to_i
75
+ config[:timeout] = arg.to_f
74
76
  end
75
77
  end
76
78
 
@@ -87,7 +89,6 @@ Yup.watermark = config[:watermark] if config.has_key?(:watermark)
87
89
  Yup.resend_delay = config[:resend_delay] if config.has_key?(:resend_delay)
88
90
 
89
91
  if config.has_key?(:persistent)
90
- # Yup::State.repair_if_need(config[:persistent])
91
92
  Yup.run_with_state(config)
92
93
  else
93
94
  Yup.run(config)
data/lib/yup.rb CHANGED
@@ -4,11 +4,12 @@ require 'logger'
4
4
  require 'yajl'
5
5
  require 'tmpdir'
6
6
 
7
+ require 'yup/version'
7
8
  require 'yup/request_forwarder'
8
9
  require 'yup/request_handler'
9
10
 
10
11
  module Yup
11
- @@resend_delay = 5.0
12
+ @@resend_delay = 60.0
12
13
  def self.resend_delay; @@resend_delay end
13
14
  def self.resend_delay=(seconds); @@resend_delay = seconds end
14
15
 
@@ -25,10 +26,10 @@ module Yup
25
26
  port = config[:listen_port] || 8080
26
27
  status_code = config[:status_code] || 200
27
28
  forward_to = config[:forward_to]
28
- timeout = config[:timeout] || 10
29
+ timeout = config[:timeout] || 60
29
30
 
30
- EventMachine.run do
31
- EventMachine.start_server(host, port, RequestHandler, forward_to, status_code, nil, timeout)
31
+ EM.run do
32
+ EM.start_server(host, port, RequestHandler, forward_to, status_code, nil, timeout)
32
33
  logger.info { "listening on #{host}:#{port}" }
33
34
  end
34
35
  end
@@ -41,7 +42,7 @@ module Yup
41
42
  status_code = config[:status_code] || 200
42
43
  forward_to = config[:forward_to]
43
44
  dbpath = config[:persistent]
44
- timeout = config[:timeout]
45
+ timeout = config[:timeout] || 60
45
46
  feedback_channel = File.join(Dir.tmpdir, "yupd-#{$$}-feedback")
46
47
  state = Yup::State.new(dbpath, forward_to, feedback_channel)
47
48
 
@@ -60,12 +61,12 @@ module Yup
60
61
  Signal.trap("INT", &db_closer)
61
62
  end
62
63
 
63
- EventMachine.run do
64
- EventMachine.start_server(host, port, RequestHandler, forward_to, status_code, state, timeout)
65
- logger.info { "Listening on #{host}:#{port}" }
66
-
67
- EventMachine.start_unix_domain_server(feedback_channel, State::FeedbackHandler, state)
64
+ EM.run do
65
+ EM.start_unix_domain_server(feedback_channel, State::FeedbackHandler, state)
68
66
  logger.info { "Feedback through #{feedback_channel}" }
67
+
68
+ EM.start_server(host, port, RequestHandler, forward_to, status_code, state, timeout)
69
+ logger.info { "Listening on #{host}:#{port}" }
69
70
  end
70
71
  end
71
72
  end
@@ -1,5 +1,4 @@
1
1
  require 'em-http-request'
2
- require 'http_request'
3
2
 
4
3
  module Yup
5
4
  class RequestForwarder
@@ -10,40 +9,56 @@ module Yup
10
9
  @body = body
11
10
  @forward_to = forward_to
12
11
  @timeout = timeout
12
+ @logger = Yup.logger.clone
13
13
 
14
- @logger = Yup.logger
14
+ @headers.merge!(
15
+ 'Host' => @forward_to,
16
+ 'Connection' => 'Close')
15
17
  end
16
18
 
17
- def run
19
+ def perform
18
20
  http_method = @http_method.to_sym
19
21
  http_url = "http://#{@forward_to}#{@request_url}"
20
22
  http = EventMachine::HttpRequest.
21
- new(http_url).
23
+ new(http_url,
24
+ :inactivity_timeout => @timeout).
22
25
  send(http_method,
23
- :timeout => @timeout,
24
- :head => @headers.merge('Host' => @forward_to),
26
+ :head => @headers,
25
27
  :body => @body)
26
28
 
29
+ @logger.progname = "Yup::RequestForwarder (##{http.__id__.to_s(36)} received at #{Time.now.to_s})"
30
+
27
31
  http.callback do
28
32
  Yup.watermark += 1
29
33
 
30
34
  if http.response_header.status / 100 == 2
31
- @logger.info '--- SUCCESS'
35
+ log_response(http)
36
+ @logger.info "Success"
32
37
  else
33
- @logger.info '--- FAIL'
34
- @logger.debug { http.inspect }
35
- @logger.debug { http.response_header.inspect }
36
- @logger.debug { http.response.inspect }
38
+ log_response(http)
39
+ @logger.info "Fail; will not retry"
37
40
  end
38
41
  end
39
42
 
40
43
  http.errback do
41
- @logger.info '--- ERROR'
42
- @logger.debug { http.inspect }
43
- @logger.debug { http.response_header.inspect }
44
- @logger.debug { http.response.inspect }
44
+ log_response(http)
45
+ @logger.info "Error: #{http.error}; will retry after #{Yup.resend_delay} seconds"
45
46
 
46
- EventMachine.add_timer(Yup.resend_delay) { self.run }
47
+ EventMachine.add_timer(Yup.resend_delay, &self.method(:retry))
48
+ end
49
+ end
50
+
51
+ def retry
52
+ self.perform
53
+ end
54
+
55
+ private
56
+ def log_response(http)
57
+ @logger.info { "HTTP request: #{@http_method.upcase} #{@request_url} HTTP/1.1" }
58
+ if http.response_header.http_status
59
+ @logger.info { "HTTP response: HTTP/#{http.response_header.http_version} #{http.response_header.http_status} #{http.response_header.http_reason}" }
60
+ @logger.debug { "HTTP response headers" + (http.response_header.empty? ? " is empty" : "\n" + http.response_header.inspect) }
61
+ @logger.debug { "HTTP response body" + (http.response.empty? ? " is empty" : "\n" + http.response.inspect) }
47
62
  end
48
63
  end
49
64
  end
@@ -53,11 +68,12 @@ module Yup
53
68
  def initialize(state, forward_to, timeout)
54
69
  @state = state
55
70
  @forward_to = forward_to
56
- @timeout = timeout
71
+ @timeout = timeout
72
+ @logger = Yup.logger.clone
73
+ @logger.progname = "Yup::State::RequestForwarder"
57
74
 
58
- @logger = Yup.logger
59
- @yajl = Yajl::Parser.new(:symbolize_keys => true)
60
- @yajl.on_parse_complete = method(:make_request)
75
+ @yajl = Yajl::Parser.new(:symbolize_keys => true)
76
+ @yajl.on_parse_complete = self.method(:make_request)
61
77
  end
62
78
 
63
79
  def run_loop
@@ -73,44 +89,85 @@ module Yup
73
89
 
74
90
  def make_request(req)
75
91
  begin
76
- http_method, request_url, headers, body = req
92
+ @http_method, @request_url, headers, body = req
77
93
  headers = Hash[headers.to_a.flatten.map(&:to_s)]
94
+ headers["Host"] = @forward_to
95
+ headers["Connection"] = "Close"
96
+
97
+ req = "#{@http_method.upcase} #{@request_url} HTTP/1.1\r\n"
98
+ headers.each do |k, v|
99
+ req << "#{k}: #{v}\r\n"
100
+ end
101
+ req << body if !body.empty?
102
+ req << "\r\n"
103
+ raw_response = send_data(req.to_s, @forward_to)
104
+
105
+ response_body = ""
106
+ http = Http::Parser.new()
107
+ http.on_body = proc do |chunk|
108
+ response_body << chunk
109
+ end
110
+ http << raw_response
78
111
 
79
- http_method = http_method.to_sym
80
- http_url = "http://#{@forward_to}#{request_url}"
81
- http = HttpRequest.
82
- send(http_method,
83
- :url => http_url,
84
- :headers => headers.merge('Host' => @forward_to),
85
- :parameters => body,
86
- :timeout => timeout)
87
-
88
- if http.code_2xx?
89
- @logger.info '--- SUCCESS'
112
+ if http.status_code / 100 == 2
113
+ log_response(raw_response, response_body, http)
114
+ @logger.info "Success"
90
115
  else
91
- @logger.info '--- FAIL'
92
- @logger.debug { http.inspect }
93
- @logger.debug { http.response_header.inspect }
94
- @logger.debug { http.response.inspect }
116
+ log_response(raw_response, response_body, http)
117
+ @logger.info "Fail; will not retry"
95
118
  end
96
- rescue Exception => e
97
- @logger.info '--- ERROR'
98
- @logger.debug { e }
99
119
 
100
- @state.to_feedback(Yajl::Encoder.encode([http_method.downcase, request_url, headers, body]))
120
+ rescue Exception, Timeout::Error => e
121
+ log_response(raw_response, response_body, http)
122
+ @logger.info "Error: #{e.message}; will retry after #{Yup.resend_delay} seconds"
123
+
124
+ @state.to_feedback(Yajl::Encoder.encode([@http_method.downcase, @request_url, headers, body]))
101
125
 
102
126
  sleep Yup.resend_delay
103
127
  end
104
128
  end
129
+
130
+ private
131
+ def log_response(raw_response, body, http)
132
+ @logger.info { "HTTP request: #{@http_method.upcase} #{@request_url} HTTP/1.1" }
133
+ if raw_response && !raw_response.empty?
134
+ @logger.info { "HTTP response: #{raw_response.lines.first.chomp}" }
135
+ @logger.debug { "HTTP response headers" + (http.headers.empty? ? " is empty" : "\n" + http.headers.inspect) }
136
+ @logger.debug { "HTTP response body" + (body.empty? ? " is empty" : "\n" + body.inspect) }
137
+ end
138
+ end
139
+
140
+ def send_data(data, host)
141
+ host, port = host.split(":")
142
+ addr = Socket.getaddrinfo(host, nil)
143
+ sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
144
+
145
+ secs = Integer(@timeout)
146
+ usecs = Integer((@timeout - secs) * 1_000_000)
147
+ optval = [secs, usecs].pack("l_2")
148
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
149
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval)
150
+
151
+ resp = Timeout::timeout(@timeout) do
152
+ sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
153
+ sock.write(data)
154
+ sock.read()
155
+ end
156
+ return resp
157
+ ensure
158
+ sock.close()
159
+ end
105
160
  end
106
161
 
107
162
  class FeedbackHandler < EM::Connection
108
163
  def initialize(state)
109
164
  @state = state
110
165
 
111
- @logger = Yup.logger
112
166
  @yajl = Yajl::Parser.new(:symbolize_keys => true)
113
167
  @yajl.on_parse_complete = method(:on_message)
168
+
169
+ @logger = Yup.logger.clone
170
+ @logger.progname = "Yup::State::FeedbackHandler"
114
171
  end
115
172
 
116
173
  def receive_data(data)
@@ -11,8 +11,10 @@ module Yup
11
11
  @state = state
12
12
  @timeout = timeout
13
13
 
14
- @logger = Yup.logger
15
14
  @chunks = []
15
+
16
+ @logger = Yup.logger.clone
17
+ @logger.progname = "Yup::RequestHandler"
16
18
  end
17
19
 
18
20
  def post_init
@@ -32,12 +34,15 @@ module Yup
32
34
  end
33
35
 
34
36
  def on_message_complete
35
- @logger.info "-- got request"
36
- @logger.info { "HTTP version : " + @parser.http_version.join('.') }
37
- @logger.info { "HTTP method : " + @parser.http_method }
38
- @logger.info { "HTTP request_url : " + @parser.request_url }
39
- @logger.debug { "HTTP headers : " + @parser.headers.inspect }
40
- @logger.debug { "HTTP body : " + @body }
37
+ @logger.info {
38
+ "Processing a new request: #{@parser.http_method} #{@parser.request_url} HTTP/#{@parser.http_version.join('.')}"
39
+ }
40
+ @logger.debug {
41
+ "HTTP headers" + (@parser.headers.empty? ? " is empty" : "\n" + @parser.headers.inspect)
42
+ }
43
+ @logger.debug {
44
+ "HTTP body" + (@body.empty? ? " is empty" : "\n" + @body)
45
+ }
41
46
 
42
47
  send_answer
43
48
  shedule_request
@@ -49,6 +54,12 @@ module Yup
49
54
  resp.status = @status_code
50
55
  resp['Server'] = 'yupd'
51
56
  send_data resp.to_s
57
+ close_connection_after_writing
58
+
59
+ @logger.info {
60
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
61
+ "Sent the answer #{@status_code} to a client #{ip}:#{port}"
62
+ }
52
63
  end
53
64
 
54
65
  def shedule_request
@@ -57,12 +68,11 @@ module Yup
57
68
  else
58
69
  unless Yup.watermark.zero?
59
70
  Yup.watermark -= 1
60
-
61
- EventMachine.next_tick do
62
- RequestForwarder.new(@parser.http_method.downcase, @parser.request_url, @parser.headers, @body, @forward_to, @timeout).run
71
+ EM.next_tick do
72
+ RequestForwarder.new(@parser.http_method.downcase, @parser.request_url, @parser.headers, @body, @forward_to, @timeout).perform
63
73
  end
64
74
  else
65
- @logger.error "-- watermark is reached, drop"
75
+ @logger.error "Watermark is reached, drop the request"
66
76
  end
67
77
  end
68
78
  end
data/lib/yup/state.rb CHANGED
@@ -5,6 +5,8 @@ rescue LoadError
5
5
  puts "Install bdb gem to use a persistent queue."
6
6
  end
7
7
 
8
+ require "timeout"
9
+
8
10
  module Yup
9
11
  class State
10
12
  RE_LEN = 1000
@@ -22,6 +24,10 @@ module Yup
22
24
  @name = name
23
25
  @feedback_channel = feedback_channel
24
26
 
27
+ @logger = Yup.logger.clone
28
+ @logger.progname = "Yup::State"
29
+
30
+ FileUtils.mkdir_p(@path)
25
31
  @env = Bdb::Env.new(0)
26
32
  @env = @env.open(@path, Bdb::DB_CREATE | Bdb::DB_INIT_MPOOL | Bdb::DB_INIT_CDB, 0)
27
33
  @queue = @env.db
@@ -30,7 +36,7 @@ module Yup
30
36
  end
31
37
 
32
38
  def push(data)
33
- Yup.logger.debug { "Pushing \"#{data}\"" }
39
+ @logger.debug { "Push: #{data}" }
34
40
  i = 0
35
41
  until (chunk = data.slice(i, RE_LEN)).nil?
36
42
  @queue.put(nil, "", chunk, Bdb::DB_APPEND)
@@ -40,12 +46,12 @@ module Yup
40
46
 
41
47
  def bpop
42
48
  data = @queue.get(nil, "", nil, Bdb::DB_CONSUME_WAIT)
43
- Yup.logger.debug { "Bpoping \"#{data}\"" }
49
+ @logger.debug { "Bpoped: #{data.strip}" }
44
50
  data
45
51
  end
46
52
 
47
53
  def to_feedback(data)
48
- Yup.logger.debug { "Pushing to the feedback channel \"#{data}\"" }
54
+ @logger.debug { "Push to the feedback channel: #{data.strip}" }
49
55
  sock = UNIXSocket.new(@feedback_channel)
50
56
  sock.send(data, 0)
51
57
  sock.close
@@ -0,0 +1,3 @@
1
+ module Yup
2
+ VERSION = File.read("#{File.dirname(__FILE__)}/../../VERSION")
3
+ end
data/test/helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler'
3
5
  begin
@@ -9,6 +11,19 @@ rescue Bundler::BundlerError => e
9
11
  end
10
12
  require 'minitest/unit'
11
13
 
14
+ require 'simplecov'
15
+ require 'simplecov-rcov'
16
+
17
+ class SimpleCov::Formatter::MergedFormatter
18
+ def format(result)
19
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
20
+ SimpleCov::Formatter::RcovFormatter.new.format(result)
21
+ end
22
+ end
23
+
24
+ SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
25
+ SimpleCov.start
26
+
12
27
  $LOAD_PATH.unshift(File.dirname(__FILE__))
13
28
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
29
  require 'yup'
@@ -0,0 +1,101 @@
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 200 OK\r\nServer: test\r\n\r\n"
35
+ close_connection_after_writing
36
+ end
37
+ end
38
+
39
+ def unbind
40
+ if $attempts > 0
41
+ EM.add_timer(1) do
42
+ Process.kill("KILL", $pid)
43
+ EM.stop_event_loop()
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ module Client
50
+ def connection_completed
51
+ send_data("GET /foo HTTP/1.0\r\n\r\n")
52
+ end
53
+
54
+ def post_init
55
+ @parser = Http::Parser.new(self)
56
+ end
57
+
58
+ def receive_data(data)
59
+ @parser << data
60
+ end
61
+
62
+ def on_message_complete
63
+ $client_parser = @parser
64
+ end
65
+ end
66
+
67
+ def test_request_handler
68
+ $attempts = 0
69
+
70
+ dbpath = Dir.mktmpdir("yupd-db")
71
+ feedback_channel = File.join(Dir.tmpdir, "yupd-#{$$}-feedback")
72
+
73
+ forward_to = "127.0.0.1:26785"
74
+ status_code = 200
75
+ state = Yup::State.new(dbpath, forward_to, feedback_channel)
76
+ timeout = 1
77
+
78
+ Yup.resend_delay = 1
79
+
80
+ $pid = Process.fork do
81
+ Yup::State::RequestForwarder.new(state, forward_to, timeout).run_loop
82
+ end
83
+
84
+ EM.run {
85
+ EM.start_server("127.0.0.1", 26785, Service)
86
+ EM.start_unix_domain_server(feedback_channel, Yup::State::FeedbackHandler, state)
87
+ EM.start_server("127.0.0.1", 26784, RequestHandlerMock, forward_to, status_code, state, timeout)
88
+ EM.connect("127.0.0.1", 26784, Client)
89
+ }
90
+
91
+ assert $client_parser
92
+ assert_equal 200, $client_parser.status_code
93
+ assert_equal "yupd", $client_parser.headers["Server"]
94
+ assert $service_parser
95
+ assert_equal "/foo", $service_parser.request_url
96
+ ensure
97
+ Process.kill("KILL", $pid) if $pid
98
+ state.close if state
99
+ FileUtils.remove_entry_secure(dbpath) if dbpath
100
+ end
101
+ end
data/test/test_yup.rb CHANGED
@@ -1,11 +1,81 @@
1
1
  require 'helper'
2
2
 
3
+ class Yup::RequestForwarder
4
+ alias :retry_original :retry
5
+ def retry()
6
+ $attempts += 1
7
+ retry_original()
8
+ end
9
+ end
10
+
3
11
  class TestYup < MiniTest::Unit::TestCase
4
- def test_request_handler
5
- flunk
12
+ class RequestHandlerMock < Yup::RequestHandler
13
+ end
14
+
15
+ class Service < EM::Connection
16
+ def post_init
17
+ @parser = Http::Parser.new(self)
18
+ end
19
+
20
+ def receive_data(data)
21
+ @parser << data
22
+ end
23
+
24
+ def on_message_complete
25
+ $service_parser = @parser
26
+
27
+ case $attempts
28
+ when 0
29
+ when 1
30
+ send_data "HTTP/1.1 200 OK\r\nServer: test\r\n\r\n"
31
+ end
32
+ end
33
+
34
+ def unbind
35
+ if $attempts > 0
36
+ EM.next_tick { EM.stop_event_loop }
37
+ end
38
+ end
6
39
  end
7
40
 
8
- def test_request_forwarder
9
- flunk
41
+ module Client
42
+ def connection_completed
43
+ send_data "GET /foo HTTP/1.0\r\n\r\n"
44
+ end
45
+
46
+ def post_init
47
+ @parser = Http::Parser.new(self)
48
+ end
49
+
50
+ def receive_data(data)
51
+ @parser << data
52
+ end
53
+
54
+ def on_message_complete
55
+ $client_parser = @parser
56
+ end
57
+ end
58
+
59
+ def test_request_handler
60
+ $attempts = 0
61
+
62
+ forward_to = "127.0.0.1:16785"
63
+ status_code = 200
64
+ state = nil
65
+ timeout = 1
66
+
67
+ Yup.resend_delay = 1
68
+
69
+ EM.run {
70
+ EM.start_server("127.0.0.1", 16785, Service)
71
+ EM.start_server("127.0.0.1", 16784, RequestHandlerMock, forward_to, status_code, state, timeout)
72
+ EM.connect("127.0.0.1", 16784, Client)
73
+ }
74
+
75
+ assert $client_parser
76
+ assert_equal 200, $client_parser.status_code
77
+ assert_equal "yupd", $client_parser.headers["Server"]
78
+ assert $service_parser
79
+ assert_equal "/foo", $service_parser.request_url
10
80
  end
11
81
  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.0.8"
8
+ s.version = "0.1.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 = "2012-06-29"
12
+ s.date = "2012-11-09"
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"]
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  ]
20
20
  s.files = [
21
21
  ".document",
22
+ ".travis.yml",
22
23
  "Gemfile",
23
24
  "Gemfile.lock",
24
25
  "LICENSE.txt",
@@ -30,7 +31,9 @@ Gem::Specification.new do |s|
30
31
  "lib/yup/request_forwarder.rb",
31
32
  "lib/yup/request_handler.rb",
32
33
  "lib/yup/state.rb",
34
+ "lib/yup/version.rb",
33
35
  "test/helper.rb",
36
+ "test/test_persistence_yup.rb",
34
37
  "test/test_yup.rb",
35
38
  "yup.gemspec"
36
39
  ]
@@ -38,11 +41,7 @@ Gem::Specification.new do |s|
38
41
  s.licenses = ["MIT"]
39
42
  s.require_paths = ["lib"]
40
43
  s.rubygems_version = "1.8.24"
41
- s.summary = "Asynchronous HTTP delegate"
42
- s.test_files = [
43
- "test/helper.rb",
44
- "test/test_yup.rb"
45
- ]
44
+ s.summary = "HTTP forwarder"
46
45
 
47
46
  if s.respond_to? :specification_version then
48
47
  s.specification_version = 3
@@ -50,43 +49,46 @@ Gem::Specification.new do |s|
50
49
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
50
  s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
52
51
  s.add_runtime_dependency(%q<em-http-request>, [">= 0"])
53
- s.add_runtime_dependency(%q<http_request.rb>, [">= 0"])
54
52
  s.add_runtime_dependency(%q<http_parser.rb>, [">= 0"])
55
53
  s.add_runtime_dependency(%q<tuple>, [">= 0"])
56
54
  s.add_runtime_dependency(%q<yajl-ruby>, [">= 0"])
57
55
  s.add_development_dependency(%q<bdb>, [">= 0"])
58
56
  s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
59
57
  s.add_development_dependency(%q<minitest>, [">= 0"])
60
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
61
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
62
- s.add_development_dependency(%q<rcov>, [">= 0"])
58
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
59
+ 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"])
63
63
  else
64
64
  s.add_dependency(%q<eventmachine>, [">= 0"])
65
65
  s.add_dependency(%q<em-http-request>, [">= 0"])
66
- s.add_dependency(%q<http_request.rb>, [">= 0"])
67
66
  s.add_dependency(%q<http_parser.rb>, [">= 0"])
68
67
  s.add_dependency(%q<tuple>, [">= 0"])
69
68
  s.add_dependency(%q<yajl-ruby>, [">= 0"])
70
69
  s.add_dependency(%q<bdb>, [">= 0"])
71
70
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
72
71
  s.add_dependency(%q<minitest>, [">= 0"])
73
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
74
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
75
- s.add_dependency(%q<rcov>, [">= 0"])
72
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
73
+ 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"])
76
77
  end
77
78
  else
78
79
  s.add_dependency(%q<eventmachine>, [">= 0"])
79
80
  s.add_dependency(%q<em-http-request>, [">= 0"])
80
- s.add_dependency(%q<http_request.rb>, [">= 0"])
81
81
  s.add_dependency(%q<http_parser.rb>, [">= 0"])
82
82
  s.add_dependency(%q<tuple>, [">= 0"])
83
83
  s.add_dependency(%q<yajl-ruby>, [">= 0"])
84
84
  s.add_dependency(%q<bdb>, [">= 0"])
85
85
  s.add_dependency(%q<yard>, ["~> 0.6.0"])
86
86
  s.add_dependency(%q<minitest>, [">= 0"])
87
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
88
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
89
- s.add_dependency(%q<rcov>, [">= 0"])
87
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
88
+ 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"])
90
92
  end
91
93
  end
92
94
 
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.0.8
4
+ version: 0.1.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: 2012-06-29 00:00:00.000000000 Z
12
+ date: 2012-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -43,22 +43,6 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
- - !ruby/object:Gem::Dependency
47
- name: http_request.rb
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
46
  - !ruby/object:Gem::Dependency
63
47
  name: http_parser.rb
64
48
  requirement: !ruby/object:Gem::Requirement
@@ -162,7 +146,7 @@ dependencies:
162
146
  requirements:
163
147
  - - ~>
164
148
  - !ruby/object:Gem::Version
165
- version: 1.0.0
149
+ version: '1.0'
166
150
  type: :development
167
151
  prerelease: false
168
152
  version_requirements: !ruby/object:Gem::Requirement
@@ -170,7 +154,7 @@ dependencies:
170
154
  requirements:
171
155
  - - ~>
172
156
  - !ruby/object:Gem::Version
173
- version: 1.0.0
157
+ version: '1.0'
174
158
  - !ruby/object:Gem::Dependency
175
159
  name: jeweler
176
160
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +162,7 @@ dependencies:
178
162
  requirements:
179
163
  - - ~>
180
164
  - !ruby/object:Gem::Version
181
- version: 1.5.2
165
+ version: 1.8.4
182
166
  type: :development
183
167
  prerelease: false
184
168
  version_requirements: !ruby/object:Gem::Requirement
@@ -186,9 +170,41 @@ dependencies:
186
170
  requirements:
187
171
  - - ~>
188
172
  - !ruby/object:Gem::Version
189
- version: 1.5.2
173
+ version: 1.8.4
190
174
  - !ruby/object:Gem::Dependency
191
- name: rcov
175
+ name: simplecov
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: simplecov-rcov
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ - !ruby/object:Gem::Dependency
207
+ name: travis-lint
192
208
  requirement: !ruby/object:Gem::Requirement
193
209
  none: false
194
210
  requirements:
@@ -214,6 +230,7 @@ extra_rdoc_files:
214
230
  - README.rdoc
215
231
  files:
216
232
  - .document
233
+ - .travis.yml
217
234
  - Gemfile
218
235
  - Gemfile.lock
219
236
  - LICENSE.txt
@@ -225,7 +242,9 @@ files:
225
242
  - lib/yup/request_forwarder.rb
226
243
  - lib/yup/request_handler.rb
227
244
  - lib/yup/state.rb
245
+ - lib/yup/version.rb
228
246
  - test/helper.rb
247
+ - test/test_persistence_yup.rb
229
248
  - test/test_yup.rb
230
249
  - yup.gemspec
231
250
  homepage: http://github.com/neglectedvalue/yup
@@ -243,7 +262,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
243
262
  version: '0'
244
263
  segments:
245
264
  - 0
246
- hash: -2903462913094353804
265
+ hash: 2564994728519902623
247
266
  required_rubygems_version: !ruby/object:Gem::Requirement
248
267
  none: false
249
268
  requirements:
@@ -255,7 +274,5 @@ rubyforge_project:
255
274
  rubygems_version: 1.8.24
256
275
  signing_key:
257
276
  specification_version: 3
258
- summary: Asynchronous HTTP delegate
259
- test_files:
260
- - test/helper.rb
261
- - test/test_yup.rb
277
+ summary: HTTP forwarder
278
+ test_files: []