happening 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|