moped 1.2.9 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of moped might be problematic. Click here for more details.

@@ -1,5 +1,47 @@
1
1
  # Overview
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### New Features
6
+
7
+ * \#114 Moped now accepts connecting with a URI. (Christopher Winslett)
8
+
9
+ Moped::Session.connect("mongodb://localhost:27017/my_db")
10
+
11
+ * \#79 Tailable cursors are now supported. These are just Ruby `Enumerators` that
12
+ keep the cursor open until the next document appears. The cursor will be closed
13
+ when it becomes "dead".
14
+
15
+ enumerator = session[:users].find.tailable.each
16
+ enumerator.next # Will stay open until next doc.
17
+
18
+ * mongoid/mongoid\#2460 Moped now makes the connection timeout configurable
19
+ by passing a `:timeout` option to the session. This defaults to 5 seconds.
20
+
21
+ Moped::Session.new([ "node1:27017", "node2:27017" ], timeout: 5)
22
+
23
+ * \#49 Support for the 2.2 aggregation framework is included. (Rodrigo Saito)
24
+
25
+ session[:users].aggregate({
26
+ "$group" => {
27
+ "_id" => "$city",
28
+ "totalpop" => { "$sum" => "$pop" }
29
+ }
30
+ })
31
+
32
+ * \#42 Moped now supports SSL connections to MongoDB. Provide the `ssl: true`
33
+ option when creating a new `Session`. This is currently experimental.
34
+
35
+ Moped::Session.new([ "ssl.mongohq.com:10004" ], ssl: true)
36
+
37
+ ### Resolved Issues
38
+
39
+ * \#110 Handle timeout errors with SSL connections gracefully and mark nodes as
40
+ down without failing any other queries.
41
+
42
+ * \#109 Moped reauthorizes on "db assertion failures" with commands that have
43
+ an unauthorized assertion code in the reply.
44
+
3
45
  ## 1.2.9
4
46
 
5
47
  * Moped now ensures that when reading bytes from the socket that it continues
@@ -11,6 +11,7 @@ require "moped/database"
11
11
  require "moped/errors"
12
12
  require "moped/indexes"
13
13
  require "moped/logging"
14
+ require "moped/mongo_uri"
14
15
  require "moped/node"
15
16
  require "moped/protocol"
16
17
  require "moped/query"
@@ -93,18 +93,20 @@ module Moped
93
93
  # to reconnect to a down node. (30)
94
94
  # @option options :refresh_interval number of seconds to cache information
95
95
  # about a node. (300)
96
+ # @option options [ Integer ] :timeout The time in seconds to wait for an
97
+ # operation to timeout. (5)
96
98
  #
97
99
  # @since 1.0.0
98
100
  def initialize(hosts, options)
101
+ @seeds = hosts
102
+ @nodes = hosts.map { |host| Node.new(host, options) }
103
+
99
104
  @options = {
100
105
  down_interval: 30,
101
106
  max_retries: 30,
102
107
  refresh_interval: 300,
103
108
  retry_interval: 1
104
109
  }.merge(options)
105
-
106
- @seeds = hosts
107
- @nodes = hosts.map { |host| Node.new(host) }
108
110
  end
109
111
 
110
112
  # Returns the list of available nodes, refreshing 1) any nodes which were
@@ -169,7 +171,7 @@ module Moped
169
171
  refreshed_nodes << node unless refreshed_nodes.include?(node)
170
172
 
171
173
  # Now refresh any newly discovered peer nodes.
172
- (node.peers - @nodes).each(&refresh_node)
174
+ (node.peers - @nodes).each(&refresh_node) if node.peers
173
175
  rescue Errors::ConnectionFailure
174
176
  # We couldn't connect to the node, so don't do anything with it.
175
177
  end
@@ -91,5 +91,28 @@ module Moped
91
91
  session.context.insert(database.name, name, documents, flags: flags || [])
92
92
  end
93
93
  end
