ruby-redis-portertech 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2011, David Turnbull et al
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,80 @@
1
+ This project was started because I needed an authenticating and routable
2
+ proxy for Redis. The main feature is a high performance, eventable, pure Ruby
3
+ implementation of the complete Redis wire protocol using the same interface as
4
+ hiredis/reader.
5
+
6
+ ### Ruby Gem
7
+
8
+ # Best with Ruby 1.9.2; works with 1.8.7, JRuby, and Rubinius.
9
+ gem install ruby-redis
10
+ # Runs a server that looks and feels like the C redis-server
11
+ ruby-redis
12
+
13
+ ### Client example
14
+
15
+ require 'redis'
16
+ EventMachine.run {
17
+ redis = EventMachine.connect '127.0.0.1', 6379, Redis::Client
18
+ # Subscribe and publish messages will call here
19
+ redis.on_pubsub do |message|
20
+ # case message[0]
21
+ # when 'psubscribe' ...
22
+ end
23
+ # All commands implemented uniformly with method_missing
24
+ # Returns instance of Redis::Command < EventMachine::Deferrable
25
+ # Pipelining is implicit
26
+ redis.set :pi, 3.14159
27
+ redis.get('pi') do |result|
28
+ p result
29
+ end
30
+ redis.blpop('mylist', 1).callback do |result|
31
+ p result
32
+ EM.stop
33
+ end.errback do |e|
34
+ EM.stop
35
+ raise e
36
+ end
37
+ }
38
+
39
+
40
+ ### Using hiredis/reader (only affects clients)
41
+
42
+ # require it before you connect
43
+ require 'hiredis/reader'
44
+
45
+ ### Fibers example (compatible with em-synchrony)
46
+
47
+ require 'redis/synchrony'
48
+ Redis.synchrony {
49
+ # Be sure to pipeline commands when you can
50
+ redis = EventMachine.connect '127.0.0.1', 6379, Redis::Client
51
+ # synchronized requests will return result or raise exception
52
+ sync = redis.synchrony
53
+ # repeat transaction until success
54
+ reply = check = nil
55
+ until reply
56
+ redis.watch 'mykey'
57
+ x = sync.get('mykey').to_i
58
+ redis.multi
59
+ redis.set 'mykey', x + 1
60
+ redis.badcmd
61
+ redis.get('mykey') {|result| check=result}
62
+ reply = sync.exec
63
+ end
64
+ redis.close
65
+ EM.stop
66
+ p reply, check # ["OK", #<RuntimeError>, "4"], "4"
67
+ }
68
+
69
+
70
+ ### Ruby to Redis type conversions
71
+
72
+ String === Status reply
73
+ RuntimeError === Error reply
74
+ String === Bulk reply
75
+ Integer === Integer reply
76
+ Array === Multi-bulk reply
77
+ Hash === Multi-bulk reply to hgetall
78
+ NilClass === Nil element or nil multi-bulk
79
+ TrueClass === :1
80
+ FalseClass === :0
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'rubygems'
4
+ rescue LoadError
5
+ end
6
+ direct_bin_filename = File.expand_path '../lib/redis/bin', File.dirname(__FILE__)
7
+ if File.exist? direct_bin_filename + '.rb'
8
+ require direct_bin_filename
9
+ else
10
+ require 'redis/bin'
11
+ end
12
+ Redis::Bin.server
@@ -0,0 +1,13 @@
1
+ %w{redis/version redis/client}.each do |file|
2
+ require File.expand_path file, File.dirname(__FILE__)
3
+ end
4
+
5
+ module Redis
6
+
7
+ # This space intentionally blank
8
+
9
+ # Server entry in redis/bin.rb
10
+ # Client entry in redis/client.rb
11
+
12
+ end
13
+
@@ -0,0 +1,127 @@
1
+ %w{reader sender version config logger strict connection protocol database
2
+ server keys strings lists sets zsets hashes pubsub}.each do |file|
3
+ require File.expand_path file, File.dirname(__FILE__)
4
+ end
5
+
6
+ module Redis
7
+ class Bin
8
+
9
+ class RubyRedisServer < EventMachine::Connection
10
+
11
+ include Strict
12
+ include Connection
13
+ include Protocol
14
+ include Sender
15
+
16
+ def initialize logger, databases, config={}
17
+ @logger = logger
18
+ @databases = databases
19
+ @database = databases[0]
20
+ @config = config
21
+ super
22
+ end
23
+
24
+ def post_init
25
+ authorized nil
26
+ set_comm_inactivity_timeout @config[:timeout]
27
+ end
28
+
29
+ def authorized password
30
+ return true if @authorized
31
+ return false unless @config[:requirepass] == password
32
+ extend Server
33
+ extend Keys
34
+ extend Strings
35
+ extend Lists
36
+ extend Sets
37
+ extend ZSets
38
+ extend Hashes
39
+ extend PubSub
40
+ @authorized = true
41
+ end
42
+
43
+ end
44
+
45
+ def self.server
46
+
47
+ if ARGV==['-v'] or ARGV==['--version']
48
+ print "Redis server version %s (Ruby)\n" % Redis::VERSION
49
+ exit 0
50
+ end
51
+
52
+ if ARGV==['--help'] or ARGV.size > 1
53
+ STDERR.print "Usage: ruby-redis [/path/to/redis.conf]\n"
54
+ STDERR.print " ruby-redis - (read config from stdin)\n"
55
+ exit 1
56
+ end
57
+
58
+ show_no_config_warning = (ARGV.size == 0)
59
+
60
+ config = Config.new(ARGV.empty? ? [] : ARGF)
61
+
62
+ Dir.chdir config[:dir]
63
+
64
+ if config[:logfile] == 'stdout'
65
+ logger = Logger.new STDOUT
66
+ else
67
+ logger = Logger.new config[:logfile]
68
+ end
69
+
70
+ if config[:loglevel] == 'debug'
71
+ logger.level = Logger::DEBUG
72
+ elsif config[:loglevel] == 'notice'
73
+ logger.level = Logger::NOTICE
74
+ elsif config[:loglevel] == 'warning'
75
+ logger.level = Logger::WARNING
76
+ elsif config[:loglevel] != 'verbose'
77
+ raise "Invalid log level. Must be one of debug, notice, warning, verbose."
78
+ else
79
+ logger.level = Logger::INFO
80
+ end
81
+
82
+ if show_no_config_warning
83
+ logger.warn "Warning: no config file specified, using the default config. In order to specify a config file use 'ruby-redis /path/to/redis.conf'"
84
+ end
85
+
86
+ if config[:daemonize]
87
+ exit!(0) if fork
88
+ Process::setsid
89
+ exit!(0) if fork
90
+ STDIN.reopen("/dev/null")
91
+ STDOUT.reopen("/dev/null", "w")
92
+ STDERR.reopen("/dev/null", "w")
93
+ begin
94
+ File.open(config[:pidfile], 'w') do |io|
95
+ io.write "%d\n" % Process.pid
96
+ end
97
+ at_exit do
98
+ File.delete config[:pidfile] if File.exist? config[:pidfile]
99
+ end
100
+ rescue StandardError => e
101
+ logger.error e.message
102
+ end
103
+ end
104
+
105
+ EventMachine.run {
106
+
107
+ databases = Array.new(config[:databases]) {Database.new}
108
+ started_message = "Server started, Ruby Redis version %s" % Redis::VERSION
109
+
110
+ if config[:unixsocket]
111
+ EventMachine::start_server config[:unixsocket], RubyRedisServer, logger, databases, config
112
+ logger.notice started_message
113
+ logger.notice "The server is now ready to accept connections at %s" % config[:unixsocket]
114
+ else
115
+ EventMachine::start_server config[:bind], config[:port], RubyRedisServer, logger, databases, config
116
+ logger.notice started_message
117
+ logger.notice "The server is now ready to accept connections on port %d" % config[:port]
118
+ end
119
+
120
+ # The test suite blocks until it gets the pid from the log.
121
+ logger.flush
122
+
123
+ }
124
+
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,213 @@
1
+ require 'eventmachine'
2
+ %w{reader sender}.each do |file|
3
+ require File.expand_path file, File.dirname(__FILE__)
4
+ end
5
+
6
+ module Redis
7
+ class Client < EventMachine::Connection
8
+
9
+ include Sender
10
+
11
+ class Command
12
+ include EventMachine::Deferrable
13
+ # EventMachine::Deferrable older than 1.0.0.beta.4 doesn't return self
14
+ # EventMachine::Completion doesn't return self in any version
15
+ test = self.new
16
+ unless self === test.callback{}
17
+ def callback; super; self; end
18
+ def errback; super; self; end
19
+ end
20
+ end
21
+
22
+ def initialize *ignore_args
23
+ if defined? Hiredis and defined? Hiredis::Reader
24
+ @reader = Hiredis::Reader.new
25
+ else
26
+ @reader = Reader.new
27
+ end
28
+ @queue = []
29
+ @multi = nil
30
+ @pubsub_callback = proc{}
31
+ end
32
+
33
+ def connection_completed
34
+ @connected = true
35
+ @port, @host = Socket.unpack_sockaddr_in(get_peername)
36
+ end
37
+
38
+ def close
39
+ @closing_connection = true
40
+ close_connection_after_writing
41
+ end
42
+
43
+ def unbind
44
+ unless !@connected || @closing_connection
45
+ EM.add_timer(1) do
46
+ reconnect(@host, @port)
47
+ end
48
+ else
49
+ until @queue.empty?
50
+ @queue.shift.fail RuntimeError.new 'connection closed'
51
+ end
52
+ end
53
+ end
54
+
55
+ # This is simple and fast but doesn't test for programming errors.
56
+ # Don't send non-pubsub commands while subscribed and you're fine.
57
+ # Subclass Client and/or create a defensive layer if you need to.
58
+ def on_pubsub &block
59
+ @pubsub_callback = block
60
+ end
61
+
62
+ def receive_data data
63
+ @reader.feed data
64
+ until (
65
+ begin
66
+ data = @reader.gets
67
+ rescue StandardError => e
68
+ @queue.shift.fail e unless @queue.empty?
69
+ close_connection
70
+ data = false
71
+ end
72
+ ) == false
73
+ deferrable = @queue.shift
74
+ if deferrable
75
+ if Exception === data
76
+ deferrable.fail data
77
+ else
78
+ deferrable.succeed data
79
+ end
80
+ else
81
+ @pubsub_callback.call data
82
+ end
83
+ end
84
+ end
85
+
86
+ def method_missing method, *args, &block
87
+ if @multi
88
+ for_queue = new_command true, false, method, *args
89
+ command = new_command false, true, method, &block
90
+ callback_multi = @multi
91
+ for_queue.callback do |status|
92
+ callback_multi << command
93
+ end
94
+ for_queue.errback do |err|
95
+ callback_multi << err
96
+ command.fail err
97
+ end
98
+ else
99
+ command = new_command true, true, method, *args, &block
100
+ end
101
+ command
102
+ end
103
+
104
+ def in_multi?
105
+ !!@multi
106
+ end
107
+
108
+ def multi *args, &block
109
+ @multi ||= []
110
+ new_command true, true, :multi, *args, &block
111
+ end
112
+
113
+ def exec *args, &block
114
+ redis_exec = new_command true, true, :exec, *args
115
+ callback_multi = @multi
116
+ @multi = nil
117
+ redis_exec.callback do |results|
118
+ if results
119
+ normalized_results = []
120
+ callback_multi.each do |command|
121
+ if Exception === command
122
+ normalized_results << command
123
+ else
124
+ result = results.shift
125
+ normalized_results << result
126
+ if Exception === result
127
+ command.fail result
128
+ else
129
+ command.succeed result
130
+ end
131
+ end
132
+ end
133
+ redis_exec.succeed normalized_results
134
+ end
135
+ end
136
+ redis_exec.callback &block if block_given?
137
+ redis_exec
138
+ end
139
+
140
+ # Some data is best transformed into a Ruby type. You can set up global
141
+ # transforms here that are automatically attached to command callbacks.
142
+ # Redis::Client.transforms[:mycustom1] = Redis::Client.transforms[:exists] # boolean
143
+ # Redis::Client.transforms[:mycustom2] = proc { |data| MyType.new data }
144
+ # Redis::Client.transforms.delete :hgetall # if you prefer the array
145
+ def self.transforms
146
+ @@transforms ||= lambda {
147
+ boolean = lambda { |tf| tf[0] == 1 ? true : false }
148
+ hash = lambda { |hash| Hash[*hash] }
149
+ pubsub = lambda { |msg| lambda { msg } }
150
+ {
151
+ #pubsub
152
+ :subscribe => pubsub,
153
+ :psubscribe => pubsub,
154
+ :unsubscribe => pubsub,
155
+ :punsubscribe => pubsub,
156
+ #keys
157
+ :exists => boolean,
158
+ :expire => boolean,
159
+ :expireat => boolean,
160
+ :move => boolean,
161
+ :persist => boolean,
162
+ :renamenx => boolean,
163
+ #strings
164
+ :msetnx => boolean,
165
+ :setnx => boolean,
166
+ #hashes
167
+ :hexists => boolean,
168
+ :hgetall => hash,
169
+ :hset => boolean,
170
+ :hsetnx => boolean,
171
+ #sets
172
+ :sismember => boolean,
173
+ :smove => boolean,
174
+ :srem => boolean,
175
+ #zsets
176
+ :zrem => boolean,
177
+ }
178
+ }.call
179
+ end
180
+
181
+ private
182
+
183
+ def new_command do_send, do_transform, method, *args, &block
184
+ command = Command.new
185
+ if do_send
186
+ send_redis args.reduce([method]){ |arr, arg|
187
+ if Hash === arg
188
+ arr += arg.to_a.flatten 1
189
+ else
190
+ arr << arg
191
+ end
192
+ }
193
+ @queue.push command
194
+ end
195
+ if do_transform
196
+ if transform = self.class.transforms[method]
197
+ command.callback do |data|
198
+ result = transform.call data
199
+ if Proc === result
200
+ command.succeed nil
201
+ @pubsub_callback.call result.call
202
+ else
203
+ command.succeed result
204
+ end
205
+ end
206
+ end
207
+ end
208
+ command.callback &block if block_given?
209
+ command
210
+ end
211
+
212
+ end
213
+ end