rcarvalho-happening 0.2.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ Changelog
2
+ =============
3
+
4
+ - Ability to get objects in bucket
5
+
6
+ 0.2.5.1
7
+
8
+ - re-license under Apache 2.0
9
+
10
+ 0.2.5
11
+
12
+ - fix building
13
+
14
+ 0.2.3
15
+
16
+ - yank older version
17
+
18
+ 0.2.2
19
+
20
+ - Refactor version and build code
21
+
22
+ 0.2.1
23
+
24
+ - Return the response object on get so that the data can be streamed [carlism]
25
+
26
+ 0.2.0
27
+
28
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+ gem "em-http-request"
3
+
4
+ group :development do
5
+ gem "shoulda", ">= 0"
6
+ gem "bundler", "~> 1.0.0"
7
+ gem "jeweler", "~> 1.6.4"
8
+ gem "mocha"
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2010-2011 Peritor GmbH
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,206 @@
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
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
38
+ item.get do |response|
39
+ puts "the response content is: #{response.response}"
40
+ EM.stop
41
+ end
42
+ end
43
+
44
+ This will enqueue your download and run it in the EventMachine event loop.
45
+
46
+ You can also react to errors:
47
+
48
+ EM.run do
49
+ on_error = Proc.new {|response| puts "An error occured: #{response.response_header.status}"; EM.stop }
50
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
51
+ item.get(:on_error => on_error) do |response|
52
+ puts "the response content is: #{response.response}"
53
+ EM.stop
54
+ end
55
+ end
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:
60
+
61
+ EM.run do
62
+ count = 100
63
+ on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop if count <= 0}
64
+ on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop if count <= 0}
65
+
66
+ count.times do |i|
67
+ item = Happening::S3::Item.new('bucket', "item_#{i}", :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
68
+ item.get(:on_success => on_success, :on_error => on_error)
69
+ end
70
+ end
71
+
72
+ Upload
73
+ =============
74
+
75
+ Happening supports the simple S3 PUT upload:
76
+
77
+ EM.run do
78
+ on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
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
83
+ end
84
+
85
+ Setting permissions looks like this:
86
+
87
+ EM.run do
88
+ on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
89
+ on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
90
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret', :permissions => 'public-write')
91
+ item.get(:on_success => on_success, :on_error => on_error)
92
+ end
93
+
94
+ Custom headers:
95
+
96
+ EM.run do
97
+ on_error = Proc.new {|http| puts "An error occured: #{http.response_header.status}"; EM.stop }
98
+ on_success = Proc.new {|http| puts "the response is: #{http.response}"; EM.stop }
99
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret', :permissions => 'public-write')
100
+ item.put(:on_success => on_success,
101
+ :on_error => on_error,
102
+ :headers => {
103
+ 'Cache-Control' => "max-age=252460800",
104
+ 'Content-Type' => 'text/html',
105
+ 'Expires' => 'Fri, 16 Nov 2018 22:09:29 GMT',
106
+ 'x-amz-meta-abc' => 'ABC'
107
+ })
108
+ end
109
+
110
+
111
+ Deleting
112
+ =============
113
+
114
+ Happening support the simple S3 PUT upload:
115
+
116
+ EM.run do
117
+ on_error = Proc.new {|response| puts "An error occured: #{response.response_header.status}"; EM.stop }
118
+ item = Happening::S3::Item.new('bucket', 'item_id', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
119
+ item.delete(:on_error => on_error) do |response|
120
+ puts "Deleted!"
121
+ EM.stop
122
+ end
123
+ end
124
+
125
+ Amazon returns no content on delete, so having a success handler is usually not needed for delete operations.
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
+
142
+
143
+ Streaming
144
+ =============
145
+
146
+ The response data can also be streamed:
147
+
148
+ EM.run do
149
+ item = Happening::S3::Item.new( bucket...
150
+ item.get(:on_error => on_error, :on_success => on_success ).stream do |chunk|
151
+ # .. handle the individual chunk
152
+ end
153
+ end
154
+
155
+ List Objects in Bucket
156
+ =======================
157
+
158
+ EM.run do
159
+ bucket = Happening::S3::Bucket.new('bucket', :aws_access_key_id => 'Your-ID', :aws_secret_access_key => 'secret')
160
+ bucket.get do |response|
161
+ puts "Response content: #{response.response}"
162
+ end
163
+ end
164
+
165
+
166
+ SSL Support
167
+ =============
168
+
169
+ Happening will use SSL/HTTPS by default. What it cannot do by default is verify the SSL certificate. This means
170
+ that traffic is encrypted but nobody can say if the SSL-endpoint is the one you except. In order to verify the
171
+ SSL certificate you need to provide Happening with the path to a certificate CA collection in PEM format:
172
+
173
+ Happening::S3.ssl_options[:cert_chain_file] = '/etc/ca-bundle.crt'
174
+
175
+ You can also set this option on each item:
176
+
177
+ Happening::S3::Item.new('bucket', 'item_id',
178
+ :aws_access_key_id => 'A',
179
+ :aws_secret_access_key => 'B',
180
+ :ssl => {
181
+ :cert_chain_file => '/etc/ca-bundle.crt'
182
+ }
183
+
184
+ Or even on the request:
185
+
186
+ item.get(:ssl => {:cert_chain_file => '/etc/ca-bundle.crt'})
187
+
188
+ The SSL options are directly passed to EventMachine, see the [EventMachine documentation](http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296) for more information on the SSL support.
189
+
190
+
191
+ Credits
192
+ =============
193
+
194
+ The AWS signing and canonical request description is based on [RightAws](http://github.com/rightscale/right_aws).
195
+
196
+
197
+ License
198
+ =============
199
+
200
+ Happening is licensed under the Apache 2.0 license. See LICENSE.txt
201
+
202
+
203
+ About
204
+ =============
205
+
206
+ Happening was written by [Jonathan Weiss](http://twitter.com/jweiss) for [Peritor](http://www.peritor.com).
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ require File.expand_path("./lib/happening")
16
+ Jeweler::Tasks.new do |gem|
17
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
18
+ gem.name = "happening"
19
+ gem.homepage = "http://github.com/peritor/happening"
20
+ gem.license = "BSD"
21
+ gem.summary = %Q{An EventMachine based S3 client }
22
+ gem.description = %Q{An EventMachine based S3 client }
23
+ gem.email = "jw@innerewut.de"
24
+ gem.authors = ["Jonathan Weiss"]
25
+ gem.version = Happening::VERSION
26
+ # dependencies defined in Gemfile
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rake/testtask'
31
+ Rake::TestTask.new(:test) do |t|
32
+ t.libs << 'test'
33
+ t.pattern = "test/**/*_test.rb"
34
+ t.verbose = true
35
+ end
36
+
37
+ task :default => :test
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "hello-gem #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
@@ -0,0 +1,106 @@
1
+ require File.dirname(__FILE__) + '/../happening'
2
+
3
+ require 'benchmark'
4
+ require 'right_aws'
5
+
6
+ AWS_ACCESS_KEY_ID = ENV['AWS_ACCESS_KEY_ID'] or raise "please set AWS_ACCESS_KEY_ID='your-key'"
7
+ AWS_SECRET_ACCESS_KEY = ENV['AWS_SECRET_ACCESS_KEY'] or raise "please set AWS_SECRET_ACCESS_KEY='your-scret'"
8
+
9
+ BUCKET = 'happening-benchmark'
10
+ FILE = 'the_file_name'
11
+ PROTOCOL = 'https'
12
+
13
+ COUNT = 100
14
+ CONTENT = File.read('/tmp/VzLinuxUG.pdf')
15
+
16
+ command = ARGV.first || 'get'
17
+
18
+ puts "running command: #{command}"
19
+
20
+ if command == 'get'
21
+ Benchmark.bm(7) do |x|
22
+ x.report("RightAWS - Get an item") do
23
+ count = COUNT
24
+ s3 = RightAws::S3Interface.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, :protocol => PROTOCOL)
25
+ count.times do |i|
26
+ s3.get_object(BUCKET, FILE)
27
+ print '.'; $stdout.flush
28
+ end
29
+ end
30
+
31
+ puts ""
32
+ x.report("Happening - Get an item") do
33
+ puts ""
34
+ count = COUNT
35
+ on_success = Proc.new do |http|
36
+ print '.'; $stdout.flush
37
+ count = count - 1
38
+ EM.stop if count <= 0
39
+ end
40
+
41
+ on_error = Proc.new do |http|
42
+ puts "Status: #{http.response_header.status}"
43
+ puts "Header: #{http.response_header.inspect}"
44
+ puts "Content:"
45
+ puts http.response.inspect + "\n"
46
+ count = count - 1
47
+ EM.stop if count <= 0
48
+ end
49
+
50
+ EM.run do
51
+ count.times do |i|
52
+ item = Happening::S3::Item.new(BUCKET, FILE, :protocol => PROTOCOL, :on_success => on_success, :on_error => on_error)
53
+ item.get
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ elsif command == 'put'
60
+ Benchmark.bm(7) do |x|
61
+ x.report("RightAWS - Put an item") do
62
+ count = COUNT
63
+ s3 = RightAws::S3Interface.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, :protocol => PROTOCOL)
64
+ count.times do |i|
65
+ s3.put(BUCKET, "upload_test_right_aws_#{i}", CONTENT)
66
+ print '.'; $stdout.flush
67
+ end
68
+ end
69
+
70
+ puts ""
71
+ x.report("Happening - Put an item") do
72
+ puts ""
73
+ count = COUNT
74
+ on_success = Proc.new do |http|
75
+ #puts "Success"
76
+ puts "Status: #{http.response_header.status}" unless http.response_header.status == 200
77
+ #puts "Header: #{http.response_header.inspect}"
78
+ #puts "Content:"
79
+ #puts http.response.inspect + "\n"
80
+ print '.'; $stdout.flush
81
+ count = count - 1
82
+ EM.stop if count <= 0
83
+ end
84
+
85
+ on_error = Proc.new do |http|
86
+ puts "Error"
87
+ puts "Status: #{http.response_header.status}"
88
+ puts "Header: #{http.response_header.inspect}"
89
+ puts "Content:"
90
+ puts http.response.inspect + "\n"
91
+ count = count - 1
92
+ EM.stop if count <= 0
93
+ end
94
+
95
+ EM.run do
96
+ count.times do |i|
97
+ item = Happening::S3::Item.new(BUCKET, "upload_test_happening_#{i}", :protocol => PROTOCOL, :on_success => on_success, :on_error => on_error, :aws_access_key_id => AWS_ACCESS_KEY_ID, :aws_secret_access_key => AWS_SECRET_ACCESS_KEY)
98
+ item.put(CONTENT)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ else
105
+ puts "unknown command: #{command}"
106
+ end
data/happening.gemspec ADDED
@@ -0,0 +1,70 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rcarvalho-happening}
8
+ s.version = "0.2.5.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Jonathan Weiss}]
12
+ s.date = %q{2012-07-26}
13
+ s.description = %q{An EventMachine based S3 client }
14
+ s.email = %q{jw@innerewut.de}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ "CHANGELOG.md",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.md",
24
+ "Rakefile",
25
+ "benchmark/right_aws.rb",
26
+ "happening.gemspec",
27
+ "lib/happening.rb",
28
+ "lib/happening/aws.rb",
29
+ "lib/happening/log.rb",
30
+ "lib/happening/s3.rb",
31
+ "lib/happening/s3/item.rb",
32
+ "lib/happening/s3/request.rb",
33
+ "lib/happening/utils.rb",
34
+ "test/aws_test.rb",
35
+ "test/s3/item_test.rb",
36
+ "test/s3/request_test.rb",
37
+ "test/s3_test.rb",
38
+ "test/test_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/peritor/happening}
41
+ s.licenses = [%q{BSD}]
42
+ s.require_paths = [%q{lib}]
43
+ s.rubygems_version = %q{1.8.7}
44
+ s.summary = %q{An EventMachine based S3 client}
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<em-http-request>, [">= 0"])
51
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
52
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
54
+ s.add_development_dependency(%q<mocha>, [">= 0"])
55
+ else
56
+ s.add_dependency(%q<em-http-request>, [">= 0"])
57
+ s.add_dependency(%q<shoulda>, [">= 0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
60
+ s.add_dependency(%q<mocha>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<em-http-request>, [">= 0"])
64
+ s.add_dependency(%q<shoulda>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
67
+ s.add_dependency(%q<mocha>, [">= 0"])
68
+ end
69
+ end
70
+
@@ -0,0 +1,65 @@
1
+ require 'time'
2
+ module Happening
3
+ class AWS
4
+ include Utils
5
+
6
+ AMAZON_HEADER_PREFIX = 'x-amz-'
7
+ AMAZON_METADATA_PREFIX = 'x-amz-meta-'
8
+ DIGEST = OpenSSL::Digest.new('sha1')
9
+
10
+ attr_accessor :aws_access_key_id, :aws_secret_access_key
11
+
12
+ def initialize(aws_access_key_id, aws_secret_access_key)
13
+ @aws_access_key_id = aws_access_key_id
14
+ @aws_secret_access_key = aws_secret_access_key
15
+ raise ArgumentError, "need AWS Access Key Id and AWS Secret Key" if blank?(aws_access_key_id) || blank?(aws_secret_access_key)
16
+ end
17
+
18
+ def sign(method, path, headers={})
19
+ headers = {
20
+ 'date' => utc_httpdate
21
+ }.update(headers)
22
+
23
+ request_description = canonical_request_description(method, path, headers)
24
+ headers.update("Authorization" => "AWS #{aws_access_key_id}:#{generate_signature(request_description)}")
25
+ end
26
+
27
+ protected
28
+
29
+ def utc_httpdate
30
+ Time.now.utc.httpdate
31
+ end
32
+
33
+ def generate_signature(request_description)
34
+ Base64.encode64(OpenSSL::HMAC.digest(DIGEST, aws_secret_access_key, request_description)).strip
35
+ end
36
+
37
+ def canonical_request_description(method, path, headers = {}, expires = nil)
38
+ s3_attributes = {}
39
+ headers.each do |key, value|
40
+ key = key.downcase
41
+ s3_attributes[key] = value.to_s.strip if key.match(/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o)
42
+ end
43
+ s3_attributes['content-type'] ||= ''
44
+ s3_attributes['content-md5'] ||= ''
45
+ s3_attributes['date'] = '' if s3_attributes.has_key?('x-amz-date')
46
+ s3_attributes['date'] = expires if expires
47
+
48
+ description = "#{method}\n"
49
+ s3_attributes.sort { |a, b| a[0] <=> b[0] }.each do |key, value|
50
+ description << (key.match(/^#{AMAZON_HEADER_PREFIX}/o) ? "#{key}:#{value}\n" : "#{value}\n")
51
+ end
52
+
53
+ # ignore all parameters by default
54
+ description << path.gsub(/\?.*$/, '')
55
+
56
+ # handle amazon parameters
57
+ description << '?acl' if path[/[&?]acl($|&|=)/]
58
+ description << '?torrent' if path[/[&?]torrent($|&|=)/]
59
+ description << '?location' if path[/[&?]location($|&|=)/]
60
+ description << '?logging' if path[/[&?]logging($|&|=)/]
61
+ description
62
+ end
63
+
64
+ end
65
+ end
@@ -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("Happening: #{msg}")
25
+ end
26
+
27
+ def self.info(msg)
28
+ logger.debug("Happening: #{msg}")
29
+ end
30
+
31
+ def self.warn(msg)
32
+ logger.debug("Happening: #{msg}")
33
+ end
34
+
35
+ def self.error(msg)
36
+ logger.debug("Happening: #{msg}")
37
+ end
38
+
39
+ end
40
+ end