]>
Commit | Line | Data |
---|---|---|
57172ffb | 1 | /* redisclient.cpp -- a C++ client library for redis. |
2 | * | |
3 | * Copyright (c) 2009, Brian Hammond <brian at fictorial dot com> | |
4 | * All rights reserved. | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * | |
9 | * * Redistributions of source code must retain the above copyright notice, | |
10 | * this list of conditions and the following disclaimer. | |
11 | * * Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * * Neither the name of Redis nor the names of its contributors may be used | |
15 | * to endorse or promote products derived from this software without | |
16 | * specific prior written permission. | |
17 | * | |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
28 | * POSSIBILITY OF SUCH DAMAGE. | |
29 | */ | |
30 | ||
31 | #include "redisclient.h" | |
32 | #include "anet.h" | |
33 | ||
34 | #include <sstream> | |
35 | ||
36 | #ifndef NDEBUG | |
37 | #include <algorithm> | |
38 | #include <iostream> | |
39 | #include <ctime> | |
40 | #endif | |
41 | ||
42 | #include <cstring> | |
43 | #include <cstdlib> | |
44 | #include <cassert> | |
45 | ||
46 | #include <sys/errno.h> | |
47 | #include <sys/socket.h> | |
48 | ||
49 | using namespace std; | |
50 | ||
51 | namespace | |
52 | { | |
53 | const string whitespace(" \f\n\r\t\v"); | |
54 | const string CRLF("\r\n"); | |
55 | ||
56 | // Modifies in-place. | |
57 | ||
58 | inline string & rtrim(string & str, const string & ws = whitespace) | |
59 | { | |
60 | string::size_type pos = str.find_last_not_of(ws); | |
61 | str.erase(pos + 1); | |
62 | return str; | |
63 | } | |
64 | ||
65 | vector<string>::size_type split(const string & str, char delim, vector<string> & elems) | |
66 | { | |
67 | stringstream ss(str); | |
68 | string item; | |
69 | vector<string>::size_type n = 0; | |
70 | while (getline(ss, item, delim)) | |
71 | { | |
72 | elems.push_back(item); | |
73 | ++n; | |
74 | } | |
75 | return n; | |
76 | } | |
77 | ||
78 | inline void split_lines(const string & str, vector<string> & elems) | |
79 | { | |
80 | split(str, '\n', elems); | |
81 | for (vector<string>::iterator it = elems.begin(); it != elems.end(); ++it) | |
82 | rtrim(*it); | |
83 | } | |
84 | ||
85 | #ifndef NDEBUG | |
86 | ||
87 | void output_proto_debug(const string & data, bool is_received = true) | |
88 | { | |
89 | string escaped_data(data); | |
90 | size_t pos; | |
91 | while ((pos = escaped_data.find("\n")) != string::npos) | |
92 | escaped_data.replace(pos, 1, "\\n"); | |
93 | while ((pos = escaped_data.find("\r")) != string::npos) | |
94 | escaped_data.replace(pos, 1, "\\r"); | |
95 | ||
96 | cerr | |
97 | << time(NULL) << ": " | |
98 | << (is_received ? "RECV '" : "SEND '") | |
99 | << escaped_data | |
100 | << "'" | |
101 | << endl; | |
102 | } | |
103 | ||
104 | #endif | |
105 | ||
106 | class makecmd | |
107 | { | |
108 | public: | |
109 | explicit makecmd(const string & initial, bool finalize = false) | |
110 | { | |
111 | buffer_ << initial; | |
112 | if (!finalize) | |
113 | buffer_ << " "; | |
114 | } | |
115 | ||
116 | template <typename T> | |
117 | makecmd & operator<<(T const & datum) | |
118 | { | |
119 | buffer_ << datum; | |
120 | return *this; | |
121 | } | |
122 | ||
123 | template <typename T> | |
124 | makecmd & operator<<(const vector<T> & data) | |
125 | { | |
126 | size_t n = data.size(); | |
127 | for (size_t i = 0; i < n; ++i) | |
128 | { | |
129 | buffer_ << data[i]; | |
130 | if (i < n - 1) | |
131 | buffer_ << " "; | |
132 | } | |
133 | return *this; | |
134 | } | |
135 | ||
136 | operator std::string () | |
137 | { | |
138 | buffer_ << CRLF; | |
139 | return buffer_.str(); | |
140 | } | |
141 | ||
142 | private: | |
143 | ostringstream buffer_; | |
144 | }; | |
145 | ||
146 | // Reads N bytes from given blocking socket. | |
147 | ||
148 | string read_n(int socket, ssize_t n) | |
149 | { | |
150 | char * buffer = new char[n + 1]; | |
151 | buffer[n] = '\0'; | |
152 | ||
153 | char * bp = buffer; | |
154 | ssize_t bytes_read = 0; | |
155 | ||
156 | while (bytes_read != n) | |
157 | { | |
158 | ssize_t bytes_received = 0; | |
159 | do bytes_received = recv(socket, bp, n - (bp - buffer), 0); | |
160 | while (bytes_received < 0 && errno == EINTR); | |
161 | ||
162 | if (bytes_received == 0) | |
163 | throw redis::connection_error("connection was closed"); | |
164 | ||
165 | bytes_read += bytes_received; | |
166 | bp += bytes_received; | |
167 | } | |
168 | ||
169 | string str(buffer); | |
170 | delete [] buffer; | |
171 | return str; | |
172 | } | |
173 | ||
174 | // Reads a single line of character data from the given blocking socket. | |
175 | // Returns the line that was read, not including EOL delimiter(s). Both LF | |
176 | // ('\n') and CRLF ("\r\n") delimiters are supported. If there was an I/O | |
177 | // error reading from the socket, connection_error is raised. If max_size | |
178 | // bytes are read before finding an EOL delimiter, a blank string is | |
179 | // returned. | |
180 | ||
181 | string read_line(int socket, ssize_t max_size = 2048) | |
182 | { | |
183 | assert(socket > 0); | |
184 | assert(max_size > 0); | |
185 | ||
186 | ostringstream oss; | |
187 | ||
188 | enum { buffer_size = 64 }; | |
189 | char buffer[buffer_size]; | |
190 | memset(buffer, 0, buffer_size); | |
191 | ||
192 | ssize_t total_bytes_read = 0; | |
193 | bool found_delimiter = false; | |
194 | ||
195 | while (total_bytes_read < max_size && !found_delimiter) | |
196 | { | |
197 | // Peek at what's available. | |
198 | ||
199 | ssize_t bytes_received = 0; | |
200 | do bytes_received = recv(socket, buffer, buffer_size, MSG_PEEK); | |
201 | while (bytes_received < 0 && errno == EINTR); | |
202 | ||
203 | if (bytes_received == 0) | |
204 | throw redis::connection_error("connection was closed"); | |
205 | ||
206 | // Some data is available; Length might be < buffer_size. | |
207 | // Look for newline in whatever was read though. | |
208 | ||
209 | char * eol = static_cast<char *>(memchr(buffer, '\n', bytes_received)); | |
210 | ||
211 | // If found, write data from the buffer to the output string. | |
212 | // Else, write the entire buffer and continue reading more data. | |
213 | ||
214 | ssize_t to_read = bytes_received; | |
215 | ||
216 | if (eol) | |
217 | { | |
218 | to_read = eol - buffer + 1; | |
219 | oss.write(buffer, to_read); | |
220 | found_delimiter = true; | |
221 | } | |
222 | else | |
223 | oss.write(buffer, bytes_received); | |
224 | ||
225 | // Now read from the socket to remove the peeked data from the socket's | |
226 | // read buffer. This will not block since we've peeked already and know | |
227 | // there's data waiting. It might fail if we were interrupted however. | |
228 | ||
229 | do bytes_received = recv(socket, buffer, to_read, 0); | |
230 | while (bytes_received < 0 && errno == EINTR); | |
231 | } | |
232 | ||
233 | // Construct final line string. Remove trailing CRLF-based whitespace. | |
234 | ||
235 | string line = oss.str(); | |
236 | return rtrim(line, CRLF); | |
237 | } | |
238 | ||
239 | unsigned long unsigned_number_from_string(const string & data) | |
240 | { | |
241 | errno = 0; | |
242 | ||
243 | unsigned long value = strtoul(data.c_str(), NULL, 10); | |
244 | ||
245 | if (value == ULONG_MAX && errno == ERANGE) | |
246 | throw redis::value_error("invalid number; out of range of long"); | |
247 | ||
248 | if (value == 0 && errno == EINVAL) | |
249 | throw redis::value_error("invalid number; unrecognized format"); | |
250 | ||
251 | return value; | |
252 | } | |
253 | ||
254 | redis::client::int_type number_from_string(const string & data) | |
255 | { | |
256 | errno = 0; | |
257 | ||
258 | redis::client::int_type value = strtol(data.c_str(), NULL, 10); | |
259 | ||
260 | if ((value == LONG_MAX || value == LONG_MIN) && errno == ERANGE) | |
261 | throw redis::value_error("invalid number; out of range of long"); | |
262 | ||
263 | if (value == 0 && errno == EINVAL) | |
264 | throw redis::value_error("invalid number; unrecognized format"); | |
265 | ||
266 | return value; | |
267 | } | |
268 | ||
269 | const string status_reply_ok("OK"); | |
270 | const string prefix_status_reply_error("-ERR "); | |
271 | const char prefix_status_reply_value = '+'; | |
272 | const char prefix_single_bulk_reply = '$'; | |
273 | const char prefix_multi_bulk_reply = '*'; | |
274 | const char prefix_int_reply = ':'; | |
275 | ||
276 | const string server_info_key_version = "redis_version"; | |
277 | const string server_info_key_bgsave_in_progress = "bgsave_in_progress"; | |
278 | const string server_info_key_connected_clients = "connected_clients"; | |
279 | const string server_info_key_connected_slaves = "connected_slaves"; | |
280 | const string server_info_key_used_memory = "used_memory"; | |
281 | const string server_info_key_changes_since_last_save = "changes_since_last_save"; | |
282 | const string server_info_key_last_save_time = "last_save_time"; | |
283 | const string server_info_key_total_connections_received = "total_connections_received"; | |
284 | const string server_info_key_total_commands_processed = "total_commands_processed"; | |
285 | const string server_info_key_uptime_in_seconds = "uptime_in_seconds"; | |
286 | const string server_info_key_uptime_in_days = "uptime_in_days"; | |
287 | } | |
288 | ||
289 | namespace redis | |
290 | { | |
291 | redis_error::redis_error(const string & err) : err_(err) | |
292 | { | |
293 | } | |
294 | ||
295 | redis_error::operator std::string () | |
296 | { | |
297 | return err_; | |
298 | } | |
299 | ||
300 | redis_error::operator const std::string () const | |
301 | { | |
302 | return err_; | |
303 | } | |
304 | ||
305 | connection_error::connection_error(const string & err) : redis_error(err) | |
306 | { | |
307 | } | |
308 | ||
309 | protocol_error::protocol_error(const string & err) : redis_error(err) | |
310 | { | |
311 | } | |
312 | ||
313 | key_error::key_error(const string & err) : redis_error(err) | |
314 | { | |
315 | } | |
316 | ||
317 | value_error::value_error(const string & err) : redis_error(err) | |
318 | { | |
319 | } | |
320 | ||
321 | client::string_type client::missing_value("**nonexistent-key**"); | |
322 | ||
323 | client::client(const string_type & host, unsigned int port) | |
324 | { | |
325 | char err[ANET_ERR_LEN]; | |
326 | socket_ = anetTcpConnect(err, const_cast<char*>(host.c_str()), port); | |
327 | if (socket_ == ANET_ERR) | |
328 | throw connection_error(err); | |
329 | anetTcpNoDelay(NULL, socket_); | |
330 | } | |
331 | ||
332 | client::~client() | |
333 | { | |
334 | if (socket_ != ANET_ERR) | |
335 | close(socket_); | |
336 | } | |
337 | ||
338 | void client::auth(const client::string_type & pass) | |
339 | { | |
340 | send_(makecmd("AUTH") << pass); | |
341 | recv_ok_reply_(); | |
342 | } | |
343 | ||
344 | void client::set(const client::string_type & key, | |
345 | const client::string_type & value) | |
346 | { | |
347 | send_(makecmd("SET") << key << ' ' << value.size() << CRLF << value); | |
348 | recv_ok_reply_(); | |
349 | } | |
350 | ||
351 | client::string_type client::get(const client::string_type & key) | |
352 | { | |
353 | send_(makecmd("GET") << key); | |
354 | return recv_bulk_reply_(); | |
355 | } | |
356 | ||
357 | client::string_type client::getset(const client::string_type & key, | |
358 | const client::string_type & value) | |
359 | { | |
360 | send_(makecmd("GETSET") << key << ' ' << value.size() << CRLF << value); | |
361 | return recv_bulk_reply_(); | |
362 | } | |
363 | ||
364 | void client::mget(const client::string_vector & keys, string_vector & out) | |
365 | { | |
366 | send_(makecmd("MGET") << keys); | |
367 | recv_multi_bulk_reply_(out); | |
368 | } | |
369 | ||
370 | bool client::setnx(const client::string_type & key, | |
371 | const client::string_type & value) | |
372 | { | |
373 | send_(makecmd("SETNX") << key << ' ' << value.size() << CRLF << value); | |
374 | return recv_int_reply_() == 1; | |
375 | } | |
376 | ||
377 | client::int_type client::incr(const client::string_type & key) | |
378 | { | |
379 | send_(makecmd("INCR") << key); | |
380 | return recv_int_reply_(); | |
381 | } | |
382 | ||
383 | client::int_type client::incrby(const client::string_type & key, | |
384 | client::int_type by) | |
385 | { | |
386 | send_(makecmd("INCRBY") << key << ' ' << by); | |
387 | return recv_int_reply_(); | |
388 | } | |
389 | ||
390 | client::int_type client::decr(const client::string_type & key) | |
391 | { | |
392 | send_(makecmd("DECR") << key); | |
393 | return recv_int_reply_(); | |
394 | } | |
395 | ||
396 | client::int_type client::decrby(const client::string_type & key, | |
397 | client::int_type by) | |
398 | { | |
399 | send_(makecmd("DECRBY") << key << ' ' << by); | |
400 | return recv_int_reply_(); | |
401 | } | |
402 | ||
403 | bool client::exists(const client::string_type & key) | |
404 | { | |
405 | send_(makecmd("EXISTS") << key); | |
406 | return recv_int_reply_() == 1; | |
407 | } | |
408 | ||
409 | void client::del(const client::string_type & key) | |
410 | { | |
411 | send_(makecmd("DEL") << key); | |
412 | recv_int_ok_reply_(); | |
413 | } | |
414 | ||
415 | client::datatype client::type(const client::string_type & key) | |
416 | { | |
417 | send_(makecmd("TYPE") << key); | |
418 | string response = recv_single_line_reply_(); | |
419 | ||
420 | if (response == "none") return datatype_none; | |
421 | if (response == "string") return datatype_string; | |
422 | if (response == "list") return datatype_list; | |
423 | if (response == "set") return datatype_set; | |
424 | ||
425 | return datatype_none; | |
426 | } | |
427 | ||
428 | client::int_type client::keys(const client::string_type & pattern, | |
429 | client::string_vector & out) | |
430 | { | |
431 | send_(makecmd("KEYS") << pattern); | |
432 | string resp = recv_bulk_reply_(); | |
433 | return split(resp, ' ', out); | |
434 | } | |
435 | ||
436 | client::string_type client::randomkey() | |
437 | { | |
438 | send_(makecmd("RANDOMKEY", true)); | |
439 | return recv_single_line_reply_(); | |
440 | } | |
441 | ||
442 | void client::rename(const client::string_type & old_name, | |
443 | const client::string_type & new_name) | |
444 | { | |
445 | send_(makecmd("RENAME") << old_name << ' ' << new_name); | |
446 | recv_ok_reply_(); | |
447 | } | |
448 | ||
449 | bool client::renamenx(const client::string_type & old_name, | |
450 | const client::string_type & new_name) | |
451 | { | |
452 | send_(makecmd("RENAMENX") << old_name << ' ' << new_name); | |
453 | return recv_int_reply_() == 1; | |
454 | } | |
455 | ||
456 | client::int_type client::dbsize() | |
457 | { | |
458 | send_(makecmd("DBSIZE")); | |
459 | return recv_int_reply_(); | |
460 | } | |
461 | ||
462 | void client::expire(const string_type & key, unsigned int secs) | |
463 | { | |
464 | send_(makecmd("EXPIRE") << key << ' ' << secs); | |
465 | recv_int_ok_reply_(); | |
466 | } | |
467 | ||
468 | void client::rpush(const client::string_type & key, | |
469 | const client::string_type & value) | |
470 | { | |
471 | send_(makecmd("RPUSH") << key << ' ' << value.length() << CRLF << value); | |
472 | recv_ok_reply_(); | |
473 | } | |
474 | ||
475 | void client::lpush(const client::string_type & key, | |
476 | const client::string_type & value) | |
477 | { | |
478 | send_(makecmd("LPUSH") << key << ' ' << value.length() << CRLF << value); | |
479 | recv_ok_reply_(); | |
480 | } | |
481 | ||
482 | client::int_type client::llen(const client::string_type & key) | |
483 | { | |
484 | send_(makecmd("LLEN") << key); | |
485 | return recv_int_reply_(); | |
486 | } | |
487 | ||
488 | client::int_type client::lrange(const client::string_type & key, | |
489 | client::int_type start, | |
490 | client::int_type end, | |
491 | client::string_vector & out) | |
492 | { | |
493 | send_(makecmd("LRANGE") << key << ' ' << start << ' ' << end); | |
494 | return recv_multi_bulk_reply_(out); | |
495 | } | |
496 | ||
497 | void client::ltrim(const client::string_type & key, | |
498 | client::int_type start, | |
499 | client::int_type end) | |
500 | { | |
501 | send_(makecmd("LTRIM") << key << ' ' << start << ' ' << end); | |
502 | recv_ok_reply_(); | |
503 | } | |
504 | ||
505 | client::string_type client::lindex(const client::string_type & key, | |
506 | client::int_type index) | |
507 | { | |
508 | send_(makecmd("LINDEX") << key << ' ' << index); | |
509 | return recv_bulk_reply_(); | |
510 | } | |
511 | ||
512 | void client::lset(const client::string_type & key, | |
513 | client::int_type index, | |
514 | const client::string_type & value) | |
515 | { | |
516 | send_(makecmd("LSET") << key << ' ' << index << ' ' << value.length() << CRLF << value); | |
517 | recv_ok_reply_(); | |
518 | } | |
519 | ||
520 | client::int_type client::lrem(const client::string_type & key, | |
521 | client::int_type count, | |
522 | const client::string_type & value) | |
523 | { | |
524 | send_(makecmd("LREM") << key << ' ' << count << ' ' << value.length() << CRLF << value); | |
525 | return recv_int_reply_(); | |
526 | } | |
527 | ||
528 | client::string_type client::lpop(const client::string_type & key) | |
529 | { | |
530 | send_(makecmd("LPOP") << key); | |
531 | return recv_bulk_reply_(); | |
532 | } | |
533 | ||
534 | client::string_type client::rpop(const client::string_type & key) | |
535 | { | |
536 | send_(makecmd("RPOP") << key); | |
537 | return recv_bulk_reply_(); | |
538 | } | |
539 | ||
540 | void client::sadd(const client::string_type & key, | |
541 | const client::string_type & value) | |
542 | { | |
543 | send_(makecmd("SADD") << key << ' ' << value.length() << CRLF << value); | |
544 | recv_int_ok_reply_(); | |
545 | } | |
546 | ||
547 | void client::srem(const client::string_type & key, | |
548 | const client::string_type & value) | |
549 | { | |
550 | send_(makecmd("SREM") << key << ' ' << value.length() << CRLF << value); | |
551 | recv_int_ok_reply_(); | |
552 | } | |
553 | ||
554 | void client::smove(const client::string_type & srckey, | |
555 | const client::string_type & dstkey, | |
556 | const client::string_type & value) | |
557 | { | |
558 | send_(makecmd("SMOVE") << srckey << ' ' << dstkey << ' ' << value.length() << CRLF << value); | |
559 | recv_int_ok_reply_(); | |
560 | } | |
561 | ||
562 | client::int_type client::scard(const client::string_type & key) | |
563 | { | |
564 | send_(makecmd("SCARD") << key); | |
565 | return recv_int_reply_(); | |
566 | } | |
567 | ||
568 | bool client::sismember(const client::string_type & key, | |
569 | const client::string_type & value) | |
570 | { | |
571 | send_(makecmd("SISMEMBER") << key << ' ' << value.length() << CRLF << value); | |
572 | return recv_int_reply_() == 1; | |
573 | } | |
574 | ||
575 | client::int_type client::sinter(const client::string_vector & keys, client::string_set & out) | |
576 | { | |
577 | send_(makecmd("SINTER") << keys); | |
578 | return recv_multi_bulk_reply_(out); | |
579 | } | |
580 | ||
581 | void client::sinterstore(const client::string_type & dstkey, | |
582 | const client::string_vector & keys) | |
583 | { | |
584 | send_(makecmd("SINTERSTORE") << dstkey << ' ' << keys); | |
585 | recv_ok_reply_(); | |
586 | } | |
587 | ||
588 | client::int_type client::sunion(const client::string_vector & keys, | |
589 | client::string_set & out) | |
590 | { | |
591 | send_(makecmd("SUNION") << keys); | |
592 | return recv_multi_bulk_reply_(out); | |
593 | } | |
594 | ||
595 | void client::sunionstore(const client::string_type & dstkey, | |
596 | const client::string_vector & keys) | |
597 | { | |
598 | send_(makecmd("SUNIONSTORE") << dstkey << ' ' << keys); | |
599 | recv_ok_reply_(); | |
600 | } | |
601 | ||
602 | client::int_type client::smembers(const client::string_type & key, | |
603 | client::string_set & out) | |
604 | { | |
605 | send_(makecmd("SMEMBERS") << key); | |
606 | return recv_multi_bulk_reply_(out); | |
607 | } | |
608 | ||
609 | void client::select(client::int_type dbindex) | |
610 | { | |
611 | send_(makecmd("SELECT") << dbindex); | |
612 | recv_ok_reply_(); | |
613 | } | |
614 | ||
615 | void client::move(const client::string_type & key, | |
616 | client::int_type dbindex) | |
617 | { | |
618 | send_(makecmd("MOVE") << key << ' ' << dbindex); | |
619 | recv_int_ok_reply_(); | |
620 | } | |
621 | ||
622 | void client::flushdb() | |
623 | { | |
624 | send_(makecmd("FLUSHDB", true)); | |
625 | recv_ok_reply_(); | |
626 | } | |
627 | ||
628 | void client::flushall() | |
629 | { | |
630 | send_(makecmd("FLUSHALL", true)); | |
631 | recv_ok_reply_(); | |
632 | } | |
633 | ||
634 | client::int_type client::sort(const client::string_type & key, | |
635 | client::string_vector & out, | |
636 | client::sort_order order, | |
637 | bool lexicographically) | |
638 | { | |
639 | send_(makecmd("SORT") << key | |
640 | << (order == sort_order_ascending ? " ASC" : " DESC") | |
641 | << (lexicographically ? " ALPHA" : "")); | |
642 | ||
643 | return recv_multi_bulk_reply_(out); | |
644 | } | |
645 | ||
646 | client::int_type client::sort(const client::string_type & key, | |
647 | client::string_vector & out, | |
648 | client::int_type limit_start, | |
649 | client::int_type limit_end, | |
650 | client::sort_order order, | |
651 | bool lexicographically) | |
652 | { | |
653 | send_(makecmd("SORT") << key | |
654 | << " LIMIT " << limit_start << ' ' << limit_end | |
655 | << (order == sort_order_ascending ? " ASC" : " DESC") | |
656 | << (lexicographically ? " ALPHA" : "")); | |
657 | ||
658 | return recv_multi_bulk_reply_(out); | |
659 | } | |
660 | ||
661 | client::int_type client::sort(const client::string_type & key, | |
662 | client::string_vector & out, | |
663 | const client::string_type & by_pattern, | |
664 | client::int_type limit_start, | |
665 | client::int_type limit_end, | |
666 | const client::string_type & get_pattern, | |
667 | client::sort_order order, | |
668 | bool lexicographically) | |
669 | { | |
670 | send_(makecmd("SORT") << key | |
671 | << " BY " << by_pattern | |
672 | << " LIMIT " << limit_start << ' ' << limit_end | |
673 | << " GET " << get_pattern | |
674 | << (order == sort_order_ascending ? " ASC" : " DESC") | |
675 | << (lexicographically ? " ALPHA" : "")); | |
676 | ||
677 | return recv_multi_bulk_reply_(out); | |
678 | } | |
679 | ||
680 | void client::save() | |
681 | { | |
682 | send_(makecmd("SAVE", true)); | |
683 | recv_ok_reply_(); | |
684 | e.g. } | |
685 | ||
686 | void client::bgsave() | |
687 | { | |
688 | send_(makecmd("BGSAVE", true)); | |
689 | recv_ok_reply_(); | |
690 | } | |
691 | ||
692 | time_t client::lastsave() | |
693 | { | |
694 | send_(makecmd("LASTSAVE", true)); | |
695 | return recv_int_reply_(); | |
696 | } | |
697 | ||
698 | void client::shutdown() | |
699 | { | |
700 | send_(makecmd("SHUTDOWN", true)); | |
701 | ||
702 | // we expected to get a connection_error as redis closes the connection on shutdown command. | |
703 | ||
704 | try | |
705 | { | |
706 | recv_ok_reply_(); | |
707 | } | |
708 | catch (connection_error & e) | |
709 | { | |
710 | } | |
711 | } | |
712 | ||
713 | void client::info(server_info & out) | |
714 | { | |
715 | send_(makecmd("INFO", true)); | |
716 | string response = recv_bulk_reply_(); | |
717 | ||
718 | if (response.empty()) | |
719 | throw protocol_error("empty"); | |
720 | ||
721 | string_vector lines; | |
722 | split_lines(response, lines); | |
723 | if (lines.empty()) | |
724 | throw protocol_error("empty line for info"); | |
725 | ||
726 | for (string_vector::const_iterator it = lines.begin(); | |
727 | it != lines.end(); ++it) | |
728 | { | |
729 | const string & line = *it; | |
730 | string_vector line_parts; | |
731 | split(line, ':', line_parts); | |
732 | if (line_parts.size() != 2) | |
733 | throw protocol_error("unexpected line format for info"); | |
734 | ||
735 | const string & key = line_parts[0]; | |
736 | const string & val = line_parts[1]; | |
737 | ||
738 | if (key == server_info_key_version) | |
739 | out.version = val; | |
740 | else if (key == server_info_key_bgsave_in_progress) | |
741 | out.bgsave_in_progress = unsigned_number_from_string(val) == 1; | |
742 | else if (key == server_info_key_connected_clients) | |
743 | out.connected_clients = unsigned_number_from_string(val); | |
744 | else if (key == server_info_key_connected_slaves) | |
745 | out.connected_slaves = unsigned_number_from_string(val); | |
746 | else if (key == server_info_key_used_memory) | |
747 | out.used_memory = unsigned_number_from_string(val); | |
748 | else if (key == server_info_key_changes_since_last_save) | |
749 | out.changes_since_last_save = unsigned_number_from_string(val); | |
750 | else if (key == server_info_key_last_save_time) | |
751 | out.last_save_time = unsigned_number_from_string(val); | |
752 | else if (key == server_info_key_total_connections_received) | |
753 | out.total_connections_received = unsigned_number_from_string(val); | |
754 | else if (key == server_info_key_total_commands_processed) | |
755 | out.total_commands_processed = unsigned_number_from_string(val); | |
756 | else if (key == server_info_key_uptime_in_seconds) | |
757 | out.uptime_in_seconds = unsigned_number_from_string(val); | |
758 | else if (key == server_info_key_uptime_in_days) | |
759 | out.uptime_in_days = unsigned_number_from_string(val); | |
760 | else | |
761 | throw protocol_error(string("unexpected info key '") + key + "'"); | |
762 | } | |
763 | } | |
764 | ||
765 | // | |
766 | // Private methods | |
767 | // | |
768 | ||
769 | void client::send_(const string & msg) | |
770 | { | |
771 | #ifndef NDEBUG | |
772 | output_proto_debug(msg, false); | |
773 | #endif | |
774 | ||
775 | if (anetWrite(socket_, const_cast<char *>(msg.data()), msg.size()) == -1) | |
776 | throw connection_error(strerror(errno)); | |
777 | } | |
778 | ||
779 | string client::recv_single_line_reply_() | |
780 | { | |
781 | string line = read_line(socket_); | |
782 | ||
783 | #ifndef NDEBUG | |
784 | output_proto_debug(line); | |
785 | #endif | |
786 | ||
787 | if (line.empty()) | |
788 | throw protocol_error("empty single line reply"); | |
789 | ||
790 | if (line.find(prefix_status_reply_error) == 0) | |
791 | { | |
792 | string error_msg = line.substr(prefix_status_reply_error.length()); | |
793 | if (error_msg.empty()) | |
794 | error_msg = "unknown error"; | |
795 | throw protocol_error(error_msg); | |
796 | } | |
797 | ||
798 | if (line[0] != prefix_status_reply_value) | |
799 | throw protocol_error("unexpected prefix for status reply"); | |
800 | ||
801 | return line.substr(1); | |
802 | } | |
803 | ||
804 | void client::recv_ok_reply_() | |
805 | { | |
806 | if (recv_single_line_reply_() != status_reply_ok) | |
807 | throw protocol_error("expected OK response"); | |
808 | } | |
809 | ||
810 | client::int_type client::recv_bulk_reply_(char prefix) | |
811 | { | |
812 | string line = read_line(socket_); | |
813 | ||
814 | #ifndef NDEBUG | |
815 | output_proto_debug(line); | |
816 | #endif | |
817 | ||
818 | if (line[0] != prefix) | |
819 | throw protocol_error("unexpected prefix for bulk reply"); | |
820 | ||
821 | return number_from_string(line.substr(1)); | |
822 | } | |
823 | ||
824 | string client::recv_bulk_reply_() | |
825 | { | |
826 | int_type length = recv_bulk_reply_(prefix_single_bulk_reply); | |
827 | ||
828 | if (length == -1) | |
829 | return client::missing_value; | |
830 | ||
831 | int_type real_length = length + 2; // CRLF | |
832 | ||
833 | string data = read_n(socket_, real_length); | |
834 | ||
835 | #ifndef NDEBUG | |
836 | output_proto_debug(data.substr(0, data.length()-2)); | |
837 | #endif | |
838 | ||
839 | if (data.empty()) | |
840 | throw protocol_error("invalid bulk reply data; empty"); | |
841 | ||
842 | if (data.length() != static_cast<string::size_type>(real_length)) | |
843 | throw protocol_error("invalid bulk reply data; data of unexpected length"); | |
844 | ||
845 | data.erase(data.size() - 2); | |
846 | ||
847 | return data; | |
848 | } | |
849 | ||
850 | client::int_type client::recv_multi_bulk_reply_(string_vector & out) | |
851 | { | |
852 | int_type length = recv_bulk_reply_(prefix_multi_bulk_reply); | |
853 | ||
854 | if (length == -1) | |
855 | throw key_error("no such key"); | |
856 | ||
857 | for (int_type i = 0; i < length; ++i) | |
858 | out.push_back(recv_bulk_reply_()); | |
859 | ||
860 | return length; | |
861 | } | |
862 | ||
863 | client::int_type client::recv_multi_bulk_reply_(string_set & out) | |
864 | { | |
865 | int_type length = recv_bulk_reply_(prefix_multi_bulk_reply); | |
866 | ||
867 | if (length == -1) | |
868 | throw key_error("no such key"); | |
869 | ||
870 | for (int_type i = 0; i < length; ++i) | |
871 | out.insert(recv_bulk_reply_()); | |
872 | ||
873 | return length; | |
874 | } | |
875 | ||
876 | client::int_type client::recv_int_reply_() | |
877 | { | |
878 | string line = read_line(socket_); | |
879 | ||
880 | #ifndef NDEBUG | |
881 | output_proto_debug(line); | |
882 | #endif | |
883 | ||
884 | if (line.empty()) | |
885 | throw protocol_error("invalid integer reply; empty"); | |
886 | ||
887 | if (line[0] != prefix_int_reply) | |
888 | throw protocol_error("unexpected prefix for integer reply"); | |
889 | ||
890 | return number_from_string(line.substr(1)); | |
891 | } | |
892 | ||
893 | void client::recv_int_ok_reply_() | |
894 | { | |
895 | if (recv_int_reply_() != 1) | |
896 | throw protocol_error("expecting int reply of 1"); | |
897 | } | |
898 | } |