94
+
95
+ # Call aggregate function over the collection.
96
+ #
97
+ # @example Execute an aggregation.
98
+ # session[:users].aggregate({
99
+ # "$group" => {
100
+ # "_id" => "$city",
101
+ # "totalpop" => { "$sum" => "$pop" }
102
+ # }
103
+ # })
104
+ #
105
+ # @param [ Hash, Array<Hash> ] documents representing the aggregate function to execute
106
+ #
107
+ # @return [ Hash ] containing the result of aggregation
108
+ #
109
+ # @since 1.3.0
110
+ def aggregate(pipeline)
111
+ pipeline = [ pipeline ] unless pipeline.is_a?(Array)
112
+ command = { aggregate: name.to_s, pipeline: pipeline }
113
+ database.session.with(consistency: :strong) do |sess|
114
+ sess.command(command)["result"]
115
+ end
116
+ end
94
117
  end
95
118
  end
@@ -1,4 +1,7 @@
1
1
  require "timeout"
2
+ require "moped/sockets/connectable"
3
+ require "moped/sockets/tcp"
4
+ require "moped/sockets/ssl"
2
5
 
3
6
  module Moped
4
7
 
@@ -6,6 +9,9 @@ module Moped
6
9
  #
7
10
  # @api private
8
11
  class Connection
12
+
13
+ attr_reader :host, :port, :timeout, :options
14
+
9
15
  # Is the connection alive?
10
16
  #
11
17
  # @example Is the connection alive?
@@ -27,7 +33,11 @@ module Moped
27
33
  #
28
34
  # @since 1.0.0
29
35
  def connect
30
- create_connection
36
+ @sock = if !!options[:ssl]
37
+ Sockets::SSL.connect(host, port, timeout)
38
+ else
39
+ Sockets::TCP.connect(host, port, timeout)
40
+ end
31
41
  end
32
42
 
33
43
  # Is the connection connected?
@@ -65,13 +75,14 @@ module Moped
65
75
  # @param [ String ] host The host to connect to.
66
76
  # @param [ Integer ] post The server port.
67
77
  # @param [ Integer ] timeout The connection timeout.
78
+ # @param [ Hash ] options Options for the connection.
79
+ #
80
+ # @option options [ Boolean ] :ssl Connect using SSL
68
81
  # @since 1.0.0
69
- def initialize(host, port, timeout)
82
+ def initialize(host, port, timeout, options = {})
70
83
  @sock = nil
71
84
  @request_id = 0
72
- @host = host
73
- @port = port
74
- @timeout = timeout
85
+ @host, @port, @timeout, @options = host, port, timeout, options
75
86
  end
76
87
 
77
88
  # Read from the connection.
@@ -148,10 +159,6 @@ module Moped
148
159
 
149
160
  private
150
161
 
151
- def create_connection
152
- @sock = TCPSocket.connect @host, @port, @timeout
153
- end
154
-
155
162
  # Read data from the socket until we get back the number of bytes that we
156
163
  # are expecting.
157
164
  #
@@ -191,105 +198,8 @@ module Moped
191
198
  #
192
199
  # @since 1.3.0
193
200
  def with_connection
194
- create_connection if @sock.nil? || !@sock.alive?
201
+ connect if @sock.nil? || !@sock.alive?
195
202
  yield @sock
196
203
  end
