gdp / apps / gdp-rest.c @ master
History | View | Annotate | Download (34.5 KB)
1 |
/* vim: set ai sw=4 sts=4 ts=4 :*/
|
---|---|
2 |
/*
|
3 |
** RESTful interface to GDP
|
4 |
**
|
5 |
** Uses SCGI between the web server and this process. We link
|
6 |
** with Sam Alexander's SCGI C Library, http://www.xamuel.com/scgilib/
|
7 |
**
|
8 |
** ----- BEGIN LICENSE BLOCK -----
|
9 |
** Applications for the Global Data Plane
|
10 |
** From the Ubiquitous Swarm Lab, 490 Cory Hall, U.C. Berkeley.
|
11 |
**
|
12 |
** Copyright (c) 2015-2019, Regents of the University of California.
|
13 |
** All rights reserved.
|
14 |
**
|
15 |
** Permission is hereby granted, without written agreement and without
|
16 |
** license or royalty fees, to use, copy, modify, and distribute this
|
17 |
** software and its documentation for any purpose, provided that the above
|
18 |
** copyright notice and the following two paragraphs appear in all copies
|
19 |
** of this software.
|
20 |
**
|
21 |
** IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
|
22 |
** SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST
|
23 |
** PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
|
24 |
** EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
25 |
**
|
26 |
** REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
|
27 |
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
28 |
** FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION,
|
29 |
** IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO
|
30 |
** OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
|
31 |
** OR MODIFICATIONS.
|
32 |
** ----- END LICENSE BLOCK -----
|
33 |
*/
|
34 |
|
35 |
#include <ep/ep.h> |
36 |
#include <ep/ep_app.h> |
37 |
#include <ep/ep_hash.h> |
38 |
#include <ep/ep_dbg.h> |
39 |
#include <ep/ep_pcvt.h> |
40 |
#include <ep/ep_sd.h> |
41 |
#include <ep/ep_stat.h> |
42 |
#include <ep/ep_xlate.h> |
43 |
#include <gdp/gdp.h> |
44 |
#include <pthread.h> |
45 |
#include <unistd.h> |
46 |
#include <stdio.h> |
47 |
#include <sysexits.h> |
48 |
#include <string.h> |
49 |
#include <ctype.h> |
50 |
#include <errno.h> |
51 |
#include <getopt.h> |
52 |
#include <sys/socket.h> |
53 |
#include <scgilib/scgilib.h> |
54 |
#include <event2/event.h> |
55 |
#include <jansson.h> |
56 |
#include <sys/types.h> |
57 |
#include <sys/wait.h> |
58 |
|
59 |
static EP_DBG Dbg = EP_DBG_INIT("gdp.rest", "RESTful interface to GDP"); |
60 |
|
61 |
#define DEF_URI_PREFIX "/gdp/v1" |
62 |
|
63 |
const char *GclUriPrefix; // prefix on all REST calls |
64 |
EP_HASH *OpenGclCache; // cache of open GCLs
|
65 |
|
66 |
gdp_open_info_t *shared_gdp_open_info; |
67 |
|
68 |
// most gdp-create parameters are available through gdp-rest
|
69 |
#define GCL_C_PARAM_SERV_e 0 |
70 |
#define GCL_C_PARAM_SERV_S 1 |
71 |
#define GCL_C_PARAM_SERV_K 2 |
72 |
#define GCL_C_PARAM_SERV_MAX 3 |
73 |
#define GCL_C_PARAM_MAX (GCL_C_PARAM_SERV_MAX + 6) |
74 |
|
75 |
const char *exec_gdp_create_param[GCL_C_PARAM_MAX] = |
76 |
{ |
77 |
/* 3 server controlled options */
|
78 |
"-e", "-S", "-K", |
79 |
/* 6 client options */
|
80 |
"-C", "-D", "-h", "-k", "-b", "-c" |
81 |
/* remaining unsupported options: "-G", "-q", "-s", "-w", "-W" */
|
82 |
}; |
83 |
const char *exec_gdp_create = "/usr/bin/gdp-create"; |
84 |
|
85 |
// WARNING: gdp-rest parses gdp-create text output (which will be kept stable!)
|
86 |
#define GCL_C_OUT_GCL_NAME 16 |
87 |
#define GCL_C_OUT_GCL_NAME_END 59 |
88 |
#define GCL_C_OUT_LOGD_NAME 75 |
89 |
#define GCL_C_OUT_BUF_SIZE 256 // likely output * 2, rounded up |
90 |
// gdp-create text output expectation (which will be kept stable!)
|
91 |
const char *gdp_c_min_out = |
92 |
"Created new GCL 0123456789012345678901234567890123456789012\n" //60 |
93 |
"\ton log server N\n"; // N is a variable length > 0 log server name |
94 |
|
95 |
/*
|
96 |
** LOG_ERROR --- generic error logging routine
|
97 |
*/
|
98 |
|
99 |
void
|
100 |
log_error(const char *fmt, ...) |
101 |
{ |
102 |
va_list av; |
103 |
|
104 |
va_start(av, fmt); |
105 |
vfprintf(stderr, fmt, av); |
106 |
fprintf(stderr, ": %s\n", strerror(errno));
|
107 |
} |
108 |
|
109 |
/*
|
110 |
** SCGI_METHOD_NAME --- return printable name of method
|
111 |
**
|
112 |
** Arguably this should be in SCGILIB.
|
113 |
*/
|
114 |
|
115 |
const char * |
116 |
scgi_method_name(types_of_methods_for_http_protocol meth) |
117 |
{ |
118 |
switch (meth)
|
119 |
{ |
120 |
case SCGI_METHOD_UNSPECIFIED:
|
121 |
return "unspecified"; |
122 |
case SCGI_METHOD_UNKNOWN:
|
123 |
return "unknown"; |
124 |
case SCGI_METHOD_GET:
|
125 |
return "GET"; |
126 |
case SCGI_METHOD_POST:
|
127 |
return "POST"; |
128 |
case SCGI_METHOD_PUT:
|
129 |
return "PUT"; |
130 |
case SCGI_METHOD_DELETE:
|
131 |
return "DELETE"; |
132 |
case SCGI_METHOD_HEAD:
|
133 |
return "HEAD"; |
134 |
case SCGI_METHOD_OPTIONS:
|
135 |
return "OPTIONS"; |
136 |
} |
137 |
return "impossible"; |
138 |
} |
139 |
|
140 |
|
141 |
void
|
142 |
write_scgi(scgi_request *req, |
143 |
char *sbuf)
|
144 |
{ |
145 |
int dead = 0; |
146 |
int i;
|
147 |
char xbuf[1024]; |
148 |
char *xbase = xbuf;
|
149 |
char *xp;
|
150 |
char *sp;
|
151 |
|
152 |
// first translate any lone newlines into crlfs (pity jansson won't do this)
|
153 |
for (i = 0, sp = sbuf; (sp = strchr(sp, '\n')) != NULL; sp++) |
154 |
if (sp == sbuf || sp[-1] != '\r') |
155 |
i++; |
156 |
|
157 |
// i is now the count of newlines without carriage returns
|
158 |
sp = sbuf; |
159 |
if (i > 0) |
160 |
{ |
161 |
bool cr = false; |
162 |
|
163 |
// find the total number of bytes we need, using malloc if necessary
|
164 |
i += strlen(sbuf) + 1;
|
165 |
if (i > sizeof xbuf) |
166 |
xbase = ep_mem_malloc(i); |
167 |
xp = xbase; |
168 |
|
169 |
// xp now points to a large enough buffer, possibly malloced
|
170 |
while (*sp != '\0') |
171 |
{ |
172 |
if (*sp == '\n' && !cr) |
173 |
*xp++ = '\r';
|
174 |
cr = *sp == '\r';
|
175 |
*xp++ = *sp++; |
176 |
} |
177 |
*xp = '\0';
|
178 |
|
179 |
sp = xbase; |
180 |
} |
181 |
|
182 |
|
183 |
// I don't quite understand what "dead" is all about. It's copied
|
184 |
// from the "helloworld" example. Something about memory management,
|
185 |
// but his example only seems to use it to print messages.
|
186 |
req->dead = &dead; |
187 |
i = scgi_write(req, sp); |
188 |
|
189 |
// free buffer memory if necessary
|
190 |
if (xbase != xbuf)
|
191 |
ep_mem_free(xbase); |
192 |
|
193 |
ep_dbg_cprintf(Dbg, 10, "scgi_write => %d, dead = %d\n", i, dead); |
194 |
if (i == 0) |
195 |
{ |
196 |
char obuf[40]; |
197 |
char nbuf[40]; |
198 |
|
199 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
200 |
ep_app_error("scgi_write (%s) failed: %s",
|
201 |
ep_pcvt_str(obuf, sizeof obuf, sbuf), nbuf);
|
202 |
} |
203 |
else if (dead) |
204 |
{ |
205 |
ep_dbg_cprintf(Dbg, 1, "dead is set\n"); |
206 |
} |
207 |
req->dead = NULL;
|
208 |
} |
209 |
|
210 |
|
211 |
EP_STAT |
212 |
gdp_failure(scgi_request *req, char *code, char *msg, char *fmt, ...) |
213 |
{ |
214 |
char buf[SCGI_MAX_OUTBUF_SIZE];
|
215 |
va_list av; |
216 |
char c;
|
217 |
json_t *j; |
218 |
char *jbuf;
|
219 |
|
220 |
// set up the JSON object
|
221 |
j = json_object(); |
222 |
// set_new steals new json_string (or auto-decrefs if !j || !key)
|
223 |
json_object_set_new_nocheck(j, "error", json_string(msg));
|
224 |
json_object_set_new_nocheck(j, "code", json_string(code));
|
225 |
json_object_set_new_nocheck(j, "uri", json_string(req->request_uri));
|
226 |
json_object_set_new_nocheck(j, "method",
|
227 |
json_string(scgi_method_name(req->request_method))); |
228 |
|
229 |
va_start(av, fmt); |
230 |
while ((c = *fmt++) != '\0') |
231 |
{ |
232 |
char *key = va_arg(av, char *); |
233 |
|
234 |
switch (c)
|
235 |
{ |
236 |
case 's': |
237 |
// set_new steals new json_string (or auto-decrefs if !j || !key)
|
238 |
json_object_set_new(j, key, json_string(va_arg(av, char *)));
|
239 |
break;
|
240 |
|
241 |
case 'd': |
242 |
// set_new steals new json_integer (or auto-decrefs if !j || !key)
|
243 |
json_object_set_new(j, key, json_integer((long) va_arg(av, int))); |
244 |
break;
|
245 |
|
246 |
default:
|
247 |
{ |
248 |
char pbuf[40]; |
249 |
|
250 |
snprintf(pbuf, sizeof pbuf, "Unknown format `%c'", c); |
251 |
// set_new steals new json_string (auto-decrefs if !j || !key)
|
252 |
json_object_set_new(j, key, json_string(pbuf)); |
253 |
} |
254 |
break;
|
255 |
} |
256 |
} |
257 |
va_end(av); |
258 |
|
259 |
// get it (malloc'ed) in string format (will return NULL if !j)
|
260 |
jbuf = json_dumps(j, JSON_INDENT(4));
|
261 |
|
262 |
// create the entire SCGI return message
|
263 |
snprintf(buf, sizeof buf,
|
264 |
"HTTP/1.1 %s %s\r\n"
|
265 |
"Content-Type: application/json\r\n"
|
266 |
"\r\n"
|
267 |
"%s\r\n",
|
268 |
code, msg, jbuf ? jbuf : "");
|
269 |
write_scgi(req, buf); |
270 |
|
271 |
// clean up
|
272 |
json_decref(j); |
273 |
free(jbuf); |
274 |
|
275 |
// should chose something more appropriate here
|
276 |
return EP_STAT_ERROR;
|
277 |
} |
278 |
|
279 |
|
280 |
/*
|
281 |
** GDP_SCGI_RECV --- guarded version of scgi_recv().
|
282 |
**
|
283 |
** The SCGI library isn't reentrant, so we have to avoid conflict
|
284 |
** here.
|
285 |
*/
|
286 |
|
287 |
EP_THR_MUTEX ScgiRecvMutex EP_THR_MUTEX_INITIALIZER; |
288 |
|
289 |
scgi_request * |
290 |
gdp_scgi_recv(void)
|
291 |
{ |
292 |
scgi_request *req; |
293 |
|
294 |
ep_thr_mutex_lock(&ScgiRecvMutex); |
295 |
req = scgi_recv(); |
296 |
ep_thr_mutex_unlock(&ScgiRecvMutex); |
297 |
return req;
|
298 |
} |
299 |
|
300 |
|
301 |
bool
|
302 |
is_integer_string(const char *s) |
303 |
{ |
304 |
do
|
305 |
{ |
306 |
if (!isdigit(*s))
|
307 |
return false; |
308 |
} while (*++s != '\0'); |
309 |
return true; |
310 |
} |
311 |
|
312 |
|
313 |
/*
|
314 |
** PARSE_QUERY --- break up the query into key/value pairs
|
315 |
*/
|
316 |
|
317 |
struct qkvpair
|
318 |
{ |
319 |
char *key;
|
320 |
char *val;
|
321 |
}; |
322 |
|
323 |
int
|
324 |
parse_query(char *qtext, struct qkvpair *qkvs, int nqkvs) |
325 |
{ |
326 |
int n = 0; |
327 |
|
328 |
for (n = 0; qtext != NULL && n < nqkvs; n++) |
329 |
{ |
330 |
char *p;
|
331 |
|
332 |
if (*qtext == '\0') |
333 |
break;
|
334 |
|
335 |
// skip to the next kv pair separator
|
336 |
qkvs[n].key = strsep(&qtext, "&;");
|
337 |
|
338 |
// separate the key and the value (if any)
|
339 |
p = strchr(qkvs[n].key, '=');
|
340 |
if (p != NULL && *p != '\0') |
341 |
*p++ = '\0';
|
342 |
qkvs[n].val = p; |
343 |
} |
344 |
|
345 |
return n;
|
346 |
} |
347 |
|
348 |
|
349 |
/*
|
350 |
** FIND_QUERY_KV --- find a key-value pair in query
|
351 |
*/
|
352 |
|
353 |
char *
|
354 |
find_query_kv(const char *key, struct qkvpair *qkvs) |
355 |
{ |
356 |
while (qkvs->key != NULL) |
357 |
{ |
358 |
if (strcasecmp(key, qkvs->key) == 0) |
359 |
return qkvs->val;
|
360 |
qkvs++; |
361 |
} |
362 |
return NULL; |
363 |
} |
364 |
|
365 |
/*
|
366 |
** CORS200 --- issue an OPTIONS response supportive of javascript CORS needs
|
367 |
*/
|
368 |
EP_STAT |
369 |
cors200(scgi_request *req) |
370 |
{ |
371 |
char buf[SCGI_MAX_OUTBUF_SIZE];
|
372 |
snprintf(buf, sizeof buf,
|
373 |
"HTTP/1.1 200 OK\r\n"
|
374 |
"Content-Type: text/plain\r\n"
|
375 |
"\r\n"
|
376 |
"\r\n");
|
377 |
write_scgi(req, buf); |
378 |
return EP_STAT_OK;
|
379 |
} |
380 |
|
381 |
/*
|
382 |
** ERROR400 --- issue a 400 "Bad Request" error
|
383 |
*/
|
384 |
|
385 |
EP_STAT |
386 |
error400(scgi_request *req, const char *detail) |
387 |
{ |
388 |
return gdp_failure(req, "400", "Bad Request", "s", "detail", detail); |
389 |
} |
390 |
|
391 |
/*
|
392 |
** ERROR404 --- issue a 404 "Not Found" error
|
393 |
*/
|
394 |
|
395 |
EP_STAT |
396 |
error404(scgi_request *req, const char *detail) |
397 |
{ |
398 |
return gdp_failure(req, "404", "Not Found", "s", |
399 |
"detail", detail);
|
400 |
} |
401 |
|
402 |
|
403 |
/*
|
404 |
** ERROR405 --- issue a 405 "Method Not Allowed" error
|
405 |
*/
|
406 |
|
407 |
EP_STAT |
408 |
error405(scgi_request *req, const char *detail) |
409 |
{ |
410 |
return gdp_failure(req, "405", "Method Not Allowed", "s", |
411 |
"detail", detail);
|
412 |
} |
413 |
|
414 |
/*
|
415 |
** ERROR409 --- issue a 409 "Conflict" error
|
416 |
*/
|
417 |
|
418 |
EP_STAT |
419 |
error409(scgi_request *req, const char *detail) |
420 |
{ |
421 |
return gdp_failure(req, "409", "Conflict", "s", "detail", detail); |
422 |
} |
423 |
|
424 |
/*
|
425 |
** ERROR500 --- issue a 500 "Internal Server Error" error
|
426 |
*/
|
427 |
|
428 |
EP_STAT |
429 |
error500(scgi_request *req, const char *detail, int eno) |
430 |
{ |
431 |
char nbuf[40]; |
432 |
|
433 |
(void) (0 == strerror_r(eno, nbuf, sizeof nbuf)); |
434 |
(void) gdp_failure(req, "500", "Internal Server Error", "ss", |
435 |
"errno", nbuf,
|
436 |
"detail", detail);
|
437 |
return ep_stat_from_errno(eno);
|
438 |
} |
439 |
|
440 |
|
441 |
/*
|
442 |
** ERROR501 --- issue a 501 "Not Implemented" error
|
443 |
*/
|
444 |
|
445 |
EP_STAT |
446 |
error501(scgi_request *req, const char *detail) |
447 |
{ |
448 |
return gdp_failure(req, "501", "Not Implemented", "s", |
449 |
"detail", detail);
|
450 |
} |
451 |
|
452 |
|
453 |
/*
|
454 |
** PROCESS_GDP_CREATE_REQ --- create new log via gdp-create, parse stdout msg
|
455 |
*/
|
456 |
|
457 |
EP_STAT |
458 |
process_gdp_create_req(scgi_request *req, const char *gclxname, |
459 |
json_t *j, json_t *j_meta, size_t options_max) |
460 |
{ |
461 |
EP_STAT estat = EP_STAT_OK; |
462 |
int opt;
|
463 |
const char *options[options_max]; |
464 |
size_t j_unprocessed; |
465 |
size_t j_meta_unprocessed; |
466 |
int o_pipe[2]; |
467 |
pid_t pid; |
468 |
int status;
|
469 |
int exit_code;
|
470 |
json_t *j_temp; |
471 |
json_t *j_resp; |
472 |
char *jbuf;
|
473 |
char sbuf[SCGI_MAX_OUTBUF_SIZE];
|
474 |
int p;
|
475 |
|
476 |
// mandatory gclxname json obj already processed by caller to arrive here
|
477 |
j_unprocessed = json_object_size(j) - 1;
|
478 |
|
479 |
// process name in slot zero
|
480 |
opt = 0;
|
481 |
options[opt++] = exec_gdp_create; |
482 |
|
483 |
// then add server controlled parameters
|
484 |
options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_e]; |
485 |
options[opt++] = "none";
|
486 |
options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_S]; |
487 |
options[opt++] = exec_gdp_create_param[GCL_C_PARAM_SERV_K]; |
488 |
options[opt++] = "/etc/gdp/keys";
|
489 |
|
490 |
// then add client requested options
|
491 |
for (p = GCL_C_PARAM_SERV_MAX; p < GCL_C_PARAM_MAX; p++)
|
492 |
{ |
493 |
// borrowed
|
494 |
if ((j_temp = json_object_get(j, exec_gdp_create_param[p])) != NULL) |
495 |
{ |
496 |
options[opt++] = exec_gdp_create_param[p]; |
497 |
// borrowed
|
498 |
options[opt++] = json_string_value(j_temp); |
499 |
if (options[opt - 1] != NULL) |
500 |
j_unprocessed--; |
501 |
} |
502 |
} |
503 |
|
504 |
// client optional "META" array elements (size is 0 if !j_meta or !array)
|
505 |
j_meta_unprocessed = json_array_size(j_meta); |
506 |
if (j_meta_unprocessed > 0) |
507 |
{ |
508 |
|
509 |
size_t j_meta_i; |
510 |
|
511 |
json_array_foreach(j_meta, j_meta_i, j_temp) |
512 |
{ |
513 |
if (json_is_string(j_temp))
|
514 |
{ |
515 |
// borrowed
|
516 |
options[opt] = json_string_value(j_temp); |
517 |
if (options[opt] != NULL && strchr(options[opt], '=') != NULL) |
518 |
{ |
519 |
opt++; |
520 |
j_meta_unprocessed--; |
521 |
} |
522 |
} |
523 |
} |
524 |
// json key "META" has been processed
|
525 |
j_unprocessed--; |
526 |
} |
527 |
|
528 |
// then add the external-name if present (implies HTTP PUT)
|
529 |
if (gclxname != NULL) |
530 |
options[opt++] = gclxname; |
531 |
|
532 |
// and finally terminate the argv-style options pointer array
|
533 |
options[opt++] = NULL;
|
534 |
|
535 |
// unprocessed client content is treated as an error
|
536 |
if (j_unprocessed > 0 || j_meta_unprocessed > 0) |
537 |
{ |
538 |
estat = error400(req, "request contains unrecognized json objects");
|
539 |
return estat;
|
540 |
} |
541 |
|
542 |
// prep to capture child's stdout
|
543 |
if ((pipe(o_pipe)) == -1) |
544 |
{ |
545 |
estat = error500(req, "gdp-rest pipe", errno);
|
546 |
return estat;
|
547 |
} |
548 |
|
549 |
if ((pid = fork()) == -1) |
550 |
{ |
551 |
estat = error500(req, "gdp-rest fork", errno);
|
552 |
close(o_pipe[STDOUT_FILENO]); |
553 |
close(o_pipe[STDIN_FILENO]); |
554 |
return estat;
|
555 |
} |
556 |
|
557 |
if (pid == 0) |
558 |
{ |
559 |
// child moves child side of pipe to own stdout
|
560 |
dup2(o_pipe[STDOUT_FILENO], STDOUT_FILENO); |
561 |
close(o_pipe[STDOUT_FILENO]); |
562 |
// child closes parent side of pipe
|
563 |
close(o_pipe[STDIN_FILENO]); |
564 |
// child execv gdp-create
|
565 |
execv(exec_gdp_create, (char * const*)options); |
566 |
// child execv launch failed
|
567 |
perror("gdp-rest execv failure");
|
568 |
exit(EXIT_FAILURE); |
569 |
} |
570 |
|
571 |
// parent closes child side of pipe
|
572 |
close(o_pipe[STDOUT_FILENO]); |
573 |
|
574 |
// wait for child
|
575 |
if (waitpid(pid, &status, 0) != pid) |
576 |
{ |
577 |
estat = error500(req, "gdp-rest waitpid", errno);
|
578 |
close(o_pipe[STDIN_FILENO]); |
579 |
return estat;
|
580 |
} |
581 |
|
582 |
// check child status for unexpected exits
|
583 |
if (! WIFEXITED(status) ||
|
584 |
(exit_code = WEXITSTATUS(status)) == EXIT_FAILURE) |
585 |
{ |
586 |
estat = gdp_failure(req, "500", "Internal Server Error", "sds", |
587 |
"detail", "gdp-rest execv gdp-create failure", |
588 |
"status", status,
|
589 |
"request", req->body);
|
590 |
close(o_pipe[STDIN_FILENO]); |
591 |
return estat;
|
592 |
} |
593 |
ep_dbg_cprintf(Dbg, 5, "gdp-rest exec gdp-create exited(%d)\n", exit_code); |
594 |
|
595 |
if (exit_code == EX_OK)
|
596 |
{ |
597 |
ssize_t bytes; |
598 |
char *created;
|
599 |
char obuf[GCL_C_OUT_BUF_SIZE];
|
600 |
|
601 |
bytes = read(o_pipe[STDIN_FILENO], obuf, sizeof obuf - 1); |
602 |
if (bytes < 0) |
603 |
{ |
604 |
estat = error500(req, "gdp-rest pipe read", errno);
|
605 |
close(o_pipe[STDIN_FILENO]); |
606 |
return estat;
|
607 |
} |
608 |
obuf[bytes] = '\0';
|
609 |
ep_dbg_cprintf(Dbg, 5, "pipe read %ld bytes = {\n%s}\n", bytes, obuf); |
610 |
if (bytes < sizeof gdp_c_min_out) |
611 |
{ |
612 |
estat = gdp_failure(req, "500", "Internal Server Error", "ss", |
613 |
"detail", "gdp-rest pipe read bytes", |
614 |
"read", obuf);
|
615 |
close(o_pipe[STDIN_FILENO]); |
616 |
return estat;
|
617 |
} |
618 |
|
619 |
// new
|
620 |
if ((j_resp = json_object()) == NULL) |
621 |
{ |
622 |
estat = error500(req, "gdp-rest response", ENOMEM);
|
623 |
close(o_pipe[STDIN_FILENO]); |
624 |
return estat;
|
625 |
} |
626 |
|
627 |
obuf[bytes - 1] = '\0'; // terminate obuf |
628 |
|
629 |
// validate input (i.e. find "Created new GCL ...", which is not first)
|
630 |
created = strstr((const char *)obuf, "Created"); |
631 |
if (created == NULL) |
632 |
{ |
633 |
estat = gdp_failure(req, "500", "Internal Server Error", "ss", |
634 |
"detail", "gdp-rest pipe read created", |
635 |
"read", obuf);
|
636 |
json_decref(j_resp); |
637 |
close(o_pipe[STDIN_FILENO]); |
638 |
return estat;
|
639 |
} |
640 |
|
641 |
// gdp-create EX_OK output is kept stable to permit string extraction
|
642 |
created[GCL_C_OUT_GCL_NAME_END] = '\0'; // terminate a newline of line 1 |
643 |
|
644 |
// new
|
645 |
j_temp = json_string(&created[GCL_C_OUT_GCL_NAME]); |
646 |
if (j_temp == NULL) |
647 |
{ |
648 |
estat = error500(req, "gdp-rest response gcl_name", EIO);
|
649 |
json_decref(j_resp); |
650 |
close(o_pipe[STDIN_FILENO]); |
651 |
return estat;
|
652 |
} |
653 |
// set_new steals new json_string (or auto-decrefs if !j || !key)
|
654 |
if ((json_object_set_new_nocheck(j_resp, "gcl_name", j_temp)) == -1) |
655 |
{ |
656 |
estat = gdp_failure(req, "500", "Internal Server Error", "ss", |
657 |
"detail", "gdp-rest response", |
658 |
"gcl_name", &created[GCL_C_OUT_GCL_NAME]);
|
659 |
json_decref(j_resp); |
660 |
close(o_pipe[STDIN_FILENO]); |
661 |
return estat;
|
662 |
} |
663 |
|
664 |
// malloc
|
665 |
if ((jbuf = json_dumps(j_resp, JSON_INDENT(4))) == NULL) |
666 |
{ |
667 |
estat = error500(req, "gdp-rest response reformat", EIO);
|
668 |
json_decref(j_resp); |
669 |
close(o_pipe[STDIN_FILENO]); |
670 |
return estat;
|
671 |
} |
672 |
snprintf(sbuf, sizeof sbuf,
|
673 |
"HTTP/1.1 201 GCL created\r\n"
|
674 |
"Content-Type: application/json\r\n"
|
675 |
"\r\n"
|
676 |
"%s\r\n",
|
677 |
jbuf); |
678 |
write_scgi(req, sbuf); |
679 |
free(jbuf); |
680 |
json_decref(j_resp); |
681 |
} |
682 |
else if ((exit_code == EX_CANTCREAT) && |
683 |
(req->request_method == SCGI_METHOD_PUT)) |
684 |
estat = error409(req, "external-name already exists on gdplogd server");
|
685 |
else if (exit_code == EX_CANTCREAT) |
686 |
estat = error409(req, "generated-name conflict on gdplogd server");
|
687 |
else if (exit_code == EX_NOHOST) |
688 |
estat = error400(req, "log server host not found");
|
689 |
else if (exit_code == EX_UNAVAILABLE) |
690 |
estat = error400(req, "key length selection insecure, denied");
|
691 |
else
|
692 |
estat = gdp_failure(req, "500", "Internal Server Error", "sd", |
693 |
"detail", "gdp-create unexpected error", |
694 |
"exit_code", exit_code);
|
695 |
|
696 |
close(o_pipe[STDIN_FILENO]); |
697 |
return estat;
|
698 |
} |
699 |
|
700 |
|
701 |
/*
|
702 |
** A_NEW_GOB --- create new GDP Object
|
703 |
*/
|
704 |
|
705 |
EP_STAT |
706 |
a_new_gob(scgi_request *req) |
707 |
{ |
708 |
EP_STAT estat = EP_STAT_OK; |
709 |
json_t *j; |
710 |
const char *gobxname; |
711 |
size_t options_max; |
712 |
json_t *j_temp; |
713 |
json_t *j_meta; |
714 |
|
715 |
ep_dbg_cprintf(Dbg, 5, "=== Create new GCL (%s)\n", req->body); |
716 |
|
717 |
// new
|
718 |
if ((j = json_loads(req->body, JSON_REJECT_DUPLICATES, NULL)) == NULL) |
719 |
{ |
720 |
estat = error400(req, "request body not recognized json format");
|
721 |
return estat;
|
722 |
} |
723 |
|
724 |
// mandatory external-name obj, to prevent URL crawl-driven log creation
|
725 |
// borrowed
|
726 |
if ((j_temp = json_object_get(j, "external-name")) == NULL) |
727 |
{ |
728 |
estat = error400(req, "mandatory external-name not found");
|
729 |
json_decref(j); |
730 |
return estat;
|
731 |
} |
732 |
json_incref(j_temp); |
733 |
// borrowed
|
734 |
gobxname = json_string_value(j_temp); |
735 |
|
736 |
// external-name obj value must be NULL for POST, non-NULL for PUT
|
737 |
if ((req->request_method == SCGI_METHOD_POST && gobxname != NULL) || |
738 |
(req->request_method == SCGI_METHOD_PUT && gobxname == NULL))
|
739 |
{ |
740 |
if (gobxname != NULL) |
741 |
estat = error400(req, "POST external-name must have null value");
|
742 |
else
|
743 |
estat = error400(req, "PUT external-name must have non-null value");
|
744 |
json_decref(j_temp); |
745 |
json_decref(j); |
746 |
return estat;
|
747 |
} |
748 |
|
749 |
// procname + 2 * (server params + client params) + glxname + terminator
|
750 |
options_max = 1 + 2 * (GCL_C_PARAM_SERV_MAX + json_object_size(j)) + 1 + 1; |
751 |
|
752 |
// peek at META array now, to size options array, then set aside for later
|
753 |
// borrowed
|
754 |
j_meta = json_object_get(j, "META");
|
755 |
json_incref(j_meta); |
756 |
|
757 |
// size is 0 if !j_meta or !array
|
758 |
options_max += json_array_size(j_meta); |
759 |
|
760 |
estat = process_gdp_create_req(req, gobxname, j, j_meta, options_max); |
761 |
|
762 |
// calls handle NULL
|
763 |
json_decref(j_meta); |
764 |
json_decref(j_temp); |
765 |
json_decref(j); |
766 |
return estat;
|
767 |
} |
768 |
|
769 |
/*
|
770 |
** A_SHOW_GOB --- show information about a GDP Object
|
771 |
*/
|
772 |
|
773 |
EP_STAT |
774 |
a_show_gob(scgi_request *req, gdp_name_t gobiname) |
775 |
{ |
776 |
return error501(req, "GCL status not implemented"); |
777 |
} |
778 |
|
779 |
|
780 |
/*
|
781 |
** A_APPEND --- append datum to GCL
|
782 |
*/
|
783 |
|
784 |
EP_STAT |
785 |
a_append(scgi_request *req, gdp_name_t gobiname, gdp_datum_t *datum) |
786 |
{ |
787 |
EP_STAT estat = EP_STAT_OK; |
788 |
gdp_gin_t *gin = NULL;
|
789 |
char rbuf[SCGI_MAX_OUTBUF_SIZE];
|
790 |
json_t *j; |
791 |
char *jbuf;
|
792 |
EP_TIME_SPEC ts; |
793 |
|
794 |
ep_dbg_cprintf(Dbg, 5, "=== Append value to GCL\n"); |
795 |
|
796 |
estat = gdp_gin_open(gobiname, GDP_MODE_AO, shared_gdp_open_info, &gin); |
797 |
EP_STAT_CHECK(estat, goto fail_open);
|
798 |
|
799 |
estat = gdp_gin_append(gin, datum, NULL);
|
800 |
EP_STAT_CHECK(estat, goto fail_append);
|
801 |
|
802 |
// success: send a response
|
803 |
j = json_object(); |
804 |
|
805 |
// set_new steals new json_integer (or auto-decrefs if !j || !key)
|
806 |
json_object_set_new_nocheck(j, "recno",
|
807 |
json_integer(gdp_datum_getrecno(datum))); |
808 |
gdp_datum_getts(datum, &ts); |
809 |
if (EP_TIME_IS_VALID(&ts))
|
810 |
{ |
811 |
char tbuf[100]; |
812 |
|
813 |
ep_time_format(&ts, tbuf, sizeof tbuf, EP_TIME_FMT_DEFAULT);
|
814 |
// set_new steals new json_string (or auto-decrefs if !j || !key)
|
815 |
json_object_set_new_nocheck(j, "timestamp", json_string(tbuf));
|
816 |
} |
817 |
|
818 |
// get it (malloc'ed) in string format (will return NULL if !j)
|
819 |
jbuf = json_dumps(j, JSON_INDENT(4));
|
820 |
|
821 |
// create the entire SCGI return message
|
822 |
snprintf(rbuf, sizeof rbuf,
|
823 |
"HTTP/1.1 200 Successfully appended\r\n"
|
824 |
"Content-Type: application/json\r\n"
|
825 |
"\r\n"
|
826 |
"%s\r\n",
|
827 |
jbuf ? jbuf : "");
|
828 |
write_scgi(req, rbuf); |
829 |
|
830 |
// clean up
|
831 |
json_decref(j); |
832 |
free(jbuf); |
833 |
|
834 |
// caller frees datum
|
835 |
|
836 |
fail_append:
|
837 |
gdp_gin_close(gin); |
838 |
fail_open:
|
839 |
if (!EP_STAT_ISOK(estat))
|
840 |
{ |
841 |
char ebuf[200]; |
842 |
gdp_pname_t gobpname; |
843 |
|
844 |
gdp_printable_name(gobiname, gobpname); |
845 |
gdp_failure(req, "420", "Cannot append to GCL", "ss", |
846 |
"GCL", gobpname,
|
847 |
"error", ep_stat_tostr(estat, ebuf, sizeof ebuf)); |
848 |
} |
849 |
|
850 |
return estat;
|
851 |
} |
852 |
|
853 |
|
854 |
/*
|
855 |
** A_READ_DATUM --- read and return a datum from a GCL
|
856 |
**
|
857 |
** XXX Currently doesn't use the GCL cache. To make that work
|
858 |
** long term we would have to have to implement LRU in that
|
859 |
** cache (which we probably need to do anyway).
|
860 |
*/
|
861 |
|
862 |
EP_STAT |
863 |
a_read_datum(scgi_request *req, gdp_name_t gobiname, gdp_recno_t recno) |
864 |
{ |
865 |
EP_STAT estat; |
866 |
gdp_gin_t *gin = NULL;
|
867 |
gdp_datum_t *datum = gdp_datum_new(); |
868 |
|
869 |
estat = gdp_gin_open(gobiname, GDP_MODE_RO, shared_gdp_open_info, &gin); |
870 |
EP_STAT_CHECK(estat, goto fail_open);
|
871 |
|
872 |
estat = gdp_gin_read_by_recno(gin, recno, datum); |
873 |
if (!EP_STAT_ISOK(estat))
|
874 |
goto fail_read;
|
875 |
|
876 |
// package up the results and send them back
|
877 |
{ |
878 |
char rbuf[1024]; |
879 |
|
880 |
// figure out the response header
|
881 |
{ |
882 |
FILE *fp; |
883 |
gdp_pname_t gobpname; |
884 |
EP_TIME_SPEC ts; |
885 |
|
886 |
fp = ep_fopen_smem(rbuf, sizeof rbuf, "w"); |
887 |
if (fp == NULL) |
888 |
{ |
889 |
char nbuf[40]; |
890 |
|
891 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
892 |
ep_app_abort("Cannot open memory for GDP read response: %s",
|
893 |
nbuf); |
894 |
} |
895 |
gdp_printable_name(gobiname, gobpname); |
896 |
fprintf(fp, "HTTP/1.1 200 GCL Message\r\n"
|
897 |
"Content-Type: application/json\r\n"
|
898 |
"GDP-GOB-Name: %s\r\n"
|
899 |
"GDP-Record-Number: %" PRIgdp_recno "\r\n", |
900 |
gobpname, |
901 |
gdp_datum_getrecno(datum)); |
902 |
gdp_datum_getts(datum, &ts); |
903 |
if (EP_TIME_IS_VALID(&ts))
|
904 |
{ |
905 |
fprintf(fp, "GDP-Commit-Timestamp: ");
|
906 |
ep_time_print(&ts, fp, EP_TIME_FMT_DEFAULT); |
907 |
fprintf(fp, "\r\n");
|
908 |
} |
909 |
fprintf(fp, "\r\n"); // end of header |
910 |
fputc('\0', fp);
|
911 |
fclose(fp); |
912 |
} |
913 |
|
914 |
// finish up sending the data out --- the extra copy is annoying
|
915 |
{ |
916 |
size_t rlen = strlen(rbuf); |
917 |
size_t dlen = evbuffer_get_length(gdp_datum_getbuf(datum)); |
918 |
char obuf[1024]; |
919 |
char *obp = obuf;
|
920 |
|
921 |
if (rlen + dlen > sizeof obuf) |
922 |
obp = ep_mem_malloc(rlen + dlen); |
923 |
|
924 |
if (obp == NULL) |
925 |
{ |
926 |
char nbuf[40]; |
927 |
|
928 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
929 |
ep_app_abort("Cannot allocate memory for GCL read response: %s",
|
930 |
nbuf); |
931 |
} |
932 |
|
933 |
memcpy(obp, rbuf, rlen); |
934 |
gdp_buf_read(gdp_datum_getbuf(datum), obp + rlen, dlen); |
935 |
scgi_send(req, obp, rlen + dlen); |
936 |
if (obp != obuf)
|
937 |
ep_mem_free(obp); |
938 |
} |
939 |
} |
940 |
|
941 |
// finished
|
942 |
gdp_datum_free(datum); |
943 |
gdp_gin_close(gin); |
944 |
return estat;
|
945 |
|
946 |
fail_read:
|
947 |
gdp_gin_close(gin); |
948 |
fail_open:
|
949 |
gdp_datum_free(datum); |
950 |
{ |
951 |
char ebuf[200]; |
952 |
gdp_pname_t gobpname; |
953 |
|
954 |
gdp_printable_name(gobiname, gobpname); |
955 |
gdp_failure(req, "404", "Cannot read GCL", "ss", |
956 |
"GOB", gobpname,
|
957 |
"reason", ep_stat_tostr(estat, ebuf, sizeof ebuf)); |
958 |
} |
959 |
return estat;
|
960 |
} |
961 |
|
962 |
|
963 |
/*
|
964 |
** GOB_DO_GET --- helper routine for GET method on a GDP Object
|
965 |
**
|
966 |
** Have to look at query to figure out the semantics.
|
967 |
** The query's the thing / wherein I'll catch the
|
968 |
** conscience of the king.
|
969 |
*/
|
970 |
|
971 |
EP_STAT |
972 |
gob_do_get(scgi_request *req, gdp_name_t gobiname, struct qkvpair *qkvs)
|
973 |
{ |
974 |
EP_STAT estat; |
975 |
char *qrecno = find_query_kv("recno", qkvs); |
976 |
char *qnrecs = find_query_kv("nrecs", qkvs); |
977 |
char *qtimeout = find_query_kv("timeout", qkvs); |
978 |
|
979 |
if (qnrecs != NULL) |
980 |
{ |
981 |
// not yet implemented
|
982 |
estat = error501(req, "nrecs query not supported");
|
983 |
} |
984 |
else if (qtimeout != NULL) |
985 |
{ |
986 |
// not yet implemented
|
987 |
estat = error501(req, "timeout query not supported");
|
988 |
} |
989 |
else if (qrecno != NULL) |
990 |
{ |
991 |
gdp_recno_t recno; |
992 |
|
993 |
if (strcmp(qrecno, "last") == 0) |
994 |
recno = -1;
|
995 |
else
|
996 |
recno = atol(qrecno); |
997 |
estat = a_read_datum(req, gobiname, recno); |
998 |
} |
999 |
else
|
1000 |
{ |
1001 |
estat = a_show_gob(req, gobiname); |
1002 |
} |
1003 |
|
1004 |
return estat;
|
1005 |
} |
1006 |
|
1007 |
|
1008 |
/*
|
1009 |
** PFX_GCL --- process SCGI requests starting with /gdp/v1/gcl
|
1010 |
*/
|
1011 |
|
1012 |
#define NQUERY_KVS 10 // max number of key-value pairs in query part |
1013 |
|
1014 |
EP_STAT |
1015 |
pfx_gcl(scgi_request *req, char *uri)
|
1016 |
{ |
1017 |
EP_STAT estat; |
1018 |
struct qkvpair qkvs[NQUERY_KVS + 1]; |
1019 |
|
1020 |
if (*uri == '/') |
1021 |
uri++; |
1022 |
|
1023 |
ep_dbg_cprintf(Dbg, 3, " gcl=%s\n query=%s\n", uri, req->query_string); |
1024 |
|
1025 |
// parse the query, if it exists
|
1026 |
{ |
1027 |
int i = parse_query(req->query_string, qkvs, NQUERY_KVS);
|
1028 |
qkvs[i].key = qkvs[i].val = NULL;
|
1029 |
} |
1030 |
|
1031 |
if (*uri == '\0') |
1032 |
{ |
1033 |
// URI does not include a GCL name, implies GDP scoped operations
|
1034 |
switch (req->request_method)
|
1035 |
{ |
1036 |
case SCGI_METHOD_POST:
|
1037 |
// fall-through
|
1038 |
case SCGI_METHOD_PUT:
|
1039 |
// create a new GDP Object
|
1040 |
estat = a_new_gob(req); |
1041 |
break;
|
1042 |
|
1043 |
case SCGI_METHOD_GET:
|
1044 |
// XXX if no GCL name, should we print all GCLs?
|
1045 |
estat = error404(req, "listing GCLs not implemented (yet)");
|
1046 |
break;
|
1047 |
|
1048 |
case SCGI_METHOD_OPTIONS:
|
1049 |
estat = cors200(req); |
1050 |
break;
|
1051 |
|
1052 |
default:
|
1053 |
// unknown URI/method
|
1054 |
estat = error405(req, |
1055 |
"only GET, POST, or PUT supported in GDP scope");
|
1056 |
break;
|
1057 |
} |
1058 |
} |
1059 |
else
|
1060 |
{ |
1061 |
gdp_name_t gcliname; |
1062 |
|
1063 |
// next component is the GCL id (name) in external format
|
1064 |
gdp_parse_name(uri, gcliname); |
1065 |
|
1066 |
// URI includes a GCL name, implies GCL scoped operations
|
1067 |
switch (req->request_method)
|
1068 |
{ |
1069 |
case SCGI_METHOD_GET:
|
1070 |
estat = gob_do_get(req, gcliname, qkvs); |
1071 |
break;
|
1072 |
|
1073 |
case SCGI_METHOD_POST:
|
1074 |
// append value to GCL
|
1075 |
{ |
1076 |
gdp_datum_t *datum = gdp_datum_new(); |
1077 |
|
1078 |
gdp_buf_write(gdp_datum_getbuf(datum), req->body, |
1079 |
req->scgi_content_length); |
1080 |
estat = a_append(req, gcliname, datum); |
1081 |
gdp_datum_free(datum); |
1082 |
} |
1083 |
break;
|
1084 |
|
1085 |
case SCGI_METHOD_OPTIONS:
|
1086 |
estat = cors200(req); |
1087 |
break;
|
1088 |
|
1089 |
default:
|
1090 |
// unknown URI/method
|
1091 |
estat = error405(req, "only GET or POST supported in GCL scope");
|
1092 |
} |
1093 |
} |
1094 |
|
1095 |
return estat;
|
1096 |
} |
1097 |
|
1098 |
|
1099 |
/*
|
1100 |
** PFX_POST --- process SCGI requests starting with /gdp/v1/post
|
1101 |
*/
|
1102 |
|
1103 |
EP_STAT |
1104 |
pfx_post(scgi_request *req, char *uri)
|
1105 |
{ |
1106 |
if (*uri == '/') |
1107 |
uri++; |
1108 |
|
1109 |
return error501(req, "/post/... URIs not yet supported"); |
1110 |
} |
1111 |
|
1112 |
|
1113 |
|
1114 |
/**********************************************************************
|
1115 |
**
|
1116 |
** KEY-VALUE STORE
|
1117 |
**
|
1118 |
** This implementation is very sloppy right now.
|
1119 |
**
|
1120 |
**********************************************************************/
|
1121 |
|
1122 |
|
1123 |
json_t *KeyValStore = NULL;
|
1124 |
gdp_gin_t *KeyValGcl = NULL;
|
1125 |
const char *KeyValStoreName; |
1126 |
gdp_name_t KeyValInternalName; |
1127 |
|
1128 |
|
1129 |
EP_STAT |
1130 |
insert_datum(gdp_datum_t *datum) |
1131 |
{ |
1132 |
if (datum == NULL) |
1133 |
return GDP_STAT_INTERNAL_ERROR;
|
1134 |
|
1135 |
gdp_buf_t *buf = gdp_datum_getbuf(datum); |
1136 |
size_t len = gdp_buf_getlength(buf); |
1137 |
unsigned char *p = gdp_buf_getptr(buf, len); |
1138 |
|
1139 |
json_t *j = json_loadb((char *) p, len, 0, NULL); |
1140 |
if (!json_is_object(j))
|
1141 |
return GDP_STAT_MSGFMT;
|
1142 |
|
1143 |
json_object_update(KeyValStore, j); |
1144 |
json_decref(j); |
1145 |
return EP_STAT_OK;
|
1146 |
} |
1147 |
|
1148 |
|
1149 |
EP_STAT |
1150 |
kv_initialize(void)
|
1151 |
{ |
1152 |
EP_STAT estat; |
1153 |
gdp_event_t *gev; |
1154 |
|
1155 |
if (KeyValStore != NULL) |
1156 |
return EP_STAT_OK;
|
1157 |
|
1158 |
// get space for our internal database
|
1159 |
KeyValStore = json_object(); |
1160 |
|
1161 |
KeyValStoreName = ep_adm_getstrparam("swarm.rest.kvstore.gclname",
|
1162 |
"swarm.rest.kvstore.gclname");
|
1163 |
|
1164 |
// open the "KeyVal" GCL
|
1165 |
gdp_parse_name(KeyValStoreName, KeyValInternalName); |
1166 |
estat = gdp_gin_open(KeyValInternalName, GDP_MODE_AO, NULL, &KeyValGcl);
|
1167 |
EP_STAT_CHECK(estat, goto fail0);
|
1168 |
|
1169 |
// read all the data
|
1170 |
estat = gdp_gin_read_by_recno_async(KeyValGcl, 1, 0, NULL, NULL); |
1171 |
EP_STAT_CHECK(estat, goto fail1);
|
1172 |
while ((gev = gdp_event_next(KeyValGcl, 0)) != NULL) |
1173 |
{ |
1174 |
if (gdp_event_gettype(gev) == GDP_EVENT_DONE)
|
1175 |
{ |
1176 |
// end of multiread --- we have it all
|
1177 |
gdp_event_free(gev); |
1178 |
break;
|
1179 |
} |
1180 |
else if (gdp_event_gettype(gev) == GDP_EVENT_DATA) |
1181 |
{ |
1182 |
// update the current stat of the key-value store
|
1183 |
estat = insert_datum(gdp_event_getdatum(gev)); |
1184 |
} |
1185 |
gdp_event_free(gev); |
1186 |
} |
1187 |
|
1188 |
// now start up the subscription (will be read in main loop)
|
1189 |
estat = gdp_gin_subscribe_by_recno(KeyValGcl, 0, 0, NULL, NULL, NULL); |
1190 |
goto done;
|
1191 |
|
1192 |
fail0:
|
1193 |
// couldn't open; try create?
|
1194 |
//estat = gdp_gcl_create(KeyValInternalName, NULL, &KeyValGcl);
|
1195 |
|
1196 |
fail1:
|
1197 |
// couldn't read GCL
|
1198 |
|
1199 |
done:
|
1200 |
return estat;
|
1201 |
} |
1202 |
|
1203 |
|
1204 |
EP_STAT |
1205 |
pfx_kv(scgi_request *req, char *uri)
|
1206 |
{ |
1207 |
EP_STAT estat; |
1208 |
|
1209 |
if (*uri == '/') |
1210 |
uri++; |
1211 |
|
1212 |
if (KeyValStore == NULL) |
1213 |
{ |
1214 |
estat = kv_initialize(); |
1215 |
EP_STAT_CHECK(estat, goto fail0);
|
1216 |
} |
1217 |
|
1218 |
if (req->request_method == SCGI_METHOD_POST)
|
1219 |
{ |
1220 |
// get the datum out of the SCGI request
|
1221 |
gdp_datum_t *datum = gdp_datum_new(); |
1222 |
gdp_buf_write(gdp_datum_getbuf(datum), req->body, req->scgi_content_length); |
1223 |
|
1224 |
// try to merge it into the in-memory representation
|
1225 |
estat = insert_datum(datum); |
1226 |
|
1227 |
// if that succeeded, append the record to the GCL
|
1228 |
if (EP_STAT_ISOK(estat))
|
1229 |
a_append(req, KeyValInternalName, datum); |
1230 |
|
1231 |
// don't forget to mop up!
|
1232 |
gdp_datum_free(datum); |
1233 |
} |
1234 |
else if (req->request_method == SCGI_METHOD_GET) |
1235 |
{ |
1236 |
FILE *fp; |
1237 |
char rbuf[2000]; |
1238 |
|
1239 |
fp = ep_fopen_smem(rbuf, sizeof rbuf, "w"); |
1240 |
if (fp == NULL) |
1241 |
{ |
1242 |
char nbuf[40]; |
1243 |
|
1244 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
1245 |
ep_app_abort("Cannot open memory for GCL read response: %s",
|
1246 |
nbuf); |
1247 |
} |
1248 |
|
1249 |
json_t *j0 = json_object_get(KeyValStore, uri); |
1250 |
if (j0 == NULL) |
1251 |
return error404(req, "No such key"); |
1252 |
|
1253 |
json_t *j1 = json_object(); |
1254 |
json_object_set(j1, uri, j0); |
1255 |
fprintf(fp, "HTTP/1.1 200 Data\r\n"
|
1256 |
"Content-Type: application/json\r\n"
|
1257 |
"\r\n"
|
1258 |
"%s\r\n",
|
1259 |
json_dumps(j1, 0));
|
1260 |
fputc('\0', fp);
|
1261 |
fclose(fp); |
1262 |
json_decref(j1); |
1263 |
|
1264 |
scgi_send(req, rbuf, strlen(rbuf)); |
1265 |
} |
1266 |
else
|
1267 |
{ |
1268 |
return error405(req, "unknown method"); |
1269 |
} |
1270 |
|
1271 |
return EP_STAT_OK;
|
1272 |
|
1273 |
fail0:
|
1274 |
return error500(req, "Couldn't initialize Key-Value store", errno); |
1275 |
} |
1276 |
|
1277 |
|
1278 |
void
|
1279 |
process_event(gdp_event_t *gev) |
1280 |
{ |
1281 |
if (gdp_event_gettype(gev) != GDP_EVENT_DATA)
|
1282 |
return;
|
1283 |
|
1284 |
if (KeyValStore == NULL) |
1285 |
return;
|
1286 |
|
1287 |
// for the time being, assume it comes from the correct GCL
|
1288 |
insert_datum(gdp_event_getdatum(gev)); |
1289 |
} |
1290 |
|
1291 |
|
1292 |
/**********************************************************************/
|
1293 |
|
1294 |
/*
|
1295 |
** PROCESS_SCGI_REQ --- process an already-read SCGI request
|
1296 |
**
|
1297 |
** This is generally called as an event
|
1298 |
*/
|
1299 |
|
1300 |
EP_STAT |
1301 |
process_scgi_req(scgi_request *req) |
1302 |
{ |
1303 |
char *uri; // the URI of the request |
1304 |
EP_STAT estat = EP_STAT_OK; |
1305 |
|
1306 |
if (ep_dbg_test(Dbg, 3)) |
1307 |
{ |
1308 |
ep_dbg_printf("Got connection on port %d from %s:\n",
|
1309 |
req->descriptor->port->port, req->remote_addr); |
1310 |
ep_dbg_printf(" %s %s\n", scgi_method_name(req->request_method),
|
1311 |
req->request_uri); |
1312 |
} |
1313 |
|
1314 |
// strip query string off of URI (I'm surprised it's not already done)
|
1315 |
uri = strchr(req->request_uri, '?');
|
1316 |
if (uri != NULL) |
1317 |
*uri = '\0';
|
1318 |
|
1319 |
// strip off leading "/gdp/v1/" prefix (error if not there)
|
1320 |
if (GclUriPrefix == NULL) |
1321 |
GclUriPrefix = ep_adm_getstrparam("swarm.rest.prefix", DEF_URI_PREFIX);
|
1322 |
uri = req->request_uri; |
1323 |
if (strncmp(uri, GclUriPrefix, strlen(GclUriPrefix)) != 0) |
1324 |
{ |
1325 |
estat = error404(req, "improper URI prefix");
|
1326 |
goto finis;
|
1327 |
} |
1328 |
uri += strlen(GclUriPrefix); |
1329 |
if (*uri == '/') |
1330 |
uri++; |
1331 |
|
1332 |
// next component is "gcl" for RESTful or "post" for RESTish
|
1333 |
if (strncmp(uri, "gcl", 3) == 0 && (uri[3] == '/' || uri[3] == '\0')) |
1334 |
{ |
1335 |
// looking at "/gdp/v1/gcl/" prefix; next component is the GCL name
|
1336 |
estat = pfx_gcl(req, uri + 3);
|
1337 |
} |
1338 |
else if (strncmp(uri, "post", 4) == 0 && (uri[4] == '/' || uri[4] == '\0')) |
1339 |
{ |
1340 |
// looking at "/gdp/v1/post" prefix
|
1341 |
estat = pfx_post(req, uri + 4);
|
1342 |
} |
1343 |
else if (strncmp(uri, "kv", 2) == 0 && (uri[2] == '/' || uri[4] == '\0')) |
1344 |
{ |
1345 |
// looking at "/gdp/v1/kv" prefix
|
1346 |
estat = pfx_kv(req, uri + 2);
|
1347 |
} |
1348 |
else
|
1349 |
{ |
1350 |
// looking at "/gdp/v1/<unknown>" prefix
|
1351 |
estat = error404(req, "unknown resource");
|
1352 |
} |
1353 |
|
1354 |
finis:
|
1355 |
return estat;
|
1356 |
} |
1357 |
|
1358 |
|
1359 |
int
|
1360 |
main(int argc, char **argv, char **env) |
1361 |
{ |
1362 |
int opt;
|
1363 |
int listenport = -1; |
1364 |
int64_t poll_delay; |
1365 |
char *gdpd_addr = NULL; |
1366 |
bool show_usage = false; |
1367 |
extern void run_scgi_protocol(void); |
1368 |
|
1369 |
while ((opt = getopt(argc, argv, "D:G:p:u:C:")) > 0) |
1370 |
{ |
1371 |
switch (opt)
|
1372 |
{ |
1373 |
case 'D': // turn on debugging |
1374 |
ep_dbg_set(optarg); |
1375 |
break;
|
1376 |
|
1377 |
case 'G': // gdp daemon host:port |
1378 |
gdpd_addr = optarg; |
1379 |
break;
|
1380 |
|
1381 |
case 'p': // select listen port |
1382 |
listenport = atoi(optarg); |
1383 |
break;
|
1384 |
|
1385 |
case 'u': // URI prefix |
1386 |
GclUriPrefix = optarg; |
1387 |
break;
|
1388 |
|
1389 |
case 'C': // (development) gcl-create alternative |
1390 |
exec_gdp_create = optarg; |
1391 |
break;
|
1392 |
|
1393 |
default:
|
1394 |
show_usage = true;
|
1395 |
break;
|
1396 |
} |
1397 |
} |
1398 |
argc -= optind; |
1399 |
argv += optind; |
1400 |
|
1401 |
if (show_usage || argc > 0) |
1402 |
{ |
1403 |
fprintf(stderr, |
1404 |
"Usage: %s [-D dbgspec] [-G host:port] [-p port] [-u prefix] "
|
1405 |
"[-C gcl-create]\n", ep_app_getprogname());
|
1406 |
exit(EX_USAGE); |
1407 |
} |
1408 |
|
1409 |
if (listenport < 0) |
1410 |
listenport = ep_adm_getintparam("swarm.rest.scgi.port", 8001); |
1411 |
|
1412 |
// Initialize the GDP library
|
1413 |
// Also initializes the EVENT library and starts the I/O thread
|
1414 |
// Initialize shared gcl open info with caching enabled
|
1415 |
{ |
1416 |
EP_STAT estat = gdp_init(gdpd_addr); |
1417 |
char ebuf[100]; |
1418 |
|
1419 |
if (!EP_STAT_ISOK(estat))
|
1420 |
{ |
1421 |
// DNS lookup failure is usually an external network
|
1422 |
// issue, so exit with an errorcode and let the system
|
1423 |
// control restart attempts
|
1424 |
if (EP_STAT_IS_SAME(estat, EP_STAT_DNS_FAILURE))
|
1425 |
{ |
1426 |
char nbuf[40]; |
1427 |
|
1428 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
1429 |
ep_app_error("Cannot initialize gdp library: %s", nbuf);
|
1430 |
return EX_TEMPFAIL;
|
1431 |
} |
1432 |
|
1433 |
ep_app_abort("Cannot initialize gdp library: %s",
|
1434 |
ep_stat_tostr(estat, ebuf, sizeof ebuf));
|
1435 |
} |
1436 |
|
1437 |
shared_gdp_open_info = gdp_open_info_new(); |
1438 |
estat = gdp_open_info_set_caching(shared_gdp_open_info, true);
|
1439 |
|
1440 |
if (!EP_STAT_ISOK(estat))
|
1441 |
{ |
1442 |
ep_app_abort("Cannot initialize gdp gcl cache preference: %s",
|
1443 |
ep_stat_tostr(estat, ebuf, sizeof ebuf));
|
1444 |
} |
1445 |
} |
1446 |
|
1447 |
ep_dbg_cprintf(Dbg, 9, "GDP initialized\n"); |
1448 |
|
1449 |
// Initialize SCGI library
|
1450 |
scgi_debug = ep_dbg_level(Dbg) / 10;
|
1451 |
if (scgi_initialize(listenport))
|
1452 |
{ |
1453 |
ep_dbg_cprintf(Dbg, 1,
|
1454 |
"%s: listening for SCGI on port %d, scgi_debug %d\n",
|
1455 |
ep_app_getprogname(), listenport, scgi_debug); |
1456 |
} |
1457 |
else
|
1458 |
{ |
1459 |
char nbuf[40]; |
1460 |
|
1461 |
(void) (0 == strerror_r(errno, nbuf, sizeof nbuf)); |
1462 |
ep_app_error("could not initialize SCGI port %d: %s",
|
1463 |
listenport, nbuf); |
1464 |
return EX_OSERR;
|
1465 |
} |
1466 |
|
1467 |
// start looking for SCGI connections
|
1468 |
// XXX This should really be done through the event library
|
1469 |
// rather than by polling. To do this right there should
|
1470 |
// be a pool of worker threads that would have the SCGI
|
1471 |
// connection handed off to them.
|
1472 |
// XXX May be able to cheat by changing the select() in
|
1473 |
// scgi_update_connections_port to wait. It's OK if this
|
1474 |
// thread hangs since the other work happens in a different
|
1475 |
// thread.
|
1476 |
//
|
1477 |
// originally 100000 x 1000LL gave > 200ms response time for EMG demo tests
|
1478 |
// reducing the poll to 1000 x 1000LL gave 110.83 ms average responses
|
1479 |
// reducing the poll to 10 x 1000LL improve minimum but widen standard dev.
|
1480 |
// dialed back the poll to 100 x 1000LL for the 2017 EC Annual Review
|
1481 |
// Ali/Andy sent a video showing EMG control of the waffle robot with
|
1482 |
// this setting -- too cool! -- commit the new setting to the repo!
|
1483 |
poll_delay = ep_adm_getlongparam("swarm.rest.scgi.pollinterval", 100); |
1484 |
ep_sd_notifyf("READY=1\n");
|
1485 |
for (;;)
|
1486 |
{ |
1487 |
gdp_event_t *gev; |
1488 |
EP_TIME_SPEC to = { 0, 0, 0.0 }; |
1489 |
|
1490 |
while ((gev = gdp_event_next(NULL, &to)) != NULL) |
1491 |
{ |
1492 |
process_event(gev); |
1493 |
gdp_event_free(gev); |
1494 |
} |
1495 |
|
1496 |
scgi_request *req = gdp_scgi_recv(); |
1497 |
int dead = 0; |
1498 |
|
1499 |
if (req == NULL) |
1500 |
{ |
1501 |
(void) ep_time_nanosleep(poll_delay * 1000LL); |
1502 |
continue;
|
1503 |
} |
1504 |
req->dead = &dead; |
1505 |
process_scgi_req(req); |
1506 |
} |
1507 |
} |