Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jakub Kuźma
committed
Jun 26, 2009
1 parent
eeeff7b
commit 81adaf3
Showing
9 changed files
with
252 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,15 @@ | ||
require "time" | ||
require "openssl" | ||
require "net/http" | ||
require "net/https" | ||
require "base64" | ||
require "forwardable" | ||
require "digest/md5" | ||
|
||
require "s3/authentication" | ||
require "xmlsimple" | ||
|
||
require "s3/base" | ||
require "s3/signature" | ||
require "s3/service" | ||
require "s3/bucket" | ||
require "s3/object" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
module S3 | ||
class Base | ||
HOST = "s3.amazonaws.com" | ||
|
||
attr_accessor :access_key_id, :secret_access_key, :host, :use_ssl | ||
alias :use_ssl? :use_ssl | ||
|
||
def initialize(options) | ||
@access_key_id = options[:access_key_id] | ||
@secret_access_key = options[:secret_access_key] | ||
@use_ssl = options[:use_ssl] || false | ||
@host = options[:host] || HOST | ||
end | ||
|
||
def port | ||
use_ssl? ? 443 : 80 | ||
end | ||
|
||
def send_request(request) | ||
http = Net::HTTP.new(host, port) | ||
http.set_debug_output(STDOUT) | ||
http.use_ssl = use_ssl? | ||
response = http.start do |http| | ||
host = http.address | ||
request['Date'] ||= Time.now.httpdate | ||
request["Authorization"] = S3::Signature.generate(:host => host, | ||
:request => request, | ||
:access_key_id => access_key_id, | ||
:secret_access_key => secret_access_key) | ||
http.request(request) | ||
end | ||
|
||
# TODO: handle 40x responses | ||
|
||
response | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
module S3 | ||
class Bucket < Base | ||
attr_accessor :name | ||
|
||
def initialize(options) | ||
@name = options[:name] | ||
options[:host], @path_prefix = self.class.parse_name(options[:name], options[:host] || HOST) | ||
super | ||
end | ||
|
||
def self.parse_name(bucket_name, host) | ||
path_prefix = "" | ||
if "#{bucket_name}.#{host}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/ | ||
# VHOST | ||
host = "#{bucket_name}.#{host}" | ||
else | ||
# PATH BASED | ||
path_prefix = "/#{bucket_name}" | ||
end | ||
[host, path_prefix] | ||
end | ||
|
||
def location | ||
path = @path_prefix | ||
path << "/?location" | ||
|
||
request = Net::HTTP::Get.new(path) | ||
response = send_request(request) | ||
parse_location(response.body) | ||
end | ||
|
||
def exists? | ||
path = @path_prefix | ||
path << "/" | ||
|
||
request = Net::HTTP::Head.new(path) | ||
send_request(request) | ||
true | ||
end | ||
|
||
def delete(options = {}) | ||
path = @path_prefix | ||
path << "/" | ||
|
||
request = Net::HTTP::Delete.new(path) | ||
send_request(request) | ||
end | ||
|
||
def save(options = {}) | ||
location = options[:location] | ||
|
||
path = @path_prefix | ||
path << "/" | ||
|
||
request = Net::HTTP::Put.new(path) | ||
|
||
if location | ||
xml = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>" | ||
request["content-type"] = "application/xml" | ||
request.body = xml | ||
end | ||
|
||
send_request(request) | ||
end | ||
|
||
def objects(options = {}) | ||
path = @path_prefix | ||
path << "/" | ||
|
||
params = {} | ||
params["max-keys"] = options[:limit] if options.has_key?(:limit) | ||
params["prefix"] = options[:prefix] if options.has_key?(:prefix) | ||
params["marker"] = options[:marker] if options.has_key?(:marker) | ||
params["delimiter"] = options[:delimiter] if options.has_key?(:delimiter) | ||
|
||
joined_params = params.map { |key, value| "#{key}=#{value}" }.join("&") | ||
|
||
path << "?#{joined_params}" unless joined_params.empty? | ||
|
||
request = Net::HTTP::Get.new(path) | ||
response = send_request(request) | ||
parse_objects(response.body) | ||
end | ||
|
||
# OBJECT methods | ||
|
||
def build_object(options = {}) | ||
Object.new(options.merge(:bucket => self)) | ||
end | ||
|
||
def save_object(object) | ||
content_type = object.content_type | ||
content = object.content | ||
content_type ||= "application/octet-stream" | ||
acl = object.acl || "public-read" | ||
content = content.read if content.kind_of?(IO) | ||
|
||
path = @path_prefix | ||
path << "/" | ||
path << object.key | ||
|
||
request = Net::HTTP::Put.new(path) | ||
|
||
request["content-type"] = content_type | ||
request["x-amz-acl"] = acl | ||
request["content-md5"] = Base64.encode64(Digest::MD5.digest(content)).chomp | ||
|
||
request.body = content | ||
|
||
response = send_request(request) | ||
end | ||
|
||
def delete_object(object) | ||
path = @path_prefix | ||
path << "/" | ||
path << object.key | ||
|
||
request = Net::HTTP::Delete.new(path) | ||
|
||
response = send_request(request) | ||
end | ||
|
||
def get_object(object, options = {}) | ||
path = @path_prefix | ||
path << "/" | ||
path << object.key | ||
|
||
request = Net::HTTP::Get.new(path) | ||
|
||
response = send_request(request) | ||
end | ||
|
||
private | ||
|
||
def parse_objects(xml_body) | ||
xml = XmlSimple.xml_in(xml_body) | ||
objects_attributes = xml["Contents"] | ||
objects_attributes.map do |object| | ||
Object.new(:key => object["Key"], | ||
:etag => object["ETag"], | ||
:last_modified => object["LastModified"], | ||
:size => object["Size"], | ||
:bucket => self) | ||
end | ||
end | ||
|
||
def parse_location(xml_body) | ||
xml = XmlSimple.xml_in(xml_body) | ||
xml["content"] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module S3 | ||
class Object | ||
attr_accessor :key, :last_modified, :etag, :size, :bucket, :content, :content_type, :acl | ||
|
||
protected | ||
|
||
def initialize(options) | ||
@acl = options[:acl] | ||
@bucket = options[:bucket] | ||
@key = options[:key] | ||
@last_modified = options[:last_modified] | ||
@etag = options[:etag] | ||
@size = options[:size] | ||
@content = options[:content] | ||
@content_type = options[:content_type] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,24 @@ | ||
module S3 | ||
class Service | ||
HOST = "s3.amazonaws.com" | ||
PORT = 80 | ||
|
||
def initialize(options) | ||
@access_key_id = options[:access_key_id] | ||
@secret_access_key = options[:secret_access_key] | ||
end | ||
|
||
class Service < Base | ||
def buckets | ||
request = Net::HTTP::Get.new("/") | ||
response = send_request(request) | ||
parse_buckets(response.body) | ||
end | ||
|
||
private | ||
|
||
http = Net::HTTP.new(HOST, PORT) | ||
http.set_debug_output(STDOUT) | ||
http.start do |http| | ||
host = http.address | ||
request['Date'] ||= Time.now.httpdate | ||
request["Authorization"] = S3::Signature.generate(:host => host, | ||
:request => request, | ||
:access_key_id => @access_key_id, | ||
:secret_access_key => @secret_access_key) | ||
http.request(request) | ||
def parse_buckets(xml_body) | ||
xml = XmlSimple.xml_in(xml_body) | ||
buckets_names = xml["Buckets"].first["Bucket"].map { |bucket| bucket["Name"].first } | ||
buckets_names.map do |bucket_name| | ||
Bucket.new(:name => bucket_name, | ||
:access_key_id => access_key_id, | ||
:secret_access_key => secret_access_key, | ||
:host => host, | ||
:use_ssl => use_ssl) | ||
end | ||
end | ||
|
||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
require 'test_helper' | ||
|
||
class BucketTest < Test::Unit::TestCase | ||
def test_parse_name_with_vhost_name | ||
host, prefix = S3::Bucket.parse_name("data.synergypeople.net", "s3.amazonaws.com") | ||
assert_equal "data.synergypeople.net.s3.amazonaws.com", host | ||
assert_equal "", prefix | ||
end | ||
|
||
def test_parse_name_with_prefix_based_name | ||
host, prefix = S3::Bucket.parse_name("synergypeople_net", "s3.amazonaws.com") | ||
assert_equal "s3.amazonaws.com", host | ||
assert_equal "/synergypeople_net", prefix | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,5 @@ | ||
require 'test_helper' | ||
|
||
class S3Test < Test::Unit::TestCase | ||
def test_something_for_real | ||
flunk "hey buddy, you should probably rename this file and start testing for real" | ||
end | ||
|
||
end |