197
-
198
- # This is a wrapper around a tcp socket.
199
- class TCPSocket < ::TCPSocket
200
- attr_reader :host, :port
201
-
202
- # Is the socket connection alive?
203
- #
204
- # @example Is the socket alive?
205
- # socket.alive?
206
- #
207
- # @return [ true, false ] If the socket is alive.
208
- #
209
- # @since 1.0.0
210
- def alive?
211
- if Kernel::select([ self ], nil, nil, 0)
212
- !eof? rescue false
213
- else
214
- true
215
- end
216
- end
217
-
218
- # Initialize the new TCPSocket.
219
- #
220
- # @example Initialize the socket.
221
- # TCPSocket.new("127.0.0.1", 27017)
222
- #
223
- # @param [ String ] host The host.
224
- # @param [ Integer ] port The port.
225
- #
226
- # @since 1.2.0
227
- def initialize(host, port, *args)
228
- @host, @port = host, port
229
- handle_socket_errors { super }
230
- end
231
-
232
- # Read from the TCP socket.
233
- #
234
- # @param [ Integer ] length The length to read.
235
- #
236
- # @return [ Object ] The data.
237
- #
238
- # @since 1.2.0
239
- def read(length)
240
- handle_socket_errors { super }
241
- end
242
-
243
- # Write to the socket.
244
- #
245
- # @example Write to the socket.
246
- # socket.write(data)
247
- #
248
- # @param [ Object ] args The data to write.
249
- #
250
- # @return [ Integer ] The number of bytes written.
251
- #
252
- # @since 1.0.0
253
- def write(*args)
254
- raise Errors::ConnectionFailure, "Socket connection was closed by remote host" unless alive?
255
- handle_socket_errors { super }
256
- end
257
-
258
- private
259
-
260
- def handle_socket_errors
261
- yield
262
- rescue Timeout::Error
263
- raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{host}:#{port}"
264
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE
265
- raise Errors::ConnectionFailure, "Could not connect to Mongo on #{host}:#{port}"
266
- rescue Errno::ECONNRESET
267
- raise Errors::ConnectionFailure, "Connection reset to Mongo on #{host}:#{port}"
268
- end
269
-
270
- class << self
271
-
272
- # Connect to the tcp server.
273
- #
274
- # @example Connect to the server.
275
- # TCPSocket.connect("127.0.0.1", 27017, 30)
276
- #
277
- # @param [ String ] host The host to connect to.
278
- # @param [ Integer ] post The server port.
279
- # @param [ Integer ] timeout The connection timeout.
280
- #
281
- # @return [ TCPSocket ] The socket.
282
- #
283
- # @since 1.0.0
284
- def connect(host, port, timeout)
285
- Timeout::timeout(timeout) do
286
- sock = new(host, port)
287
- sock.set_encoding('binary')
288
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
289
- sock
290
- end
291
- end
292
- end
293
- end
294
204
  end
295
205
  end
@@ -73,7 +73,7 @@ module Moped
73
73
  #
74
74
  # @since 1.0.0
75
75
  def command(command)
76
- session.context.command name, command
76
+ session.context.command(name.to_s, command)
77
77
  end
78
78
 
