Annotation of parser3/src/classes/redis.C, revision 1.1

1.1     ! moko        1: /** @file
        !             2:        Parser: @b redis parser class.
        !             3: 
        !             4:        Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
        !             5:        Authors: Konstantin Morshnev <moko@design.ru>
        !             6: */
        !             7: 
        !             8: #include "pa_vmethod_frame.h"
        !             9: #include "pa_request.h"
        !            10: #include "pa_vstring.h"
        !            11: #include "pa_vhash.h"
        !            12: #include "pa_varray.h"
        !            13: #include "pa_vbool.h"
        !            14: #include "pa_vvoid.h"
        !            15: #include "pa_vint.h"
        !            16: #include "pa_vdouble.h"
        !            17: #include "pa_vfile.h"
        !            18: #include "pa_vredis.h"
        !            19: #include "pa_os.h"
        !            20: 
        !            21: volatile const char * IDENT_REDIS_C="$Id$" IDENT_PA_VREDIS_H;
        !            22: 
        !            23: // defines
        !            24: 
        !            25: #define DEFAULT_TIMEOUT 2 // seconds
        !            26: 
        !            27: class MRedis: public Methoded {
        !            28: public: // VStateless_class
        !            29:        Value* create_new_value(Pool&) { return new VRedis(); }
        !            30: public:
        !            31:        MRedis();
        !            32: };
        !            33: 
        !            34: DECLARE_CLASS_VAR(redis, new MRedis);
        !            35: 
        !            36: #ifdef WITH_REDIS
        !            37: 
        !            38: static void _open(Request& r, MethodParams& params) {
        !            39:        VRedis& self=GET_SELF(r, VRedis);
        !            40: 
        !            41:        const char* host="localhost";
        !            42:        int port=6379;
        !            43:        bool tcp_specified=false;
        !            44:        const char* unix_socket=0;
        !            45:        double timeout=DEFAULT_TIMEOUT;
        !            46:        const char* user=0;
        !            47:        const char* password=0;
        !            48:        int db=0;
        !            49:        int protocol=3;
        !            50:        int auto_reconnect=0;
        !            51: 
        !            52:        if(params.count()>0)
        !            53:                if(HashStringValue* options=params.as_hash(0)) {
        !            54:                        for(HashStringValue::Iterator i(*options); i; i.next()) {
        !            55:                                String::Body key=i.key();
        !            56:                                Value* value=i.value();
        !            57:                                if(key=="host") {
        !            58:                                        host=value->as_string().cstr();
        !            59:                                        tcp_specified=true;
        !            60:                                } else if(key=="port") {
        !            61:                                        port=r.process(*value).as_int();
        !            62:                                        tcp_specified=true;
        !            63:                                } else if(key=="unix_socket") {
        !            64:                                        unix_socket=value->as_string().cstr();
        !            65:                                } else if(key=="timeout") {
        !            66:                                        timeout=r.process(*value).as_double();
        !            67:                                } else if(key=="user") {
        !            68:                                        user=value->as_string().cstr();
        !            69:                                } else if(key=="password") {
        !            70:                                        password=value->as_string().cstr();
        !            71:                                } else if(key=="db") {
        !            72:                                        db=r.process(*value).as_int();
        !            73:                                } else if(key=="protocol") {
        !            74:                                        protocol=r.process(*value).as_int();
        !            75:                                        if(protocol!=2 && protocol!=3)
        !            76:                                                throw Exception(PARSER_RUNTIME, 0, "protocol must be 2 or 3");
        !            77:                                } else if(key=="auto_reconnect") {
        !            78:                                        auto_reconnect=r.process(*value).as_int();
        !            79:                                } else
        !            80:                                        throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
        !            81:                        }
        !            82:                }
        !            83: 
        !            84:        if(unix_socket && tcp_specified)
        !            85:                throw Exception(PARSER_RUNTIME, 0, "unix_socket can not be used with host or port");
        !            86: 
        !            87:        redisOptions options;
        !            88:        memset(&options, 0, sizeof(options));
        !            89:        if(unix_socket)
        !            90:                REDIS_OPTIONS_SET_UNIX(&options, unix_socket);
        !            91:        else
        !            92:                REDIS_OPTIONS_SET_TCP(&options, host, port);
        !            93: 
        !            94:        struct timeval tv;
        !            95:        if(timeout>0) { // 0 is none
        !            96:                tv.tv_sec=(long)timeout;
        !            97:                tv.tv_usec=(long)((timeout-tv.tv_sec)*1000000);
        !            98:                options.connect_timeout=&tv;
        !            99:                options.command_timeout=&tv;
        !           100:        }
        !           101: 
        !           102:        // ^r.open[] of an opened object: close the old connection first
        !           103:        if(self.fcontext) {
        !           104:                redisFree(self.fcontext);
        !           105:                self.fcontext=0;
        !           106:        }
        !           107: 
        !           108:        redisContext* context=redisConnectWithOptions(&options);
        !           109:        if(!context)
        !           110:                throw Exception("redis", 0, "connect failed: out of memory");
        !           111:        if(context->err) {
        !           112:                const char* error=pa_strdup(context->errstr);
        !           113:                redisFree(context);
        !           114:                throw Exception("redis", 0, "connect failed: %s", error);
        !           115:        }
        !           116: 
        !           117:        self.fcontext=context;
        !           118:        self.fuser=user;
        !           119:        self.fpassword=password;
        !           120:        self.fdb=db;
        !           121:        self.fprotocol=protocol;
        !           122:        self.fauto_reconnect=auto_reconnect;
        !           123: 
        !           124:        try {
        !           125:                self.handshake();
        !           126:        } catch(...) {
        !           127:                redisFree(context);
        !           128:                self.fcontext=0;
        !           129:                throw;
        !           130:        }
        !           131: }
        !           132: 
        !           133: static void _release(Request& r, MethodParams&) {
        !           134:        VRedis& self=GET_SELF(r, VRedis);
        !           135: 
        !           136:        if(self.fcontext) {
        !           137:                redisFree(self.fcontext);
        !           138:                self.fcontext=0;
        !           139:        }
        !           140: }
        !           141: 
        !           142: static Value* reply_value(redisReply& reply, bool binary, const String& command) {
        !           143:        switch(reply.type) {
        !           144:                case REDIS_REPLY_STRING:
        !           145:                case REDIS_REPLY_STATUS:
        !           146:                case REDIS_REPLY_VERB:
        !           147:                case REDIS_REPLY_BIGNUM: {
        !           148:                        char* data=pa_strdup(reply.str, reply.len);
        !           149:                        if(binary) {
        !           150:                                VFile& file=*new VFile;
        !           151:                                file.set_binary(true/*tainted*/, data, reply.len);
        !           152:                                return &file;
        !           153:                        }
        !           154:                        return new VString(*new String(String::C(data, reply.len), String::L_TAINTED));
        !           155:                }
        !           156:                case REDIS_REPLY_INTEGER:
        !           157:                        if(reply.integer>=PA_WINT_MIN && reply.integer<=PA_WINT_MAX)
        !           158:                                return new VInt(reply.integer);
        !           159:                        // beyond the precise int range
        !           160:                        return new VString(*new String(pa_itoa(reply.integer)));
        !           161:                case REDIS_REPLY_DOUBLE:
        !           162:                        return new VDouble(reply.dval);
        !           163:                case REDIS_REPLY_BOOL:
        !           164:                        return &VBool::get(reply.integer!=0);
        !           165:                case REDIS_REPLY_NIL:
        !           166:                        return VVoid::get();
        !           167:                case REDIS_REPLY_ARRAY:
        !           168:                case REDIS_REPLY_SET:
        !           169:                case REDIS_REPLY_PUSH: {
        !           170:                        VArray& result=*new VArray(reply.elements);
        !           171:                        ArrayValue& array=result.array();
        !           172:                        for(size_t i=0; i<reply.elements; i++)
        !           173:                                array+=reply_value(*reply.element[i], binary, command);
        !           174:                        return &result;
        !           175:                }
        !           176:                case REDIS_REPLY_MAP: {
        !           177:                        VHash& result=*new VHash;
        !           178:                        for(size_t i=0; i+1<reply.elements; i+=2)
        !           179:                                result.hash().put(reply_value(*reply.element[i], false, command)->as_string(), reply_value(*reply.element[i+1], binary, command));
        !           180:                        return &result;
        !           181:                }
        !           182:                case REDIS_REPLY_ERROR:
        !           183:                        throw Exception("redis", &command, "%s", reply.str);
        !           184:                case REDIS_REPLY_ATTR:
        !           185:                        return VVoid::get();
        !           186:        }
        !           187:        throw Exception("redis", &command, "unknown reply type %d", reply.type);
        !           188: }
        !           189: 
        !           190: // a string, a number, a bool or a file
        !           191: static void add_argument(Array<const char*>& argv, Array<size_t>& lengths, Value& value) {
        !           192:        if(VFile* file=dynamic_cast<VFile*>(&value)) {
        !           193:                argv+=file->value_ptr();
        !           194:                lengths+=file->value_size();
        !           195:        } else if(dynamic_cast<VBool*>(&value)) {
        !           196:                // as redis itself encodes booleans in integer replies
        !           197:                argv+=value.as_bool() ? "1" : "0";
        !           198:                lengths+=1;
        !           199:        } else if(dynamic_cast<VArray*>(&value) || dynamic_cast<VHash*>(&value)) {
        !           200:                throw Exception(PARSER_RUNTIME, 0, "redis command argument can not be a nested array or a hash");
        !           201:        } else {
        !           202:                const char* string=value.as_string().cstr();
        !           203:                argv+=string;
        !           204:                lengths+=strlen(string);
        !           205:        }
        !           206: }
        !           207: 
        !           208: // arguments start at params[first]: strings, numbers, files, not nested arrays and hashes as key value pairs
        !           209: static void redis_command(Request& r, MethodParams& params, const String& command, size_t first, bool binary) {
        !           210:        VRedis& self=GET_SELF(r, VRedis);
        !           211: 
        !           212:        Array<const char*> argv;
        !           213:        Array<size_t> lengths;
        !           214:        const char* command_cstr=command.cstr();
        !           215:        argv+=command_cstr;
        !           216:        lengths+=strlen(command_cstr);
        !           217: 
        !           218:        for(size_t i=first; i<params.count(); i++) {
        !           219:                Value& param=params[i];
        !           220:                if(VArray* array=dynamic_cast<VArray*>(&param)) {
        !           221:                        for(ArrayValue::Iterator e(array->array()); e; e.next())
        !           222:                                if(e.value())
        !           223:                                        add_argument(argv, lengths, *e.value());
        !           224:                } else if(VHash* hash=dynamic_cast<VHash*>(&param)) {
        !           225:                        for(HashStringValue::Iterator e(hash->hash()); e; e.next()) {
        !           226:                                const char* key=e.key().cstr();
        !           227:                                argv+=key;
        !           228:                                lengths+=strlen(key);
        !           229:                                add_argument(argv, lengths, *e.value());
        !           230:                        }
        !           231:                } else
        !           232:                        add_argument(argv, lengths, param);
        !           233:        }
        !           234: 
        !           235:        redisReply* reply=self.command((int)argv.count(), argv.ptr(0), lengths.ptr(0));
        !           236:        Temp_redis_reply temp_reply(reply);
        !           237: 
        !           238:        r.write(*reply_value(*reply, binary, command));
        !           239: }
        !           240: 
        !           241: // ^r.call[command;arguments...]
        !           242: static void _call(Request& r, MethodParams& params) {
        !           243:        redis_command(r, params, params.as_string(0, "command must be string"), 1, false);
        !           244: }
        !           245: 
        !           246: // ^r.call_binary[command;arguments...]: the strings of the reply are files
        !           247: static void _call_binary(Request& r, MethodParams& params) {
        !           248:        redis_command(r, params, params.as_string(0, "command must be string"), 1, true);
        !           249: }
        !           250: 
        !           251: // ^r.command_name[arguments...]: the command is the name of the method called, see VRedis::get_element4call
        !           252: void redis_command_method(Request& r, MethodParams& params) {
        !           253:        redis_command(r, params, *r.get_method_frame()->method.name, 0, false);
        !           254: }
        !           255: 
        !           256: #else
        !           257: 
        !           258: static void _open(Request&, MethodParams&) {
        !           259:        throw Exception("redis", 0, "compiled without redis support");
        !           260: }
        !           261: 
        !           262: #endif // WITH_REDIS
        !           263: 
        !           264: MRedis::MRedis(): Methoded("redis") {
        !           265:        // ^redis::open[ $.host[localhost] $.port(6379) $.unix_socket[path] $.user[] $.password[] $.db(0) $.timeout(seconds, 0 is none) $.protocol(3) $.auto_reconnect(seconds) ]
        !           266:        add_native_method("open", Method::CT_DYNAMIC, _open, 0, 1);
        !           267: 
        !           268: #ifdef WITH_REDIS
        !           269:        // ^r.call[command;arguments...]
        !           270:        add_native_method("call", Method::CT_DYNAMIC, _call, 1, MAX_REDIS_COMMAND_PARAMS);
        !           271: 
        !           272:        // ^r.call_binary[command;arguments...]
        !           273:        add_native_method("call_binary", Method::CT_DYNAMIC, _call_binary, 1, MAX_REDIS_COMMAND_PARAMS);
        !           274: 
        !           275:        // ^r.release[]
        !           276:        add_native_method("release", Method::CT_DYNAMIC, _release, 0, 0);
        !           277: #endif // WITH_REDIS
        !           278: }

E-mail: