Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Lua client added thanks to Daniele Alessandri

  • Loading branch information...
commit f2aa84bd638b8d18e80531abfd7191f5d0a58c71 1 parent e63943a
@antirez authored
View
1  .gitignore
@@ -6,3 +6,4 @@ redis-benchmark
doc-tools
mkrelease.sh
release
+myredis.conf
View
3  client-libraries/README
@@ -26,4 +26,7 @@ Perl lib source code:
Redis-php PHP C module:
http://code.google.com/p/phpredis/
+Lua lib source code:
+http://github.com/nrk/redis-lua/tree/master
+
For all the rest check the Redis tarball or Git repository.
View
BIN  client-libraries/lua/.LICENSE.swp
Binary file not shown
View
22 client-libraries/lua/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2009
+Daniele Alessandri
+http://www.clorophilla.net/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
4 client-libraries/lua/README
@@ -0,0 +1,4 @@
+redis-lua
+-------------------------------------------------------------------------------
+
+A Lua client library for the redis key value storage system.
View
322 client-libraries/lua/redis.lua
@@ -0,0 +1,322 @@
+module('Redis', package.seeall)
+
+require('socket') -- requires LuaSocket as a dependency
+
+-- ############################################################################
+
+local protocol = {
+ newline = '\r\n', ok = 'OK', err = 'ERR', null = 'nil',
+}
+
+-- ############################################################################
+
+local function toboolean(value)
+ return value == 1
+end
+
+local function _write(self, buffer)
+ local _, err = self.socket:send(buffer)
+ if err then error(err) end
+end
+
+local function _read(self, len)
+ if len == nil then len = '*l' end
+ local line, err = self.socket:receive(len)
+ if not err then return line else error('Connection error: ' .. err) end
+end
+
+-- ############################################################################
+
+local function _read_response(self)
+ if options and options.close == true then return end
+
+ local res = _read(self)
+ local prefix = res:sub(1, -#res)
+ local response_handler = protocol.prefixes[prefix]
+
+ if not response_handler then
+ error("Unknown response prefix: " .. prefix)
+ else
+ return response_handler(self, res)
+ end
+end
+
+
+local function _send_raw(self, buffer)
+ -- TODO: optimize
+ local bufferType = type(buffer)
+
+ if bufferType == 'string' then
+ _write(self, buffer)
+ elseif bufferType == 'table' then
+ _write(self, table.concat(buffer))
+ else
+ error('Argument error: ' .. bufferType)
+ end
+
+ return _read_response(self)
+end
+
+local function _send_inline(self, command, ...)
+ if arg.n == 0 then
+ _write(self, command .. protocol.newline)
+ else
+ local arguments = arg
+ arguments.n = nil
+
+ if #arguments > 0 then
+ arguments = table.concat(arguments, ' ')
+ else
+ arguments = ''
+ end
+
+ _write(self, command .. ' ' .. arguments .. protocol.newline)
+ end
+
+ return _read_response(self)
+end
+
+local function _send_bulk(self, command, ...)
+ local arguments = arg
+ local data = tostring(table.remove(arguments))
+ arguments.n = nil
+
+ -- TODO: optimize
+ if #arguments > 0 then
+ arguments = table.concat(arguments, ' ')
+ else
+ arguments = ''
+ end
+
+ return _send_raw(self, {
+ command, ' ', arguments, ' ', #data, protocol.newline, data, protocol.newline
+ })
+end
+
+
+local function _read_line(self, response)
+ return response:sub(2)
+end
+
+local function _read_error(self, response)
+ local err_line = response:sub(2)
+
+ if err_line:sub(1, 3) == protocol.err then
+ error("Redis error: " .. err_line:sub(5))
+ else
+ error("Redis error: " .. err_line)
+ end
+end
+
+local function _read_bulk(self, response)
+ local str = response:sub(2)
+ local len = tonumber(str)
+
+ if not len then
+ error('Cannot parse ' .. str .. ' as data length.')
+ else
+ if len == -1 then return nil end
+ local data = _read(self, len + 2)
+ return data:sub(1, -3);
+ end
+end
+
+local function _read_multibulk(self, response)
+ local str = response:sub(2)
+
+ -- TODO: add a check if the returned value is indeed a number
+ local list_count = tonumber(str)
+
+ if list_count == -1 then
+ return nil
+ else
+ local list = {}
+
+ if list_count > 0 then
+ for i = 1, list_count do
+ table.insert(list, i, _read_bulk(self, _read(self)))
+ end
+ end
+
+ return list
+ end
+end
+
+local function _read_integer(self, response)
+ local res = response:sub(2)
+ local number = tonumber(res)
+
+ if not number then
+ if res == protocol.null then
+ return nil
+ else
+ error('Cannot parse ' .. res .. ' as numeric response.')
+ end
+ end
+
+ return number
+end
+
+-- ############################################################################
+
+protocol.prefixes = {
+ ['+'] = _read_line,
+ ['-'] = _read_error,
+ ['$'] = _read_bulk,
+ ['*'] = _read_multibulk,
+ [':'] = _read_integer,
+}
+
+-- ############################################################################
+
+local methods = {
+ -- miscellaneous commands
+ ping = {
+ 'PING', _send_inline, function(response)
+ if response == 'PONG' then return true else return false end
+ end
+ },
+ echo = { 'ECHO', _send_bulk },
+ -- TODO: the server returns an empty -ERR on authentication failure
+ auth = { 'AUTH' },
+
+ -- connection handling
+ quit = { 'QUIT', function(self, command)
+ _write(self, command .. protocol.newline)
+ end
+ },
+
+ -- commands operating on string values
+ set = { 'SET', _send_bulk },
+ set_preserve = { 'SETNX', _send_bulk, toboolean },
+ get = { 'GET' },
+ get_multiple = { 'MGET' },
+ increment = { 'INCR' },
+ increment_by = { 'INCRBY' },
+ decrement = { 'DECR' },
+ decrement_by = { 'DECRBY' },
+ exists = { 'EXISTS', _send_inline, toboolean },
+ delete = { 'DEL', _send_inline, toboolean },
+ type = { 'TYPE' },
+
+ -- commands operating on the key space
+ keys = {
+ 'KEYS', _send_inline, function(response)
+ local keys = {}
+ response:gsub('%w+', function(key)
+ table.insert(keys, key)
+ end)
+ return keys
+ end
+ },
+ random_key = { 'RANDOMKEY' },
+ rename = { 'RENAME' },
+ rename_preserve = { 'RENAMENX' },
+ database_size = { 'DBSIZE' },
+
+ -- commands operating on lists
+ push_tail = { 'RPUSH', _send_bulk },
+ push_head = { 'LPUSH', _send_bulk },
+ list_length = { 'LLEN', _send_inline, function(response, key)
+ --[[ TODO: redis seems to return a -ERR when the specified key does
+ not hold a list value, but this behaviour is not
+ consistent with the specs docs. This might be due to the
+ -ERR response paradigm being new, which supersedes the
+ check for negative numbers to identify errors. ]]
+ if response == -2 then
+ error('Key ' .. key .. ' does not hold a list value')
+ end
+ return response
+ end
+ },
+ list_range = { 'LRANGE' },
+ list_trim = { 'LTRIM' },
+ list_index = { 'LINDEX' },
+ list_set = { 'LSET', _send_bulk },
+ list_remove = { 'LREM', _send_bulk },
+ pop_first = { 'LPOP' },
+ pop_last = { 'RPOP' },
+
+ -- commands operating on sets
+ set_add = { 'SADD' },
+ set_remove = { 'SREM' },
+ set_cardinality = { 'SCARD' },
+ set_is_member = { 'SISMEMBER' },
+ set_intersection = { 'SINTER' },
+ set_intersection_store = { 'SINTERSTORE' },
+ set_members = { 'SMEMBERS' },
+
+ -- multiple databases handling commands
+ select_database = { 'SELECT' },
+ move_key = { 'MOVE' },
+ flush_database = { 'FLUSHDB' },
+ flush_databases = { 'FLUSHALL' },
+
+ -- sorting
+ --[[
+ TODO: should we pass sort parameters as a table? e.g:
+ params = {
+ by = 'weight_*',
+ get = 'object_*',
+ limit = { 0, 10 },
+ sort = { 'desc', 'alpha' }
+ }
+ --]]
+ sort = { 'SORT' },
+
+ -- persistence control commands
+ save = { 'SAVE' },
+ background_save = { 'BGSAVE' },
+ last_save = { 'LASTSAVE' },
+ shutdown = { 'SHUTDOWN', function(self, command)
+ _write(self, command .. protocol.newline)
+ end
+ },
+
+ -- remote server control commands
+ info = {
+ 'INFO', _send_inline, function(response)
+ local info = {}
+ response:gsub('([^\r\n]*)\r\n', function(kv)
+ local k,v = kv:match(('([^:]*):([^:]*)'):rep(1))
+ info[k] = v
+ end)
+ return info
+ end
+ },
+}
+
+function connect(host, port)
+ local client_socket = socket.connect(host, port)
+
+ if not client_socket then
+ error('Could not connect to ' .. host .. ':' .. port)
+ end
+
+ local redis_client = {
+ socket = client_socket,
+ raw_cmd = function(self, buffer)
+ return _send_raw(self, buffer .. protocol.newline)
+ end,
+ }
+
+ return setmetatable(redis_client, {
+ __index = function(self, method)
+ local redis_meth = methods[method]
+ if redis_meth then
+ return function(self, ...)
+ if not redis_meth[2] then
+ table.insert(redis_meth, 2, _send_inline)
+ end
+
+ local response = redis_meth[2](self, redis_meth[1], ...)
+ if redis_meth[3] then
+ return redis_meth[3](response, ...)
+ else
+ return response
+ end
+ end
+ end
+ end
+ })
+end
View
20 redis.conf
@@ -62,6 +62,17 @@ databases 16
# slaveof <masterip> <masterport>
+################################## SECURITY ###################################
+
+# Require clients to issue AUTH <PASSWORD> before processing any other
+# commands. This might be useful in environments in which you do not trust
+# others with access to the host running redis-server.
+#
+# This should stay commented out for backward compatibility and because most
+# people do not need auth (e.g. they run their own servers).
+
+#requirepass foobared
+
############################### ADVANCED CONFIG ###############################
# Glue small output buffers together in order to send small replies in a
@@ -74,12 +85,3 @@ glueoutputbuf yes
# pool so it uses more CPU and can be a bit slower. Usually it's a good
# idea.
shareobjects no
-
-# Require clients to issue AUTH <PASSWORD> before processing any other
-# commands. This might be useful in environments in which you do not trust
-# others with access to the host running redis-server.
-#
-# This should stay commented out for backward compatibility and because most
-# people do not need auth (e.g. they run their own servers).
-
-#requirepass foobared
Please sign in to comment.
Something went wrong with that request. Please try again.