79
79
  # Drop the database.
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+ module Moped
3
+
4
+ # Parses MongoDB uri
5
+ #
6
+ # @api public
7
+ class MongoUri
8
+
9
+ SCHEME = /(mongodb:\/\/)/
10
+ USER = /([-.\w:]+)/
11
+ PASS = /([^@,]+)/
12
+ NODES = /((([-.\w]+)(?::(\w+))?,?)+)/
13
+ DATABASE = /(?:\/([-\w]+))?/
14
+ OPTIONS = /(?:\?(.+))/
15
+
16
+ URI = /#{SCHEME}(#{USER}:#{PASS}@)?#{NODES}#{DATABASE}#{OPTIONS}?/
17
+
18
+ attr_reader :match
19
+
20
+ # Helper to determine if authentication is provided
21
+ #
22
+ # @example Boolean response if username/password given
23
+ # uri.auth_provided?
24
+ #
25
+ # @return [ true, false ] If authorization is provided.
26
+ #
27
+ # @since 1.3.0
28
+ def auth_provided?
29
+ !username.nil? && !password.nil?
30
+ end
31
+
32
+ # Get the database provided in the URI.
33
+ #
34
+ # @example Get the database.
35
+ # uri.database
36
+ #
37
+ # @return [ String ] The database.
38
+ #
39
+ # @since 1.3.0
40
+ def database
41
+ @database ||= match[9]
42
+ end
43
+
44
+ # Get the hosts provided in the URI.
45
+ #
46
+ # @example Get the hosts.
47
+ # uri.hosts
48
+ #
49
+ # @return [ Array<String> ] The hosts.
50
+ #
51
+ # @since 1.3.0
52
+ def hosts
53
+ @hosts ||= match[5].split(",")
54
+ end
55
+
56
+ # Create the new uri from the provided string.
57
+ #
58
+ # @example Create the new uri.
59
+ # MongoUri.new(uri)
60
+ #
61
+ # @param [ String ] string The uri string.
62
+ #
63
+ # @since 1.3.0
64
+ def initialize(string)
65
+ @match = string.match(URI)
66
+ end
67
+
68
+ # Get the options provided in the URI.
69
+ # @example Get the options
70
+ # uri.options
71
+ #
72
+ # @return [ Hash ] Options hash usable by Moped
73
+ #
74
+ # @since 1.3.0
75
+ def options
76
+ options_string, options = @match[10], {database: database}
77
+
78
+ unless options_string.nil?
79
+ options_string.split(/\&/).each do |option_string|
80
+ key, value = option_string.split(/=/)
81
+
82
+ if value == "true"
83
+ options[key.to_sym] = true
84
+ elsif value == "false"
85
+ options[key.to_sym] = false
86
+ elsif value =~ /[\d]/
87
+ options[key.to_sym] = value.to_i
88
+ else
89
+ options[key.to_sym] = value.to_sym
90
+ end
91
+ end
92
+ end
93
+
94
+ options
95
+ end
96
+
97
+ # Get the password provided in the URI.
98
+ #
99
+ # @example Get the password.
100
+ # uri.password
101
+ #
102
+ # @return [ String ] The password.
103
+ #
104
+ # @since 1.3.0
105
+ def password
106
+ @password ||= match[4]
107
+ end
108
+
109
+ # Get the uri as a Mongoid friendly configuration hash.
110
+ #
111
+ # @example Get the uri as a hash.
112
+ # uri.to_hash
113
+ #
114
+ # @return [ Hash ] The uri as options.
115
+ #
116
+ # @since 1.3.0
117
+ def to_hash
118
+ config = { database: database, hosts: hosts }
119
+ if username && password
120
+ config.merge!(username: username, password: password)
121
+ end
122
+ config
123
+ end
124
+
125
+ # Create Moped usable arguments
126
+ #
127
+ # @example Get the moped args
128
+ # uri.moped_arguments
129
+ #
130
+ # @return [ Array ] Array of arguments usable by Moped
131
+ #
132
+ # @since 1.3.0
133
+ def moped_arguments
134
+ [hosts, options]
135
+ end
136
+
137
+ # Get the username provided in the URI.
138
+ #
139
+ # @example Get the username.
140
+ # uri.username
141
+ #
142
+ # @return [ String ] The username.
143
+ #
144
+ # @since 1.3.0
145
+ def username
146
+ @username ||= match[3]
147
+ end
148
+ end
149
+ end
@@ -12,7 +12,8 @@ module Moped
12
12
  # @attribute [r] port The connection port.
13
13
  # @attribute [r] resolved_address The host/port pair.
14
14
  # @attribute [r] timeout The connection timeout.
15
- attr_reader :address, :down_at, :ip_address, :peers, :port, :resolved_address, :timeout
15
+ # @attribute [r] options Additional options for the node (ssl).
16
+ attr_reader :address, :down_at, :ip_address, :peers, :port, :resolved_address, :timeout, :options
16
17
 
17
18
  # Is this node equal to another?
18
19
  #
@@ -68,10 +69,15 @@ module Moped
68
69
  operation = Protocol::Command.new(database, cmd, options)
69
70
 
70
71
  process(operation) do |reply|
71
- result = reply.documents[0]
72
- raise Errors::OperationFailure.new(
73
- operation, result
74
- ) if result["ok"] != 1 || result["err"] || result["errmsg"]
72
+ result = reply.documents.first
73
+ if reply.command_failure?
74
+ if reply.unauthorized? && auth.has_key?(database)
75
+ login(database, *auth[database])
76
+ command(database, cmd, options)
77
+ else
78
+ raise Errors::OperationFailure.new(operation, result)
79
+ end
80
+ end
75
81
  result
