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