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*>(¶m)) {
! 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*>(¶m)) {
! 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: