happening 0.0.1
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/LICENSE.txt +15 -0
- data/README.md +116 -0
- data/lib/aws.rb +59 -0
- data/lib/log.rb +40 -0
- data/lib/s3/item.rb +196 -0
- data/test/aws_test.rb +41 -0
- data/test/s3/item_test.rb +333 -0
- data/test/test_helper.rb +111 -0
- metadata +81 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (c) 2010 Peritor GmbH <info@peritor.com>
|
3
|
+
*
|
4
|
+
* Permission to use, copy, modify, and distribute this software for any
|
5
|
+
* purpose with or without fee is hereby granted, provided that the above
|
6
|
+
* copyright notice and this permission notice appear in all copies.
|
7
|
+
*
|
8
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
9
|
+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
10
|
+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
11
|
+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
12
|
+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
13
|
+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
14
|
+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
15
|
+
*/
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
Amazon S3 Ruby library that leverages [EventMachine](http://rubyeventmachine.com/) and [em-http-request](http://github.com/igrigorik/em-http-request).
|
2
|
+
|
3
|
+
By using EventMachine Happening does not block on S3 downloads/uploads thus allowing for a higher concurrency.
|
4
|
+
|
5
|
+
Happening was developed by [Peritor](http://www.peritor.com) for usage inside Nanite/EventMachine.
|
6
|
+
Alternatives like RightAws block during the HTTP calls thus blocking the Nanite-Agent.
|
7
|
+
|
8
|
+
For now it only supports GET, PUT and DELETE operations on S3 items. The PUT operations support S3 ACLs/permissions.
|
9
|
+
Happening will handle redirects and retries on errors by default.
|
10
|
+
|
11
|
+
Installation
|
12
|
+
============
|
13
|
+
|
14
|
+
gem install happening
|
15
|
+
|
16
|
+
Usage
|
17
|
+
=============
|
18
|
+
|
19
|
+
require 'happening'
|
20
|
+
|
21
|
+
EM.run do
|
22
|
+
item = Happening::S3::Item.new('bucket', 'item_id')
|
23
|
+
item.get # non-authenticated download, works only for public-read content
|
24
|
+
|
25
|
+
item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
|
26
|
+
item.get # authenticated download
|
27
|
+
|
28
|
+
item.put("The new content")
|
29
|
+
|
30
|
+
item.delete
|
31
|
+
end
|
32
|
+
|
33
|
+
The above examples are a bit useless, as you never get any content back.
|
34
|
+
You need to specify a callback that interacts with the http response:
|
35
|
+
|
36
|
+
EM.run do
|
37
|
+
on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
|
38
|
+
item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
|
39
|
+
item.get # authenticated download
|
40
|
+
end
|
41
|
+
|
42
|
+
This will enqueue your download and run it in the EventMachine event loop.
|
43
|
+
|
44
|
+
You can also react to errors:
|
45
|
+
|
46
|
+
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
|
+
item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
|
50
|
+
item.get
|
51
|
+
end
|
52
|
+
|
53
|
+
Downloading many files would look like this:
|
54
|
+
|
55
|
+
EM.run do
|
56
|
+
count = 100
|
57
|
+
on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop if count <= 0}
|
58
|
+
on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop if count <= 0}
|
59
|
+
|
60
|
+
count.times do |i|
|
61
|
+
item = Happening::S3::Item.new('bucket', "item_#{i}", :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
|
62
|
+
item.get
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Upload
|
67
|
+
=============
|
68
|
+
|
69
|
+
Happening supports the simple S3 PUT upload:
|
70
|
+
|
71
|
+
EM.run do
|
72
|
+
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') )
|
76
|
+
end
|
77
|
+
|
78
|
+
Setting permissions looks like this:
|
79
|
+
|
80
|
+
EM.run do
|
81
|
+
on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
|
82
|
+
on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
|
83
|
+
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
|
85
|
+
end
|
86
|
+
|
87
|
+
Deleting
|
88
|
+
=============
|
89
|
+
|
90
|
+
Happening support the simple S3 PUT upload:
|
91
|
+
|
92
|
+
EM.run do
|
93
|
+
on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
|
94
|
+
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')
|
96
|
+
item.delete
|
97
|
+
end
|
98
|
+
|
99
|
+
Amazon returns no content on delete, so having a success handler is usually not needed for delete operations.
|
100
|
+
|
101
|
+
Credits
|
102
|
+
=============
|
103
|
+
|
104
|
+
The AWS signing and canonical request description is based on [RightAws](http://github.com/rightscale/right_aws).
|
105
|
+
|
106
|
+
|
107
|
+
License
|
108
|
+
=============
|
109
|
+
|
110
|
+
Happening is licensed under the OpenBSD / two-clause BSD license, modeled after the ISC license. See LICENSE.txt
|
111
|
+
|
112
|
+
|
113
|
+
About
|
114
|
+
=============
|
115
|
+
|
116
|
+
Happening was written by [Jonathan Weiss](http://twitter.com/jweiss) for [Peritor](http://www.peritor.com).
|
data/lib/aws.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Happening
|
2
|
+
class AWS
|
3
|
+
|
4
|
+
AMAZON_HEADER_PREFIX = 'x-amz-'
|
5
|
+
AMAZON_METADATA_PREFIX = 'x-amz-meta-'
|
6
|
+
DIGEST = OpenSSL::Digest.new('sha1')
|
7
|
+
|
8
|
+
attr_accessor :aws_access_key_id, :aws_secret_access_key
|
9
|
+
|
10
|
+
def initialize(aws_access_key_id, aws_secret_access_key)
|
11
|
+
@aws_access_key_id = aws_access_key_id
|
12
|
+
@aws_secret_access_key = aws_secret_access_key
|
13
|
+
raise ArgumentError, "need AWS Access Key Id and AWS Secret Key" unless aws_access_key_id.present? && aws_secret_access_key.present?
|
14
|
+
end
|
15
|
+
|
16
|
+
def sign(method, path, headers={})
|
17
|
+
headers = {
|
18
|
+
'date' => Time.now.httpdate
|
19
|
+
}.update(headers)
|
20
|
+
|
21
|
+
request_description = canonical_request_description(method, path, headers)
|
22
|
+
headers.update("Authorization" => "AWS #{aws_access_key_id}:#{generate_signature(request_description)}")
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def generate_signature(request_description)
|
28
|
+
Base64.encode64(OpenSSL::HMAC.digest(DIGEST, aws_secret_access_key, request_description)).strip
|
29
|
+
end
|
30
|
+
|
31
|
+
def canonical_request_description(method, path, headers = {}, expires = nil)
|
32
|
+
s3_attributes = {}
|
33
|
+
headers.each do |key, value|
|
34
|
+
key = key.downcase
|
35
|
+
s3_attributes[key] = value.to_s.strip if key.match(/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o)
|
36
|
+
end
|
37
|
+
s3_attributes['content-type'] ||= ''
|
38
|
+
s3_attributes['content-md5'] ||= ''
|
39
|
+
s3_attributes['date'] = '' if s3_attributes.has_key?('x-amz-date')
|
40
|
+
s3_attributes['date'] = expires if expires
|
41
|
+
|
42
|
+
description = "#{method}\n"
|
43
|
+
s3_attributes.sort { |a, b| a[0] <=> b[0] }.each do |key, value|
|
44
|
+
description << (key.match(/^#{AMAZON_HEADER_PREFIX}/o) ? "#{key}:#{value}\n" : "#{value}\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
# ignore all parameters by default
|
48
|
+
description << path.gsub(/\?.*$/, '')
|
49
|
+
|
50
|
+
# handle amazon parameters
|
51
|
+
description << '?acl' if path[/[&?]acl($|&|=)/]
|
52
|
+
description << '?torrent' if path[/[&?]torrent($|&|=)/]
|
53
|
+
description << '?location' if path[/[&?]location($|&|=)/]
|
54
|
+
description << '?logging' if path[/[&?]logging($|&|=)/]
|
55
|
+
description
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/log.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Happening
|
2
|
+
class Log
|
3
|
+
|
4
|
+
@@logger = Logger.new(STDOUT)
|
5
|
+
@@logger.level = Logger::ERROR
|
6
|
+
|
7
|
+
def self.logger=(log)
|
8
|
+
@@logger = log
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
@@logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.level=(lev)
|
16
|
+
logger.level = lev
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.level
|
20
|
+
logger.level
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.debug(msg)
|
24
|
+
logger.debug(msg)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.info(msg)
|
28
|
+
logger.info(msg)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.warn(msg)
|
32
|
+
logger.warn(msg)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.error(msg)
|
36
|
+
logger.error(msg)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/s3/item.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Happening
|
5
|
+
module S3
|
6
|
+
class Item
|
7
|
+
|
8
|
+
REQUIRED_FIELDS = [:server]
|
9
|
+
|
10
|
+
attr_accessor :bucket, :aws_id, :options
|
11
|
+
|
12
|
+
def initialize(bucket, aws_id, options = {})
|
13
|
+
@options = {
|
14
|
+
:timeout => 10,
|
15
|
+
:on_error => nil,
|
16
|
+
:on_success => nil,
|
17
|
+
:server => 's3.amazonaws.com',
|
18
|
+
:protocol => 'https',
|
19
|
+
:aws_access_key_id => nil,
|
20
|
+
:aws_secret_access_key => nil,
|
21
|
+
:retry_count => 4,
|
22
|
+
:permissions => 'private'
|
23
|
+
}.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)
|
25
|
+
@aws_id = aws_id.to_s
|
26
|
+
@bucket = bucket.to_s
|
27
|
+
|
28
|
+
validate
|
29
|
+
end
|
30
|
+
|
31
|
+
def get
|
32
|
+
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
|
39
|
+
end
|
40
|
+
|
41
|
+
def put(data)
|
42
|
+
permissions = options[:permissions] != 'private' ? {'x-amz-acl' => options[:permissions] } : {}
|
43
|
+
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
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete
|
53
|
+
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
|
60
|
+
end
|
61
|
+
|
62
|
+
def url
|
63
|
+
URI::Generic.new(options[:protocol], nil, server, port, nil, path(!dns_bucket?), nil, nil, nil).to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def http_class
|
67
|
+
EventMachine::HttpRequest
|
68
|
+
end
|
69
|
+
|
70
|
+
def server
|
71
|
+
dns_bucket? ? "#{bucket}.#{options[:server]}" : options[:server]
|
72
|
+
end
|
73
|
+
|
74
|
+
def path(with_bucket=true)
|
75
|
+
with_bucket ? "/#{bucket}/#{CGI::escape(aws_id)}" : "/#{CGI::escape(aws_id)}"
|
76
|
+
end
|
77
|
+
|
78
|
+
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
|
+
|
162
|
+
def needs_to_sign?
|
163
|
+
options[:aws_access_key_id].present?
|
164
|
+
end
|
165
|
+
|
166
|
+
def dns_bucket?
|
167
|
+
# http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?BucketRestrictions.html
|
168
|
+
return false unless (3..63) === bucket.size
|
169
|
+
bucket.split('.').each do |component|
|
170
|
+
return false unless component[/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/]
|
171
|
+
end
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
def port
|
176
|
+
(options[:protocol].to_s == 'https') ? 443 : 80
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate
|
180
|
+
raise ArgumentError, "need a bucket name" unless bucket.present?
|
181
|
+
raise ArgumentError, "need a AWS Key" unless aws_id.present?
|
182
|
+
|
183
|
+
REQUIRED_FIELDS.each do |field|
|
184
|
+
raise ArgumentError, "need field #{field}" unless options[field].present?
|
185
|
+
end
|
186
|
+
|
187
|
+
raise ArgumentError, "unknown protocoll #{options[:protocol]}" unless ['http', 'https'].include?(options[:protocol])
|
188
|
+
end
|
189
|
+
|
190
|
+
def aws
|
191
|
+
@aws ||= Happening::AWS.new(options[:aws_access_key_id], options[:aws_secret_access_key])
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
data/test/aws_test.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/test_helper"
|
2
|
+
|
3
|
+
class ItemTest < Test::Unit::TestCase
|
4
|
+
context "An Happening::AWS instance" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@aws = Happening::AWS.new('the-aws-access-key', 'the-aws-secret-key')
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when constructing" do
|
11
|
+
should "require Access Key and Secret Key" do
|
12
|
+
assert_raise(ArgumentError) do
|
13
|
+
Happening::AWS.new(nil, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
assert_raise(ArgumentError) do
|
17
|
+
Happening::AWS.new('', '')
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_nothing_raised do
|
21
|
+
Happening::AWS.new('abc', 'abc')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when signing parameters" do
|
27
|
+
should "return a header hash" do
|
28
|
+
assert_not_nil @aws.sign("GET", '/')['Authorization']
|
29
|
+
end
|
30
|
+
|
31
|
+
should "include the current date" do
|
32
|
+
assert_not_nil @aws.sign("GET", '/')['date']
|
33
|
+
end
|
34
|
+
|
35
|
+
should "keep given headers" do
|
36
|
+
assert_equal 'bar', @aws.sign("GET", '/', {'foo' => 'bar'})['foo']
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../test_helper"
|
2
|
+
|
3
|
+
class ItemTest < Test::Unit::TestCase
|
4
|
+
context "An Happening::S3::Item instance" do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
Happening::Log.level = Logger::ERROR
|
8
|
+
@item = Happening::S3::Item.new('the-bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :server => '127.0.0.1')
|
9
|
+
|
10
|
+
@time = "Thu, 25 Feb 2010 10:00:00 GMT"
|
11
|
+
Time.stubs(:now).returns(stub(:httpdate => @time, :to_i => 99, :usec => 88))
|
12
|
+
end
|
13
|
+
|
14
|
+
context "validation" do
|
15
|
+
should "require a bucket and a key" do
|
16
|
+
assert_raise(ArgumentError) do
|
17
|
+
item = Happening::S3::Item.new()
|
18
|
+
end
|
19
|
+
|
20
|
+
assert_raise(ArgumentError) do
|
21
|
+
item = Happening::S3::Item.new('the-key')
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_nothing_raised(ArgumentError) do
|
25
|
+
item = Happening::S3::Item.new('the-bucket', 'the-key')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
should "not allow unknown options" do
|
31
|
+
assert_raise(ArgumentError) do
|
32
|
+
item = Happening::S3::Item.new('the-bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :lala => 'lulul')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
should "check valid protocol" do
|
37
|
+
assert_raise(ArgumentError) do
|
38
|
+
item = Happening::S3::Item.new('the-bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :protocol => 'lulul')
|
39
|
+
end
|
40
|
+
|
41
|
+
assert_nothing_raised do
|
42
|
+
item = Happening::S3::Item.new('the-bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :protocol => 'http')
|
43
|
+
end
|
44
|
+
|
45
|
+
assert_nothing_raised do
|
46
|
+
item = Happening::S3::Item.new('the-bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :protocol => 'https')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when building the item url" do
|
52
|
+
should "build the full path out of the server, bucket, and key" do
|
53
|
+
@item = Happening::S3::Item.new('the-bucketissoooooooooooooooooooooooooooooooooooooolonggggggggggggggggggggggggggggggggggg', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret', :server => '127.0.0.1')
|
54
|
+
assert_equal "https://127.0.0.1:443/the-bucketissoooooooooooooooooooooooooooooooooooooolonggggggggggggggggggggggggggggggggggg/the-key", @item.url
|
55
|
+
end
|
56
|
+
|
57
|
+
should "use the DNS bucket name where possible" do
|
58
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => '123', :aws_secret_access_key => 'secret')
|
59
|
+
assert_equal "https://bucket.s3.amazonaws.com:443/the-key", @item.url
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when getting an item" do
|
64
|
+
|
65
|
+
should "call the on success callback" do
|
66
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {}, fake_response("data-here"))
|
67
|
+
|
68
|
+
called = false
|
69
|
+
data = nil
|
70
|
+
on_success = Proc.new {|http| called = true, data = http.response}
|
71
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :on_success => on_success)
|
72
|
+
run_in_em_loop do
|
73
|
+
@item.get
|
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 "sign requests if AWS credentials are passend" do
|
86
|
+
time = "Thu, 25 Feb 2010 12:06:33 GMT"
|
87
|
+
Time.stubs(:now).returns(mock(:httpdate => time))
|
88
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {"Authorization"=>"AWS abc:3OEcVbE//maUUmqh3A5ETEcr9TE=", 'date' => time}, fake_response("data-here"))
|
89
|
+
|
90
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
91
|
+
run_in_em_loop do
|
92
|
+
@item.get
|
93
|
+
|
94
|
+
EM.add_timer(1) {
|
95
|
+
EM.stop_event_loop
|
96
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :get, {"Authorization"=>"AWS abc:3OEcVbE//maUUmqh3A5ETEcr9TE=", 'date' => time})
|
97
|
+
}
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
should "retry on error" do
|
103
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {}, error_response(400))
|
104
|
+
|
105
|
+
@item = Happening::S3::Item.new('bucket', 'the-key')
|
106
|
+
run_in_em_loop do
|
107
|
+
@item.get
|
108
|
+
|
109
|
+
EM.add_timer(1) {
|
110
|
+
EM.stop_event_loop
|
111
|
+
assert_equal 5, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :get, {})
|
112
|
+
}
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
should "handle re-direct" do
|
118
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {}, redirect_response('https://bucket.s3-external-3.amazonaws.com/the-key'))
|
119
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3-external-3.amazonaws.com:443/the-key', :get, {}, fake_response('hy there'))
|
120
|
+
|
121
|
+
@item = Happening::S3::Item.new('bucket', 'the-key')
|
122
|
+
run_in_em_loop do
|
123
|
+
@item.get
|
124
|
+
|
125
|
+
EM.add_timer(1) {
|
126
|
+
EM.stop_event_loop
|
127
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :get, {})
|
128
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3-external-3.amazonaws.com:443/the-key', :get, {})
|
129
|
+
}
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when deleting an item" do
|
136
|
+
should "send a DELETE to the items location" do
|
137
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
138
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
139
|
+
'date' => @time,
|
140
|
+
'url' => "/bucket/the-key"}, fake_response("data-here"))
|
141
|
+
|
142
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
143
|
+
run_in_em_loop do
|
144
|
+
@item.delete
|
145
|
+
|
146
|
+
EM.add_timer(1) {
|
147
|
+
EM.stop_event_loop
|
148
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
149
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
150
|
+
'date' => @time,
|
151
|
+
'url' => "/bucket/the-key"})
|
152
|
+
}
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
should "handle re-direct" do
|
158
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
159
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
160
|
+
'date' => @time,
|
161
|
+
'url' => "/bucket/the-key"}, redirect_response('https://bucket.s3-external-3.amazonaws.com/the-key'))
|
162
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3-external-3.amazonaws.com:443/the-key', :delete, {
|
163
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
164
|
+
'date' => @time,
|
165
|
+
'url' => "/bucket/the-key"}, fake_response("success!"))
|
166
|
+
|
167
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
168
|
+
run_in_em_loop do
|
169
|
+
@item.delete
|
170
|
+
|
171
|
+
EM.add_timer(1) {
|
172
|
+
EM.stop_event_loop
|
173
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
174
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
175
|
+
'date' => @time,
|
176
|
+
'url' => "/bucket/the-key"})
|
177
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3-external-3.amazonaws.com:443/the-key', :delete, {
|
178
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
179
|
+
'date' => @time,
|
180
|
+
'url' => "/bucket/the-key"})
|
181
|
+
}
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
should "handle retry" do
|
187
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
188
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
189
|
+
'date' => @time,
|
190
|
+
'url' => "/bucket/the-key"}, error_response(400))
|
191
|
+
|
192
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
193
|
+
run_in_em_loop do
|
194
|
+
@item.delete
|
195
|
+
|
196
|
+
EM.add_timer(1) {
|
197
|
+
EM.stop_event_loop
|
198
|
+
assert_equal 5, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :delete, {
|
199
|
+
"Authorization"=>"AWS abc:nvkrlq4wor1qbFXZh6rHnAbiRjk=",
|
200
|
+
'date' => @time,
|
201
|
+
'url' => "/bucket/the-key"})
|
202
|
+
}
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context "when saving an item" do
|
209
|
+
|
210
|
+
should "post to the desired location" do
|
211
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
212
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
213
|
+
'date' => @time,
|
214
|
+
'url' => "/bucket/the-key"}, fake_response("data-here"))
|
215
|
+
|
216
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
217
|
+
run_in_em_loop do
|
218
|
+
@item.put('content')
|
219
|
+
|
220
|
+
EM.add_timer(1) {
|
221
|
+
EM.stop_event_loop
|
222
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
223
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
224
|
+
'date' => @time,
|
225
|
+
'url' => "/bucket/the-key"})
|
226
|
+
}
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
should "set the desired permissions" do
|
232
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
233
|
+
"Authorization"=>"AWS abc:cqkfX+nC7WIkYD+yWaUFuoRuePA=",
|
234
|
+
'date' => @time,
|
235
|
+
'url' => "/bucket/the-key",
|
236
|
+
"x-amz-acl" => 'public-read'}, fake_response("data-here"))
|
237
|
+
|
238
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123' , :permissions => 'public-read')
|
239
|
+
run_in_em_loop do
|
240
|
+
@item.put('content')
|
241
|
+
|
242
|
+
EM.add_timer(1) {
|
243
|
+
EM.stop_event_loop
|
244
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
245
|
+
"Authorization"=>"AWS abc:cqkfX+nC7WIkYD+yWaUFuoRuePA=",
|
246
|
+
'date' => @time,
|
247
|
+
'url' => "/bucket/the-key",
|
248
|
+
'x-amz-acl' => 'public-read'})
|
249
|
+
}
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
should "re-post to a new location" do
|
255
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
256
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
257
|
+
'date' => @time,
|
258
|
+
'url' => "/bucket/the-key"}, redirect_response('https://bucket.s3-external-3.amazonaws.com/the-key'))
|
259
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3-external-3.amazonaws.com:443/the-key', :put, {
|
260
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
261
|
+
'date' => @time,
|
262
|
+
'url' => "/bucket/the-key"}, fake_response('Thanks!'))
|
263
|
+
|
264
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
265
|
+
run_in_em_loop do
|
266
|
+
@item.put('content')
|
267
|
+
|
268
|
+
EM.add_timer(1) {
|
269
|
+
EM.stop_event_loop
|
270
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
271
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
272
|
+
'date' => @time,
|
273
|
+
'url' => "/bucket/the-key"})
|
274
|
+
|
275
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3-external-3.amazonaws.com:443/the-key', :put, {
|
276
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
277
|
+
'date' => @time,
|
278
|
+
'url' => "/bucket/the-key"})
|
279
|
+
}
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
should "retry on error" do
|
285
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
286
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
287
|
+
'date' => @time,
|
288
|
+
'url' => "/bucket/the-key"}, error_response(400))
|
289
|
+
|
290
|
+
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
291
|
+
run_in_em_loop do
|
292
|
+
@item.put('content')
|
293
|
+
|
294
|
+
EM.add_timer(1) {
|
295
|
+
EM.stop_event_loop
|
296
|
+
assert_equal 5, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
297
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
298
|
+
'date' => @time,
|
299
|
+
'url' => "/bucket/the-key"})
|
300
|
+
}
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
should "call error handler after retry reached" do
|
306
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
307
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
308
|
+
'date' => @time,
|
309
|
+
'url' => "/bucket/the-key"}, error_response(400))
|
310
|
+
|
311
|
+
called = false
|
312
|
+
on_error = Proc.new {|http| called = true}
|
313
|
+
|
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)
|
315
|
+
run_in_em_loop do
|
316
|
+
@item.put('content')
|
317
|
+
|
318
|
+
EM.add_timer(1) {
|
319
|
+
EM.stop_event_loop
|
320
|
+
assert called
|
321
|
+
assert_equal 2, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :put, {
|
322
|
+
"Authorization"=>"AWS abc:lZMKxGDKcQ1PH8yjbpyN7o2sPWg=",
|
323
|
+
'date' => @time,
|
324
|
+
'url' => "/bucket/the-key"})
|
325
|
+
}
|
326
|
+
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
$:.unshift(File.dirname(__FILE__) + "/../")
|
8
|
+
|
9
|
+
require 'happening'
|
10
|
+
|
11
|
+
require 'em-http/mock'
|
12
|
+
|
13
|
+
EventMachine.instance_eval do
|
14
|
+
# Switching out EM's defer since it makes tests just a tad more unreliable
|
15
|
+
alias :defer_original :defer
|
16
|
+
def defer
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end unless EM.respond_to?(:defer_original)
|
20
|
+
|
21
|
+
class Test::Unit::TestCase
|
22
|
+
def setup
|
23
|
+
EventMachine::MockHttpRequest.reset_counts!
|
24
|
+
EventMachine::MockHttpRequest.reset_registry!
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_in_em_loop
|
28
|
+
EM.run {
|
29
|
+
yield
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Happening
|
35
|
+
module S3
|
36
|
+
class Item
|
37
|
+
def http_class
|
38
|
+
EventMachine::MockHttpRequest
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fake_response(data)
|
45
|
+
<<-HEREDOC
|
46
|
+
HTTP/1.0 200 OK
|
47
|
+
Date: Mon, 16 Nov 2009 20:39:15 GMT
|
48
|
+
Expires: -1
|
49
|
+
Cache-Control: private, max-age=0
|
50
|
+
Content-Type: text/html; charset=ISO-8859-1
|
51
|
+
Set-Cookie: PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca
|
52
|
+
Set-Cookie: NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly
|
53
|
+
Server: gws
|
54
|
+
X-XSS-Protection: 0
|
55
|
+
X-Cache: MISS from .
|
56
|
+
Via: 1.0 .:80 (squid)
|
57
|
+
Connection: close
|
58
|
+
|
59
|
+
#{data}
|
60
|
+
HEREDOC
|
61
|
+
end
|
62
|
+
|
63
|
+
# amazon tells us to upload to another location, e.g. happening-benchmark.s3-external-3.amazonaws.com instead of happening-benchmark.s3.amazonaws.com
|
64
|
+
def redirect_response(location)
|
65
|
+
<<-HEREDOC
|
66
|
+
HTTP/1.0 301 Moved Permanently
|
67
|
+
Date: Mon, 16 Nov 2009 20:39:15 GMT
|
68
|
+
Expires: -1
|
69
|
+
Cache-Control: private, max-age=0
|
70
|
+
Content-Type: text/html; charset=ISO-8859-1
|
71
|
+
Via: 1.0 .:80 (squid)
|
72
|
+
Connection: close
|
73
|
+
Location: #{location}
|
74
|
+
|
75
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>TemporaryRedirect</Code><Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message><RequestId>137D5486D66095AE</RequestId><Bucket>happening-benchmark</Bucket><HostId>Nyk+Zq9GbtxcspdbKDWyGhsZhyUZquZP55tteYef4QVodsn73HUUad0xrIeD09lF</HostId><Endpoint>#{location}</Endpoint></Error>
|
76
|
+
HEREDOC
|
77
|
+
end
|
78
|
+
|
79
|
+
def error_response(error_code)
|
80
|
+
<<-HEREDOC
|
81
|
+
HTTP/1.0 #{error_code} OK
|
82
|
+
Date: Mon, 16 Nov 2009 20:39:15 GMT
|
83
|
+
Content-Type: text/html; charset=ISO-8859-1
|
84
|
+
Connection: close
|
85
|
+
|
86
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>TemporaryRedirect</Code><Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message><RequestId>137D5486D66095AE</RequestId><Bucket>happening-benchmark</Bucket><HostId>Nyk+Zq9GbtxcspdbKDWyGhsZhyUZquZP55tteYef4QVodsn73HUUad0xrIeD09lF</HostId><Endpoint>https://s3.amazonaws.com</Endpoint></Error>
|
87
|
+
HEREDOC
|
88
|
+
end
|
89
|
+
|
90
|
+
module EventMachine
|
91
|
+
class MockHttpRequest
|
92
|
+
@@pass_through_requests = false
|
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
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: happening
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Weiss
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-26 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: em-http-request
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: An EventMachine based S3 client - using em-http-request
|
36
|
+
email: info@peritor.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE.txt
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- LICENSE.txt
|
46
|
+
- README.md
|
47
|
+
- lib/aws.rb
|
48
|
+
- lib/log.rb
|
49
|
+
- lib/s3/item.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/peritor/happening
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --charset=UTF-8
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.5
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: An EventMachine based S3 client
|
78
|
+
test_files:
|
79
|
+
- test/aws_test.rb
|
80
|
+
- test/s3/item_test.rb
|
81
|
+
- test/test_helper.rb
|