em-hiredis 0.0.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 +5 -0
- data/Gemfile +4 -0
- data/README.md +61 -0
- data/Rakefile +2 -0
- data/em-hiredis.gemspec +23 -0
- data/lib/em-hiredis/client.rb +131 -0
- data/lib/em-hiredis/connection.rb +61 -0
- data/lib/em-hiredis/event_emitter.rb +29 -0
- data/lib/em-hiredis/version.rb +5 -0
- data/lib/em-hiredis.rb +22 -0
- metadata +76 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
Getting started
|
2
|
+
===============
|
3
|
+
|
4
|
+
Connect to redis
|
5
|
+
|
6
|
+
redis_client = EM::Hiredis.connect
|
7
|
+
|
8
|
+
The client is a deferrable which succeeds when the underlying connection is established so you can bind to this. This isn't necessary however - any commands sent before the connection is established (or while reconnecting) will be sent to redis on connect.
|
9
|
+
|
10
|
+
redis_client.callback { puts "Redis now connected" }
|
11
|
+
|
12
|
+
All redis commands are available without any remapping of names
|
13
|
+
|
14
|
+
redis.set('foo', 'bar').callback {
|
15
|
+
redis.get('foo').callback { |value|
|
16
|
+
p [:returned, value]
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
As a shortcut, if you're only interested in binding to the success case you can simply provide a block to any command
|
21
|
+
|
22
|
+
redis.get('foo') { |value|
|
23
|
+
p [:returned, value]
|
24
|
+
}
|
25
|
+
|
26
|
+
Handling failure
|
27
|
+
----------------
|
28
|
+
|
29
|
+
All commands return a deferrable. In the case that redis replies with an error (for example you called a hash operation against a set), or in the case that the redis connection is broken before the command returns, the deferrable will fail. If you care about the failure case you should bind to the errback - for example:
|
30
|
+
|
31
|
+
redis.sadd('aset', 'member').callback {
|
32
|
+
response_deferrable = redis.hget('aset', 'member')
|
33
|
+
response_deferrable.errback { |e|
|
34
|
+
p e # => #<RuntimeError: ERR Operation against a key holding the wrong kind of value>
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
Pubsub
|
39
|
+
------
|
40
|
+
|
41
|
+
This example should explain things. Once a redis connection is in a pubsub state, you must make sure you only send pubsub commands.
|
42
|
+
|
43
|
+
redis = EM::Hiredis::Client.connect
|
44
|
+
subscriber = EM::Hiredis::Client.connect
|
45
|
+
|
46
|
+
subscriber.subscribe('bar.0')
|
47
|
+
subscriber.psubscribe('bar.*')
|
48
|
+
|
49
|
+
subscriber.on(:message) { |channel, message|
|
50
|
+
p [:message, channel, message]
|
51
|
+
}
|
52
|
+
|
53
|
+
subscriber.on(:pmessage) { |key, channel, message|
|
54
|
+
p [:pmessage, key, channel, message]
|
55
|
+
}
|
56
|
+
|
57
|
+
EM.add_periodic_timer(1) {
|
58
|
+
redis.publish("bar.#{rand(2)}", "hello").errback { |e|
|
59
|
+
p [:publisherror, e]
|
60
|
+
}
|
61
|
+
}
|
data/Rakefile
ADDED
data/em-hiredis.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-hiredis/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-hiredis"
|
7
|
+
s.version = EM::Hiredis::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Martyn Loughran"]
|
10
|
+
s.email = ["me@mloughran.com"]
|
11
|
+
s.homepage = "http://github.com/mloughran/em-hiredis"
|
12
|
+
s.summary = %q{Eventmachine redis client}
|
13
|
+
s.description = %q{Eventmachine redis client using hiredis native parser}
|
14
|
+
|
15
|
+
s.add_dependency 'hiredis', '~> 0.2.0'
|
16
|
+
|
17
|
+
s.rubyforge_project = "em-hiredis"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module EM::Hiredis
|
2
|
+
class Client
|
3
|
+
PUBSUB_MESSAGES = %w{message pmessage}.freeze
|
4
|
+
|
5
|
+
include EM::Hiredis::EventEmitter
|
6
|
+
include EM::Deferrable
|
7
|
+
|
8
|
+
def self.connect(host = 'localhost', port = 6379)
|
9
|
+
new(host, port)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(host, port)
|
13
|
+
@host, @port = host, port
|
14
|
+
@subs, @psubs = [], []
|
15
|
+
@defs = []
|
16
|
+
@connection = EM.connect(host, port, Connection, host, port)
|
17
|
+
|
18
|
+
@connection.on(:closed) {
|
19
|
+
if @connected
|
20
|
+
@defs.each { |d| d.fail("Redis disconnected") }
|
21
|
+
@defs = []
|
22
|
+
@deferred_status = nil
|
23
|
+
@connected = false
|
24
|
+
@reconnecting = true
|
25
|
+
reconnect
|
26
|
+
else
|
27
|
+
EM.add_timer(1) { reconnect }
|
28
|
+
end
|
29
|
+
}
|
30
|
+
|
31
|
+
@connection.on(:connected) {
|
32
|
+
@connected = true
|
33
|
+
select(@db) if @db
|
34
|
+
@subs.each { |s| method_missing(:subscribe, s) }
|
35
|
+
@psubs.each { |s| method_missing(:psubscribe, s) }
|
36
|
+
succeed
|
37
|
+
|
38
|
+
if @reconnecting
|
39
|
+
@reconnecting = false
|
40
|
+
emit(:reconnected)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
@connection.on(:message) { |reply|
|
45
|
+
if RuntimeError === reply
|
46
|
+
raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
|
47
|
+
deferred = @defs.shift
|
48
|
+
deferred.fail(reply) if deferred
|
49
|
+
else
|
50
|
+
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
51
|
+
kind, subscription, d1, d2 = *reply
|
52
|
+
|
53
|
+
case kind.to_sym
|
54
|
+
when :message
|
55
|
+
emit(:message, subscription, d1)
|
56
|
+
when :pmessage
|
57
|
+
emit(:pmessage, subscription, d1, d2)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
|
61
|
+
deferred = @defs.shift
|
62
|
+
deferred.succeed(reply) if deferred
|
63
|
+
end
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
@connected = false
|
68
|
+
@reconnecting = false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Indicates that commands have been sent to redis but a reply has not yet
|
72
|
+
# been received.
|
73
|
+
#
|
74
|
+
# This can be useful for example to avoid stopping the
|
75
|
+
# eventmachine reactor while there are outstanding commands
|
76
|
+
#
|
77
|
+
def pending_commands?
|
78
|
+
@connected && @defs.size > 0
|
79
|
+
end
|
80
|
+
|
81
|
+
def subscribe(channel)
|
82
|
+
@subs << channel
|
83
|
+
method_missing(:subscribe, channel)
|
84
|
+
end
|
85
|
+
|
86
|
+
def unsubscribe(channel)
|
87
|
+
@subs.delete(channel)
|
88
|
+
method_missing(:unsubscribe, channel)
|
89
|
+
end
|
90
|
+
|
91
|
+
def psubscribe(channel)
|
92
|
+
@psubs << channel
|
93
|
+
method_missing(:psubscribe, channel)
|
94
|
+
end
|
95
|
+
|
96
|
+
def punsubscribe(channel)
|
97
|
+
@psubs.delete(channel)
|
98
|
+
method_missing(:punsubscribe, channel)
|
99
|
+
end
|
100
|
+
|
101
|
+
def select(db)
|
102
|
+
@db = db
|
103
|
+
method_missing(:select, db)
|
104
|
+
end
|
105
|
+
|
106
|
+
def method_missing(sym, *args)
|
107
|
+
deferred = EM::DefaultDeferrable.new
|
108
|
+
# Shortcut for defining the callback case with just a block
|
109
|
+
deferred.callback { |result| yield(result) } if block_given?
|
110
|
+
|
111
|
+
if @connected
|
112
|
+
@connection.send_command(sym, *args)
|
113
|
+
@defs.push(deferred)
|
114
|
+
else
|
115
|
+
callback {
|
116
|
+
@connection.send_command(sym, *args)
|
117
|
+
@defs.push(deferred)
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
return deferred
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def reconnect
|
127
|
+
EM::Hiredis.logger.debug("Trying to reconnect to Redis")
|
128
|
+
@connection.reconnect @host, @port
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'hiredis/reader'
|
2
|
+
|
3
|
+
module EM::Hiredis
|
4
|
+
class Connection < EM::Connection
|
5
|
+
include EM::Hiredis::EventEmitter
|
6
|
+
|
7
|
+
def initialize(host, port)
|
8
|
+
super
|
9
|
+
@host, @port = host, port
|
10
|
+
end
|
11
|
+
|
12
|
+
def connection_completed
|
13
|
+
EM::Hiredis.logger.info("Connected to Redis")
|
14
|
+
@reader = ::Hiredis::Reader.new
|
15
|
+
emit(:connected)
|
16
|
+
end
|
17
|
+
|
18
|
+
def receive_data(data)
|
19
|
+
@reader.feed(data)
|
20
|
+
until (reply = @reader.gets) == false
|
21
|
+
emit(:message, reply)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def unbind
|
26
|
+
EM::Hiredis.logger.info("Disconnected from Redis")
|
27
|
+
emit(:closed)
|
28
|
+
end
|
29
|
+
|
30
|
+
def send_command(sym, *args)
|
31
|
+
send_data(command(sym, *args))
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
COMMAND_DELIMITER = "\r\n"
|
37
|
+
|
38
|
+
def command(*args)
|
39
|
+
command = []
|
40
|
+
command << "*#{args.size}"
|
41
|
+
|
42
|
+
args.each do |arg|
|
43
|
+
arg = arg.to_s
|
44
|
+
command << "$#{string_size arg}"
|
45
|
+
command << arg
|
46
|
+
end
|
47
|
+
|
48
|
+
command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
49
|
+
end
|
50
|
+
|
51
|
+
if "".respond_to?(:bytesize)
|
52
|
+
def string_size(string)
|
53
|
+
string.to_s.bytesize
|
54
|
+
end
|
55
|
+
else
|
56
|
+
def string_size(string)
|
57
|
+
string.to_s.size
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module EM::Hiredis
|
2
|
+
module EventEmitter
|
3
|
+
def on(event, &listener)
|
4
|
+
_listeners[event] << listener
|
5
|
+
end
|
6
|
+
|
7
|
+
def emit(event, *args)
|
8
|
+
_listeners[event].each { |l| l.call(*args) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_listener(event, &listener)
|
12
|
+
_listeners[event].delete(listener)
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove_all_listeners(event)
|
16
|
+
_listeners.delete(event)
|
17
|
+
end
|
18
|
+
|
19
|
+
def listeners(event)
|
20
|
+
_listeners[event]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _listeners
|
26
|
+
@_listeners ||= Hash.new { |h,k| h[k] = [] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/em-hiredis.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module EM
|
4
|
+
module Hiredis
|
5
|
+
class << self
|
6
|
+
attr_writer :logger
|
7
|
+
|
8
|
+
def logger
|
9
|
+
@logger ||= begin
|
10
|
+
require 'logger'
|
11
|
+
log = Logger.new(STDOUT)
|
12
|
+
log.level = Logger::WARN
|
13
|
+
log
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'em-hiredis/event_emitter'
|
21
|
+
require 'em-hiredis/connection'
|
22
|
+
require 'em-hiredis/client'
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-hiredis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Martyn Loughran
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-03-11 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: hiredis
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.2.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Eventmachine redis client using hiredis native parser
|
28
|
+
email:
|
29
|
+
- me@mloughran.com
|
30
|
+
executables: []
|
31
|
+
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files: []
|
35
|
+
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- em-hiredis.gemspec
|
42
|
+
- lib/em-hiredis.rb
|
43
|
+
- lib/em-hiredis/client.rb
|
44
|
+
- lib/em-hiredis/connection.rb
|
45
|
+
- lib/em-hiredis/event_emitter.rb
|
46
|
+
- lib/em-hiredis/version.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/mloughran/em-hiredis
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project: em-hiredis
|
71
|
+
rubygems_version: 1.5.3
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Eventmachine redis client
|
75
|
+
test_files: []
|
76
|
+
|