diff --git a/.gitignore b/.gitignore index c8a56bc70..56c27fdd6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ redis-benchmark doc-tools mkrelease.sh release +myredis.conf diff --git a/client-libraries/README b/client-libraries/README index 46b1375f9..f35b74a46 100644 --- a/client-libraries/README +++ b/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. diff --git a/client-libraries/lua/.LICENSE.swp b/client-libraries/lua/.LICENSE.swp new file mode 100644 index 000000000..b4773ae8a Binary files /dev/null and b/client-libraries/lua/.LICENSE.swp differ diff --git a/client-libraries/lua/LICENSE b/client-libraries/lua/LICENSE new file mode 100644 index 000000000..2da032712 --- /dev/null +++ b/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. \ No newline at end of file diff --git a/client-libraries/lua/README b/client-libraries/lua/README new file mode 100644 index 000000000..98a553485 --- /dev/null +++ b/client-libraries/lua/README @@ -0,0 +1,4 @@ +redis-lua +------------------------------------------------------------------------------- + +A Lua client library for the redis key value storage system. \ No newline at end of file diff --git a/client-libraries/lua/redis.lua b/client-libraries/lua/redis.lua new file mode 100644 index 000000000..757aae113 --- /dev/null +++ b/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 diff --git a/redis.conf b/redis.conf index c1b4297c9..f217352eb 100644 --- a/redis.conf +++ b/redis.conf @@ -62,6 +62,17 @@ databases 16 # slaveof +################################## SECURITY ################################### + +# Require clients to issue AUTH 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 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