happening 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +15 -0
- data/lib/happening/aws.rb +9 -3
- data/lib/happening/s3/item.rb +15 -7
- data/lib/happening/s3/request.rb +5 -4
- data/lib/happening/utils.rb +26 -0
- data/lib/happening.rb +1 -5
- data/test/aws_test.rb +1 -1
- data/test/s3/item_test.rb +22 -4
- data/test/s3/request_test.rb +1 -1
- data/test/s3_test.rb +1 -1
- data/test/test_helper.rb +1 -1
- metadata +35 -6
data/README.md
CHANGED
@@ -124,6 +124,21 @@ Happening support the simple S3 PUT upload:
|
|
124
124
|
|
125
125
|
Amazon returns no content on delete, so having a success handler is usually not needed for delete operations.
|
126
126
|
|
127
|
+
Head
|
128
|
+
=============
|
129
|
+
|
130
|
+
You can also just load the headers of an S3 item:
|
131
|
+
|
132
|
+
EM.run do
|
133
|
+
on_error = Proc.new {|response| puts "An error occured: #{response.response_header.status}"; EM.stop }
|
134
|
+
item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
|
135
|
+
item.head(:on_error => on_error) do |response|
|
136
|
+
puts "Headers: #{response.inspect}"
|
137
|
+
EM.stop
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
127
142
|
SSL Support
|
128
143
|
=============
|
129
144
|
|
data/lib/happening/aws.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'time'
|
1
2
|
module Happening
|
2
3
|
class AWS
|
4
|
+
include Utils
|
3
5
|
|
4
6
|
AMAZON_HEADER_PREFIX = 'x-amz-'
|
5
7
|
AMAZON_METADATA_PREFIX = 'x-amz-meta-'
|
@@ -10,12 +12,12 @@ module Happening
|
|
10
12
|
def initialize(aws_access_key_id, aws_secret_access_key)
|
11
13
|
@aws_access_key_id = aws_access_key_id
|
12
14
|
@aws_secret_access_key = aws_secret_access_key
|
13
|
-
raise ArgumentError, "need AWS Access Key Id and AWS Secret Key"
|
15
|
+
raise ArgumentError, "need AWS Access Key Id and AWS Secret Key" if blank?(aws_access_key_id) || blank?(aws_secret_access_key)
|
14
16
|
end
|
15
17
|
|
16
18
|
def sign(method, path, headers={})
|
17
19
|
headers = {
|
18
|
-
'date' =>
|
20
|
+
'date' => utc_httpdate
|
19
21
|
}.update(headers)
|
20
22
|
|
21
23
|
request_description = canonical_request_description(method, path, headers)
|
@@ -24,6 +26,10 @@ module Happening
|
|
24
26
|
|
25
27
|
protected
|
26
28
|
|
29
|
+
def utc_httpdate
|
30
|
+
Time.now.utc.httpdate
|
31
|
+
end
|
32
|
+
|
27
33
|
def generate_signature(request_description)
|
28
34
|
Base64.encode64(OpenSSL::HMAC.digest(DIGEST, aws_secret_access_key, request_description)).strip
|
29
35
|
end
|
@@ -56,4 +62,4 @@ module Happening
|
|
56
62
|
end
|
57
63
|
|
58
64
|
end
|
59
|
-
end
|
65
|
+
end
|
data/lib/happening/s3/item.rb
CHANGED
@@ -4,12 +4,13 @@ require 'cgi'
|
|
4
4
|
module Happening
|
5
5
|
module S3
|
6
6
|
class Item
|
7
|
+
include Utils
|
7
8
|
|
8
9
|
REQUIRED_FIELDS = [:server]
|
9
10
|
VALID_HEADERS = ['Cache-Control', 'Content-Disposition', 'Content-Encoding', 'Content-Length', 'Content-MD5', 'Content-Type', 'Expect', 'Expires']
|
10
11
|
|
11
12
|
attr_accessor :bucket, :aws_id, :options
|
12
|
-
|
13
|
+
|
13
14
|
def initialize(bucket, aws_id, options = {})
|
14
15
|
@options = {
|
15
16
|
:timeout => 10,
|
@@ -20,14 +21,21 @@ module Happening
|
|
20
21
|
:retry_count => 4,
|
21
22
|
:permissions => 'private',
|
22
23
|
:ssl => Happening::S3.ssl_options
|
23
|
-
}.update(options
|
24
|
-
|
24
|
+
}.update(symbolize_keys(options))
|
25
|
+
assert_valid_keys(options, :timeout, :server, :protocol, :aws_access_key_id, :aws_secret_access_key, :retry_count, :permissions, :ssl)
|
25
26
|
@aws_id = aws_id.to_s
|
26
27
|
@bucket = bucket.to_s
|
27
28
|
|
28
29
|
validate
|
29
30
|
end
|
30
31
|
|
32
|
+
def head(request_options = {}, &blk)
|
33
|
+
headers = needs_to_sign? ? aws.sign("HEAD", path) : {}
|
34
|
+
request_options[:on_success] = blk if blk
|
35
|
+
request_options.update(:headers => headers)
|
36
|
+
Happening::S3::Request.new(:head, url, {:ssl => options[:ssl]}.update(request_options)).execute
|
37
|
+
end
|
38
|
+
|
31
39
|
def get(request_options = {}, &blk)
|
32
40
|
headers = needs_to_sign? ? aws.sign("GET", path) : {}
|
33
41
|
request_options[:on_success] = blk if blk
|
@@ -64,7 +72,7 @@ module Happening
|
|
64
72
|
protected
|
65
73
|
|
66
74
|
def needs_to_sign?
|
67
|
-
options[:aws_access_key_id]
|
75
|
+
present?(options[:aws_access_key_id])
|
68
76
|
end
|
69
77
|
|
70
78
|
def dns_bucket?
|
@@ -81,11 +89,11 @@ module Happening
|
|
81
89
|
end
|
82
90
|
|
83
91
|
def validate
|
84
|
-
raise ArgumentError, "need a bucket name" unless
|
85
|
-
raise ArgumentError, "need a AWS Key" unless
|
92
|
+
raise ArgumentError, "need a bucket name" unless present?(bucket)
|
93
|
+
raise ArgumentError, "need a AWS Key" unless present?(aws_id)
|
86
94
|
|
87
95
|
REQUIRED_FIELDS.each do |field|
|
88
|
-
raise ArgumentError, "need field #{field}" unless options[field]
|
96
|
+
raise ArgumentError, "need field #{field}" unless present?(options[field])
|
89
97
|
end
|
90
98
|
|
91
99
|
raise ArgumentError, "unknown protocoll #{options[:protocol]}" unless ['http', 'https'].include?(options[:protocol])
|
data/lib/happening/s3/request.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module Happening
|
2
2
|
module S3
|
3
3
|
class Request
|
4
|
+
include Utils
|
4
5
|
|
5
|
-
VALID_HTTP_METHODS = [:get, :put, :delete]
|
6
|
+
VALID_HTTP_METHODS = [:head, :get, :put, :delete]
|
6
7
|
|
7
8
|
attr_accessor :http_method, :url, :options, :response
|
8
9
|
|
@@ -19,7 +20,7 @@ module Happening
|
|
19
20
|
:verify_peer => false
|
20
21
|
}
|
21
22
|
}.update(options)
|
22
|
-
|
23
|
+
assert_valid_keys(options, :timeout, :on_success, :on_error, :retry_count, :headers, :data, :ssl)
|
23
24
|
@http_method = http_method
|
24
25
|
@url = url
|
25
26
|
|
@@ -99,7 +100,7 @@ module Happening
|
|
99
100
|
|
100
101
|
def handle_redirect
|
101
102
|
new_location = response.response_header['LOCATION'] rescue ''
|
102
|
-
raise "Could not find the location to redirect to, empty location header?" if
|
103
|
+
raise "Could not find the location to redirect to, empty location header?" if blank?(new_location)
|
103
104
|
|
104
105
|
new_request = self.class.new(http_method, new_location, options)
|
105
106
|
new_request.execute
|
@@ -107,4 +108,4 @@ module Happening
|
|
107
108
|
|
108
109
|
end
|
109
110
|
end
|
110
|
-
end
|
111
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Happening
|
2
|
+
module Utils
|
3
|
+
protected
|
4
|
+
|
5
|
+
def symbolize_keys(hash)
|
6
|
+
hash.inject({}) do |h, kv|
|
7
|
+
h[kv[0].to_sym] = kv[1]
|
8
|
+
h
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def assert_valid_keys(hash, *valid_keys)
|
13
|
+
unknown_keys = hash.keys - [valid_keys].flatten
|
14
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def present?(obj)
|
18
|
+
!blank?(obj)
|
19
|
+
end
|
20
|
+
|
21
|
+
def blank?(obj)
|
22
|
+
obj.respond_to?(:empty?) ? obj.empty? : !obj
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/happening.rb
CHANGED
@@ -3,11 +3,7 @@ require 'em-http'
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
-
require '
|
7
|
-
unless {}.respond_to?(:assert_valid_keys)
|
8
|
-
require 'active_support/core_ext'
|
9
|
-
end
|
10
|
-
|
6
|
+
require File.dirname(__FILE__) + '/happening/utils'
|
11
7
|
require File.dirname(__FILE__) + '/happening/log'
|
12
8
|
require File.dirname(__FILE__) + '/happening/aws'
|
13
9
|
require File.dirname(__FILE__) + '/happening/s3'
|
data/test/aws_test.rb
CHANGED
data/test/s3/item_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
2
|
|
3
3
|
class ItemTest < Test::Unit::TestCase
|
4
4
|
context "An Happening::S3::Item instance" do
|
@@ -8,7 +8,8 @@ class ItemTest < Test::Unit::TestCase
|
|
8
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
9
|
|
10
10
|
@time = "Thu, 25 Feb 2010 10:00:00 GMT"
|
11
|
-
Time.stubs(:now).returns(
|
11
|
+
Time.stubs(:now).returns(Time.parse(@time))
|
12
|
+
#stub(:utc_httpdate => @time, :to_i => 99, :usec => 88))
|
12
13
|
end
|
13
14
|
|
14
15
|
context "validation" do
|
@@ -106,7 +107,7 @@ class ItemTest < Test::Unit::TestCase
|
|
106
107
|
|
107
108
|
should "sign requests if AWS credentials are passend" do
|
108
109
|
time = "Thu, 25 Feb 2010 12:06:33 GMT"
|
109
|
-
Time.stubs(:now).returns(
|
110
|
+
Time.stubs(:now).returns(Time.parse(time))
|
110
111
|
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :get, {"Authorization"=>"AWS abc:3OEcVbE//maUUmqh3A5ETEcr9TE=", 'date' => time}, fake_response("data-here"))
|
111
112
|
|
112
113
|
@item = Happening::S3::Item.new('bucket', 'the-key', :aws_access_key_id => 'abc', :aws_secret_access_key => '123')
|
@@ -254,7 +255,24 @@ class ItemTest < Test::Unit::TestCase
|
|
254
255
|
end
|
255
256
|
end
|
256
257
|
end
|
257
|
-
|
258
|
+
|
259
|
+
context "when loading the headers" do
|
260
|
+
should "request via HEAD" do
|
261
|
+
EventMachine::MockHttpRequest.register('https://bucket.s3.amazonaws.com:443/the-key', :head, {}, fake_response('hy there'))
|
262
|
+
|
263
|
+
@item = Happening::S3::Item.new('bucket', 'the-key')
|
264
|
+
run_in_em_loop do
|
265
|
+
@item.head
|
266
|
+
|
267
|
+
EM.add_timer(1) {
|
268
|
+
EM.stop_event_loop
|
269
|
+
assert_equal 1, EventMachine::MockHttpRequest.count('https://bucket.s3.amazonaws.com:443/the-key', :head, {})
|
270
|
+
}
|
271
|
+
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
258
276
|
context "when saving an item" do
|
259
277
|
|
260
278
|
should "post to the desired location" do
|
data/test/s3/request_test.rb
CHANGED
data/test/s3_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: happening
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jonathan Weiss
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-05-20 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: jeweler
|
37
37
|
prerelease: false
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
@@ -44,8 +44,36 @@ dependencies:
|
|
44
44
|
segments:
|
45
45
|
- 0
|
46
46
|
version: "0"
|
47
|
-
type: :
|
47
|
+
type: :development
|
48
48
|
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: shoulda
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: mocha
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :development
|
76
|
+
version_requirements: *id004
|
49
77
|
description: An EventMachine based S3 client - using em-http-request
|
50
78
|
email: info@peritor.com
|
51
79
|
executables: []
|
@@ -64,6 +92,7 @@ files:
|
|
64
92
|
- lib/happening/s3.rb
|
65
93
|
- lib/happening/s3/item.rb
|
66
94
|
- lib/happening/s3/request.rb
|
95
|
+
- lib/happening/utils.rb
|
67
96
|
- test/aws_test.rb
|
68
97
|
- test/s3/item_test.rb
|
69
98
|
- test/s3/request_test.rb
|