awsraw 0.1.9 → 1.0.0.alpha.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.
- checksums.yaml +4 -4
- data/README.md +121 -49
- data/Rakefile +0 -6
- data/awsraw.gemspec +6 -5
- data/lib/awsraw.rb +8 -3
- data/lib/awsraw/credentials.rb +7 -0
- data/lib/awsraw/error.rb +4 -0
- data/lib/awsraw/s3/canonicalized_resource.rb +50 -0
- data/lib/awsraw/s3/client.rb +50 -28
- data/lib/awsraw/s3/content_md5_header.rb +34 -0
- data/lib/awsraw/s3/faraday_middleware.rb +39 -0
- data/lib/awsraw/s3/query_string_signer.rb +26 -37
- data/lib/awsraw/s3/signature.rb +52 -0
- data/lib/awsraw/s3/string_to_sign.rb +49 -0
- data/lib/awsraw/version.rb +2 -2
- data/spec/s3/canonicalized_resource_spec.rb +51 -0
- data/spec/s3/content_md5_header_spec.rb +27 -0
- data/spec/s3/faraday_middleware_spec.rb +88 -0
- data/spec/s3/query_string_signer_spec.rb +13 -57
- data/spec/s3/signature_spec.rb +107 -0
- data/spec/s3/string_to_sign_spec.rb +94 -0
- metadata +59 -56
- checksums.yaml.gz.sig +0 -2
- data.tar.gz.sig +0 -0
- data/.travis.yml +0 -7
- data/lib/awsraw/s3.rb +0 -15
- data/lib/awsraw/s3/configuration.rb +0 -12
- data/lib/awsraw/s3/http_request_builder.rb +0 -51
- data/lib/awsraw/s3/md5_digester.rb +0 -19
- data/lib/awsraw/s3/request.rb +0 -65
- data/lib/awsraw/s3/response.rb +0 -26
- data/lib/awsraw/s3/signer.rb +0 -86
- data/spec/s3/client_spec.rb +0 -27
- data/spec/s3/configuration_spec.rb +0 -23
- data/spec/s3/http_request_builder_spec.rb +0 -45
- data/spec/s3/md5_digester_spec.rb +0 -21
- data/spec/s3/request_spec.rb +0 -23
- data/spec/s3/signer_spec.rb +0 -75
- metadata.gz.sig +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74ab136d9a8688640905f2ffd967f828dc960f42
|
4
|
+
data.tar.gz: d961172c156fe8a271b94d33665597cea23e249b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2512ee73dde56b39b937258e0432eacd3b12a2056905ae28b921df5afb758d7d98cafe8225f3d96cefbcb9daf2833c42037724e8e9cc04ff57024f64b1c22d6d
|
7
|
+
data.tar.gz: c39c859a8b5516efaf9fd33dd0afc234c23efa70d79d802571b0baf68aebc81af417afcaae06a5886b830e46be35a9f9a6ea02e76f37f0dd92fe9882f559dab3
|
data/README.md
CHANGED
@@ -1,76 +1,148 @@
|
|
1
|
-
# AWSRaw
|
1
|
+
# AWSRaw
|
2
2
|
|
3
3
|
A client for [Amazon Web Services](http://www.amazonaws.com/) in the style of
|
4
|
-
[FlickRaw](http://hanklords.github.com/flickraw/)
|
4
|
+
[FlickRaw](http://hanklords.github.com/flickraw/).
|
5
5
|
|
6
6
|
## Background
|
7
7
|
|
8
|
-
AWSRaw
|
9
|
-
|
10
|
-
|
11
|
-
sending a `DELETE` request to the bucket/key URL will result in the file
|
12
|
-
being deleted.
|
8
|
+
AWSRaw has a simple goal: to let you follow the [AWS API
|
9
|
+
docs](http://aws.amazon.com/documentation/), and translate that into Ruby code
|
10
|
+
with the minimum of fuss.
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
able to use a new AWS feature by simply following the AWS docs' examples
|
17
|
-
directly is very nice, instead of having to dig deep into a higher-level
|
18
|
-
library to figure out how they've mapped that new feature into their
|
19
|
-
terminology and API.
|
12
|
+
This is the opposite of [fog](http://fog.io). AWSRaw tries to add as little
|
13
|
+
abstraction as possible on top of the AWS REST API.
|
20
14
|
|
21
|
-
|
15
|
+
You use a regular HTTP library to make requests, and AWSRaw provides useful
|
16
|
+
additions like request signing.
|
22
17
|
|
23
|
-
|
18
|
+
|
19
|
+
## Examples
|
20
|
+
|
21
|
+
### Credentials
|
22
|
+
|
23
|
+
For all the examples below, you'll need to set up your credentials like this:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
config.host = 'fake.s3.dev'
|
31
|
-
config.regional_hosts = {
|
32
|
-
'ap-southeast-2' => 'fake.s3.dev'
|
33
|
-
}
|
34
|
-
end
|
26
|
+
credentials = AWSRaw::Credentials.new(
|
27
|
+
:access_key_id => "...",
|
28
|
+
:secret_access_key => "..."
|
29
|
+
)
|
35
30
|
```
|
36
31
|
|
37
|
-
## Usage
|
38
|
-
|
39
32
|
### S3
|
40
33
|
|
41
|
-
|
34
|
+
Set up your Faraday connection something like this:
|
42
35
|
|
43
36
|
```ruby
|
44
|
-
|
37
|
+
connection = Faraday.new("http://s3.amazonaws.com") do |faraday|
|
38
|
+
faraday.use AWSRaw::S3::FaradayMiddleware, credentials
|
39
|
+
faraday.response :logger
|
40
|
+
faraday.adapter Faraday.default_adapter
|
41
|
+
end
|
42
|
+
```
|
45
43
|
|
46
|
-
|
47
|
-
ENV['AWS_ACCESS_KEY_ID'],
|
48
|
-
ENV['AWS_SECRET_ACCESS_KEY'])
|
44
|
+
A simple GET request:
|
49
45
|
|
50
|
-
|
46
|
+
```ruby
|
47
|
+
response = connection.get("/mah-sekret-buckit/reaction.gif")
|
48
|
+
```
|
51
49
|
|
52
|
-
|
53
|
-
:bucket => "mah-sekret-buckit",
|
54
|
-
:key => "/reaction.gif",
|
55
|
-
:content => file,
|
56
|
-
:headers => { "Content-Type" => "image/gif" })
|
50
|
+
A PUT request:
|
57
51
|
|
58
|
-
|
52
|
+
```ruby
|
53
|
+
connection.put do |request|
|
54
|
+
request.url "/mah-sekret-buckit/reaction.gif"
|
55
|
+
request.headers["Content-Type"] = "image/gif"
|
56
|
+
request.body = File.new("reaction.gif")
|
57
|
+
end
|
59
58
|
```
|
60
59
|
|
61
|
-
|
62
|
-
|
60
|
+
See the [AWS S3 REST API docs](http://docs.aws.amazon.com/AmazonS3/latest/API/APIRest.html)
|
61
|
+
for all the requests you can make.
|
62
|
+
|
63
|
+
|
64
|
+
#### On request bodies
|
65
|
+
|
66
|
+
If your request has a body and you don't provide a Content-MD5 header for it,
|
67
|
+
AWSRaw will try to calculate one. (The S3 API requires the Content-MD5 header
|
68
|
+
for correct request signing.)
|
69
|
+
|
70
|
+
It can handle the body behaving as either a String or a File. If you want to do
|
71
|
+
something different with the body, you'll need to set the Content-MD5 header
|
72
|
+
yourself.
|
73
|
+
|
74
|
+
You must also provide a Content-Type header for your request if there's a
|
75
|
+
request body. AWSRaw will raise an exception if you don't.
|
76
|
+
|
77
|
+
|
78
|
+
#### Signing query strings
|
79
|
+
|
80
|
+
If you need a signed URI with an expiry date, this is how to do it. See the
|
81
|
+
[AWS docs on the
|
82
|
+
subject](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth).
|
83
|
+
|
63
84
|
|
64
85
|
```ruby
|
65
|
-
|
86
|
+
signer = AWSRaw::S3::QueryStringSigner.new(credentials)
|
87
|
+
|
88
|
+
uri = signer.sign(
|
89
|
+
"https://s3.amazonaws.com/mah-sekret-buckit/reaction.gif",
|
90
|
+
Time.now + 600 # The URI will expire in 10 minutes.
|
91
|
+
)
|
92
|
+
```
|
93
|
+
|
94
|
+
|
95
|
+
#### HTML Form Uploads
|
66
96
|
|
67
|
-
|
68
|
-
|
69
|
-
|
97
|
+
You can use AWSRaw to generate signatures for browser-based uploads. See the
|
98
|
+
[AWS docs on the
|
99
|
+
topic](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html).
|
70
100
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
101
|
+
```ruby
|
102
|
+
policy = [
|
103
|
+
{ "bucket" => "mah-secret-buckit" }
|
104
|
+
]
|
105
|
+
|
106
|
+
policy_json = JSON.generate(policy)
|
107
|
+
|
108
|
+
http_post_variables = {
|
109
|
+
"AWSAccessKeyID" => credentials.access_key_id,
|
110
|
+
"key" => "reaction.gif",
|
111
|
+
"policy" => AWSRaw::S3::Signature.encode_form_policy(policy_json),
|
112
|
+
"signature" => AWSRaw::S3::Signature.form_signature(policy_json, credentials)
|
113
|
+
}
|
76
114
|
```
|
115
|
+
|
116
|
+
Then get your browser to do an XHR request using the http_post_variables, and
|
117
|
+
Bob's your aunty.
|
118
|
+
|
119
|
+
|
120
|
+
## Status
|
121
|
+
|
122
|
+
This is still a bit experimental, and is missing some key features, but what's
|
123
|
+
there is solid and well tested.
|
124
|
+
|
125
|
+
Right now AWSRaw only has direct support for
|
126
|
+
[Faraday](https://github.com/lostisland/faraday), but you could still use it
|
127
|
+
with other client libraries with a bit of work.
|
128
|
+
|
129
|
+
So far we've only built S3 support. We'd love to see pull requests for other
|
130
|
+
AWS services.
|
131
|
+
|
132
|
+
|
133
|
+
## Contributing
|
134
|
+
|
135
|
+
1. Fork it
|
136
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
137
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
138
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
139
|
+
5. Create new Pull Request
|
140
|
+
|
141
|
+
|
142
|
+
## To Do
|
143
|
+
|
144
|
+
- Add smart handling of errors
|
145
|
+
- Identify cases where string-to-sign doesn't match, and display something helpful
|
146
|
+
- Raise exceptions for errors?
|
147
|
+
- Add easy ways to nicely format XML responses
|
148
|
+
|
data/Rakefile
CHANGED
data/awsraw.gemspec
CHANGED
@@ -4,9 +4,9 @@ require "awsraw/version"
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "awsraw"
|
7
|
-
s.version =
|
8
|
-
s.authors = ["Pete Yandell", "David Goodlad", "Jack 'chendo' Chen"
|
9
|
-
s.email = ["pete@notahat.com", "david@goodlad.net", "gems.awsraw@chen.do"
|
7
|
+
s.version = AWSRaw::VERSION
|
8
|
+
s.authors = ["Pete Yandell", "David Goodlad", "Jack 'chendo' Chen"]
|
9
|
+
s.email = ["pete@notahat.com", "david@goodlad.net", "gems.awsraw@chen.do"]
|
10
10
|
s.license = 'MIT'
|
11
11
|
s.homepage = "http://github.com/envato/awsraw"
|
12
12
|
s.summary = %q{Minimal AWS client}
|
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
# specify any dependencies here; for example:
|
23
23
|
s.add_development_dependency "rake"
|
24
|
-
s.add_development_dependency "rspec"
|
25
|
-
|
24
|
+
s.add_development_dependency "rspec", "~> 2.14"
|
25
|
+
s.add_runtime_dependency "finer_struct", "~> 0.0.5"
|
26
|
+
s.add_runtime_dependency "faraday", "~> 0.8.8"
|
26
27
|
end
|
data/lib/awsraw.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
require "awsraw/error"
|
1
2
|
require "awsraw/version"
|
3
|
+
require "awsraw/s3/canonicalized_resource"
|
4
|
+
require "awsraw/s3/client"
|
5
|
+
require "awsraw/s3/content_md5_header"
|
6
|
+
require "awsraw/s3/faraday_middleware"
|
7
|
+
require "awsraw/s3/query_string_signer"
|
8
|
+
require "awsraw/s3/signature"
|
9
|
+
require "awsraw/s3/string_to_sign"
|
2
10
|
|
3
|
-
module AWSRaw
|
4
|
-
|
5
|
-
end
|
data/lib/awsraw/error.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module AWSRaw
|
4
|
+
module S3
|
5
|
+
|
6
|
+
module CanonicalizedResource
|
7
|
+
|
8
|
+
def self.canonicalized_resource(uri)
|
9
|
+
uri = URI(uri)
|
10
|
+
bucket = bucket_from_hostname(uri.hostname)
|
11
|
+
|
12
|
+
[bucket && "/#{bucket}", uri.path, canonicalized_subresources(uri.query)].join
|
13
|
+
end
|
14
|
+
|
15
|
+
# Extract the bucket name from the hostname for virtual-host-style and
|
16
|
+
# cname-style S3 requests. Returns nil for path-style requests.
|
17
|
+
#
|
18
|
+
# See: http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
19
|
+
def self.bucket_from_hostname(hostname)
|
20
|
+
if hostname =~ %r{s3[-\w\d]*\.amazonaws\.com$}
|
21
|
+
components = hostname.split(".")
|
22
|
+
if components.length > 3
|
23
|
+
components[0..-4].join(".")
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
else
|
28
|
+
hostname
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
VALID_SUBRESOURCES = %w{acl lifecycle location logging notification partNumber policy requestPayment torrent uploadId uploads versionId versioning versions website}
|
33
|
+
|
34
|
+
# Generates the canonicalized subresources for a URI, as per:
|
35
|
+
#
|
36
|
+
# http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
|
37
|
+
#
|
38
|
+
# Note: This is incomplete, in that it doesn't handle header values
|
39
|
+
# that are overridden by parameters, nor does it handle the "delete"
|
40
|
+
# parameter for multi-object Delete requests.
|
41
|
+
def self.canonicalized_subresources(query)
|
42
|
+
query ||= ""
|
43
|
+
subresources = query.split("&") & VALID_SUBRESOURCES
|
44
|
+
"?#{subresources.sort.join('&')}" unless subresources.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/awsraw/s3/client.rb
CHANGED
@@ -1,35 +1,42 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require 'awsraw/
|
4
|
-
require 'awsraw/s3/
|
5
|
-
|
6
|
-
require 'awsraw/s3/signer'
|
7
|
-
require 'awsraw/s3/md5_digester'
|
1
|
+
require 'uri'
|
2
|
+
require 'faraday'
|
3
|
+
require 'awsraw/credentials'
|
4
|
+
require 'awsraw/s3/faraday_middleware'
|
5
|
+
|
8
6
|
module AWSRaw
|
9
7
|
module S3
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
# A client for the AWS S3 rest API.
|
14
|
-
#
|
15
|
-
# http://docs.amazonwebservices.com/AmazonS3/latest/API/APIRest.html
|
9
|
+
# Legacy client, to support AWSRaw pre-1.0 requests. You shouldn't be using
|
10
|
+
# this anymore.
|
16
11
|
class Client
|
17
12
|
|
18
13
|
def initialize(access_key_id, secret_access_key)
|
19
|
-
@
|
20
|
-
|
14
|
+
@credentials = AWSRaw::Credentials.new(
|
15
|
+
:access_key_id => access_key_id,
|
16
|
+
:secret_access_key => secret_access_key
|
17
|
+
)
|
21
18
|
end
|
22
19
|
|
23
20
|
def request(params = {})
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
host = params[:region] ? "s3-#{params[:region]}.amazonaws.com" : "s3.amazonaws.com"
|
22
|
+
path = URI.escape("/#{params[:bucket]}#{params[:key]}")
|
23
|
+
url = URI::HTTP.build(
|
24
|
+
:host => host,
|
25
|
+
:path => path,
|
26
|
+
:query => params[:query]
|
27
|
+
)
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
faraday_response = connection.send(params[:method].downcase) do |request|
|
30
|
+
request.url(url)
|
31
|
+
request.headers = params[:headers] || {}
|
32
|
+
request.body = params[:content]
|
30
33
|
end
|
31
34
|
|
32
|
-
|
35
|
+
Response.new(
|
36
|
+
:code => faraday_response.status,
|
37
|
+
:headers => faraday_response.headers,
|
38
|
+
:content => faraday_response.body
|
39
|
+
)
|
33
40
|
end
|
34
41
|
|
35
42
|
def request!(params = {})
|
@@ -39,18 +46,33 @@ module AWSRaw
|
|
39
46
|
|
40
47
|
private
|
41
48
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
def connection
|
50
|
+
@connection ||= Faraday.new do |faraday|
|
51
|
+
faraday.use AWSRaw::S3::FaradayMiddleware, @credentials
|
52
|
+
faraday.adapter Faraday.default_adapter
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class Response
|
59
|
+
def initialize(params = {})
|
60
|
+
@code = params[:code]
|
61
|
+
@headers = params[:headers]
|
62
|
+
@content = params[:content]
|
48
63
|
end
|
49
64
|
|
50
|
-
|
51
|
-
|
65
|
+
attr_accessor :code
|
66
|
+
attr_accessor :headers
|
67
|
+
attr_accessor :content
|
68
|
+
|
69
|
+
def success?
|
70
|
+
code =~ /^2\d\d$/
|
52
71
|
end
|
53
72
|
|
73
|
+
def failure?
|
74
|
+
!success?
|
75
|
+
end
|
54
76
|
end
|
55
77
|
|
56
78
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module AWSRaw
|
4
|
+
module S3
|
5
|
+
module ContentMD5Header
|
6
|
+
|
7
|
+
def self.generate_content_md5(body)
|
8
|
+
return nil if body.nil?
|
9
|
+
|
10
|
+
digest = Digest::MD5.new
|
11
|
+
if body.respond_to?(:read)
|
12
|
+
read_file_into_digest(digest, body)
|
13
|
+
else
|
14
|
+
digest << body
|
15
|
+
end
|
16
|
+
|
17
|
+
digest.base64digest
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# This mimics the behaviour of Ruby's Digest::Instance#file method.
|
23
|
+
# Unfortunately that takes a filename not a file, so we can't use it.
|
24
|
+
def self.read_file_into_digest(digest, file)
|
25
|
+
buffer = ""
|
26
|
+
while file.read(16384, buffer)
|
27
|
+
digest << buffer
|
28
|
+
end
|
29
|
+
file.rewind # ...so the HTTP client can read the body for sending.
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|