]>
Commit | Line | Data |
---|---|---|
1 | # RubyRedis is an alternative implementatin of Ruby client library written | |
2 | # by Salvatore Sanfilippo. | |
3 | # | |
4 | # The aim of this library is to create an alternative client library that is | |
5 | # much simpler and does not implement every command explicitly but uses | |
6 | # method_missing instead. | |
7 | ||
8 | require 'socket' | |
9 | ||
10 | class RedisClient | |
11 | BulkCommands = { | |
12 | "set"=>true, "setnx"=>true, "rpush"=>true, "lpush"=>true, "lset"=>true, | |
13 | "lrem"=>true, "sadd"=>true, "srem"=>true, "sismember"=>true, | |
14 | "echo"=>true, "getset"=>true, "smove"=>true | |
15 | } | |
16 | ||
17 | ConvertToBool = lambda{|r| r == 0 ? false : r} | |
18 | ||
19 | ReplyProcessor = { | |
20 | "exists" => ConvertToBool, | |
21 | "sismember"=> ConvertToBool, | |
22 | "sadd"=> ConvertToBool, | |
23 | "srem"=> ConvertToBool, | |
24 | "smove"=> ConvertToBool, | |
25 | "move"=> ConvertToBool, | |
26 | "setnx"=> ConvertToBool, | |
27 | "del"=> ConvertToBool, | |
28 | "renamenx"=> ConvertToBool, | |
29 | "expire"=> ConvertToBool, | |
30 | "keys" => lambda{|r| r.split(" ")}, | |
31 | "info" => lambda{|r| | |
32 | info = {} | |
33 | r.each_line {|kv| | |
34 | k,v = kv.split(':', 2) | |
35 | k,v = k.chomp, v = v.chomp | |
36 | info[k.to_sym] = v | |
37 | } | |
38 | info | |
39 | } | |
40 | } | |
41 | ||
42 | def initialize(opts={}) | |
43 | opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts) | |
44 | @host = opts[:host] | |
45 | @port = opts[:port] | |
46 | @db = opts[:db] | |
47 | connect_to_server | |
48 | end | |
49 | ||
50 | def to_s | |
51 | "Redis Client connected to #{@host}:#{@port} against DB #{@db}" | |
52 | end | |
53 | ||
54 | def connect_to_server | |
55 | @sock = TCPSocket.new(@host, @port, 0) | |
56 | call_command(["select",@db]) if @db != 0 | |
57 | end | |
58 | ||
59 | def method_missing(*argv) | |
60 | call_command(argv) | |
61 | end | |
62 | ||
63 | def call_command(argv) | |
64 | # this wrapper to raw_call_command handle reconnection on socket | |
65 | # error. We try to reconnect just one time, otherwise let the error | |
66 | # araise. | |
67 | begin | |
68 | raw_call_command(argv) | |
69 | rescue Errno::ECONNRESET | |
70 | @sock.close | |
71 | connect_to_server | |
72 | raw_call_command(argv) | |
73 | end | |
74 | end | |
75 | ||
76 | def raw_call_command(argv) | |
77 | bulk = nil | |
78 | argv[0] = argv[0].to_s.downcase | |
79 | if BulkCommands[argv[0]] | |
80 | bulk = argv[-1].to_s | |
81 | argv[-1] = bulk.length | |
82 | end | |
83 | @sock.write(argv.join(" ")+"\r\n") | |
84 | @sock.write(bulk+"\r\n") if bulk | |
85 | ||
86 | # Post process the reply if needed | |
87 | processor = ReplyProcessor[argv[0]] | |
88 | processor ? processor.call(read_reply) : read_reply | |
89 | end | |
90 | ||
91 | def select(*args) | |
92 | raise "SELECT not allowed, use the :db option when creating the object" | |
93 | end | |
94 | ||
95 | def [](key) | |
96 | get(key) | |
97 | end | |
98 | ||
99 | def []=(key,value) | |
100 | set(key,value) | |
101 | end | |
102 | ||
103 | def read_reply | |
104 | line = @sock.gets | |
105 | raise Errno::ECONNRESET,"Connection lost" if !line | |
106 | case line[0..0] | |
107 | when "-" | |
108 | raise line.strip | |
109 | when "+" | |
110 | line[1..-1].strip | |
111 | when ":" | |
112 | line[1..-1].to_i | |
113 | when "$" | |
114 | bulklen = line[1..-1].to_i | |
115 | return nil if bulklen == -1 | |
116 | data = @sock.read(bulklen) | |
117 | @sock.read(2) # CRLF | |
118 | data | |
119 | when "*" | |
120 | objects = line[1..-1].to_i | |
121 | return nil if bulklen == -1 | |
122 | res = [] | |
123 | objects.times { | |
124 | res << read_reply | |
125 | } | |
126 | res | |
127 | end | |
128 | end | |
129 | end |