76
82
  end
77
83
  end
@@ -222,11 +228,13 @@ module Moped
222
228
  # Node.new("127.0.0.1:27017")
223
229
  #
224
230
  # @param [ String ] address The location of the server node.
231
+ # @param [ Hash ] options Additional options for the node (ssl)
225
232
  #
226
233
  # @since 1.0.0
227
- def initialize(address)
234
+ def initialize(address, options = {})
228
235
  @address = address
229
- @timeout = 5
236
+ @options = options
237
+ @timeout = options[:timeout] || 5
230
238
  @down_at = nil
231
239
  @refreshed_at = nil
232
240
  @primary = nil
@@ -387,25 +395,30 @@ module Moped
387
395
  # @since 1.0.0
388
396
  def refresh
389
397
  if resolve_address
390
- info = command("admin", ismaster: 1)
398
+ begin
399
+ info = command("admin", ismaster: 1)
391
400
 
392
- @refreshed_at = Time.now
393
- primary = true if info["ismaster"]
394
- secondary = true if info["secondary"]
401
+ @refreshed_at = Time.now
402
+ primary = true if info["ismaster"]
403
+ secondary = true if info["secondary"]
395
404
 
396
- peers = []
397
- peers.push(info["primary"]) if info["primary"]
398
- peers.concat(info["hosts"]) if info["hosts"]
399
- peers.concat(info["passives"]) if info["passives"]
400
- peers.concat(info["arbiters"]) if info["arbiters"]
405
+ peers = []
406
+ peers.push(info["primary"]) if info["primary"]
407
+ peers.concat(info["hosts"]) if info["hosts"]
408
+ peers.concat(info["passives"]) if info["passives"]
409
+ peers.concat(info["arbiters"]) if info["arbiters"]
401
410
 
402
- @peers = peers.map { |peer| Node.new(peer) }
403
- @primary, @secondary = primary, secondary
404
- @arbiter = info["arbiterOnly"]
405
- @passive = info["passive"]
411
+ @peers = peers.map { |peer| Node.new(peer, options) }
412
+ @primary, @secondary = primary, secondary
413
+ @arbiter = info["arbiterOnly"]
414
+ @passive = info["passive"]
406
415
 
407
- if !primary && Threaded.executing?(:ensure_primary)
408
- raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
416
+ if !primary && Threaded.executing?(:ensure_primary)
417
+ raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
418
+ end
419
+ rescue Timeout::Error
420
+ @peers = []
421
+ down!
409
422
  end
410
423
  end
411
424
  end
@@ -496,7 +509,7 @@ module Moped
496
509
  end
497
510
 
498
511
  def connection
499
- @connection ||= Connection.new(ip_address, port, timeout)
512
+ @connection ||= Connection.new(ip_address, port, timeout, options)
500
513
  end
501
514
 
502
515
  def connected?
@@ -55,16 +55,60 @@ module Moped
55
55
 
56
56
  finalize
57
57
 
58
+ # Is the reply the result of a command failure?
59
+ #
60
+ # @example Did the command fail?
61
+ # reply.command_failure?
62
+ #
63
+ # @note This is when ok is not 1, or "err" or "errmsg" are present.
64
+ #
65
+ # @return [ true, false ] If the command failed.
66
+ #
67
+ # @since 1.2.10
68
+ def command_failure?
69
+ result = documents[0]
70
+ result["ok"] != 1 || result["err"] || result["errmsg"]
71
+ end
72
+
73
+ # Was the provided cursor id not found on the server?
74
+ #
75
+ # @example Is the cursor not on the server?
76
+ # reply.cursor_not_found?
77
+ #
78
+ # @return [ true, false ] If the cursor went missing.
79
+ #
80
+ # @since 1.2.0
58
81
  def cursor_not_found?
