1 /**
2 * HTTP control interface
3 *
4 * Copyright (C) 2018 Christian Zuckschwerdt
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12 /*
13 # HTTP control interface
14
15 A choice of endpoints are available:
16 - "/": serves a user interface (currently redirected to hosted app)
17 (also serves "/favicon.ico", "/app.css", "/app.js", "/vendor.css", "/vendor.js")
18 - "/jsonrpc": JSON-RPC API
19 - "/cmd": simple JSON command API
20 - "/events": HTTP (chunked) streaming API, streams JSON events
21 - "/stream": HTTP (plain) streaming API, streams JSON events
22 - "/api": RESTful API (not implemented)
23 - "ws:": Websocket API (similar to cmd/events API)
24
25 ## JSON-RPC API
26
27 S.a. https://www.jsonrpc.org/specification
28
29 Examples:
30
31 {"jsonrpc": "2.0", "method": "sample_rate", "params": [1024000], "id": 0}
32 {"jsonrpc": "2.0", "result": "Ok", "id": 0}
33 {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
34
35 ## JSON command / Websocket API
36
37 Simplified JSON command and query.
38
39 Examples:
40
41 {"cmd": "sample_rate", "val": 1024000}
42 {"result": "Ok"}
43 {"error": "Invalid Request"}}
44
45 ## HTTP events / streaming / Websocket API
46
47 You will receive JSON events, one per line terminated with CRLF.
48 On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds.
49 Use e.g. httpie with `http --stream --timeout=70 :8433/events`
50 or `(echo "GET /stream HTTP/1.0\n"; sleep 600) | socat - tcp:127.0.0.1:8433`
51
52 ## Queries
53
54 - "registered_protocols"
55 - "enabled_protocols"
56 - "protocol_info"
57 .name
58 .modulation
59 .short_width
60 .long_width
61 .sync_width
62 .tolerance
63 .gap_limit
64 .reset_limit
65 .fields
66
67 - "device_info"
68 device 0: Realtek, RTL2838UHIDIR, SN: 00000001
69 Found Rafael Micro R820T tuner
70 Using device 0: Generic RTL2832U OEM
71
72 - "settings"
73 "device": 0
74 "gain": 0
75 "center_frequency": 433920000
76 "hop_interval": 600
77 "ppm_error": 0
78 "sample_rate": 250000
79 "report_meta": ["time", "reltime", "notime", "hires", "utc", "protocol", "level"]
80 "convert": "native"|"si"|"customary"
81
82 ## Commands
83
84 - "device": 0
85 - "gain": 0
86 - "center_frequency": 433920000
87 - "hop_interval": 600
88 - "ppm_error": 0
89 - "sample_rate": 250000
90 - "report_meta": "time"|"reltime"|"notime"|"hires"|"utc"|"protocol"|"level"
91 - "convert": "native"|"si"|"customary"
92 - "protocol": 1
93
94 */
95
96 #include "http_server.h"
97 #include "data.h"
98 #include "rtl_433.h"
99 #include "r_api.h"
100 #include "r_device.h" // used for protocols
101 #include "r_private.h" // used for protocols
102 #include "r_util.h"
103 #include "optparse.h"
104 #include "abuf.h"
105 #include "list.h" // used for protocols
106 #include "jsmn.h"
107 #include "mongoose.h"
108 #include "fatal.h"
109 #include <stdbool.h>
110
111 // embed index.html so browsers allow access as local
112 #define INDEX_HTML \
113 "<!DOCTYPE html>" \
114 "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" \
115 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">" \
116 "<link rel=\"icon\" href=\"https://triq.org/rxui/favicon.ico\">" \
117 "<title>rxui</title>" \
118 "<link href=\"https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons\" rel=\"stylesheet\">" \
119 "<link href=\"https://triq.org/rxui/css/app.css\" rel=\"preload\" as=\"style\">" \
120 "<link href=\"https://triq.org/rxui/css/chunk-vendors.css\" rel=\"preload\" as=\"style\">" \
121 "<link href=\"https://triq.org/rxui/js/app.js\" rel=\"preload\" as=\"script\">" \
122 "<link href=\"https://triq.org/rxui/js/chunk-vendors.js\" rel=\"preload\" as=\"script\">" \
123 "<link href=\"https://triq.org/rxui/css/chunk-vendors.css\" rel=\"stylesheet\">" \
124 "<link href=\"https://triq.org/rxui/css/app.css\" rel=\"stylesheet\">" \
125 "<div id=\"app\"></div>" \
126 "<noscript><strong>We're sorry but rxui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>" \
127 "<script src=\"https://triq.org/rxui/js/chunk-vendors.js\"></script>" \
128 "<script src=\"https://triq.org/rxui/js/app.js\"></script>"
129
130 // generic ring list
131
132 #define DEFAULT_HISTORY_SIZE 100
133
134 typedef struct {
135 unsigned size;
136 void **data;
137 void **head;
138 void **tail;
139 } ring_list_t;
140
ring_list_new(unsigned size)141 static ring_list_t *ring_list_new(unsigned size)
142 {
143 ring_list_t *ring = calloc(1, sizeof(ring_list_t));
144 if (!ring) {
145 WARN_CALLOC("ring_list_new()");
146 return NULL;
147 }
148
149 ring->data = calloc(size, sizeof(void *));
150 if (!ring->data) {
151 WARN_CALLOC("ring_list_new()");
152 free(ring);
153 return NULL;
154 }
155
156 ring->size = size;
157 ring->tail = ring->data;
158
159 return ring;
160 }
161
162 // the ring needs to be empty before calling this
ring_list_free(ring_list_t * ring)163 static void ring_list_free(ring_list_t *ring)
164 {
165 if (ring) {
166 if (ring->data)
167 free(ring->data);
168 free(ring);
169 }
170 }
171
172 // free the data returned
ring_list_shift(ring_list_t * ring)173 static void *ring_list_shift(ring_list_t *ring)
174 {
175 if (!ring->head)
176 return NULL;
177
178 void *ret = *ring->head;
179
180 ++ring->head;
181 if (ring->head >= ring->data + ring->size)
182 ring->head -= ring->size;
183 if (ring->head == ring->tail)
184 ring->head = NULL;
185
186 return ret;
187 }
188
189 // retain data before passing in and free the data returned.
ring_list_push(ring_list_t * ring,void * data)190 static void *ring_list_push(ring_list_t *ring, void *data)
191 {
192 *ring->tail = data;
193
194 if (!ring->head)
195 ring->head = ring->tail;
196
197 ++ring->tail;
198 if (ring->tail >= ring->data + ring->size)
199 ring->tail -= ring->size;
200
201 if (ring->tail == ring->head)
202 return ring_list_shift(ring);
203
204 return NULL;
205 }
206
ring_list_iter(ring_list_t * ring)207 static void **ring_list_iter(ring_list_t *ring)
208 {
209 return ring->head;
210 }
211
ring_list_next(ring_list_t * ring,void ** iter)212 static void **ring_list_next(ring_list_t *ring, void **iter)
213 {
214 if (!iter)
215 return NULL;
216
217 ++iter;
218 if (iter >= ring->data + ring->size)
219 iter -= ring->size;
220 if (iter == ring->tail)
221 iter = NULL;
222
223 return iter;
224 }
225
226 // data helpers that could go into r_api
227
meta_data(r_cfg_t * cfg)228 static data_t *meta_data(r_cfg_t *cfg)
229 {
230 return data_make(
231 "frequencies", "", DATA_ARRAY, data_array(cfg->frequencies, DATA_INT, cfg->frequency),
232 "hop_times", "", DATA_ARRAY, data_array(cfg->hop_times, DATA_INT, cfg->hop_time),
233 "center_frequency", "", DATA_INT, cfg->center_frequency,
234 "duration", "", DATA_INT, cfg->duration,
235 "samp_rate", "", DATA_INT, cfg->samp_rate,
236 "conversion_mode", "", DATA_INT, cfg->conversion_mode,
237 "fsk_pulse_detect_mode", "", DATA_INT, cfg->fsk_pulse_detect_mode,
238 "after_successful_events_flag", "", DATA_INT, cfg->after_successful_events_flag,
239 "report_meta", "", DATA_INT, cfg->report_meta,
240 "report_protocol", "", DATA_INT, cfg->report_protocol,
241 "report_time", "", DATA_INT, cfg->report_time,
242 "report_time_hires", "", DATA_INT, cfg->report_time_hires,
243 "report_time_tz", "", DATA_INT, cfg->report_time_tz,
244 "report_time_utc", "", DATA_INT, cfg->report_time_utc,
245 "report_description", "", DATA_INT, cfg->report_description,
246 "report_stats", "", DATA_INT, cfg->report_stats,
247 "stats_interval", "", DATA_INT, cfg->stats_interval,
248 NULL);
249 }
250
protocols_data(r_cfg_t * cfg)251 static data_t *protocols_data(r_cfg_t *cfg)
252 {
253 list_t devs = {0};
254 list_ensure_size(&devs, cfg->num_r_devices);
255
256 for (int i = 0; i < cfg->num_r_devices; ++i) {
257 r_device *dev = &cfg->devices[i];
258
259 int enabled = 0;
260 for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {
261 r_device *r_dev = *iter;
262 if (r_dev->protocol_num == dev->protocol_num) {
263 enabled = 1;
264 break;
265 }
266 }
267 int fields_len = 0;
268 for (char **iter = dev->fields; iter && *iter; ++iter) {
269 fields_len++;
270 }
271 data_t *data = data_make(
272 "num", "", DATA_INT, dev->protocol_num,
273 "name", "", DATA_STRING, dev->name,
274 "mod", "", DATA_INT, dev->modulation,
275 "short", "", DATA_DOUBLE, dev->short_width,
276 "long", "", DATA_DOUBLE, dev->long_width,
277 "reset", "", DATA_DOUBLE, dev->reset_limit,
278 "gap", "", DATA_DOUBLE, dev->gap_limit,
279 "sync", "", DATA_DOUBLE, dev->sync_width,
280 "tolerance", "", DATA_DOUBLE, dev->tolerance,
281 "fields", "", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),
282 "def", "", DATA_INT, dev->disabled == 0,
283 "en", "", DATA_INT, enabled,
284 "verbose", "", DATA_INT, dev->verbose,
285 "verbose_bits", "", DATA_INT, dev->verbose_bits,
286 NULL);
287 list_push(&devs, data);
288 }
289
290 for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {
291 r_device *dev = *iter;
292 if (dev->protocol_num > 0) {
293 continue;
294 }
295 int fields_len = 0;
296 for (char **iter2 = dev->fields; iter2 && *iter2; ++iter2) {
297 fields_len++;
298 }
299 data_t *data = data_make(
300 "name", "", DATA_STRING, dev->name,
301 "mod", "", DATA_INT, dev->modulation,
302 "short", "", DATA_DOUBLE, dev->short_width,
303 "long", "", DATA_DOUBLE, dev->long_width,
304 "reset", "", DATA_DOUBLE, dev->reset_limit,
305 "gap", "", DATA_DOUBLE, dev->gap_limit,
306 "sync", "", DATA_DOUBLE, dev->sync_width,
307 "tolerance", "", DATA_DOUBLE, dev->tolerance,
308 "fields", "", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),
309 "en", "", DATA_INT, 1,
310 "verbose", "", DATA_INT, dev->verbose,
311 "verbose_bits", "", DATA_INT, dev->verbose_bits,
312 NULL);
313 list_push(&devs, data);
314 }
315
316 data_t *data = data_make(
317 "protocols", "", DATA_ARRAY, data_array(devs.len, DATA_DATA, devs.elems),
318 NULL);
319 list_free_elems(&devs, NULL);
320 return data;
321 }
322
323 // very narrowly tailored JSON parsing
324
325 typedef struct rpc rpc_t;
326
327 typedef void (*rpc_response_fn)(rpc_t *rpc, int error_code, char const *message, int is_json);
328
329 struct rpc {
330 struct mg_connection *nc;
331 rpc_response_fn response;
332 int ver;
333 char *method;
334 char *arg;
335 uint32_t val;
336 //list_t params;
337 char *id;
338 };
339
jsoneq(const char * json,jsmntok_t * tok,const char * s)340 static int jsoneq(const char *json, jsmntok_t *tok, const char *s)
341 {
342 if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&
343 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
344 return 0;
345 }
346 return -1;
347 }
348
jsondup(const char * json,jsmntok_t * tok)349 static char *jsondup(const char *json, jsmntok_t *tok)
350 {
351 int len = tok->end - tok->start;
352 char *p = malloc(len + 1);
353 if (!p) {
354 WARN_MALLOC("jsondup()");
355 return NULL;
356 }
357 p[len] = '\0';
358 return memcpy(p, json + tok->start, len);
359 }
360
jsondupq(const char * json,jsmntok_t * tok)361 static char *jsondupq(const char *json, jsmntok_t *tok)
362 {
363 int len = tok->end - tok->start + 2;
364 char *p = malloc(len + 1);
365 if (!p) {
366 WARN_MALLOC("jsondupq()");
367 return NULL;
368 }
369 p[len] = '\0';
370 return memcpy(p, json + tok->start - 1, len);
371 }
372
373 // {"cmd": "report_meta", "arg": "utc", "val": 1}
json_parse(rpc_t * rpc,struct mg_str const * json)374 static int json_parse(rpc_t *rpc, struct mg_str const *json)
375 {
376 int i;
377 int r;
378 jsmn_parser p;
379 jsmntok_t t[16]; /* We expect no more than 7 tokens */
380
381 char *cmd = NULL;
382 char *arg = NULL;
383 uint32_t val = 0;
384
385 jsmn_init(&p);
386 r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));
387 if (r < 0) {
388 printf("Failed to parse JSON: %d\n", r);
389 return -1;
390 }
391
392 /* Assume the top-level element is an object */
393 if (r < 1 || t[0].type != JSMN_OBJECT) {
394 printf("Object expected\n");
395 return -1;
396 }
397
398 /* Loop over all keys of the root object */
399 for (i = 1; i < r; i++) {
400 if (jsoneq(json->p, &t[i], "cmd") == 0) {
401 i++;
402 free(cmd);
403 cmd = jsondup(json->p, &t[i]);
404 }
405 else if (jsoneq(json->p, &t[i], "arg") == 0) {
406 i++;
407 free(arg);
408 arg = jsondup(json->p, &t[i]);
409 }
410 else if (jsoneq(json->p, &t[i], "val") == 0) {
411 i++;
412 char *endptr = NULL;
413 val = strtol(json->p + t[i].start, &endptr, 10);
414 // compare endptr to t[i].end
415 }
416 else {
417 printf("Unexpected key: %.*s\n", t[i].end - t[i].start, json->p + t[i].start);
418 }
419 }
420
421 if (!cmd) {
422 free(arg);
423 return -1;
424 }
425 rpc->method = cmd;
426 rpc->arg = arg;
427 rpc->val = val;
428 return 0;
429 }
430
431 // {"jsonrpc": "2.0", "method": "report_meta", "params": ["utc", 1], "id": 0}
jsonrpc_parse(rpc_t * rpc,struct mg_str const * json)432 static int jsonrpc_parse(rpc_t *rpc, struct mg_str const *json)
433 {
434 int r;
435 jsmn_parser p;
436 jsmntok_t t[16]; /* We expect no more than 11 tokens */
437
438 char *cmd = NULL;
439 char *id = NULL;
440 char *arg = NULL;
441 uint32_t val = 0;
442
443 jsmn_init(&p);
444 r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));
445 if (r < 0) {
446 printf("Failed to parse JSON: %d\n", r);
447 return -1;
448 }
449
450 /* Assume the top-level element is an object */
451 if (r < 1 || t[0].type != JSMN_OBJECT) {
452 printf("Object expected\n");
453 return -1;
454 }
455
456 /* Loop over all keys of the root object */
457 for (int i = 1; i < r; i++) {
458 if (jsoneq(json->p, &t[i], "jsonrpc") == 0) {
459 i++;
460 // (jsoneq(json->p, &t[i], "2.0") == 0);
461 }
462 else if (jsoneq(json->p, &t[i], "method") == 0) {
463 i++;
464 free(cmd);
465 cmd = jsondup(json->p, &t[i]);
466 }
467 else if (jsoneq(json->p, &t[i], "id") == 0) {
468 i++;
469 if (t[i].type == JSMN_STRING) {
470 free(id);
471 id = jsondupq(json->p, &t[i]);
472 }
473 else if (t[i].type == JSMN_PRIMITIVE) {
474 free(id);
475 id = jsondup(json->p, &t[i]);
476 }
477 }
478 else if (jsoneq(json->p, &t[i], "params") == 0) {
479 //printf("- Params:\n");
480 if (t[i + 1].type != JSMN_ARRAY) {
481 continue; /* We expect groups to be an array of strings */
482 }
483 for (int j = 0; j < t[i + 1].size; j++) {
484 jsmntok_t *g = &t[i + j + 2];
485 if (g->type == JSMN_STRING) {
486 free(arg);
487 arg = jsondup(json->p, g);
488 }
489 else if (g->type == JSMN_PRIMITIVE) {
490 // Number, null/true/false not supported
491 char *endptr = NULL;
492 val = strtol(json->p + g->start, &endptr, 10);
493 }
494 //printf(" * %.*s\n", g->end - g->start, json + g->start);
495 }
496 i += t[i + 1].size + 1;
497 }
498 else {
499 printf("Unexpected key: %.*s\n", t[i].end - t[i].start, json->p + t[i].start);
500 }
501 }
502
503 if (!cmd) {
504 free(id);
505 free(arg);
506 return -1;
507 }
508 rpc->method = cmd;
509 rpc->arg = arg;
510 rpc->val = val;
511 rpc->id = id;
512 return 0;
513 }
514
rpc_exec(rpc_t * rpc,r_cfg_t * cfg)515 static void rpc_exec(rpc_t *rpc, r_cfg_t *cfg)
516 {
517 if (!rpc || !rpc->method || !*rpc->method) {
518 rpc->response(rpc, -1, "Method invalid", 0);
519 }
520 // Getter
521 else if (!strcmp(rpc->method, "get_dev_query")) {
522 rpc->response(rpc, 0, cfg->dev_query, 0);
523 }
524 else if (!strcmp(rpc->method, "get_dev_info")) {
525 rpc->response(rpc, 1, cfg->dev_info, 0);
526 }
527 else if (!strcmp(rpc->method, "get_gain")) {
528 rpc->response(rpc, 0, cfg->gain_str, 0);
529 }
530
531 else if (!strcmp(rpc->method, "get_ppm_error")) {
532 rpc->response(rpc, 2, NULL, cfg->ppm_error);
533 }
534 else if (!strcmp(rpc->method, "get_hop_interval")) {
535 rpc->response(rpc, 2, NULL, cfg->hop_time[0]);
536 }
537 else if (!strcmp(rpc->method, "get_center_frequency")) {
538 rpc->response(rpc, 3, NULL, cfg->center_frequency); // unsigned
539 }
540 else if (!strcmp(rpc->method, "get_sample_rate")) {
541 rpc->response(rpc, 3, NULL, cfg->samp_rate); // unsigned
542 }
543 else if (!strcmp(rpc->method, "get_grab_mode")) {
544 rpc->response(rpc, 2, NULL, cfg->grab_mode);
545 }
546 else if (!strcmp(rpc->method, "get_raw_mode")) {
547 rpc->response(rpc, 2, NULL, cfg->raw_mode);
548 }
549 else if (!strcmp(rpc->method, "get_verbosity")) {
550 rpc->response(rpc, 2, NULL, cfg->verbosity);
551 }
552 else if (!strcmp(rpc->method, "get_verbose_bits")) {
553 rpc->response(rpc, 2, NULL, cfg->verbose_bits);
554 }
555 else if (!strcmp(rpc->method, "get_conversion_mode")) {
556 rpc->response(rpc, 2, NULL, cfg->conversion_mode);
557 }
558 else if (!strcmp(rpc->method, "get_stats")) {
559 char buf[20480]; // we expect the stats string to be around 15k bytes.
560 data_t *data = create_report_data(cfg, 2/*report active devices*/);
561 // flush_report_data(cfg); // snapshot, do not flush
562 data_print_jsons(data, buf, sizeof(buf));
563 rpc->response(rpc, 1, buf, 0);
564 data_free(data);
565 }
566 else if (!strcmp(rpc->method, "get_meta")) {
567 char buf[2048]; // we expect the meta string to be around 500 bytes.
568 data_t *data = meta_data(cfg);
569 data_print_jsons(data, buf, sizeof(buf));
570 rpc->response(rpc, 1, buf, 0);
571 data_free(data);
572 }
573 else if (!strcmp(rpc->method, "get_protocols")) {
574 char buf[65536]; // we expect the protocol string to be around 60k bytes.
575 data_t *data = protocols_data(cfg);
576 data_print_jsons(data, buf, sizeof(buf));
577 rpc->response(rpc, 1, buf, 0);
578 data_free(data);
579 }
580
581 // Setter
582 else if (!strcmp(rpc->method, "hop_interval")) {
583 cfg->hop_time[0] = rpc->val;
584 rpc->response(rpc, 0, "Ok", 0);
585 }
586 else if (!strcmp(rpc->method, "report_meta")) {
587 if (!rpc->arg)
588 rpc->response(rpc, -1, "Missing arg", 0);
589 else if (!strcasecmp(rpc->arg, "time"))
590 cfg->report_time = REPORT_TIME_DATE;
591 else if (!strcasecmp(rpc->arg, "reltime"))
592 cfg->report_time = REPORT_TIME_SAMPLES;
593 else if (!strcasecmp(rpc->arg, "notime"))
594 cfg->report_time = REPORT_TIME_OFF;
595 else if (!strcasecmp(rpc->arg, "hires"))
596 cfg->report_time_hires = rpc->val;
597 else if (!strcasecmp(rpc->arg, "utc"))
598 cfg->report_time_utc = rpc->val;
599 else if (!strcasecmp(rpc->arg, "protocol"))
600 cfg->report_protocol = rpc->val;
601 else if (!strcasecmp(rpc->arg, "level"))
602 cfg->report_meta = rpc->val;
603 else if (!strcasecmp(rpc->arg, "bits"))
604 cfg->verbose_bits = rpc->val;
605 else if (!strcasecmp(rpc->arg, "description"))
606 cfg->report_description = rpc->val;
607 else
608 cfg->report_meta = rpc->val;
609 rpc->response(rpc, 0, "Ok", 0);
610 }
611 else if (!strcmp(rpc->method, "convert")) {
612 cfg->conversion_mode = rpc->val;
613 rpc->response(rpc, 0, "Ok", 0);
614 }
615 else if (!strcmp(rpc->method, "raw_mode")) {
616 cfg->raw_mode = rpc->val;
617 rpc->response(rpc, 0, "Ok", 0);
618 }
619 else if (!strcmp(rpc->method, "verbosity")) {
620 cfg->verbosity = rpc->val;
621 rpc->response(rpc, 0, "Ok", 0);
622 }
623 else if (!strcmp(rpc->method, "verbose_bits")) {
624 cfg->verbose_bits = rpc->val;
625 rpc->response(rpc, 0, "Ok", 0);
626 }
627 else if (!strcmp(rpc->method, "protocol")) {
628 // set_protocol(rpc->val);
629 rpc->response(rpc, 0, "Ok", 0);
630 }
631
632 // Apply
633 else if (!strcmp(rpc->method, "device")) {
634 if (!rpc->arg)
635 rpc->response(rpc, -1, "Missing arg", 0);
636 /*
637 if (cfg->set_dev_query)
638 rpc->response(rpc, -1, "Try again later", 0);
639 cfg->set_dev_query = strdup(rpc->arg);
640 if (!cfg->set_dev_query) {
641 WARN_STRDUP("rpc_exec()");
642 }
643 */
644 rpc->response(rpc, -1, "Not implemented", 0);
645 }
646 else if (!strcmp(rpc->method, "gain")) {
647 if (!rpc->arg)
648 rpc->response(rpc, -1, "Missing arg", 0);
649 set_gain_str(cfg, rpc->arg);
650 rpc->response(rpc, 0, "Ok", 0);
651 }
652 else if (!strcmp(rpc->method, "center_frequency")) {
653 set_center_freq(cfg, rpc->val);
654 rpc->response(rpc, 0, "Ok", 0);
655 }
656 else if (!strcmp(rpc->method, "ppm_error")) {
657 set_freq_correction(cfg, rpc->val);
658 rpc->response(rpc, 0, "Ok", 0);
659 }
660 else if (!strcmp(rpc->method, "sample_rate")) {
661 set_sample_rate(cfg, rpc->val);
662 rpc->response(rpc, 0, "Ok", 0);
663 }
664
665 // Invalid
666 else {
667 rpc->response(rpc, -1, "Unknown method", 0);
668 }
669 }
670
671 // http server
672
673 #define KEEP_ALIVE 60 /* seconds */
674
675 struct http_server_context {
676 struct mg_connection *conn;
677 struct mg_serve_http_opts server_opts;
678 r_cfg_t *cfg;
679 struct data_output *output;
680 ring_list_t *history;
681 };
682
683 struct nc_context {
684 int is_chunked;
685 };
686
handle_options(struct mg_connection * nc,struct http_message * hm)687 static void handle_options(struct mg_connection *nc, struct http_message *hm)
688 {
689 UNUSED(hm);
690 mg_printf(nc,
691 "HTTP/1.1 204 No Content\r\n"
692 "Content-Length: 0\r\n"
693 "Cache-Control: max-age=0, private, must-revalidate\r\n"
694 "Access-Control-Allow-Origin: *\r\n"
695 "Access-Control-Expose-Headers:\r\n"
696 "Access-Control-Allow-Credentials: true\r\n"
697 "Access-Control-Max-Age: 1728000\r\n"
698 "Access-Control-Allow-Headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-CSRF-Token\r\n"
699 "Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS\r\n"
700 "\r\n");
701 }
702
handle_get(struct mg_connection * nc,struct http_message * hm,char const * buf,unsigned int len)703 static void handle_get(struct mg_connection *nc, struct http_message *hm, char const *buf, unsigned int len)
704 {
705 UNUSED(hm);
706 //mg_send_head(nc, 200, -1, NULL);
707 mg_printf(nc,
708 "HTTP/1.1 200 OK\r\n"
709 "Content-Length: %u\r\n"
710 "\r\n", len);
711 mg_send(nc, buf, (size_t)len);
712 }
713
handle_redirect(struct mg_connection * nc,struct http_message * hm)714 static void handle_redirect(struct mg_connection *nc, struct http_message *hm)
715 {
716 // get the host header
717 struct mg_str host = {0};
718 for (int i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
719 // struct mg_str hn = hm->header_names[i];
720 // struct mg_str hv = hm->header_values[i];
721 // fprintf(stderr, "Header: %.*s: %.*s\n", (int)hn.len, hn.p, (int)hv.len, hv.p);
722 if (mg_vcasecmp(&hm->header_names[i], "Host") == 0) {
723 host = hm->header_values[i];
724 break;
725 }
726 }
727
728 mg_printf(nc, "%s%s%.*s%s\r\n",
729 "HTTP/1.1 307 Temporary Redirect\r\n",
730 "Location: http://triq.org/rxui/#",
731 (int)host.len, host.p,
732 "\r\n\r\n");
733 }
734
735 // reply to ws command
rpc_response_ws(rpc_t * rpc,int ret_code,char const * message,int arg)736 static void rpc_response_ws(rpc_t *rpc, int ret_code, char const *message, int arg)
737 {
738 if (ret_code < 0) {
739 mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
740 "{\"error\": {\"code\": %d, \"message\": \"%s\"}}",
741 ret_code, message);
742 }
743 else if (ret_code == 0 && message) {
744 mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
745 "{\"result\": \"%s\"}",
746 message);
747 }
748 else if (ret_code == 0) {
749 mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
750 "{\"result\": null}");
751 }
752 else if (ret_code == 1) {
753 mg_send_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT, message, strlen(message));
754 }
755 else if (ret_code == 2) {
756 mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
757 "{\"result\": %d}",
758 arg);
759 }
760 else /* if (ret_code == 3) */ {
761 mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,
762 "{\"result\": %u}",
763 (unsigned)arg);
764 }
765 }
766
767 // reply to jsonrpc command
rpc_response_jsonrpc(rpc_t * rpc,int ret_code,char const * message,int arg)768 static void rpc_response_jsonrpc(rpc_t *rpc, int ret_code, char const *message, int arg)
769 {
770 char const *id = rpc->id ? rpc->id : "null";
771 if (ret_code < 0) {
772 mg_printf_http_chunk(rpc->nc,
773 "{\"jsonrpc\": \"2.0\", \"error\": {\"code\": %d, \"message\": \"%s\"}, \"id\": %s}",
774 ret_code, message, id);
775 }
776 else if (ret_code == 0 && message) {
777 mg_printf_http_chunk(rpc->nc,
778 "{\"jsonrpc\": \"2.0\", \"result\": \"%s\", \"id\": %s}",
779 message, id);
780 }
781 else if (ret_code == 0) {
782 mg_printf_http_chunk(rpc->nc,
783 "{\"jsonrpc\": \"2.0\", \"result\": null, \"id\": %s}",
784 id);
785 }
786 else if (ret_code == 1) {
787 mg_printf_http_chunk(rpc->nc,
788 "{\"jsonrpc\": \"2.0\", \"result\": %s, \"id\": %s}",
789 message, id);
790 }
791 else if (ret_code == 2) {
792 mg_printf_http_chunk(rpc->nc,
793 "{\"jsonrpc\": \"2.0\", \"result\": %d, \"id\": %s}",
794 arg, id);
795 }
796 else /* if (ret_code == 3) */ {
797 mg_printf_http_chunk(rpc->nc,
798 "{\"jsonrpc\": \"2.0\", \"result\": %u, \"id\": %s}",
799 (unsigned)arg, id);
800 }
801 mg_send_http_chunk(rpc->nc, "", 0); /* Send empty chunk, the end of response */
802 }
803
804 // reply to json command
rpc_response_jsoncmd(rpc_t * rpc,int ret_code,char const * message,int arg)805 static void rpc_response_jsoncmd(rpc_t *rpc, int ret_code, char const *message, int arg)
806 {
807 if (ret_code < 0) {
808 mg_printf_http_chunk(rpc->nc,
809 "{\"error\": {\"code\": %d, \"message\": \"%s\"}}",
810 ret_code, message);
811 }
812 else if (ret_code == 0 &&message) {
813 mg_printf_http_chunk(rpc->nc,
814 "{\"result\": \"%s\"}",
815 message);
816 }
817 else if (ret_code == 0) {
818 mg_printf_http_chunk(rpc->nc,
819 "{\"result\": null}");
820 }
821 else if (ret_code == 1) {
822 mg_printf_http_chunk(rpc->nc,
823 "{\"result\": %s}",
824 message);
825 }
826 else if (ret_code == 2) {
827 mg_printf_http_chunk(rpc->nc,
828 "{\"result\": %d}",
829 arg);
830 }
831 else /* if (ret_code == 3) */ {
832 mg_printf_http_chunk(rpc->nc,
833 "{\"result\": %u}",
834 (unsigned)arg);
835 }
836 mg_send_http_chunk(rpc->nc, "", 0); /* Send empty chunk, the end of response */
837 }
838
839 // {"cmd":"sample_rate","val":1024000}
840 // http --stream --timeout=70 :8433/events
841 //s.a. https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data.html
handle_json_events(struct mg_connection * nc,struct http_message * hm)842 static void handle_json_events(struct mg_connection *nc, struct http_message *hm)
843 {
844 UNUSED(hm);
845 /* Send headers */
846 mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
847
848 /* Mark connection */
849 struct nc_context *ctx = calloc(1, sizeof(*ctx));
850 if (!ctx) {
851 WARN_CALLOC("handle_json_events()");
852 return;
853 }
854 ctx->is_chunked = 1;
855 nc->user_data = ctx;
856
857 mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer
858 }
859
860 // (echo "GET /stream HTTP/1.0\n"; sleep 600) | socat - tcp:127.0.0.1:8433
handle_json_stream(struct mg_connection * nc,struct http_message * hm)861 static void handle_json_stream(struct mg_connection *nc, struct http_message *hm)
862 {
863 UNUSED(hm);
864 /* Send headers */
865 mg_printf(nc, "HTTP/1.1 200 OK\r\n\r\n");
866
867 /* Mark connection */
868 struct nc_context *ctx = calloc(1, sizeof(*ctx));
869 if (!ctx) {
870 WARN_CALLOC("handle_json_stream()");
871 return;
872 }
873 ctx->is_chunked = 0;
874 nc->user_data = ctx;
875
876 mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer
877 }
878
879 // Handles GET with query string and POST with form-encoded body
880 // curl -D - 'http://127.0.0.1:8433/cmd?cmd=report_meta&arg=level'
881 // curl -D - -d "cmd=report_meta&arg=level" -X POST 'http://127.0.0.1:8433/cmd'
882 // http :8433/cmd cmd==center_frequency val==868000000'
883 // http --form POST :8433/cmd cmd=report_meta arg=level val=1
handle_cmd_rpc(struct mg_connection * nc,struct http_message * hm)884 static void handle_cmd_rpc(struct mg_connection *nc, struct http_message *hm)
885 {
886 struct http_server_context *ctx = nc->user_data;
887 char cmd[100], arg[100], val[100];
888 rpc_t rpc = {
889 .nc = nc,
890 .response = rpc_response_jsoncmd,
891 .method = cmd,
892 .arg = arg,
893 };
894
895 /* Send headers */
896 mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
897
898 /* Get URL variables */
899 if (mg_vcmp(&hm->method, "GET") == 0) {
900 mg_get_http_var(&hm->query_string, "cmd", cmd, sizeof(cmd));
901 mg_get_http_var(&hm->query_string, "arg", arg, sizeof(arg));
902 mg_get_http_var(&hm->query_string, "val", val, sizeof(val));
903 }
904 /* Get form variables */
905 else {
906 mg_get_http_var(&hm->body, "cmd", cmd, sizeof(cmd));
907 mg_get_http_var(&hm->body, "arg", arg, sizeof(arg));
908 mg_get_http_var(&hm->body, "val", val, sizeof(val));
909 }
910 char *endptr = NULL;
911 rpc.val = strtol(val, &endptr, 10);
912 fprintf(stderr, "POST Got %s, arg %s, val %s (%u)\n", cmd, arg, val, rpc.val);
913
914 rpc_exec(&rpc, ctx->cfg);
915 }
916
917 // Handles POST with JSONRPC command
918 // http POST :8433/jsonrpc jsonrpc=2.0 method=sample_rate params:='[1024000]'
handle_json_rpc(struct mg_connection * nc,struct http_message * hm)919 static void handle_json_rpc(struct mg_connection *nc, struct http_message *hm)
920 {
921 struct http_server_context *ctx = nc->user_data;
922
923 rpc_t rpc = {
924 .nc = nc,
925 .response = rpc_response_jsonrpc,
926 };
927
928 /* Send headers */
929 mg_printf(nc, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
930
931 /* Parse JSON */
932 int ret = jsonrpc_parse(&rpc, &hm->body);
933 if (!ret) {
934 rpc_exec(&rpc, ctx->cfg);
935 }
936 else {
937 char *error = "{\"error\":\"Invalid command\"}";
938 mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));
939 }
940
941 free(rpc.method);
942 free(rpc.id);
943 free(rpc.arg);
944 }
945
946 // Handles WS with JSON command
handle_ws_rpc(struct mg_connection * nc,struct websocket_message * wm)947 static void handle_ws_rpc(struct mg_connection *nc, struct websocket_message *wm)
948 {
949 struct http_server_context *ctx = nc->user_data;
950
951 rpc_t rpc = {
952 .nc = nc,
953 .response = rpc_response_ws,
954 };
955
956 struct mg_str d = {(char *)wm->data, wm->size};
957
958 /* Parse JSON */
959 int ret = json_parse(&rpc, &d);
960 if (!ret) {
961 rpc_exec(&rpc, ctx->cfg);
962 }
963 else {
964 char *error = "{\"error\":\"Invalid command\"}";
965 mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));
966 }
967
968 free(rpc.method);
969 free(rpc.id);
970 free(rpc.arg);
971 }
972
973 static void ev_handler(struct mg_connection *nc, int ev, void *ev_data);
974
send_keep_alive(struct mg_connection * nc)975 static void send_keep_alive(struct mg_connection *nc)
976 {
977 if (nc->handler != ev_handler)
978 return; // this should not happen
979
980 struct nc_context *ctx = nc->user_data;
981 if (!ctx)
982 return; // this should not happen
983
984 if (ctx->is_chunked) {
985 mg_send_http_chunk(nc, "\r\n", 2);
986 }
987 else {
988 mg_send(nc, "\r\n", 2);
989 }
990 mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
991 }
992
ev_handler(struct mg_connection * nc,int ev,void * ev_data)993 static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
994 {
995 switch (ev) {
996 case MG_EV_TIMER:
997 send_keep_alive(nc);
998 break;
999 case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
1000 struct http_server_context *ctx = nc->user_data;
1001 /* New websocket connection. Send meta. */
1002 data_t *meta = meta_data(ctx->cfg);
1003 data_output_print(ctx->output, meta);
1004 data_free(meta);
1005 /* Send history */
1006 for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))
1007 mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, (char *)*iter, strlen((char *)*iter));
1008 break;
1009 }
1010 case MG_EV_WEBSOCKET_FRAME: {
1011 struct websocket_message *wm = (struct websocket_message *)ev_data;
1012
1013 handle_ws_rpc(nc, wm);
1014 break;
1015 }
1016 case MG_EV_HTTP_REQUEST: {
1017 struct http_message *hm = (struct http_message *)ev_data;
1018
1019 if (mg_vcmp(&hm->method, "OPTIONS") == 0) {
1020 handle_options(nc, hm);
1021 }
1022 else if (mg_vcmp(&hm->uri, "/") == 0) {
1023 handle_get(nc, hm, INDEX_HTML, sizeof(INDEX_HTML));
1024 handle_redirect(nc, hm);
1025 }
1026 else if (mg_vcmp(&hm->uri, "/ui") == 0) {
1027 handle_redirect(nc, hm);
1028 }
1029 else if (mg_vcmp(&hm->uri, "/jsonrpc") == 0) {
1030 handle_json_rpc(nc, hm);
1031 }
1032 else if (mg_vcmp(&hm->uri, "/cmd") == 0) {
1033 handle_cmd_rpc(nc, hm);
1034 }
1035 else if (mg_vcmp(&hm->uri, "/events") == 0) {
1036 handle_json_events(nc, hm);
1037 }
1038 else if (mg_vcmp(&hm->uri, "/stream") == 0) {
1039 handle_json_stream(nc, hm);
1040 }
1041 else if (mg_vcmp(&hm->uri, "/api") == 0) {
1042 //handle_api_query(nc, hm);
1043 }
1044 #ifdef SERVE_STATIC
1045 else {
1046 struct http_server_context *ctx = nc->user_data;
1047 mg_serve_http(nc, hm, ctx->server_opts); /* Serve static content */
1048 }
1049 #endif
1050 break;
1051 }
1052 case MG_EV_CLOSE:
1053 //fprintf(stderr, "MG_EV_CLOSE %p %p %p\n", ev_data, nc, nc->user_data);
1054 break;
1055 default:
1056 break;
1057 }
1058 }
1059
is_websocket(const struct mg_connection * nc)1060 static int is_websocket(const struct mg_connection *nc)
1061 {
1062 return nc->flags & MG_F_IS_WEBSOCKET;
1063 }
1064
1065 // event handler to broadcast to all our sockets
http_broadcast_send(struct http_server_context * ctx,char const * msg,size_t len)1066 static void http_broadcast_send(struct http_server_context *ctx, char const *msg, size_t len)
1067 {
1068 struct mg_connection *nc;
1069 struct mg_mgr *mgr = ctx->conn->mgr;
1070
1071 char *dup = strdup(msg);
1072 if (!dup) {
1073 WARN_STRDUP("http_broadcast_send()");
1074 }
1075 else {
1076 free(ring_list_push(ctx->history, dup));
1077 }
1078
1079 for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
1080 if (nc->handler != ev_handler)
1081 continue;
1082
1083 struct nc_context *cctx = nc->user_data; // might not be valid
1084 if (is_websocket(nc)) {
1085 mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, msg, len);
1086 }
1087 else if (cctx && cctx->is_chunked) {
1088 mg_send_http_chunk(nc, msg, len);
1089 mg_send_http_chunk(nc, "\r\n", 2);
1090 mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
1091 }
1092 else if (cctx && !cctx->is_chunked) {
1093 mg_send(nc, msg, len);
1094 mg_send(nc, "\r\n", 2);
1095 mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer
1096 }
1097 }
1098 }
1099
http_server_start(struct mg_mgr * mgr,char const * host,char const * port,r_cfg_t * cfg,struct data_output * output)1100 static struct http_server_context *http_server_start(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg, struct data_output *output)
1101 {
1102 struct mg_bind_opts bind_opts;
1103 const char *err_str;
1104
1105 //struct http_server_context
1106 struct http_server_context *ctx = calloc(1, sizeof(struct http_server_context));
1107 if (!ctx) {
1108 WARN_CALLOC("http_server_start()");
1109 return NULL;
1110 }
1111
1112 ctx->cfg = cfg;
1113 ctx->output = output;
1114 ctx->history = ring_list_new(DEFAULT_HISTORY_SIZE);
1115
1116 char address[253 + 6 + 1]; // dns max + port
1117 // if the host is an IPv6 address it needs quoting
1118 if (strchr(host, ':'))
1119 snprintf(address, sizeof(address), "[%s]:%s", host, port);
1120 else
1121 snprintf(address, sizeof(address), "%s:%s", host, port);
1122
1123 /* Set HTTP server options */
1124 memset(&bind_opts, 0, sizeof(bind_opts));
1125 bind_opts.user_data = ctx;
1126 bind_opts.error_string = &err_str;
1127
1128 ctx->conn = mg_bind_opt(mgr, address, ev_handler, bind_opts);
1129 if (ctx->conn == NULL) {
1130 fprintf(stderr, "Error starting server on address %s: %s\n", address,
1131 *bind_opts.error_string);
1132 free(ctx);
1133 return NULL;
1134 }
1135
1136 mg_set_protocol_http_websocket(ctx->conn);
1137 ctx->server_opts.document_root = "."; // Serve current directory
1138 ctx->server_opts.enable_directory_listing = "yes";
1139
1140 printf("Starting HTTP server on address %s, serving %s\n", address,
1141 ctx->server_opts.document_root);
1142
1143 return ctx;
1144 }
1145
1146 #define SHUTDOWN_JSON "{\"shutdown\":\"goodbye\"}"
1147
http_server_stop(struct http_server_context * ctx)1148 static int http_server_stop(struct http_server_context *ctx)
1149 {
1150 if (!ctx)
1151 return 0;
1152
1153 // close the server
1154 ctx->conn->user_data = NULL;
1155 ctx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;
1156
1157 // close connections with a goodbye
1158 struct mg_mgr *mgr = ctx->conn->mgr;
1159 for (struct mg_connection *nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
1160 if (nc->handler != ev_handler)
1161 continue;
1162
1163 struct nc_context *cctx = nc->user_data; // might not be valid
1164 if (is_websocket(nc)) {
1165 mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1166 }
1167 else if (cctx && cctx->is_chunked) {
1168 mg_send_http_chunk(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1169 mg_send_http_chunk(nc, "\r\n", 2);
1170 mg_send_http_chunk(nc, "", 0); /* Send empty chunk, the end of response */
1171 }
1172 else if (cctx && !cctx->is_chunked) {
1173 mg_send(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);
1174 mg_send(nc, "\r\n", 2);
1175 }
1176 }
1177
1178 for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))
1179 free((data_t *)*iter);
1180 ring_list_free(ctx->history);
1181
1182 return 0;
1183 }
1184
1185 /* HTTP data output */
1186
1187 typedef struct {
1188 struct data_output output;
1189 struct http_server_context *server;
1190 } data_output_http_t;
1191
print_http_data(data_output_t * output,data_t * data,char const * format)1192 static void print_http_data(data_output_t *output, data_t *data, char const *format)
1193 {
1194 UNUSED(format);
1195 data_output_http_t *http = (data_output_http_t *)output;
1196
1197 // collect well-known top level keys
1198 data_t *data_model = NULL;
1199 for (data_t *d = data; d; d = d->next) {
1200 if (!strcmp(d->key, "model"))
1201 data_model = d;
1202 }
1203
1204 if (data_model) {
1205 // "events"
1206 char buf[2048]; // we expect the biggest strings to be around 500 bytes.
1207 size_t len = data_print_jsons(data, buf, sizeof(buf));
1208 http_broadcast_send(http->server, buf, len);
1209 }
1210 else {
1211 // "states"
1212 size_t buf_size = 20000; // state message need a large buffer
1213 char *buf = malloc(buf_size);
1214 if (!buf) {
1215 WARN_MALLOC("print_http_data()");
1216 return; // NOTE: skip output on alloc failure.
1217 }
1218 size_t len = data_print_jsons(data, buf, buf_size);
1219 http_broadcast_send(http->server, buf, len);
1220 free(buf);
1221 }
1222 }
1223
data_output_http_free(data_output_t * output)1224 static void data_output_http_free(data_output_t *output)
1225 {
1226 data_output_http_t *http = (data_output_http_t *)output;
1227
1228 if (!http)
1229 return;
1230
1231 http_server_stop(http->server);
1232
1233 free(http);
1234 }
1235
data_output_http_create(struct mg_mgr * mgr,char const * host,char const * port,r_cfg_t * cfg)1236 struct data_output *data_output_http_create(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg)
1237 {
1238 data_output_http_t *http = calloc(1, sizeof(data_output_http_t));
1239 if (!http) {
1240 WARN_CALLOC("data_output_http_create()");
1241 return NULL;
1242 }
1243
1244 http->output.print_data = print_http_data;
1245 http->output.output_free = data_output_http_free;
1246
1247 http->server = http_server_start(mgr, host, port, cfg, &http->output);
1248 if (!http->server) {
1249 exit(1);
1250 }
1251
1252 return &http->output;
1253 }
1254