happening 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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