/formats/json.c

http://github.com/nicolasff/webdis · C · 293 lines · 216 code · 58 blank · 19 comment · 37 complexity · 37c396adc72bf8715c8912ad45b9a9d3 MD5 · raw file

  1. #include "json.h"
  2. #include "common.h"
  3. #include "cmd.h"
  4. #include "http.h"
  5. #include "client.h"
  6. #include <string.h>
  7. #include <hiredis/hiredis.h>
  8. #include <hiredis/async.h>
  9. static json_t *
  10. json_wrap_redis_reply(const struct cmd *cmd, const redisReply *r);
  11. void
  12. json_reply(redisAsyncContext *c, void *r, void *privdata) {
  13. redisReply *reply = r;
  14. struct cmd *cmd = privdata;
  15. json_t *j;
  16. char *jstr;
  17. (void)c;
  18. if(cmd == NULL) {
  19. /* broken connection */
  20. return;
  21. }
  22. if (reply == NULL) { /* broken Redis link */
  23. format_send_error(cmd, 503, "Service Unavailable");
  24. return;
  25. }
  26. /* encode redis reply as JSON */
  27. j = json_wrap_redis_reply(cmd, r);
  28. /* get JSON as string, possibly with JSONP wrapper */
  29. jstr = json_string_output(j, cmd->jsonp);
  30. /* send reply */
  31. format_send_reply(cmd, jstr, strlen(jstr), "application/json");
  32. /* cleanup */
  33. json_decref(j);
  34. free(jstr);
  35. }
  36. /**
  37. * Parse info message and return object.
  38. */
  39. static json_t *
  40. json_info_reply(const char *s) {
  41. const char *p = s;
  42. size_t sz = strlen(s);
  43. json_t *jroot = json_object();
  44. /* TODO: handle new format */
  45. while(p < s + sz) {
  46. char *key, *val, *nl, *colon;
  47. /* find key */
  48. colon = strchr(p, ':');
  49. if(!colon) {
  50. break;
  51. }
  52. key = calloc(colon - p + 1, 1);
  53. memcpy(key, p, colon - p);
  54. p = colon + 1;
  55. /* find value */
  56. nl = strchr(p, '\r');
  57. if(!nl) {
  58. free(key);
  59. break;
  60. }
  61. val = calloc(nl - p + 1, 1);
  62. memcpy(val, p, nl - p);
  63. p = nl + 1;
  64. if(*p == '\n') p++;
  65. /* add to object */
  66. json_object_set_new(jroot, key, json_string(val));
  67. free(key);
  68. free(val);
  69. }
  70. return jroot;
  71. }
  72. static json_t *
  73. json_hgetall_reply(const redisReply *r) {
  74. /* zip keys and values together in a json object */
  75. json_t *jroot;
  76. unsigned int i;
  77. if(r->elements % 2 != 0) {
  78. return NULL;
  79. }
  80. jroot = json_object();
  81. for(i = 0; i < r->elements; i += 2) {
  82. redisReply *k = r->element[i], *v = r->element[i+1];
  83. /* keys and values need to be strings */
  84. if(k->type != REDIS_REPLY_STRING || v->type != REDIS_REPLY_STRING) {
  85. json_decref(jroot);
  86. return NULL;
  87. }
  88. json_object_set_new(jroot, k->str, json_string(v->str));
  89. }
  90. return jroot;
  91. }
  92. static json_t *
  93. json_wrap_redis_reply(const struct cmd *cmd, const redisReply *r) {
  94. unsigned int i;
  95. json_t *jlist, *jroot = json_object(); /* that's what we return */
  96. /* copy verb, as jansson only takes a char* but not its length. */
  97. char *verb;
  98. if(cmd->count) {
  99. verb = calloc(cmd->argv_len[0]+1, 1);
  100. memcpy(verb, cmd->argv[0], cmd->argv_len[0]);
  101. } else {
  102. verb = strdup("");
  103. }
  104. switch(r->type) {
  105. case REDIS_REPLY_STATUS:
  106. case REDIS_REPLY_ERROR:
  107. jlist = json_array();
  108. json_array_append_new(jlist,
  109. r->type == REDIS_REPLY_ERROR ? json_false() : json_true());
  110. json_array_append_new(jlist, json_string(r->str));
  111. json_object_set_new(jroot, verb, jlist);
  112. break;
  113. case REDIS_REPLY_STRING:
  114. if(strcasecmp(verb, "INFO") == 0) {
  115. json_object_set_new(jroot, verb, json_info_reply(r->str));
  116. } else {
  117. json_object_set_new(jroot, verb, json_string(r->str));
  118. }
  119. break;
  120. case REDIS_REPLY_INTEGER:
  121. json_object_set_new(jroot, verb, json_integer(r->integer));
  122. break;
  123. case REDIS_REPLY_ARRAY:
  124. if(strcasecmp(verb, "HGETALL") == 0) {
  125. json_t *jobj = json_hgetall_reply(r);
  126. if(jobj) {
  127. json_object_set_new(jroot, verb, jobj);
  128. break;
  129. }
  130. }
  131. jlist = json_array();
  132. for(i = 0; i < r->elements; ++i) {
  133. redisReply *e = r->element[i];
  134. switch(e->type) {
  135. case REDIS_REPLY_STRING:
  136. json_array_append_new(jlist, json_string(e->str));
  137. break;
  138. case REDIS_REPLY_INTEGER:
  139. json_array_append_new(jlist, json_integer(e->integer));
  140. break;
  141. default:
  142. json_array_append_new(jlist, json_null());
  143. break;
  144. }
  145. }
  146. json_object_set_new(jroot, verb, jlist);
  147. break;
  148. default:
  149. json_object_set_new(jroot, verb, json_null());
  150. break;
  151. }
  152. free(verb);
  153. return jroot;
  154. }
  155. char *
  156. json_string_output(json_t *j, const char *jsonp) {
  157. char *json_reply = json_dumps(j, JSON_COMPACT);
  158. /* check for JSONP */
  159. if(jsonp) {
  160. size_t jsonp_len = strlen(jsonp);
  161. size_t json_len = strlen(json_reply);
  162. size_t ret_len = jsonp_len + 1 + json_len + 3;
  163. char *ret = calloc(1 + ret_len, 1);
  164. memcpy(ret, jsonp, jsonp_len);
  165. ret[jsonp_len]='(';
  166. memcpy(ret + jsonp_len + 1, json_reply, json_len);
  167. memcpy(ret + jsonp_len + 1 + json_len, ");\n", 3);
  168. free(json_reply);
  169. return ret;
  170. }
  171. return json_reply;
  172. }
  173. /* extract JSON from WebSocket frame and fill struct cmd. */
  174. struct cmd *
  175. json_ws_extract(struct http_client *c, const char *p, size_t sz) {
  176. struct cmd *cmd = NULL;
  177. json_t *j;
  178. char *jsonz; /* null-terminated */
  179. unsigned int i, cur;
  180. int argc = 0;
  181. json_error_t jerror;
  182. (void)c;
  183. jsonz = calloc(sz + 1, 1);
  184. memcpy(jsonz, p, sz);
  185. j = json_loads(jsonz, sz, &jerror);
  186. free(jsonz);
  187. if(!j) {
  188. return NULL;
  189. }
  190. if(json_typeof(j) != JSON_ARRAY) {
  191. json_decref(j);
  192. return NULL; /* invalid JSON */
  193. }
  194. /* count elements */
  195. for(i = 0; i < json_array_size(j); ++i) {
  196. json_t *jelem = json_array_get(j, i);
  197. switch(json_typeof(jelem)) {
  198. case JSON_STRING:
  199. case JSON_INTEGER:
  200. argc++;
  201. break;
  202. default:
  203. break;
  204. }
  205. }
  206. if(!argc) { /* not a single item could be decoded */
  207. json_decref(j);
  208. return NULL;
  209. }
  210. /* create command and add args */
  211. cmd = cmd_new(argc);
  212. for(i = 0, cur = 0; i < json_array_size(j); ++i) {
  213. json_t *jelem = json_array_get(j, i);
  214. char *tmp;
  215. switch(json_typeof(jelem)) {
  216. case JSON_STRING:
  217. tmp = strdup(json_string_value(jelem));
  218. cmd->argv[cur] = tmp;
  219. cmd->argv_len[cur] = strlen(tmp);
  220. cur++;
  221. break;
  222. case JSON_INTEGER:
  223. tmp = malloc(40);
  224. sprintf(tmp, "%d", (int)json_integer_value(jelem));
  225. cmd->argv[cur] = tmp;
  226. cmd->argv_len[cur] = strlen(tmp);
  227. cur++;
  228. break;
  229. default:
  230. break;
  231. }
  232. }
  233. json_decref(j);
  234. return cmd;
  235. }