dynamodb 0.0.2 → 1.1.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.
- data/.gitignore +1 -0
- data/Gemfile +2 -1
- data/README.md +41 -11
- data/dynamodb.gemspec +3 -2
- data/lib/dynamodb.rb +18 -17
- data/lib/dynamodb/connection.rb +34 -70
- data/lib/dynamodb/failure_response.rb +41 -0
- data/lib/dynamodb/http_handler.rb +74 -0
- data/lib/dynamodb/request.rb +107 -0
- data/lib/dynamodb/success_response.rb +51 -0
- data/lib/dynamodb/version.rb +3 -0
- data/lib/net/http/connection_pool.rb +226 -0
- data/lib/net/http/connection_pool/connection.rb +189 -0
- data/lib/net/http/connection_pool/session.rb +126 -0
- data/spec/dynamodb/connection_spec.rb +14 -117
- data/spec/dynamodb/failure_response_spec.rb +27 -0
- data/spec/dynamodb/http_handler_spec.rb +71 -0
- data/spec/dynamodb/request_spec.rb +31 -0
- data/spec/dynamodb/success_response_spec.rb +93 -0
- data/spec/dynamodb_spec.rb +24 -0
- metadata +18 -28
- data/lib/dynamodb/credentials.rb +0 -30
- data/lib/dynamodb/response.rb +0 -33
- data/lib/dynamodb/security_token_service.rb +0 -110
- data/lib/dynamodb/typhoeus/request.rb +0 -27
- data/spec/dynamodb/credentials_spec.rb +0 -22
- data/spec/dynamodb/response_spec.rb +0 -87
- data/spec/dynamodb/security_token_service_spec.rb +0 -97
@@ -0,0 +1,51 @@
|
|
1
|
+
module DynamoDB
|
2
|
+
# Successful response from Dynamo
|
3
|
+
class SuccessResponse
|
4
|
+
attr_reader :http_response
|
5
|
+
|
6
|
+
def initialize(http_response)
|
7
|
+
@http_response = http_response
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash_key_element
|
11
|
+
DynamoDB.deserialize(data["LastEvaluatedKey"]["HashKeyElement"])
|
12
|
+
end
|
13
|
+
|
14
|
+
def range_key_element
|
15
|
+
DynamoDB.deserialize(data["LastEvaluatedKey"]["RangeKeyElement"])
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return single item response as a Hash
|
19
|
+
#
|
20
|
+
# Some DynamoDB operations, such as `GetItem`, will only return a
|
21
|
+
# single 'Item' entry. This converts that entry in a Hash where
|
22
|
+
# values have been type-casted into their Ruby equivalents.
|
23
|
+
def item
|
24
|
+
return unless data["Item"]
|
25
|
+
@item ||= DynamoDB.deserialize(data["Item"])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return an Array of item responses
|
29
|
+
#
|
30
|
+
# DynamoDB operations like `Query` return a collection of entries
|
31
|
+
# under the 'Items' key. This returns an Array of Hashes where
|
32
|
+
# values have been casted to their Ruby equivalents.
|
33
|
+
def items
|
34
|
+
return unless data["Items"]
|
35
|
+
@items ||= data["Items"].map { |i| DynamoDB.deserialize(i) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def success?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def body
|
43
|
+
http_response.body
|
44
|
+
end
|
45
|
+
|
46
|
+
# Access the deserialized JSON response body
|
47
|
+
def data
|
48
|
+
@data ||= MultiJson.load(http_response.body)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
8
|
+
#
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
12
|
+
# language governing permissions and limitations under the License.
|
13
|
+
|
14
|
+
require 'net/http/connection_pool/session'
|
15
|
+
require 'net/http/connection_pool/connection'
|
16
|
+
require 'thread'
|
17
|
+
require 'logger'
|
18
|
+
|
19
|
+
# A wrapper around Net::HTTP that provides a manged pool of persistant HTTP
|
20
|
+
# connections.
|
21
|
+
#
|
22
|
+
# pool = Net::HTTP::ConnectionPool.new
|
23
|
+
# connection = pool.connection_for('domain.com')
|
24
|
+
# connection.request(Net::HTTP::Get.new('/')) do |resp|
|
25
|
+
# # Connection#request yields Net::HTTPResponse objects
|
26
|
+
# puts resp.body
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @private
|
30
|
+
class Net::HTTP::ConnectionPool
|
31
|
+
|
32
|
+
# @param [Hash] options
|
33
|
+
#
|
34
|
+
# @option options [Numeric] :http_idle_timeout (60) The number of seconds a
|
35
|
+
# connection is allowed to sit idle before it is closed and removed
|
36
|
+
# from the pool.
|
37
|
+
#
|
38
|
+
# @option options [Numeric] :http_open_timeout (15) The number of seconds to
|
39
|
+
# wait when opening a http session before raising a timeout exception.
|
40
|
+
#
|
41
|
+
# @option options [Boolean] :http_wire_trace (false) When +true+, HTTP
|
42
|
+
# debug output will be sent to the +:logger+.
|
43
|
+
#
|
44
|
+
# @option options [Logger] :logger (Logger.new($stdout)) Where debug out
|
45
|
+
# is sent (wire traces).
|
46
|
+
#
|
47
|
+
def initialize options = {}
|
48
|
+
@pool_mutex = Mutex.new
|
49
|
+
@pool = []
|
50
|
+
@open_timeout = options[:http_open_timeout] || 15
|
51
|
+
@idle_timeout = options[:http_idle_timeout] || 60
|
52
|
+
@http_wire_trace = !!options[:http_wire_trace]
|
53
|
+
if logger = options[:logger]
|
54
|
+
@logger = logger
|
55
|
+
elsif http_wire_trace?
|
56
|
+
@logger = Logger.new($stdout)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Integer]
|
61
|
+
attr_reader :idle_timeout
|
62
|
+
|
63
|
+
# @return [Integer]
|
64
|
+
attr_accessor :open_timeout
|
65
|
+
|
66
|
+
# @return [Boolean] Returns +true+ when HTTP debug output (wire traces)
|
67
|
+
# will be logged.
|
68
|
+
attr_reader :http_wire_trace
|
69
|
+
|
70
|
+
alias_method :http_wire_trace?, :http_wire_trace
|
71
|
+
alias_method :log_wire_trace?, :http_wire_trace
|
72
|
+
alias_method :log_wire_trace, :http_wire_trace
|
73
|
+
|
74
|
+
# @return [Logger] Where debug output is sent.
|
75
|
+
attr_reader :logger
|
76
|
+
|
77
|
+
# Requests a connection object from the connection pool.
|
78
|
+
#
|
79
|
+
# connection = pool.connection_for('domain.com')
|
80
|
+
# connection.request(Net::HTTP::Get.new('/index.html')) {|resp|}
|
81
|
+
# connection.request(Net::HTTP::Get.new('/about.html')) {|resp|}
|
82
|
+
#
|
83
|
+
# # same thing in block form
|
84
|
+
# pool.connection_for('domain.com') do |connection|
|
85
|
+
# connection.request(Net::HTTP::Get.new('/index.html')) {|resp|}
|
86
|
+
# connection.request(Net::HTTP::Get.new('/about.html')) {|resp|}
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# Because the pool manages HTTP sessions you do not have to
|
90
|
+
# worry about closing a connection or returning a connection
|
91
|
+
# to the pool.
|
92
|
+
#
|
93
|
+
# @param [String] host
|
94
|
+
#
|
95
|
+
# @param [Hash] options
|
96
|
+
#
|
97
|
+
# @option options [Integer] :port Which port the connection should use.
|
98
|
+
# Defaults to 80, unless +:ssl+ is +true+, then it defaults to 443.
|
99
|
+
#
|
100
|
+
# @option options [Boolean] :ssl If the connection should be made over
|
101
|
+
# SSL. Defaults to +false+, unless +:port+ is 443, then it defaults
|
102
|
+
# to +true+.
|
103
|
+
#
|
104
|
+
# @option options [Boolean] :ssl_verify_peer (true) If true, ssl
|
105
|
+
# connections should verify peer certificates. This should only ever be
|
106
|
+
# set false false for debugging purposes.
|
107
|
+
#
|
108
|
+
# @option options [String] :ssl_ca_file Full path to the SSL certificate
|
109
|
+
# authority bundle file that should be used when verifying peer
|
110
|
+
# certificates. If you do not pass +:ssl_ca_file+ or +:ssl_ca_path+
|
111
|
+
# the the system default will be used if available.
|
112
|
+
#
|
113
|
+
# @option options [String] :ssl_ca_path Full path of the directory that
|
114
|
+
# contains the unbundled SSL certificate authority files for verifying
|
115
|
+
# peer certificates. If you do not pass +:ssl_ca_file+ or +:ssl_ca_path+
|
116
|
+
# the the system default will be used if available.
|
117
|
+
#
|
118
|
+
# @option options [URI::HTTP,String] :proxy_uri (nil) A URI string or
|
119
|
+
# URI::HTTP object to use as a proxy. You should not provide
|
120
|
+
# +:proxy_uri+ with any other proxy options.
|
121
|
+
#
|
122
|
+
# :proxy_uri => 'http://user:pass@host.com:80'
|
123
|
+
#
|
124
|
+
# @option options [String] :proxy_address
|
125
|
+
#
|
126
|
+
# @option options [String] :proxy_port
|
127
|
+
#
|
128
|
+
# @option options [String] :proxy_user
|
129
|
+
#
|
130
|
+
# @option options [String] :proxy_password
|
131
|
+
#
|
132
|
+
# @yield [connection]
|
133
|
+
#
|
134
|
+
# @yieldparam [optional,Connection] connection
|
135
|
+
#
|
136
|
+
# @return [Connection]
|
137
|
+
#
|
138
|
+
def connection_for host, options = {}, &block
|
139
|
+
connection = Connection.new(self, host, options)
|
140
|
+
yield(connection) if block_given?
|
141
|
+
connection
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the number of sessions currently in the pool, not counting those
|
145
|
+
# currently in use.
|
146
|
+
def size
|
147
|
+
@pool_mutex.synchronize { @pool.size }
|
148
|
+
end
|
149
|
+
|
150
|
+
# Removes stale http sessions from the pool (that have exceeded
|
151
|
+
# the idle timeout).
|
152
|
+
def clean!
|
153
|
+
@pool_mutex.synchronize { _clean }
|
154
|
+
end
|
155
|
+
|
156
|
+
# Closes and removes removes all sessions from the pool.
|
157
|
+
# If empty! is called while there are outstanding requests they may
|
158
|
+
# get checked back into the pool, leaving the pool in a non-empty state.
|
159
|
+
def empty!
|
160
|
+
@pool_mutex.synchronize do
|
161
|
+
@pool.each(&:finish)
|
162
|
+
@pool = []
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Makes a single HTTP request. See {Connection#request} for more information
|
167
|
+
# on making an HTTP request.
|
168
|
+
# @return [nil]
|
169
|
+
# @private
|
170
|
+
def request connection, *args, &block
|
171
|
+
session_for(connection) do |session|
|
172
|
+
session.read_timeout = connection.read_timeout
|
173
|
+
session.request(*args, &block)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
# Yields an open http session for the given connection.
|
180
|
+
def session_for connection, &block
|
181
|
+
|
182
|
+
session = nil
|
183
|
+
|
184
|
+
# search the pool for an idle session that can be used
|
185
|
+
@pool_mutex.synchronize do
|
186
|
+
_clean # removes stale sessions
|
187
|
+
session = @pool.find{|idle_session| idle_session.key == connection.key }
|
188
|
+
@pool.delete(session) if session
|
189
|
+
end
|
190
|
+
|
191
|
+
begin
|
192
|
+
# opens a new HTTP session if no suitable idle session was found
|
193
|
+
session = _create_session(connection) unless session
|
194
|
+
yield(session)
|
195
|
+
rescue Exception => error
|
196
|
+
session.finish if session
|
197
|
+
raise error
|
198
|
+
else
|
199
|
+
# only check the session back into the pool if no errors were raised
|
200
|
+
@pool_mutex.synchronize { @pool << session }
|
201
|
+
end
|
202
|
+
|
203
|
+
nil
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
def _create_session connection
|
208
|
+
Session.start(connection,
|
209
|
+
:open_timeout => open_timeout,
|
210
|
+
:debug_logger => log_wire_trace? ? logger : nil)
|
211
|
+
end
|
212
|
+
|
213
|
+
def _clean
|
214
|
+
now = Time.now
|
215
|
+
@pool.delete_if do |idle_session|
|
216
|
+
if
|
217
|
+
idle_session.last_used_at.nil? or
|
218
|
+
now - idle_session.last_used_at > idle_timeout
|
219
|
+
then
|
220
|
+
idle_session.finish
|
221
|
+
true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
8
|
+
#
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
12
|
+
# language governing permissions and limitations under the License.
|
13
|
+
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
class Net::HTTP::ConnectionPool
|
17
|
+
|
18
|
+
# Represents a HTTP connection. Call {#request} on a connection like
|
19
|
+
# you would with a Net::HTTPSession object.
|
20
|
+
#
|
21
|
+
# == Getting a Connection object
|
22
|
+
#
|
23
|
+
# To get a connection object, you start with a connection pool:
|
24
|
+
#
|
25
|
+
# pool = Net::HTTP::ConnectionPool.new
|
26
|
+
# connection = pool.connection_for('domain.com')
|
27
|
+
#
|
28
|
+
# {ConnectionPool#connection_for} accepts a number of options to control
|
29
|
+
# the connection settings (SSL, proxy, timeouts, etc).
|
30
|
+
#
|
31
|
+
# == Making Requests
|
32
|
+
#
|
33
|
+
# Given a connection object, you call #request. {Connection#request}
|
34
|
+
# yields Net::HTTPResponse objects (when given a block). You should
|
35
|
+
# read the response (via #body or #read_body) before the end of the
|
36
|
+
# block.
|
37
|
+
#
|
38
|
+
# connection.request(Net::HTTP::Get.new('/')) do |resp|
|
39
|
+
# puts resp.body
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
class Connection
|
43
|
+
|
44
|
+
# Use {ConnectionPool#connection_for} to construct {Connection} objects.
|
45
|
+
# @private
|
46
|
+
def initialize pool, host, options = {}
|
47
|
+
|
48
|
+
@pool = pool
|
49
|
+
|
50
|
+
@host = host
|
51
|
+
|
52
|
+
@port = options.key?(:port) ? options[:port] : (options[:ssl] ? 443 : 80)
|
53
|
+
|
54
|
+
@ssl = options.key?(:ssl) ? options[:ssl] : (port == 443)
|
55
|
+
|
56
|
+
@ssl_verify_peer = options.key?(:ssl_verify_peer) ?
|
57
|
+
options[:ssl_verify_peer] : true
|
58
|
+
|
59
|
+
@ssl_ca_file = options[:ssl_ca_file]
|
60
|
+
|
61
|
+
@ssl_ca_path = options[:ssl_ca_path]
|
62
|
+
|
63
|
+
if uri = options[:proxy_uri]
|
64
|
+
uri = URI.parse(uri) if uri.is_a?(String)
|
65
|
+
@proxy_address = uri.host
|
66
|
+
@proxy_port = uri.port
|
67
|
+
@proxy_user = uri.user
|
68
|
+
@proxy_password = uri.password
|
69
|
+
else
|
70
|
+
@proxy_address = options[:proxy_address]
|
71
|
+
@proxy_port = options[:proxy_port]
|
72
|
+
@proxy_user = options[:proxy_user]
|
73
|
+
@proxy_password = options[:proxy_password]
|
74
|
+
end
|
75
|
+
|
76
|
+
@read_timeout = options[:read_timeout] || 60
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [ConnectionPool]
|
81
|
+
attr_reader :pool
|
82
|
+
|
83
|
+
# @return [String]
|
84
|
+
attr_reader :host
|
85
|
+
|
86
|
+
# @return [Integer]
|
87
|
+
attr_reader :port
|
88
|
+
|
89
|
+
# @return [Boolean]
|
90
|
+
attr_reader :ssl
|
91
|
+
|
92
|
+
# @return [Boolean]
|
93
|
+
attr_reader :ssl_verify_peer
|
94
|
+
|
95
|
+
# @return [String,nil]
|
96
|
+
attr_reader :ssl_ca_file
|
97
|
+
|
98
|
+
# @return [String,nil]
|
99
|
+
attr_reader :ssl_ca_path
|
100
|
+
|
101
|
+
# @return [String,nil]
|
102
|
+
attr_reader :proxy_address
|
103
|
+
|
104
|
+
# @return [Integer,nil]
|
105
|
+
attr_reader :proxy_port
|
106
|
+
|
107
|
+
# @return [String,nil]
|
108
|
+
attr_reader :proxy_user
|
109
|
+
|
110
|
+
# @return [String,nil]
|
111
|
+
attr_reader :proxy_password
|
112
|
+
|
113
|
+
# @return [Numeric,nil]
|
114
|
+
attr_accessor :read_timeout
|
115
|
+
|
116
|
+
# @return [Boolean] Returns +true+ if this connection requires SSL.
|
117
|
+
def ssl?
|
118
|
+
@ssl
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [Boolean] Returns +true+ if ssl connections should verify the
|
122
|
+
# peer certificate.
|
123
|
+
def ssl_verify_peer?
|
124
|
+
@ssl_verify_peer
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [Boolean] Returns +true+ if this connection proxies requests.
|
128
|
+
def proxy?
|
129
|
+
!!proxy_address
|
130
|
+
end
|
131
|
+
|
132
|
+
# Makes a single HTTP request. The Net::HTTPResponse is yielded to the
|
133
|
+
# given block.
|
134
|
+
#
|
135
|
+
# pool = Net::HTTP::ConnectionPool.new
|
136
|
+
# connection = pool.connection_for('www.google.com')
|
137
|
+
#
|
138
|
+
# connection.request(Net::HTTP::Get.new('/')) do |response|
|
139
|
+
# # yeilds a Net::HTTPResponse object
|
140
|
+
# puts "STATUS CODE: #{response.code}"
|
141
|
+
# puts "HEADERS: #{response.to_hash.inspect}"
|
142
|
+
# puts "BODY:\n#{response.body}"
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# If you want to read the HTTP response body in chunks (useful for
|
146
|
+
# large responses you do not want to load into memory), you should
|
147
|
+
# pass a block to the #read_body method of the yielded response.
|
148
|
+
#
|
149
|
+
# File.open('output.txt', 'w') do |file|
|
150
|
+
# connection.request(Net::HTTP::Get.new('/')) do |response|
|
151
|
+
# response.read_body do |chunk|
|
152
|
+
# file.write(chunk)
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# If you omit the block when calling #request, you will not be able
|
158
|
+
# to read the response. This method never returns the
|
159
|
+
# Net::HTTPResponse generated.
|
160
|
+
#
|
161
|
+
# This method passes *args to Net::HTTPSession#request. See the
|
162
|
+
# Ruby standard lib documentation for more documentation about
|
163
|
+
# accepted arguments.
|
164
|
+
#
|
165
|
+
# @note You should read the yielded response object before the end
|
166
|
+
# of the passed block. Do no save a reference to yielded response
|
167
|
+
# objects.
|
168
|
+
#
|
169
|
+
# @yield [response]
|
170
|
+
# @yieldparam [Net::HTTPResponse] response
|
171
|
+
# @return [nil]
|
172
|
+
def request *args, &block
|
173
|
+
pool.request(self, *args, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
# @return [String] Returns a key that can be used to group connections
|
177
|
+
# that may share the same HTTP sessions.
|
178
|
+
def key
|
179
|
+
@key ||= begin
|
180
|
+
%w(
|
181
|
+
host port
|
182
|
+
ssl ssl_verify_peer ssl_ca_file ssl_ca_path
|
183
|
+
proxy_address proxy_port proxy_user proxy_password
|
184
|
+
).map{|part| send(part).to_s }.join(":")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|