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