59
82
  flags.include?(:cursor_not_found)
60
83
  end
61
84
 
85
+ # Did the query fail on the server?
86
+ #
87
+ # @example Did the query fail?
88
+ # reply.query_failed?
89
+ #
90
+ # @return [ true, false ] If the query failed.
91
+ #
92
+ # @since 1.2.0
62
93
  def query_failed?
63
94
  flags.include?(:query_failure)
64
95
  end
65
96
 
97
+ # Is the reply an error message that we are not authorized for the query
98
+ # or command?
99
+ #
100
+ # @example Was the query unauthorized.
101
+ # reply.unauthorized?
102
+ #
103
+ # @note So far this can be a "code" of 10057 in the error message or an
104
+ # "assertionCode" of 10057.
105
+ #
106
+ # @return [ true, false ] If we had an authorization error.
107
+ #
108
+ # @since 1.2.10
66
109
  def unauthorized?
67
- documents.first["code"] == UNAUTHORIZED
110
+ result = documents[0]
111
+ result["code"] == UNAUTHORIZED || result["assertionCode"] == UNAUTHORIZED
68
112
  end
69
113
 
70
114
  class << self
@@ -79,6 +79,7 @@ module Moped
79
79
  end if block_given?
80
80
  enum
81
81
  end
82
+ alias :cursor :each
82
83
 
83
84
  # Explain the current query.
84
85
  #
@@ -211,7 +212,7 @@ module Moped
211
212
  end
212
213
 
213
214
  # Keeping moped compatibility with mongodb >= 2.2.0-rc0
214
- options[:upsert] && !result ? [] : result
215
+ options[:upsert] && !result ? {} : result
215
216
  end
216
217
 
217
218
  # Remove a single document matching the query's selector.
@@ -296,6 +297,19 @@ module Moped
296
297
  self
297
298
  end
298
299
 
300
+ # Tell the query to create a tailable cursor.
301
+ #
302
+ # @example Tell the query the cursor is tailable.
303
+ # db[:people].find.tailable
304
+ #
305
+ # @return [ Query ] The query.
306
+ #
307
+ # @since 1.3.0
308
+ def tailable
309
+ operation.flags.push(:tailable)
310
+ self
311
+ end
312
+
299
313
  # Update a single document matching the query's selector.
300
314
  #
301
315
  # @example Update the first matching document.
@@ -177,14 +177,17 @@ module Moped
177
177
  # specified safety level e.g., "fsync: true", or "w: 2, wtimeout: 5".
178
178
  # @option options [ Symbol, String ] :database The database to use.
179
179
  # @option options [ :strong, :eventual ] :consistency (:eventual).
180
+ # @option options [ Boolean ] :ssl Connect using SSL.
180
181
  # @option options [ Integer ] :max_retries The maximum number of attempts
181
182
  # to retry an operation. (30)
182
183
  # @option options [ Integer ] :retry_interval The time in seconds to retry
183
184
  # connections to a secondary or primary after a failure. (1)
185
+ # @option options [ Integer ] :timeout The time in seconds to wait for an
186
+ # operation to timeout. (5)
184
187
  #
185
188
  # @since 1.0.0
186
189
  def initialize(seeds, options = {})
187
- @cluster = Cluster.new(seeds, {})
190
+ @cluster = Cluster.new(seeds, options)
188
191
  @context = Context.new(self)
189
192
  @options = options
190
193
  @options[:consistency] ||= :eventual
@@ -312,6 +315,26 @@ module Moped
312
315
  end
313
316
  end
314
317
 
318
+ class << self
319
+
320
+ # Create a new session from a URI.
321
+ #
322
+ # @example Initialize a new session.
323
+ # Session.connect("mongodb://localhost:27017/my_db")
324
+ #
325
+ # @param [ String ] MongoDB URI formatted string.
326
+ #
327
+ # @return [ Session ] The new session.
328
+ #
329
+ # @since 3.0.0
330
+ def connect(uri)
331
+ uri = MongoUri.new(uri)
332
+ session = new(*uri.moped_arguments)
333
+ session.login(uri.username, uri.password) if uri.auth_provided?
334
+ session
335
+ end
336
+ end
337
+
315
338
  private
316
339
 
317
340
  def current_database
@@ -0,0 +1,114 @@
1
+ module Moped
2
+ module Sockets
3
+ module Connectable
4
+
5
+ attr_reader :host, :port
6
+
7
+ # Is the socket connection alive?
8
+ #
9
+ # @example Is the socket alive?
10
+ # socket.alive?
11
+ #
12
+ # @return [ true, false ] If the socket is alive.
13
+ #
14
+ # @since 1.0.0
15
+ def alive?
16
+ if Kernel::select([ self ], nil, nil, 0)
17
+ !eof? rescue false
18
+ else
19
+ true
20
+ end
21
+ rescue IOError
22
+ false
23
+ end
24
+
25
+ # Bring in the class methods when included.
26
+ #
27
+ # @example Extend the class methods.
28
+ # Connectable.included(class)
29
+ #
30
+ # @param [ Class ] klass The class including the module.
31
+ #
32
+ # @since 1.3.0
33
+ def self.included(klass)
34
+ klass.send(:extend, ClassMethods)
35
+ end
36
+
37
+ # Read from the TCP socket.
38
+ #
39
+ # @param [ Integer ] length The length to read.
40
+ #
41
+ # @return [ Object ] The data.
42
+ #
43
+ # @since 1.2.0
44
+ def read(length)
45
+ handle_socket_errors { super }
46
+ end
47
+
48
+ # Write to the socket.
49
+ #
50
+ # @example Write to the socket.
51
+ # socket.write(data)
52
+ #
53
+ # @param [ Object ] args The data to write.
54
+ #
55
+ # @return [ Integer ] The number of bytes written.
56
+ #
57
+ # @since 1.0.0
58
+ def write(*args)
59
+ raise Errors::ConnectionFailure, "Socket connection was closed by remote host" unless alive?
60
+ handle_socket_errors { super }
61
+ end
62
+
63
+ private
64
+
65
+ # Handle the potential socket errors that can occur.
66
+ #
67
+ # @api private
68
+ #
69
+ # @example Handle the socket errors while executing the block.
70
+ # handle_socket_errors do
71
+ # #...
72
+ # end
73
+ #
74
+ # @return [ Object ] The result of the yield.
75
+ #
76
+ # @since 1.0.0
77
+ def handle_socket_errors
78
+ yield
79
+ rescue Timeout::Error
80
+ raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{host}:#{port}"
81
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EPIPE
82
+ raise Errors::ConnectionFailure, "Could not connect to Mongo on #{host}:#{port}"
83
+ rescue Errno::ECONNRESET
84
+ raise Errors::ConnectionFailure, "Connection reset to Mongo on #{host}:#{port}"
85
+ rescue OpenSSL::SSL::SSLError => e
86
+ raise Errors::ConnectionFailure, "SSL Error '#{e.to_s}' for connection to Mongo on #{host}:#{port}"
87
+ end
88
+
89
+ module ClassMethods
90
+
91
+ # Connect to the tcp server.
92
+ #
93
+ # @example Connect to the server.
94
+ # TCPSocket.connect("127.0.0.1", 27017, 30)
95
+ #
96
+ # @param [ String ] host The host to connect to.
97
+ # @param [ Integer ] post The server port.
98
+ # @param [ Integer ] timeout The connection timeout.
99
+ #
100
+ # @return [ TCPSocket ] The socket.
101
+ #
102
+ # @since 1.0.0
103
+ def connect(host, port, timeout)
104
+ Timeout::timeout(timeout) do
105
+ sock = new(host, port)
106
+ sock.set_encoding('binary')
107
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
108
+ sock
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,50 @@
1
+ require 'openssl'
2
+
3
+ module Moped
4
+ module Sockets
5
+
6
+ # This is a wrapper around a tcp socket.
7
+ class SSL < OpenSSL::SSL::SSLSocket
8
+ include Connectable
9
+
10
+ attr_reader :socket
11
+
12
+ # Initialize the new TCPSocket with SSL.
13
+ #
14
+ # @example Initialize the socket.
15
+ # SSL.new("127.0.0.1", 27017)
16
+ #
17
+ # @param [ String ] host The host.
18
+ # @param [ Integer ] port The port.
19
+ #
20
+ # @since 1.2.0
21
+ def initialize(host, port)
22
+ @host, @port = host, port
23
+ handle_socket_errors do
24
+ @socket = TCPSocket.new(host, port)
25
+ super(socket)
26
+ self.sync_close = true
27
+ connect
28
+ end
29
+ end
30
+
31
+ # Set the encoding of the underlying socket.
32
+ #
33
+ # @param [ String ] string The encoding.
34
+ #
35
+ # @since 1.3.0
36
+ def set_encoding(string)
37
+ socket.set_encoding(string)
38
+ end
39
+
40
+ # Set a socket option on the underlying socket.
41
+ #
42
+ # @param [ Array<Object> ] args The option arguments.
43
+ #
44
+ # @since 1.3.0
45
+ def setsockopt(*args)
46
+ socket.setsockopt(*args)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module Moped
2
+ module Sockets
3
+
4
+ # This is a wrapper around a tcp socket.
5
+ class TCP < ::TCPSocket
6
+ include Connectable
7
+
8
+ # Initialize the new TCPSocket.
9
+ #
10
+ # @example Initialize the socket.
11
+ # TCPSocket.new("127.0.0.1", 27017)
12
+ #
13
+ # @param [ String ] host The host.
14
+ # @param [ Integer ] port The port.
15
+ #
16
+ # @since 1.2.0
17
+ def initialize(host, port)
18
+ @host, @port = host, port
19
+ handle_socket_errors { super }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Moped
3
- VERSION = "1.2.9"
3
+ VERSION = "1.3.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moped
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.9
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-12 00:00:00.000000000 Z
12
+ date: 2012-11-18 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A MongoDB driver for Ruby.
15
15
  email:
@@ -49,6 +49,7 @@ files:
49
49
  - lib/moped/errors.rb
50
50
  - lib/moped/indexes.rb
51
51
  - lib/moped/logging.rb
52
+ - lib/moped/mongo_uri.rb
52
53
  - lib/moped/node.rb
53
54
  - lib/moped/protocol/command.rb
54
55
  - lib/moped/protocol/commands/authenticate.rb
@@ -65,13 +66,16 @@ files:
65
66
  - lib/moped/query.rb
66
67
  - lib/moped/session/context.rb
67
68
  - lib/moped/session.rb
69
+ - lib/moped/sockets/connectable.rb
70
+ - lib/moped/sockets/ssl.rb
71
+ - lib/moped/sockets/tcp.rb
68
72
  - lib/moped/threaded.rb
69
73
  - lib/moped/version.rb
70
74
  - lib/moped.rb
71
75
  - CHANGELOG.md
72
76
  - LICENSE
73
77
  - README.md
74
- homepage: http://mongoid.org/moped
78
+ homepage: http://mongoid.org/en/moped
75
79
  licenses: []
76
80
  post_install_message:
77
81
  rdoc_options: []
@@ -85,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
89
  version: '0'
86
90
  segments:
87
91
  - 0
88
- hash: 450437274289811416
92
+ hash: 2772381747018055837
89
93
  required_rubygems_version: !ruby/object:Gem::Requirement
90
94
  none: false
91
95
  requirements:
@@ -94,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
98
  version: '0'
95
99
  segments:
96
100
  - 0
97
- hash: 450437274289811416
101
+ hash: 2772381747018055837
98
102
  requirements: []
99
103
  rubyforge_project:
100
104
  rubygems_version: 1.8.24