happening 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -34,9 +34,11 @@ The above examples are a bit useless, as you never get any content back.
34
34
  You need to specify a callback that interacts with the http response:
35
35
 
36
36
  EM.run do
37
- on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
38
37
  item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
39
- item.get # authenticated download
38
+ item.get do |response|
39
+ puts "the response content is: #{response.response}"
40
+ EM.stop
41
+ end
40
42
  end
41
43
 
42
44
  This will enqueue your download and run it in the EventMachine event loop.
@@ -44,13 +46,17 @@ This will enqueue your download and run it in the EventMachine event loop.
44
46
  You can also react to errors:
45
47
 
46
48
  EM.run do
47
- on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
48
- on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
49
+ on_error = Proc.new {|response| puts "An error occured: #{response.response_header.status}"; EM.stop }
49
50
  item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
50
- item.get
51
+ item.get(:on_error => on_error) do |response|
52
+ puts "the response content is: #{response.response}"
53
+ EM.stop
54
+ end
51
55
  end
52
-
53
- Downloading many files would look like this:
56
+
57
+ If you don't supply an error handler yourself, Happening will be default raise an Exception.
58
+
59
+ Downloading many files could look like this:
54
60
 
55
61
  EM.run do
56
62
  count = 100
@@ -59,7 +65,7 @@ Downloading many files would look like this:
59
65
 
60
66
  count.times do |i|
61
67
  item = Happening::S3::Item.new('bucket', "item_#{i}", :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
62
- item.get
68
+ item.get(:on_success => on_success, :on_error => on_error)
63
69
  end
64
70
  end
65
71
 
@@ -70,9 +76,10 @@ Happening supports the simple S3 PUT upload:
70
76
 
71
77
  EM.run do
72
78
  on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
73
- on_success = Proc.new {|http| puts "Upload finished!"; EM.stop }
74
- item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
75
- item.put( File.read('/etc/passwd') )
79
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret', :on_success => on_success, :on_error => on_error)
80
+ item.put( File.read('/etc/passwd'), :on_error => on_error ) do |response|
81
+ puts "Upload finished!"; EM.stop
82
+ end
76
83
  end
77
84
 
78
85
  Setting permissions looks like this:
@@ -81,7 +88,7 @@ Setting permissions looks like this:
81
88
  on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
82
89
  on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
83
90
  item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret', :permissions => 'public-write')
84
- item.get
91
+ item.get(:on_success => on_success, :on_error => on_error)
85
92
  end
86
93
 
87
94
  Deleting
@@ -92,7 +99,7 @@ Happening support the simple S3 PUT upload:
92
99
  EM.run do
93
100
  on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
94
101
  on_success = Proc.new {|http| puts "Deleted!"; EM.stop }
95
- item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
102
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret', :on_success => on_success, :on_error => on_error)
96
103
  item.delete
97
104
  end
98
105
 
@@ -12,8 +12,6 @@ module Happening
12
12
  def initialize(bucket, aws_id, options = {})
13
13
  @options = {
14
14
  :timeout => 10,
15
- :on_error => nil,
16
- :on_success => nil,
17
15
  :server => 's3.amazonaws.com',
18
16
  :protocol => 'https',
19
17
  :aws_access_key_id => nil,
@@ -21,51 +19,38 @@ module Happening
21
19
  :retry_count => 4,
22
20
  :permissions => 'private'
23
21
  }.update(options.symbolize_keys)
24
- options.assert_valid_keys(:timeout, :on_success, :on_error, :server, :protocol, :aws_access_key_id, :aws_secret_access_key, :retry_count, :permissions)
22
+ options.assert_valid_keys(:timeout, :server, :protocol, :aws_access_key_id, :aws_secret_access_key, :retry_count, :permissions)
25
23
  @aws_id = aws_id.to_s
26
24
  @bucket = bucket.to_s
27
25
 
28
26
  validate
29
27
  end
30
28
 
31
- def get
29
+ def get(request_options = {}, &blk)
32
30
  headers = needs_to_sign? ? aws.sign("GET", path) : {}
33
- Happening::Log.debug "GET #{url}"
34
- http = http_class.new(url).get(:timeout => options[:timeout], :head => headers)
35
-
36
- http.errback { error_callback(:get, http) }
37
- http.callback { success_callback(:get, http) }
38
- nil
31
+ request_options[:on_success] = blk if blk
32
+ request_options.update(:headers => headers)
33
+ Happening::S3::Request.new(:get, url, request_options).execute
39
34
  end
40
35
 
41
- def put(data)
36
+ def put(data, request_options = {})
42
37
  permissions = options[:permissions] != 'private' ? {'x-amz-acl' => options[:permissions] } : {}
43
38
  headers = needs_to_sign? ? aws.sign("PUT", path, permissions.update({'url' => path})) : {}
44
- Happening::Log.debug "PUT #{url}"
45
- http = http_class.new(url).put(:timeout => options[:timeout], :head => headers, :body => data)
46
-
47
- http.errback { error_callback(:put, http) }
48
- http.callback { success_callback(:put, http, data) }
49
- nil
39
+
40
+ request_options.update(:headers => headers, :data => data)
41
+ Happening::S3::Request.new(:put, url, request_options).execute
50
42
  end
51
43
 
52
- def delete
44
+ def delete(request_options = {})
53
45
  headers = needs_to_sign? ? aws.sign("DELETE", path, {'url' => path}) : {}
54
- Happening::Log.debug "DELETE #{url}"
55
- http = http_class.new(url).delete(:timeout => options[:timeout], :head => headers)
56
-
57
- http.errback { error_callback(:delete, http) }
58
- http.callback { success_callback(:delete, http) }
59
- nil
46
+
47
+ request_options.update(:headers => headers)
48
+ Happening::S3::Request.new(:delete, url, request_options).execute
60
49
  end
61
50
 
62
51
  def url
63
52
  URI::Generic.new(options[:protocol], nil, server, port, nil, path(!dns_bucket?), nil, nil, nil).to_s
64
53
  end
65
-
66
- def http_class
67
- EventMachine::HttpRequest
68
- end
69
54
 
70
55
  def server
71
56
  dns_bucket? ? "#{bucket}.#{options[:server]}" : options[:server]
@@ -76,89 +61,7 @@ module Happening
76
61
  end
77
62
 
78
63
  protected
79
-
80
- def error_callback(http_method, http)
81
- call_user_error_handler(http)
82
- end
83
-
84
- def success_callback(http_method, http, data=nil)
85
- Happening::Log.debug "Response #{http.response_header.status}"
86
- case http.response_header.status
87
- when 0, 400, 401, 404, 403, 409, 411, 412, 416, 500, 503
88
- if should_retry?
89
- Happening::Log.debug "retrying after: status #{http.response_header.status rescue ''}"
90
- handle_retry(http_method, data)
91
- else
92
- call_user_error_handler(http)
93
- end
94
- when 300, 301, 303, 304, 307
95
- Happening::Log.info "being redirected_to: #{http.response_header['LOCATION'] rescue ''}"
96
- handle_redirect(http_method, http.response_header['LOCATION'], data)
97
- else
98
- call_user_success_handler(http)
99
- end
100
- end
101
-
102
- def call_user_error_handler(http)
103
- options[:on_error].call(http) if options[:on_error].respond_to?(:call)
104
- end
105
-
106
- def call_user_success_handler(http)
107
- options[:on_success].call(http) if options[:on_success].respond_to?(:call)
108
- end
109
-
110
- def should_retry?
111
- options[:retry_count] > 0
112
- end
113
-
114
- def handle_retry(http_method, data)
115
- if should_retry?
116
- new_request = self.class.new(bucket, aws_id, options.update(:retry_count => options[:retry_count] - 1 ))
117
- case http_method
118
- when :get
119
- new_request.get
120
- when :put
121
- new_request.put(data)
122
- when :delete
123
- new_request.delete
124
- else
125
- raise "unknown http method #{http_method}"
126
- end
127
- else
128
- Happening::Log.info "Re-tried too often - giving up" if Happening.debug?
129
- end
130
- end
131
-
132
- def handle_redirect(http_method, location, data)
133
- new_server, new_path = extract_location(location)
134
-
135
- new_request = self.class.new(bucket, aws_id, options.update(:server => new_server))
136
- case http_method
137
- when :get
138
- new_request.get
139
- when :put
140
- new_request.put(data)
141
- when :delete
142
- new_request.delete
143
- else
144
- raise "unknown http method #{http_method}"
145
- end
146
- end
147
-
148
- def extract_location(location)
149
- uri = URI.parse(location)
150
- if match = uri.host.match(/\A#{bucket}\.(.*)/)
151
- server = match[1]
152
- path = uri.path
153
- elsif match = uri.path.match(/\A\/#{bucket}\/(.*)/)
154
- server = uri.host
155
- path = match[1]
156
- else
157
- raise "being redirected to an not understood place: #{location}"
158
- end
159
- return server, path.sub(/^\//, '')
160
- end
161
-
64
+
162
65
  def needs_to_sign?
163
66
  options[:aws_access_key_id].present?
164
67
  end
@@ -0,0 +1,103 @@
1
+ module Happening
2
+ module S3
3
+ class Request
4
+
5
+ VALID_HTTP_METHODS = [:get, :put, :delete]
6
+
7
+ attr_accessor :http_method, :url, :options, :response
8
+
9
+ def initialize(http_method, url, options = {})
10
+ @options = {
11
+ :timeout => 10,
12
+ :retry_count => 4,
13
+ :headers => {},
14
+ :on_error => nil,
15
+ :on_success => nil,
16
+ :data => nil
17
+ }.update(options)
18
+ options.assert_valid_keys(:timeout, :on_success, :on_error, :retry_count, :headers, :data)
19
+ @http_method = http_method
20
+ @url = url
21
+
22
+ validate
23
+ end
24
+
25
+ def execute
26
+ Happening::Log.debug "#{http_method.to_s.upcase} #{url}"
27
+ @response = http_class.new(url).send(http_method, :timeout => options[:timeout], :head => options[:headers], :body => options[:data])
28
+
29
+ @response.errback { error_callback }
30
+ @response.callback { success_callback }
31
+ nil
32
+ end
33
+
34
+ def http_class
35
+ EventMachine::HttpRequest
36
+ end
37
+
38
+ protected
39
+
40
+ def validate
41
+ raise ArgumentError, "#{http_method} is not a valid HTTP method that #{self.class.name} understands." unless VALID_HTTP_METHODS.include?(http_method)
42
+ end
43
+
44
+ def error_callback
45
+ Happening::Log.error "Response #{response.response_header.status rescue ''}"
46
+ if options[:on_error].respond_to?(:call)
47
+ call_user_error_handler
48
+ else
49
+ raise Happening::Error.new("Failed reponse! Status code was #{response.response_header.status rescue ''}")
50
+ end
51
+ end
52
+
53
+ def success_callback
54
+ Happening::Log.debug "Response #{response.response_header.status rescue ''}"
55
+ case response.response_header.status
56
+ when 0, 400, 401, 404, 403, 409, 411, 412, 416, 500, 503
57
+ if should_retry?
58
+ Happening::Log.debug "retrying after: status #{response.response_header.status rescue ''}"
59
+ handle_retry
60
+ else
61
+ error_callback
62
+ end
63
+ when 300, 301, 303, 304, 307
64
+ Happening::Log.info "being redirected_to: #{response.response_header['LOCATION'] rescue ''}"
65
+ handle_redirect
66
+ else
67
+ call_user_success_handler
68
+ end
69
+ end
70
+
71
+ def call_user_success_handler
72
+ options[:on_success].call(response) if options[:on_success].respond_to?(:call)
73
+ end
74
+
75
+ def call_user_error_handler
76
+ options[:on_error].call(response) if options[:on_error].respond_to?(:call)
77
+ end
78
+
79
+ def should_retry?
80
+ options[:retry_count] > 0
81
+ end
82
+
83
+
84
+ def handle_retry
85
+ if should_retry?
86
+ new_request = self.class.new(http_method, url, options.update(:retry_count => options[:retry_count] - 1 ))
87
+ new_request.execute
88
+ else
89
+ Happening::Log.info "Re-tried too often - giving up" if Happening.debug?
90
+ end
91
+ end
92
+
93
+ def handle_redirect
94
+ new_location = response.response_header['LOCATION'] rescue ''
95
+ raise "Could not find the location to redirect to, empty location header?" if new_location.blank?
96
+
97
+ new_request = self.class.new(http_method, new_location, options)
98
+ new_request.execute
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -68,9 +68,31 @@ class ItemTest < Test::Unit::TestCase
68
68
  called = false
69
69
  data = nil
70
70
  on_success = Proc.new {|http| called = true, data = http.response}
71
- @item = Happening::S3::Item.new('bucket', 'the-key', :on_success => on_success)
71
+ @item = Happening::S3::Item.new('bucket', 'the-key')
72
72
  run_in_em_loop do
73
- @item.get
73
+ @item.get(:on_success => on_success)
74
+
75
+ EM.add_timer(1) {
76
+ assert called
77
+ assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :get, {})
78
+ assert_equal "data-here\n", data
79
+ EM.stop_event_loop
80
+ }
81
+
82
+ end
83
+ end
84
+
85
+ should "support direct blocks" do
86
+ EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {}, fake_response("data-here"))
87
+
88
+ called = false
89
+ data = nil
90
+ @item = Happening::S3::Item.new('bucket', 'the-key')
91
+ run_in_em_loop do
92
+ @item.get do |http|
93
+ called = true
94
+ data = http.response
95
+ end
74
96
 
75
97
  EM.add_timer(1) {
76
98
  assert called
@@ -104,7 +126,7 @@ class ItemTest < Test::Unit::TestCase
104
126
 
105
127
  @item = Happening::S3::Item.new('bucket', 'the-key')
106
128
  run_in_em_loop do
107
- @item.get
129
+ @item.get(:on_error => Proc.new{} ) #ignore error
108
130
 
109
131
  EM.add_timer(1) {
110
132
  EM.stop_event_loop
@@ -191,7 +213,7 @@ class ItemTest < Test::Unit::TestCase
191
213
 
192
214
  @item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
193
215
  run_in_em_loop do
194
- @item.delete
216
+ @item.delete(:on_error => Proc.new{} ) #ignore error
195
217
 
196
218
  EM.add_timer(1) {
197
219
  EM.stop_event_loop
@@ -289,7 +311,7 @@ class ItemTest < Test::Unit::TestCase
289
311
 
290
312
  @item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
291
313
  run_in_em_loop do
292
- @item.put('content')
314
+ @item.put('content', :on_error => Proc.new{} )
293
315
 
294
316
  EM.add_timer(1) {
295
317
  EM.stop_event_loop
@@ -311,9 +333,9 @@ class ItemTest < Test::Unit::TestCase
311
333
  called = false
312
334
  on_error = Proc.new {|http| called = true}
313
335
 
314
- @item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123', :retry_count => 1, :on_error => on_error)
336
+ @item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
315
337
  run_in_em_loop do
316
- @item.put('content')
338
+ @item.put('content', :on_error => on_error, :retry_count => 1)
317
339
 
318
340
  EM.add_timer(1) {
319
341
  EM.stop_event_loop
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + "/../test_helper"
2
+
3
+ class ItemTest < Test::Unit::TestCase
4
+ context "An Happening::S3::Request instance" do
5
+
6
+ setup do
7
+ Happening::Log.level = Logger::DEBUG
8
+ @response_stub = stub()
9
+ @response_stub.stubs(:errback)
10
+ @response_stub.stubs(:callback)
11
+ end
12
+
13
+ context "validation" do
14
+ should "check HTTP method" do
15
+ assert_raise(ArgumentError) do
16
+ Happening::S3::Request.new(:foo, 'https://www.example.com')
17
+ end
18
+
19
+ assert_nothing_raised do
20
+ Happening::S3::Request.new(:get, 'https://www.example.com')
21
+ end
22
+ end
23
+
24
+ should "check the options" do
25
+ assert_raise(ArgumentError) do
26
+ Happening::S3::Request.new(:get, 'https://www.example.com', {:foo => :bar})
27
+ end
28
+
29
+ assert_nothing_raised do
30
+ Happening::S3::Request.new(:get, 'https://www.example.com', {:timeout => 4})
31
+ end
32
+ end
33
+ end
34
+
35
+ context "when executing" do
36
+ should "have no response before executing" do
37
+ assert_nil Happening::S3::Request.new(:get, 'https://www.example.com').response
38
+ end
39
+
40
+ should "call em-http-request" do
41
+ request = mock(:get => @response_stub)
42
+ EventMachine::MockHttpRequest.expects(:new).with('https://www.example.com').returns(request)
43
+ Happening::S3::Request.new(:get, 'https://www.example.com').execute
44
+ end
45
+
46
+ should "pass the given headers and options" do
47
+ request = mock('em-http-request')
48
+ request.expects(:get).with(:timeout => 10, :head => {'a' => 'b'}, :body => nil).returns(@response_stub)
49
+ EventMachine::MockHttpRequest.expects(:new).with('https://www.example.com').returns(request)
50
+ Happening::S3::Request.new(:get, 'https://www.example.com', :headers => {'a' => 'b'}).execute
51
+ end
52
+
53
+ should "post any given data" do
54
+ request = mock('em-http-request')
55
+ request.expects(:put).with(:timeout => 10, :body => 'the-data', :head => {}).returns(@response_stub)
56
+ EventMachine::MockHttpRequest.expects(:new).with('https://www.example.com').returns(request)
57
+ Happening::S3::Request.new(:put, 'https://www.example.com', :data => 'the-data').execute
58
+ end
59
+
60
+ context "when handling errors" do
61
+ should "call the user error handler" do
62
+ EventMachine::MockHttpRequest.register('http://www.example.com:80/', :get, {}, error_response(400))
63
+
64
+ called = false
65
+ on_error = Proc.new {|http| called = true}
66
+
67
+ run_in_em_loop do
68
+ Happening::S3::Request.new(:get, 'http://www.example.com/', :on_error => on_error).execute
69
+
70
+ EM.add_timer(1) {
71
+ EM.stop_event_loop
72
+ assert called
73
+ assert_equal 5, EventMachine::MockHttpRequest.count('http://www.example.com:80/', :get, {})
74
+ }
75
+
76
+ end
77
+ end
78
+
79
+ should "use a default error handler if there is no user handler" do
80
+ EventMachine::MockHttpRequest.register('http://www.example.com:80/', :get, {}, error_response(400))
81
+
82
+ assert_raise(Happening::Error) do
83
+ run_in_em_loop do
84
+ Happening::S3::Request.new(:get, 'http://www.example.com/').execute
85
+ end
86
+ end
87
+ EM.stop_event_loop if EM.reactor_running?
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+ end
@@ -33,7 +33,7 @@ end
33
33
 
34
34
  module Happening
35
35
  module S3
36
- class Item
36
+ class Request
37
37
  def http_class
38
38
  EventMachine::MockHttpRequest
39
39
  end
@@ -91,21 +91,4 @@ module EventMachine
91
91
  class MockHttpRequest
92
92
  @@pass_through_requests = false
93
93
  end
94
- end
95
- # def send_request(&blk)
96
- # # raise @options.inspect
97
- # query = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}#{encode_query(@uri.path, @options[:query], @uri.query)}"
98
- # cache_key = query + @options.to_s
99
- # if s = @@registry[cache_key] and fake = s[@method]
100
- # @@registry_count[cache_key][@method] += 1
101
- # client = FakeHttpClient.new(nil)
102
- # client.setup(fake, @uri)
103
- # client
104
- # elsif @@pass_through_requests
105
- # real_send_request
106
- # else
107
- # raise "this request #{query} for method #{@method} isn't registered, and pass_through_requests is current set to false"
108
- # end
109
- # end
110
- # end
111
- # end
94
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: happening
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Weiss
@@ -47,6 +47,7 @@ files:
47
47
  - lib/aws.rb
48
48
  - lib/log.rb
49
49
  - lib/s3/item.rb
50
+ - lib/s3/request.rb
50
51
  has_rdoc: true
51
52
  homepage: http://github.com/peritor/happening
52
53
  licenses: []
@@ -78,4 +79,5 @@ summary: An EventMachine based S3 client
78
79
  test_files:
79
80
  - test/aws_test.rb
80
81
  - test/s3/item_test.rb
82
+ - test/s3/request_test.rb
81
83
  - test/test_helper.rb