/* * Serve static files. * * Takes an object containing mongoose http server options. * Commonly used properties: * - `document_root`: Path to the web root directory * - `enable_directory_listing`: Set to "no" to disable directory listing. * Enabled by default. * - `extra_headers`: Extra HTTP headers to add to each server response. * * For the full option object definition see: * https://docs.cesanta.com/mongoose/dev/index.html#/c-api/http.h/struct_mg_serve_http_opts/ */ SJ_PRIVATE enum v7_err Http_response_serve(struct v7 *v7, v7_val_t *res) { struct mg_serve_http_opts opts; struct http_message hm; enum v7_err rcode = V7_OK; DECLARE_CONN(); size_t i, n; v7_val_t request = v7_get(v7, v7_get_this(v7), "_r", ~0); v7_val_t url_v = v7_get(v7, request, "url", ~0); const char *url = v7_get_string_data(v7, &url_v, &n); const char *quest = strchr(url, '?'); memset(&opts, 0, sizeof(opts)); memset(&hm, 0, sizeof(hm)); /* Set up "fake" parsed HTTP message */ hm.uri.p = url; hm.uri.len = quest == NULL ? n : n - (quest - url); if (v7_argc(v7) > 0) { populate_opts_from_js_argument(v7, v7_arg(v7, 0), &opts); } mg_serve_http(c, &hm, opts); for (i = 0; i < ARRAY_SIZE(s_map); i++) { free(*(char **) ((char *) &opts + s_map[i].offset)); } *res = v7_get_this(v7); clean: return rcode; }
/* JS signature: listen(addr, [options]) */ SJ_PRIVATE enum v7_err Http_Server_listen(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; char buf[50], *p = buf; const char *ca_cert = NULL, *cert = NULL; v7_val_t this_obj = v7_get_this(v7); v7_val_t arg0 = v7_arg(v7, 0); v7_val_t opts = v7_arg(v7, 1); if (!v7_is_number(arg0) && !v7_is_string(arg0)) { rcode = v7_throwf(v7, "TypeError", "Function expected"); goto clean; } if (!v7_is_undefined(opts) && !v7_is_object(opts)) { rcode = v7_throwf(v7, "TypeError", "Options must be an object"); goto clean; } if (!v7_is_undefined(opts)) { v7_val_t ca_cert_v = v7_get(v7, opts, "ssl_ca_cert", ~0); v7_val_t cert_v = v7_get(v7, opts, "ssl_cert", ~0); if (!v7_is_undefined(ca_cert_v) && !v7_is_string(ca_cert_v)) { rcode = v7_throwf(v7, "TypeError", "ca_cert must be a string"); goto clean; } if (!v7_is_undefined(cert_v) && !v7_is_string(cert_v)) { rcode = v7_throwf(v7, "TypeError", "cert must be a string"); goto clean; } if (!v7_is_undefined(ca_cert_v)) { ca_cert = v7_to_cstring(v7, &ca_cert_v); } if (!v7_is_undefined(cert_v)) { cert = v7_to_cstring(v7, &cert_v); } } p = v7_stringify(v7, arg0, buf, sizeof(buf), 0); rcode = start_http_server(v7, p, this_obj, ca_cert, cert); if (rcode != V7_OK) { goto clean; } *res = this_obj; clean: if (p != buf) { free(p); } return rcode; }
// Convert object to string, push string on stack V7_PRIVATE enum v7_err toString(struct v7 *v7, struct v7_val *obj) { struct v7_val *f = NULL; if ((f = v7_get(obj, "toString")) == NULL) { f = v7_get(&s_prototypes[V7_CLASS_OBJECT], "toString"); } CHECK(f != NULL, V7_INTERNAL_ERROR); TRY(v7_push(v7, f)); TRY(v7_call2(v7, obj, 0, 0)); return V7_OK; }
/* * Subscribes a mqtt client to a topic. */ enum v7_err MQTT_subscribe(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; struct user_data *ud; struct mg_connection *nc; struct mg_mqtt_topic_expression expr; v7_val_t topicv = v7_arg(v7, 0); const char *topic; nc = v7_get_ptr(v7, v7_get(v7, v7_get_this(v7), "_nc", ~0)); if (nc == NULL) { rcode = v7_throwf(v7, "Error", "unsupported protocol"); goto clean; } ud = (struct user_data *) nc->user_data; topic = v7_get_cstring(v7, &topicv); if (topic == NULL || strlen(topic) == 0) { rcode = v7_throwf(v7, "TypeError", "invalid topic"); goto clean; } expr.topic = topic; expr.qos = 0; mg_mqtt_subscribe(nc, &expr, 1, ud->msgid++); *res = v7_mk_boolean(v7, 1); clean: return rcode; }
/* * Publishes a message to a topic. * * Args: * - `topic`: topic, string. * - `message`: message, string. * * Only QOS 0 is implemented at the moment. */ enum v7_err MQTT_publish(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; struct user_data *ud; struct mg_connection *nc; const char *topic; const char *message; size_t message_len; v7_val_t topicv = v7_arg(v7, 0), messagev = v7_arg(v7, 1); (void) res; topic = v7_get_cstring(v7, &topicv); if (topic == NULL || strlen(topic) == 0) { rcode = v7_throwf(v7, "TypeError", "invalid topic"); goto clean; } if (!v7_is_string(messagev)) { rcode = v7_throwf(v7, "TypeError", "invalid message"); goto clean; } message = v7_get_string(v7, &messagev, &message_len); nc = v7_get_ptr(v7, v7_get(v7, v7_get_this(v7), "_nc", ~0)); if (nc == NULL) { rcode = v7_throwf(v7, "Error", "invalid connection"); goto clean; } ud = (struct user_data *) nc->user_data; mg_mqtt_publish(nc, topic, ud->msgid++, MG_MQTT_QOS(0), message, message_len); clean: return rcode; }
void sj_print_exception(struct v7 *v7, v7_val_t exc, const char *msg) { /* * TOD(mkm) add some API to hal to fetch the current debug mode * and avoid logging to stdout if according no error messages should go * there (e.g. because it's used to implement a serial protocol). */ FILE *fs[] = {stdout, stderr}; size_t i; v7_val_t msg_v = V7_UNDEFINED; /* * own because the exception could be a string, * and if not owned here, print_stack_trace could get * an unrelocated argument an ASN violation. */ v7_own(v7, &exc); v7_own(v7, &msg_v); msg_v = v7_get(v7, exc, "message", ~0); for (i = 0; i < sizeof(fs) / sizeof(fs[0]); i++) { fprintf(fs[i], "%s: ", msg); if (!v7_is_undefined(msg_v)) { v7_fprintln(fs[i], v7, msg_v); } else { v7_fprintln(fs[i], v7, exc); } v7_fprint_stack_trace(fs[i], v7, exc); } v7_disown(v7, &msg_v); v7_disown(v7, &exc); }
static void invoke_cb(struct user_data *ud, const char *name, v7_val_t ev) { struct v7 *v7 = ud->v7; v7_val_t met = v7_get(v7, ud->ws, name, ~0); if (!v7_is_undefined(met)) { sj_invoke_cb1(v7, met, ev); } }
static v7_val_t WebSocket_send(struct v7 *v7, v7_val_t this_obj, v7_val_t args) { v7_val_t datav = v7_array_get(v7, args, 0); v7_val_t ncv = v7_get(v7, this_obj, "_nc", ~0); struct mg_connection *nc; /* * TODO(alashkin): check why v7_is_instanceof throws exception * in case of string */ int is_blob = !v7_is_string(datav) && v7_is_instanceof(v7, datav, "Blob"); if (!v7_is_string(datav) && !is_blob) { v7_throw(v7, "arg should be string or Blob"); return v7_create_undefined(); } if (!v7_is_foreign(ncv) || (nc = (struct mg_connection *) v7_to_foreign(ncv)) == NULL) { v7_throw(v7, "ws not connected"); return v7_create_undefined(); } if (is_blob) { _WebSocket_send_blob(v7, nc, datav); } else { _WebSocket_send_string(v7, nc, datav); } return v7_create_undefined(); }
SJ_PRIVATE enum v7_err Http_response_writeHead(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; DECLARE_CONN(); unsigned long code = 200; v7_val_t arg0 = v7_arg(v7, 0), arg1 = v7_arg(v7, 1); if (v7_is_truthy(v7, v7_get(v7, v7_get_this(v7), "_whd", ~0))) { rcode = v7_throwf(v7, "Error", "Headers already sent"); goto clean; } if (v7_is_number(arg0)) { code = v7_to_number(arg0); } write_http_status(c, code); http_write_headers(v7, arg1, c); mg_send(c, "\r\n", 2); v7_set(v7, v7_get_this(v7), "_whd", ~0, v7_mk_boolean(1)); *res = v7_get_this(v7); clean: return rcode; }
static enum v7_err start_http_server(struct v7 *v7, const char *addr, v7_val_t obj, const char *ca_cert, const char *cert) { enum v7_err rcode = V7_OK; struct mg_connection *c; struct user_data *ud; struct mg_bind_opts opts; memset(&opts, 0, sizeof(opts)); #ifdef MG_ENABLE_SSL opts.ssl_ca_cert = ca_cert; opts.ssl_cert = cert; #else (void) ca_cert; (void) cert; #endif c = mg_bind_opt(&sj_mgr, addr, http_ev_handler, opts); if (c == NULL) { rcode = v7_throwf(v7, "Error", "Cannot bind"); goto clean; } mg_set_protocol_http_websocket(c); c->user_data = ud = (struct user_data *) malloc(sizeof(*ud)); ud->v7 = v7; ud->obj = obj; ud->handler = v7_get(v7, obj, "_cb", 3); v7_own(v7, &ud->obj); clean: return rcode; }
/* * Read the content of the UART. It does not block. * Optional `max_len` parameter, defaults to max size_t. */ static enum v7_err UART_read(struct v7 *v7, v7_val_t *res) { v7_val_t this_obj = v7_get_this(v7); v7_val_t dev = v7_get(v7, this_obj, "_dev", ~0), maxv = v7_arg(v7, 0); size_t max = v7_is_number(maxv) ? (size_t) v7_to_number(maxv) : ~0; *res = sj_hal_read_uart(v7, v7_to_foreign(dev), max); return V7_OK; }
static struct clubby *get_clubby(struct v7 *v7, v7_val_t obj) { v7_val_t clubbyv = v7_get(v7, obj, s_clubby_prop, sizeof(s_clubby_prop)); if (!v7_is_foreign(clubbyv)) { return 0; } return v7_get_ptr(v7, clubbyv); }
static struct ns_connection *get_nc(struct v7_val *obj) { struct v7_val key = v7_str_to_val("nc"), *p = v7_get(obj, &key); struct ns_connection *nc = NULL; if (p != NULL) { unsigned long num = (unsigned long) p->v.num; nc = (struct ns_connection *) num; } return nc; }
static enum v7_err esp_sj_uart_get_state(struct v7 *v7, struct esp_sj_uart_state **us) { int uart_no = v7_to_number(v7_get(v7, v7_get_this(v7), "_u", 2)); if (uart_no < 0 || uart_no > 1) { return v7_throwf(v7, "Error", "Invalid UART number"); } *us = &sj_us[uart_no]; return V7_OK; }
static v7_val_t WebSocket_readyState(struct v7 *v7, v7_val_t this_obj, v7_val_t args) { v7_val_t ncv = v7_get(v7, this_obj, "_nc", ~0); if (v7_is_undefined(ncv)) { return WEBSOCKET_CLOSED; } else { return WEBSOCKET_OPEN; } }
static void export_read_only_vars_to_v7(struct v7 *v7) { struct ro_var *rv; if (v7 == NULL) return; v7_val_t obj = v7_mk_object(v7); for (rv = g_ro_vars; rv != NULL; rv = rv->next) { v7_set(v7, obj, rv->name, ~0, v7_mk_string(v7, *rv->ptr, ~0, 1)); } v7_val_t Sys = v7_get(v7, v7_get_global(v7), "Sys", ~0); v7_set(v7, Sys, "ro_vars", ~0, obj); }
SJ_PRIVATE enum v7_err GPIO_setISR(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t pinv = v7_arg(v7, 0); v7_val_t typev = v7_arg(v7, 1); v7_val_t cb = v7_arg(v7, 2); v7_val_t current_cb; char prop_name[15]; int pin, type, len, has_isr, new_isr_provided; if (!v7_is_number(pinv) || !v7_is_number(typev)) { printf("Invalid arguments\n"); *res = v7_mk_boolean(v7, 0); goto clean; } pin = v7_get_double(v7, pinv); type = v7_get_double(v7, typev); len = snprintf(prop_name, sizeof(prop_name), "_ih_%d", (int) pin); current_cb = v7_get(v7, v7_get_global(v7), prop_name, len); has_isr = v7_is_callable(v7, current_cb); new_isr_provided = v7_is_callable(v7, cb); if (!has_isr && !new_isr_provided) { printf("Missing callback\n"); *res = v7_mk_boolean(v7, 0); goto clean; }; if (has_isr && new_isr_provided && current_cb != cb) { printf("Only one interruption handler is allowed for pin\n"); *res = v7_mk_boolean(v7, 0); goto clean; } if (type == 0 && has_isr) { v7_set(v7, v7_get_global(v7), prop_name, len, V7_UNDEFINED); } else if (!has_isr && new_isr_provided) { v7_set(v7, v7_get_global(v7), prop_name, len, cb); } if (type != 0 && !s_gpio_intr_installed) { sj_gpio_intr_init(gpio_intr_handler_proxy, v7); s_isr_cb_proxy_v = v7_mk_cfunction(isr_cb_proxy); v7_own(v7, &s_isr_cb_proxy_v); s_gpio_intr_installed = 1; } *res = v7_mk_boolean(v7, sj_gpio_intr_set(pin, (enum gpio_int_mode) type) == 0); goto clean; clean: return rcode; }
static v7_val_t WebSocket_close(struct v7 *v7, v7_val_t this_obj, v7_val_t args) { struct ns_connection *nc; v7_val_t ncv = v7_get(v7, this_obj, "_nc", ~0); (void) args; if (v7_is_foreign(ncv) && (nc = (struct ns_connection *) v7_to_foreign(ncv)) != NULL) { nc->flags |= NSF_CLOSE_IMMEDIATELY; } return v7_create_undefined(); }
static void invoke_cb(struct user_data *ud, const char *name, v7_val_t ev) { struct v7 *v7 = ud->v7; v7_val_t met = v7_get(v7, ud->ws, name, ~0); if (!v7_is_undefined(met)) { v7_val_t res, args = v7_create_array(v7); v7_array_set(v7, args, 0, ev); if (v7_apply(v7, &res, met, v7_create_undefined(), args) != V7_OK) { /* TODO(mkm): make it print stack trace */ fprintf(stderr, "cb threw an exception\n"); } } }
static void call_sum(struct v7 *v7) { v7_val_t func, result, args; func = v7_get(v7, v7_get_global(v7), "sum", 3); args = v7_mk_array(v7); v7_array_push(v7, args, v7_mk_number(123.0)); v7_array_push(v7, args, v7_mk_number(456.789)); v7_apply(v7, func, v7_mk_undefined(), args, &result); printf("Result: %g\n", v7_to_number(result)); }
static enum v7_err GPIO_setisr(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; v7_val_t pinv = v7_arg(v7, 0); v7_val_t typev = v7_arg(v7, 1); v7_val_t cb = v7_arg(v7, 2); v7_val_t current_cb; char prop_name[15]; int pin, type, len, has_isr, new_isr_provided; if (!v7_is_number(pinv) || !v7_is_number(typev)) { printf("Invalid arguments\n"); *res = v7_create_boolean(0); goto clean; } pin = v7_to_number(pinv); type = v7_to_number(typev); len = snprintf(prop_name, sizeof(prop_name), "_ih_%d", (int) pin); current_cb = v7_get(v7, v7_get_global(v7), prop_name, len); has_isr = v7_is_function(current_cb); new_isr_provided = v7_is_function(cb); if (!has_isr && !new_isr_provided) { printf("Missing callback\n"); *res = v7_create_boolean(0); goto clean; }; if (has_isr && new_isr_provided && current_cb != cb) { printf("Only one interruption handler is allowed for pin\n"); *res = v7_create_boolean(0); goto clean; } if (type == 0 && has_isr) { v7_set(v7, v7_get_global(v7), prop_name, len, 0, v7_create_undefined()); } else if (!has_isr && new_isr_provided) { v7_set(v7, v7_get_global(v7), prop_name, len, 0, cb); } if (type != 0 && !s_gpio_intr_installed) { sj_gpio_intr_init(gpio_intr_handler_proxy); s_gpio_intr_installed = 1; } *res = v7_create_boolean(sj_gpio_intr_set(pin, type) == 0); goto clean; clean: return rcode; }
static void populate_opts_from_js_argument(struct v7 *v7, v7_val_t obj, struct mg_serve_http_opts *opts) { size_t i; for (i = 0; i < ARRAY_SIZE(s_map); i++) { v7_val_t v = v7_get(v7, obj, s_map[i].name, ~0); if (v7_is_string(v)) { size_t n; const char *str = v7_get_string_data(v7, &v, &n); *(char **) ((char *) opts + s_map[i].offset) = strdup(str); } } }
clubby_handle_t console_get_current_clubby(struct v7 *v7) { v7_val_t clubby_v = v7_get(v7, v7_get_global(v7), s_clubby_prop, ~0); if (!v7_is_object(clubby_v)) { LOG(LL_ERROR, ("Clubby is not set")); return NULL; } clubby_handle_t ret = sj_clubby_get_handle(v7, clubby_v); LOG(LL_DEBUG, ("clubby handle: %p", ret)); return ret; }
static enum v7_err UART_write(struct v7 *v7, v7_val_t *res) { v7_val_t this_obj = v7_get_this(v7); v7_val_t dev = v7_get(v7, this_obj, "_dev", ~0), data = v7_arg(v7, 0); size_t len; const char *d = v7_get_string_data(v7, &data, &len); (void) v7; (void) this_obj; sj_hal_write_uart(v7_to_foreign(dev), d, len); return V7_OK; }
int update_sysconf(struct v7 *v7, const char *path, v7_val_t val) { v7_val_t sys = v7_get(v7, v7_get_global(v7), "Sys", ~0); if (!v7_is_object(sys)) { return 1; } v7_val_t conf = v7_get(v7, sys, "conf", ~0); if (!v7_is_object(conf)) { return 1; } v7_val_t prev_obj, curr_obj; prev_obj = curr_obj = conf; const char *prev_tok, *curr_tok; prev_tok = curr_tok = path; for (;;) { while (*curr_tok != 0 && *curr_tok != '.') { curr_tok++; } curr_obj = v7_get(v7, prev_obj, prev_tok, (curr_tok - prev_tok)); if (v7_is_undefined(curr_obj)) { return 1; } else if (!v7_is_object(curr_obj)) { v7_set(v7, prev_obj, prev_tok, (curr_tok - prev_tok), val); return 0; } if (*curr_tok == 0) { return 1; } curr_tok++; prev_tok = curr_tok; prev_obj = curr_obj; } return 1; }
void sj_http_init(struct v7 *v7) { v7_val_t Http = v7_mk_undefined(); sj_http_server_proto = v7_mk_undefined(); sj_http_response_proto = v7_mk_undefined(); sj_http_request_proto = v7_mk_undefined(); /* own temporary Http var */ v7_own(v7, &Http); /* other values are owned forever */ v7_own(v7, &sj_http_server_proto); v7_own(v7, &sj_http_response_proto); v7_own(v7, &sj_http_request_proto); Http = v7_get(v7, v7_get_global(v7), "Http", ~0); sj_http_server_proto = v7_get(v7, Http, "_serv", ~0); sj_http_response_proto = v7_get(v7, Http, "_resp", ~0); sj_http_request_proto = v7_get(v7, Http, "_req", ~0); v7_disown(v7, &Http); }
int v7_example(void) { size_t n; const char *domain_str; struct v7 *v7 = v7_create(); v7_val_t domain, port0, config; /* Load JSON configuration */ if (v7_parse_json_file(v7, "config.json", &config) != V7_OK) { printf("%s\n", "Cannot load JSON config"); return 1; } /* Lookup values in JSON configuration object */ domain = v7_get(v7, config, "domain", 6); port0 = v7_array_get(v7, v7_get(v7, config, "ports", 5), 0); domain_str = v7_get_string(v7, &domain, &n); printf("Domain: [%.*s], port 0: [%d]\n", (int) n, domain_str, (int) v7_get_double(port0)); v7_destroy(v7); return 0; }
enum v7_err fill_ssl_connect_opts(struct v7 *v7, v7_val_t opts, int force_ssl, struct mg_connect_opts *copts) { enum v7_err rcode = V7_OK; v7_val_t v_use_ssl = v7_get(v7, opts, "use_ssl", ~0); v7_val_t v_ca_cert = v7_get(v7, opts, "ssl_ca_cert", ~0); v7_val_t v_cert = v7_get(v7, opts, "ssl_cert", ~0); v7_val_t v_server_name = v7_get(v7, opts, "ssl_server_name", ~0); if (!v7_is_undefined(v_ca_cert) && !v7_is_string(v_ca_cert)) { rcode = v7_throwf(v7, "TypeError", "ssl_ca_cert must be a string"); goto clean; } if (!v7_is_undefined(v_cert) && !v7_is_string(v_cert)) { rcode = v7_throwf(v7, "TypeError", "ssl_cert must be a string"); goto clean; } if (!v7_is_undefined(v_server_name) && !v7_is_string(v_server_name)) { rcode = v7_throwf(v7, "TypeError", "ssl_server_name must be a string"); goto clean; } copts->ssl_ca_cert = v7_get_cstring(v7, &v_ca_cert); copts->ssl_cert = v7_get_cstring(v7, &v_cert); copts->ssl_server_name = v7_get_cstring(v7, &v_server_name); if ((force_ssl || (v7_is_boolean(v_use_ssl) && v7_get_bool(v7, v_use_ssl) != 0)) && copts->ssl_ca_cert == NULL) { /* Defaults to configuration */ copts->ssl_ca_cert = get_cfg()->tls.ca_file; } clean: return rcode; }
/* * Regiter a callback to be invoked when there is at least N bytes available. * N defaults to maxint if undefined */ static enum v7_err UART_recv(struct v7 *v7, v7_val_t *res) { v7_val_t this_obj = v7_get_this(v7); v7_val_t cb = v7_arg(v7, 0); v7_val_t wantv = v7_arg(v7, 1); v7_val_t udv = v7_get(v7, this_obj, "_ud", ~0); size_t want = v7_is_number(wantv) ? (size_t) v7_to_number(wantv) : ~0; struct user_data *ud = (struct user_data *) v7_to_foreign(udv); ud->cb = cb; v7_own(v7, &ud->cb); ud->want = want; /* TODO(mkm): trigger cb if there is already something in the buffer */ return V7_OK; }
SJ_PRIVATE enum v7_err Http_response_write(struct v7 *v7, v7_val_t *res) { enum v7_err rcode = V7_OK; DECLARE_CONN(); if (!v7_is_truthy(v7, v7_get(v7, v7_get_this(v7), "_whd", ~0))) { write_http_status(c, 200); mg_send(c, "\r\n", 2); v7_set(v7, v7_get_this(v7), "_whd", ~0, v7_mk_boolean(1)); } Http_write_data(v7, c); *res = v7_get_this(v7); clean: return rcode; }