trunk/src/emu/webengine.c
| r250088 | r250089 | |
| 1 | | // license:BSD-3-Clause |
| 2 | | // copyright-holders:Miodrag Milanovic |
| 3 | | /*************************************************************************** |
| 4 | | |
| 5 | | webengine.c |
| 6 | | |
| 7 | | Handle MAME internal web server. |
| 8 | | |
| 9 | | ***************************************************************************/ |
| 10 | | |
| 11 | | #include "mongoose/mongoose.h" |
| 12 | | #include "jsoncpp/include/json/json.h" |
| 13 | | #include "emu.h" |
| 14 | | #include "emuopts.h" |
| 15 | | #include "ui/ui.h" |
| 16 | | #include "webengine.h" |
| 17 | | #include "lua.hpp" |
| 18 | | |
| 19 | | #include "osdepend.h" |
| 20 | | |
| 21 | | //************************************************************************** |
| 22 | | // WEB ENGINE |
| 23 | | //************************************************************************** |
| 24 | | |
| 25 | | char* websanitize_statefilename ( char* unsanitized ) |
| 26 | | { |
| 27 | | // It's important that we remove any dangerous characters from any filename |
| 28 | | // we receive from a web client. This can be a serious security hole. |
| 29 | | // As MAME/MESS policy is lowercase filenames, also lowercase it. |
| 30 | | |
| 31 | | char* sanitized = new char[64]; |
| 32 | | int insertpoint =0; |
| 33 | | char charcompare; |
| 34 | | |
| 35 | | while (*unsanitized != 0) |
| 36 | | { |
| 37 | | charcompare = *unsanitized; |
| 38 | | // ASCII 48-57 are 0-9 |
| 39 | | // ASCII 97-122 are lowercase A-Z |
| 40 | | |
| 41 | | if ((charcompare >= 48 && charcompare <= 57) || (charcompare >= 97 && charcompare <= 122)) |
| 42 | | { |
| 43 | | sanitized[insertpoint] = charcompare; |
| 44 | | insertpoint++; |
| 45 | | sanitized[insertpoint] = '\0'; // Make sure we're null-terminated. |
| 46 | | } |
| 47 | | // ASCII 65-90 are uppercase A-Z. These need to be lowercased. |
| 48 | | if (charcompare >= 65 && charcompare <= 90) |
| 49 | | { |
| 50 | | sanitized[insertpoint] = tolower(charcompare); // Lowercase it |
| 51 | | insertpoint++; |
| 52 | | sanitized[insertpoint] = '\0'; // Make sure we're null-terminated. |
| 53 | | } |
| 54 | | unsanitized++; |
| 55 | | } |
| 56 | | return (sanitized); |
| 57 | | } |
| 58 | | |
| 59 | | int web_engine::json_game_handler(struct mg_connection *conn) |
| 60 | | { |
| 61 | | Json::Value data; |
| 62 | | data["name"] = m_machine->system().name; |
| 63 | | data["description"] = m_machine->system().description; |
| 64 | | data["year"] = m_machine->system().year; |
| 65 | | data["manufacturer"] = m_machine->system().manufacturer; |
| 66 | | data["parent"] = m_machine->system().parent; |
| 67 | | data["source_file"] = m_machine->system().source_file; |
| 68 | | data["flags"] = m_machine->system().flags; |
| 69 | | data["ispaused"] = m_machine->paused(); |
| 70 | | |
| 71 | | Json::FastWriter writer; |
| 72 | | std::string json = writer.write(data); |
| 73 | | // Send HTTP reply to the client |
| 74 | | mg_printf(conn, |
| 75 | | "HTTP/1.1 200 OK\r\n" |
| 76 | | "Content-Type: application/json\r\n" |
| 77 | | "Content-Length: %d\r\n" // Always set Content-Length |
| 78 | | "\r\n" |
| 79 | | "%s", |
| 80 | | (int)json.length(), json.c_str()); |
| 81 | | |
| 82 | | // Returning non-zero tells mongoose that our function has replied to |
| 83 | | // the client, and mongoose should not send client any more data. |
| 84 | | |
| 85 | | return MG_TRUE; |
| 86 | | } |
| 87 | | |
| 88 | | int web_engine::json_slider_handler(struct mg_connection *conn) |
| 89 | | { |
| 90 | | const slider_state *curslider; |
| 91 | | std::string tempstring; |
| 92 | | Json::Value array(Json::arrayValue); |
| 93 | | |
| 94 | | // add all sliders |
| 95 | | for (curslider = machine().ui().get_slider_list(); curslider != NULL; curslider = curslider->next) |
| 96 | | { |
| 97 | | INT32 curval = (*curslider->update)(machine(), curslider->arg, &tempstring, SLIDER_NOCHANGE); |
| 98 | | Json::Value data; |
| 99 | | data["description"] = curslider->description; |
| 100 | | data["minval"] = curslider->minval; |
| 101 | | data["maxval"] = curslider->maxval; |
| 102 | | data["defval"] = curslider->defval; |
| 103 | | data["incval"] = curslider->incval; |
| 104 | | data["curval"] = curval; |
| 105 | | array.append(data); |
| 106 | | } |
| 107 | | |
| 108 | | // add all sliders |
| 109 | | for (curslider = (slider_state*)machine().osd().get_slider_list(); curslider != NULL; curslider = curslider->next) |
| 110 | | { |
| 111 | | INT32 curval = (*curslider->update)(machine(), curslider->arg, &tempstring, SLIDER_NOCHANGE); |
| 112 | | Json::Value data; |
| 113 | | data["description"] = curslider->description; |
| 114 | | data["minval"] = curslider->minval; |
| 115 | | data["maxval"] = curslider->maxval; |
| 116 | | data["defval"] = curslider->defval; |
| 117 | | data["incval"] = curslider->incval; |
| 118 | | data["curval"] = curval; |
| 119 | | array.append(data); |
| 120 | | } |
| 121 | | Json::FastWriter writer; |
| 122 | | std::string json = writer.write(array); |
| 123 | | // Send HTTP reply to the client |
| 124 | | mg_printf(conn, |
| 125 | | "HTTP/1.1 200 OK\r\n" |
| 126 | | "Content-Type: application/json\r\n" |
| 127 | | "Content-Length: %d\r\n" // Always set Content-Length |
| 128 | | "\r\n" |
| 129 | | "%s", |
| 130 | | (int)json.length(), json.c_str()); |
| 131 | | |
| 132 | | return MG_TRUE; |
| 133 | | } |
| 134 | | |
| 135 | | void reg_string(struct lua_State *L, const char *name, const char *val) { |
| 136 | | lua_pushstring(L, name); |
| 137 | | lua_pushstring(L, val); |
| 138 | | lua_rawset(L, -3); |
| 139 | | } |
| 140 | | |
| 141 | | void reg_int(struct lua_State *L, const char *name, int val) { |
| 142 | | lua_pushstring(L, name); |
| 143 | | lua_pushinteger(L, val); |
| 144 | | lua_rawset(L, -3); |
| 145 | | } |
| 146 | | |
| 147 | | void reg_function(struct lua_State *L, const char *name, |
| 148 | | lua_CFunction func, struct mg_connection *conn) { |
| 149 | | lua_pushstring(L, name); |
| 150 | | lua_pushlightuserdata(L, conn); |
| 151 | | lua_pushcclosure(L, func, 1); |
| 152 | | lua_rawset(L, -3); |
| 153 | | } |
| 154 | | |
| 155 | | static int lua_write(lua_State *L) { |
| 156 | | int i, num_args; |
| 157 | | const char *str; |
| 158 | | size_t size; |
| 159 | | struct mg_connection *conn = (struct mg_connection *) |
| 160 | | lua_touserdata(L, lua_upvalueindex(1)); |
| 161 | | |
| 162 | | num_args = lua_gettop(L); |
| 163 | | for (i = 1; i <= num_args; i++) { |
| 164 | | if (lua_isstring(L, i)) { |
| 165 | | str = lua_tolstring(L, i, &size); |
| 166 | | mg_send_data(conn, str, size); |
| 167 | | } |
| 168 | | } |
| 169 | | |
| 170 | | return 0; |
| 171 | | } |
| 172 | | |
| 173 | | static int lua_header(lua_State *L) { |
| 174 | | struct mg_connection *conn = (struct mg_connection *) |
| 175 | | lua_touserdata(L, lua_upvalueindex(1)); |
| 176 | | |
| 177 | | const char *header = luaL_checkstring(L,1); |
| 178 | | const char *value = luaL_checkstring(L,2); |
| 179 | | |
| 180 | | mg_send_header(conn, header, value); |
| 181 | | |
| 182 | | return 0; |
| 183 | | } |
| 184 | | |
| 185 | | |
| 186 | | static void prepare_lua_environment(struct mg_connection *ri, lua_State *L) { |
| 187 | | extern void luaL_openlibs(lua_State *); |
| 188 | | int i; |
| 189 | | |
| 190 | | luaL_openlibs(L); |
| 191 | | |
| 192 | | if (ri == NULL) return; |
| 193 | | |
| 194 | | // Register mg module |
| 195 | | lua_newtable(L); |
| 196 | | reg_function(L, "write", lua_write, ri); |
| 197 | | reg_function(L, "header", lua_header, ri); |
| 198 | | |
| 199 | | // Export request_info |
| 200 | | lua_pushstring(L, "request_info"); |
| 201 | | lua_newtable(L); |
| 202 | | reg_string(L, "request_method", ri->request_method); |
| 203 | | reg_string(L, "uri", ri->uri); |
| 204 | | reg_string(L, "http_version", ri->http_version); |
| 205 | | reg_string(L, "query_string", ri->query_string); |
| 206 | | reg_string(L, "remote_ip", ri->remote_ip); |
| 207 | | reg_int(L, "remote_port", ri->remote_port); |
| 208 | | reg_string(L, "local_ip", ri->local_ip); |
| 209 | | reg_int(L, "local_port", ri->local_port); |
| 210 | | lua_pushstring(L, "content"); |
| 211 | | lua_pushlstring(L, ri->content == NULL ? "" : ri->content, ri->content_len); |
| 212 | | lua_rawset(L, -3); |
| 213 | | reg_int(L, "num_headers", ri->num_headers); |
| 214 | | lua_pushstring(L, "http_headers"); |
| 215 | | lua_newtable(L); |
| 216 | | for (i = 0; i < ri->num_headers; i++) { |
| 217 | | reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value); |
| 218 | | } |
| 219 | | lua_rawset(L, -3); |
| 220 | | lua_rawset(L, -3); |
| 221 | | |
| 222 | | lua_setglobal(L, "mg"); |
| 223 | | |
| 224 | | } |
| 225 | | |
| 226 | | |
| 227 | | static void lsp(struct mg_connection *conn, const char *p, int len, lua_State *L) { |
| 228 | | int i, j, pos = 0; |
| 229 | | for (i = 0; i < len; i++) { |
| 230 | | if (p[i] == '<' && p[i + 1] == '?') { |
| 231 | | for (j = i + 1; j < len ; j++) { |
| 232 | | if (p[j] == '?' && p[j + 1] == '>') { |
| 233 | | if (i-pos!=0) mg_send_data(conn, p + pos, i - pos); |
| 234 | | if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), "") == 0) { |
| 235 | | lua_pcall(L, 0, LUA_MULTRET, 0); |
| 236 | | } |
| 237 | | pos = j + 2; |
| 238 | | i = pos - 1; |
| 239 | | break; |
| 240 | | } |
| 241 | | } |
| 242 | | } |
| 243 | | } |
| 244 | | if (i > pos) { |
| 245 | | mg_send_data(conn, p + pos, i - pos); |
| 246 | | } |
| 247 | | } |
| 248 | | |
| 249 | | static int filename_endswith(const char *str, const char *suffix) |
| 250 | | { |
| 251 | | if (!str || !suffix) |
| 252 | | return 0; |
| 253 | | size_t lenstr = strlen(str); |
| 254 | | size_t lensuffix = strlen(suffix); |
| 255 | | if (lensuffix > lenstr) |
| 256 | | return 0; |
| 257 | | return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; |
| 258 | | } |
| 259 | | |
| 260 | | // This function will be called by mongoose on every new request. |
| 261 | | int web_engine::begin_request_handler(struct mg_connection *conn) |
| 262 | | { |
| 263 | | std::string file_path = std::string(mg_get_option(m_server, "document_root")).append(PATH_SEPARATOR).append(conn->uri); |
| 264 | | if (filename_endswith(file_path.c_str(), ".lp")) |
| 265 | | { |
| 266 | | FILE *fp = NULL; |
| 267 | | if ((fp = fopen(file_path.c_str(), "rb")) != NULL) { |
| 268 | | fseek (fp, 0, SEEK_END); |
| 269 | | size_t size = ftell(fp); |
| 270 | | fseek (fp, 0, SEEK_SET); |
| 271 | | char *data = (char*)mg_mmap(fp,size); |
| 272 | | |
| 273 | | lua_State *L = luaL_newstate(); |
| 274 | | prepare_lua_environment(conn, L); |
| 275 | | lsp(conn, data, (int) size, L); |
| 276 | | if (L != NULL) lua_close(L); |
| 277 | | mg_munmap(data,size); |
| 278 | | fclose(fp); |
| 279 | | return MG_TRUE; |
| 280 | | } else { |
| 281 | | return MG_FALSE; |
| 282 | | } |
| 283 | | } |
| 284 | | else if (!strncmp(conn->uri, "/json/",6)) |
| 285 | | { |
| 286 | | if (!strcmp(conn->uri, "/json/game")) |
| 287 | | { |
| 288 | | return json_game_handler(conn); |
| 289 | | } |
| 290 | | if (!strcmp(conn->uri, "/json/slider")) |
| 291 | | { |
| 292 | | return json_slider_handler(conn); |
| 293 | | } |
| 294 | | } |
| 295 | | else if (!strncmp(conn->uri, "/keypost",8)) |
| 296 | | { |
| 297 | | // Is there any sane way to determine the length of the buffer before getting it? |
| 298 | | // A request for a way was previously filed with the mongoose devs, |
| 299 | | // but it looks like it was never implemented. |
| 300 | | |
| 301 | | // For now, we'll allow a paste buffer of 32k. |
| 302 | | // To-do: Send an error if the paste is too big? |
| 303 | | char cmd_val[32768]; |
| 304 | | |
| 305 | | int pastelength = mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); |
| 306 | | if (pastelength > 0) { |
| 307 | | machine().ioport().natkeyboard().post_utf8(cmd_val); |
| 308 | | } |
| 309 | | // Send HTTP reply to the client |
| 310 | | mg_printf(conn, |
| 311 | | "HTTP/1.1 200 OK\r\n" |
| 312 | | "Content-Type: text/plain\r\n" |
| 313 | | "Content-Length: 2\r\n" // Always set Content-Length |
| 314 | | "\r\n" |
| 315 | | "OK"); |
| 316 | | |
| 317 | | // Returning non-zero tells mongoose that our function has replied to |
| 318 | | // the client, and mongoose should not send client any more data. |
| 319 | | return MG_TRUE; |
| 320 | | } |
| 321 | | else if (!strncmp(conn->uri, "/keyupload",8)) |
| 322 | | { |
| 323 | | char *upload_data; |
| 324 | | int data_length, ofs = 0; |
| 325 | | char var_name[100], file_name[255]; |
| 326 | | while ((ofs = mg_parse_multipart(conn->content + ofs, conn->content_len - ofs, var_name, sizeof(var_name), file_name, sizeof(file_name), (const char **)&upload_data, &data_length)) > 0) { |
| 327 | | mg_printf_data(conn, "File: %s, size: %d bytes", file_name, data_length); |
| 328 | | } |
| 329 | | |
| 330 | | // That upload_data contains more than we need. It also has the headers. |
| 331 | | // We'll need to strip it down to just what we want. |
| 332 | | |
| 333 | | if ((&data_length > 0) && (sizeof(file_name) > 0)) |
| 334 | | { |
| 335 | | // MSVC doesn't yet support variable-length arrays, so chop the string the old-fashioned way |
| 336 | | upload_data[data_length] = '\0'; |
| 337 | | |
| 338 | | // Now paste the stripped down paste_data.. |
| 339 | | machine().ioport().natkeyboard().post_utf8(upload_data); |
| 340 | | } |
| 341 | | return MG_TRUE; |
| 342 | | } |
| 343 | | else if (!strncmp(conn->uri, "/cmd",4)) |
| 344 | | { |
| 345 | | char cmd_name[64]; |
| 346 | | mg_get_var(conn, "name", cmd_name, sizeof(cmd_name)); |
| 347 | | |
| 348 | | if(!strcmp(cmd_name,"softreset")) |
| 349 | | { |
| 350 | | m_machine->schedule_soft_reset(); |
| 351 | | } |
| 352 | | else if(!strcmp(cmd_name,"hardreset")) |
| 353 | | { |
| 354 | | m_machine->schedule_hard_reset(); |
| 355 | | } |
| 356 | | else if(!strcmp(cmd_name,"exit")) |
| 357 | | { |
| 358 | | m_machine->schedule_exit(); |
| 359 | | } |
| 360 | | else if(!strcmp(cmd_name,"togglepause")) |
| 361 | | { |
| 362 | | if (m_machine->paused()) |
| 363 | | m_machine->resume(); |
| 364 | | else |
| 365 | | m_machine->pause(); |
| 366 | | } |
| 367 | | else if(!strcmp(cmd_name,"savestate")) |
| 368 | | { |
| 369 | | char cmd_val[64]; |
| 370 | | mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); |
| 371 | | char *filename = websanitize_statefilename(cmd_val); |
| 372 | | m_machine->schedule_save(filename); |
| 373 | | } |
| 374 | | else if(!strcmp(cmd_name,"loadstate")) |
| 375 | | { |
| 376 | | char cmd_val[64]; |
| 377 | | mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); |
| 378 | | char *filename = cmd_val; |
| 379 | | m_machine->schedule_load(filename); |
| 380 | | } |
| 381 | | else if(!strcmp(cmd_name,"loadauto")) |
| 382 | | { |
| 383 | | // This is here to just load the autosave and only the autosave. |
| 384 | | m_machine->schedule_load("auto"); |
| 385 | | } |
| 386 | | |
| 387 | | // Send HTTP reply to the client |
| 388 | | mg_printf(conn, |
| 389 | | "HTTP/1.1 200 OK\r\n" |
| 390 | | "Content-Type: text/plain\r\n" |
| 391 | | "Content-Length: 2\r\n" // Always set Content-Length |
| 392 | | "\r\n" |
| 393 | | "OK"); |
| 394 | | |
| 395 | | // Returning non-zero tells mongoose that our function has replied to |
| 396 | | // the client, and mongoose should not send client any more data. |
| 397 | | return MG_TRUE; |
| 398 | | } |
| 399 | | else if (!strncmp(conn->uri, "/slider",7)) |
| 400 | | { |
| 401 | | char cmd_id[64]; |
| 402 | | char cmd_val[64]; |
| 403 | | mg_get_var(conn, "id", cmd_id, sizeof(cmd_id)); |
| 404 | | mg_get_var(conn, "val", cmd_val, sizeof(cmd_val)); |
| 405 | | int cnt = 0; |
| 406 | | int id = atoi(cmd_id); |
| 407 | | const slider_state *curslider; |
| 408 | | for (curslider = machine().ui().get_slider_list(); curslider != NULL; curslider = curslider->next) |
| 409 | | { |
| 410 | | if (cnt==id) |
| 411 | | (*curslider->update)(machine(), curslider->arg, NULL, atoi(cmd_val)); |
| 412 | | cnt++; |
| 413 | | } |
| 414 | | for (curslider = (slider_state*)machine().osd().get_slider_list(); curslider != NULL; curslider = curslider->next) |
| 415 | | { |
| 416 | | if (cnt==id) |
| 417 | | (*curslider->update)(machine(), curslider->arg, NULL, atoi(cmd_val)); |
| 418 | | cnt++; |
| 419 | | } |
| 420 | | |
| 421 | | // Send HTTP reply to the client |
| 422 | | mg_printf(conn, |
| 423 | | "HTTP/1.1 200 OK\r\n" |
| 424 | | "Content-Type: text/plain\r\n" |
| 425 | | "Content-Length: 2\r\n" // Always set Content-Length |
| 426 | | "\r\n" |
| 427 | | "OK"); |
| 428 | | |
| 429 | | // Returning non-zero tells mongoose that our function has replied to |
| 430 | | // the client, and mongoose should not send client any more data. |
| 431 | | return MG_TRUE; |
| 432 | | } |
| 433 | | else if (!strncmp(conn->uri, "/screenshot.png",15)) |
| 434 | | { |
| 435 | | screen_device_iterator iter(m_machine->root_device()); |
| 436 | | screen_device *screen = iter.first(); |
| 437 | | |
| 438 | | if (screen == NULL) |
| 439 | | { |
| 440 | | return 0; |
| 441 | | } |
| 442 | | |
| 443 | | std::string fname("screenshot.png"); |
| 444 | | emu_file file(m_machine->options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); |
| 445 | | file_error filerr = file.open(fname.c_str()); |
| 446 | | |
| 447 | | if (filerr != FILERR_NONE) |
| 448 | | { |
| 449 | | return 0; |
| 450 | | } |
| 451 | | |
| 452 | | m_machine->video().save_snapshot(screen, file); |
| 453 | | std::string fullpath(file.fullpath()); |
| 454 | | file.close(); |
| 455 | | mg_send_header(conn, "Cache-Control", "no-cache, no-store, must-revalidate"); |
| 456 | | mg_send_header(conn, "Pragma", "no-cache"); |
| 457 | | mg_send_header(conn, "Expires", "0"); |
| 458 | | mg_send_file(conn, fullpath.c_str(), NULL); |
| 459 | | return MG_MORE; // It is important to return MG_MORE after mg_send_file! |
| 460 | | } |
| 461 | | return 0; |
| 462 | | } |
| 463 | | |
| 464 | | static int ev_handler(struct mg_connection *conn, enum mg_event ev) { |
| 465 | | if (ev == MG_REQUEST) { |
| 466 | | if (conn->is_websocket) { |
| 467 | | // This handler is called for each incoming websocket frame, one or more |
| 468 | | // times for connection lifetime. |
| 469 | | // Echo websocket data back to the client. |
| 470 | | return conn->content_len == 4 && !memcmp(conn->content, "exit", 4) ? MG_FALSE : MG_TRUE; |
| 471 | | } else { |
| 472 | | web_engine *engine = static_cast<web_engine *>(conn->server_param); |
| 473 | | return engine->begin_request_handler(conn); |
| 474 | | } |
| 475 | | } else if (ev== MG_WS_CONNECT) { |
| 476 | | // New websocket connection. Send connection ID back to the client. |
| 477 | | mg_websocket_printf(conn, WEBSOCKET_OPCODE_TEXT, "update_machine"); |
| 478 | | return MG_FALSE; |
| 479 | | } else if (ev == MG_AUTH) { |
| 480 | | return MG_TRUE; |
| 481 | | } else { |
| 482 | | return MG_FALSE; |
| 483 | | } |
| 484 | | } |
| 485 | | |
| 486 | | //------------------------------------------------- |
| 487 | | // web_engine - constructor |
| 488 | | //------------------------------------------------- |
| 489 | | |
| 490 | | web_engine::web_engine(emu_options &options) |
| 491 | | : m_options(options), |
| 492 | | m_machine(NULL), |
| 493 | | m_server(NULL), |
| 494 | | //m_lastupdatetime(0), |
| 495 | | m_exiting_core(false), |
| 496 | | m_http(m_options.http()) |
| 497 | | |
| 498 | | { |
| 499 | | if (m_http) { |
| 500 | | m_server = mg_create_server(this, ev_handler); |
| 501 | | |
| 502 | | mg_set_option(m_server, "listening_port", options.http_port()); |
| 503 | | mg_set_option(m_server, "document_root", options.http_path()); |
| 504 | | } |
| 505 | | |
| 506 | | } |
| 507 | | |
| 508 | | //------------------------------------------------- |
| 509 | | // ~web_engine - destructor |
| 510 | | //------------------------------------------------- |
| 511 | | |
| 512 | | web_engine::~web_engine() |
| 513 | | { |
| 514 | | if (m_http) |
| 515 | | close(); |
| 516 | | } |
| 517 | | |
| 518 | | //------------------------------------------------- |
| 519 | | // close - close and cleanup of lua engine |
| 520 | | //------------------------------------------------- |
| 521 | | |
| 522 | | void web_engine::close() |
| 523 | | { |
| 524 | | m_exiting_core = 1; |
| 525 | | // Cleanup, and free server instance |
| 526 | | mg_destroy_server(&m_server); |
| 527 | | } |
| 528 | | |
| 529 | | void web_engine::serve() |
| 530 | | { |
| 531 | | if (m_http) mg_poll_server(m_server, 0); |
| 532 | | } |
| 533 | | |
| 534 | | void web_engine::push_message(const char *message) |
| 535 | | { |
| 536 | | struct mg_connection *c; |
| 537 | | if (m_server!=NULL) { |
| 538 | | // Iterate over all connections, and push current time message to websocket ones. |
| 539 | | for (c = mg_next(m_server, NULL); c != NULL; c = mg_next(m_server, c)) { |
| 540 | | if (c->is_websocket) { |
| 541 | | mg_websocket_write(c, 1, message, strlen(message)); |
| 542 | | } |
| 543 | | } |
| 544 | | } |
| 545 | | } |