Compare commits

...
337 Commits
Author SHA1 Message Date
Sergey Lyubka d5993ba27e Fix #1550 - excladdrinuse 2022-05-21 16:18:34 +01:00
Sergey Lyubka ef563603a9 Merge pull request #1557 from cesanta/pc
Remove mg_log_set_callback. Use MG_PUTCHAR macro instead
2022-05-20 10:16:57 +01:00
Sergey Lyubka 7e2ca72310 Remove mg_log_set_callback. Use MG_PUTCHAR macro instead 2022-05-20 09:45:17 +01:00
Sergey Lyubka 7c17c806ad 7.6 -> 7.7 2022-05-19 11:44:32 +01:00
Sergey Lyubka 8e0f529a00 Add MIP - an experimental TCP/IP stack 2022-05-18 21:19:21 +01:00
Sergey Lyubka 47e15a3d47 RTX nits 2022-05-17 19:29:53 +01:00
Sergey Lyubka cac7007069 Merge pull request #1554 from zudwalex/zephyr_ci
Use docker for Zephyr CI
2022-05-16 13:39:25 +01:00
Alex Alashkin ac55c900e7 Use docker for Zephyr CI 2022-05-16 15:30:25 +03:00
Sergey Lyubka 25806e48dc Fix ESP32 build 2022-05-16 11:54:26 +01:00
Sergey Lyubka 3871f4b6ab Fix #1546 - dont send excess for range requests 2022-05-16 11:45:08 +01:00
Sergey Lyubka 27c892806b Fix #1540 - case sensitive match in mg_is_chunked() 2022-05-16 11:19:45 +01:00
Sergey Lyubka 2b9f0480a1 Fix #1549 - fix typo in log format specifier 2022-05-15 21:02:41 +01:00
Sergey Lyubka 72cc433265 Make SNTP test more resilient 2022-05-15 19:41:37 +01:00
Sergey Lyubka 7721b074e6 Log ms in test_sntp() 2022-05-15 19:31:29 +01:00
Sergey Lyubka f5fd176b35 Increate SNTP timeout 3s->5s. Change mg_sntp_send() to mg_sntp_request(), remove one unnecessary parameter 2022-05-15 18:41:01 +01:00
Sergey Lyubka 6db1d7925e Merge pull request #1552 from zudwalex/ti_merged
Add TI support
2022-05-15 17:40:46 +01:00
Alex Alashkin 0ed7545947 Add TI support 2022-05-15 19:25:50 +03:00
Sergey Lyubka 95e22e8d4f Exporting only dashboard func 2022-05-15 14:40:58 +01:00
Sergey Lyubka cf7d1d8041 Address #1551 - better timers handling 2022-05-15 14:29:34 +01:00
Sergey Lyubka 443d0f0c6c Rename complete -> device-dashboard, remove multiple-logins and dashboard 2022-05-14 09:08:23 +01:00
Sergey Lyubka c20e48179f Un-break the test and untie serving code in dash example 2022-05-13 22:15:33 +01:00
Sergey Lyubka 3fbb7de712 Refactor dashboard example 2022-05-13 20:08:08 +01:00
Sergey Lyubka 0294603dfc MG_ARCH_RTX nits 2022-05-12 15:07:02 +01:00
Sergey Lyubka 5458252e4a Add missing src/arch_rtx.h 2022-05-12 08:23:17 +01:00
Sergey Lyubka 31ce219544 Add MG_ARCH_RTX 2022-05-12 08:07:51 +01:00
Sergey Lyubka 86cd567968 Nit - use quotes, not brackets for custom arch 2022-05-11 21:14:44 +01:00
Sergey Lyubka 5c66d32bc0 Refactor mg_open_listener() 2022-05-10 13:43:47 +01:00
Sergey Lyubka 5ac5c93c90 Refactor mg_open_listener() 2022-05-10 13:41:45 +01:00
Sergey Lyubka 84480f4072 Fix #1539 - proper timer cleanup 2022-05-10 07:38:03 +01:00
Sergey Lyubka b82953405d Fix valgrind test 2022-05-09 19:11:27 +01:00
Sergey Lyubka 5271626947 Disable SSI by default 2022-05-08 21:54:15 +01:00
Sergey Lyubka bc16854ccb Get rid of MG_ENABLE_LOG, change mg_hexdump() 2022-05-06 21:09:13 +01:00
Sergey Lyubka 6db0485c7b Set c->loc for accepted and UDP connections 2022-05-06 20:19:40 +01:00
Sergey Lyubka b5fc0d30a8 Add extraconnsize 2022-05-04 17:18:35 +01:00
Sergey Lyubka 38557aac7f Add MG_ARCH_NEWLIB 2022-05-01 13:47:33 +01:00
Sergey Lyubka d621fbfab1 Remove unused macros 2022-05-01 08:18:18 +01:00
Sergey Lyubka f19eec7fae Refactor multithreading API 2022-04-25 14:56:15 +01:00
Sergey Lyubka 8174842b05 docs 2022-04-24 11:39:54 +01:00
Sergey Lyubka ff07d261b2 docs 2022-04-24 11:37:23 +01:00
Sergey Lyubka c55db8a895 docs 2022-04-24 11:33:47 +01:00
Sergey Lyubka 70a9554b72 docs 2022-04-24 11:26:25 +01:00
Sergey Lyubka bdd7d92108 Fix #1533 - fix iotest error on windows 2022-04-22 20:53:54 +01:00
Sergey Lyubka a7cbc19755 Fix #1532 - improve ipv6 DNS lookup 2022-04-22 20:44:53 +01:00
Sergey Lyubka 93ff3e4d1f Add message sequence test 2022-04-21 03:13:31 +01:00
Sergey Lyubka 1dfc748e87 Add message sequence test 2022-04-21 02:50:50 +01:00
Sergey Lyubka e1f17a7ff1 Merge pull request #1531 from zudwalex/fix_tests
Fix zephyr tests
2022-04-20 11:38:00 +01:00
Alex Alashkin 56c40ff0fd Fix zephyr tests 2022-04-20 09:28:36 +03:00
Sergey Lyubka 9dd3b75165 Handle #1529 - do not mark normal conn termination as an error 2022-04-19 20:28:10 +01:00
Sergey Lyubka 20fc8f0279 Handle #1529: add automatic linkage against ws2_32.lib on _WIN32 targets 2022-04-19 18:51:28 +01:00
Sergey Lyubka 944704a12d Fix #1528: pack.c build on Win32 2022-04-19 18:46:29 +01:00
Sergey Lyubka fba9f14b2c Merge pull request #1526 from zudwalex/zephyr3
Add Zephyr build to Github actions
2022-04-19 09:03:30 +01:00
Alex Alaskin a7c24aeeee Add Zephyr build to Github actions 2022-04-13 19:54:08 +03:00
Sergey Lyubka 186eb998ba Adjust to timer API change, add root makefile 2022-04-13 14:03:00 +01:00
Sergey Lyubka d630ba743d Merge pull request #1524 from zudwalex/zephyr2
Add Zephyr examples
2022-04-13 11:17:22 +01:00
Sergey Lyubka e9fabdcc96 Handle #1523 - multiple WS messages 2022-04-13 11:14:57 +01:00
Sergey Lyubka 31698678fb Squash warning 2022-04-13 10:52:06 +01:00
Sergey Lyubka ebd2e89faa Handle #1522 - Fix TLS buffering 2022-04-13 10:48:17 +01:00
Alex Alashkin 17e0fb1c15 Add Zephyr examples 2022-04-13 11:10:22 +03:00
Sergey Lyubka 6e3e77a1a0 Enhance http-streaming-client 2022-04-12 19:06:37 +01:00
Sergey Lyubka ef44f90491 Fix #1520 - move state to struct mg_mgr 2022-04-12 14:14:55 +01:00
Sergey Lyubka 66376885e9 Fix #1521 - add mg_tou64() 2022-04-12 10:20:43 +01:00
Sergey Lyubka db941e2627 Fix #1519 - make opts structs in API calls const 2022-04-12 10:04:55 +01:00
Sergey Lyubka 5f78d34f4d Fix #1513 - fix mg_strdup() documentation 2022-04-12 09:57:25 +01:00
Sergey Lyubka 85281ae128 Fix #1514 - explicitely initialize io in mg_iobuf_init() 2022-04-12 09:54:05 +01:00
Sergey Lyubka e5d1b213c9 Merge pull request #1518 from jameshilliard/mqtt-suback
Ensure MQTT_CMD_SUBACK is received before calling mg_mqtt_pub in tests
2022-04-11 10:45:07 +01:00
James Hilliard e4ab9a4130 Ensure MQTT_CMD_SUBACK is received before calling mg_mqtt_pub in tests 2022-04-07 19:11:51 -06:00
Sergey Lyubka a1ec179229 Use uint64_t for uptime in millis, not int64_t 2022-04-07 13:50:25 +01:00
Sergey Lyubka 2f77855ec1 Handle #1502 - send CLOSE when CLOSE is received 2022-04-05 14:45:39 +01:00
Sergey Lyubka d11173fe90 Fix #1510 - wrap X509_VERIFY_PARAM_set1_host() usage inside a conditional 2022-04-05 13:09:17 +01:00
Sergey Lyubka fde8b82b78 Merge pull request #1507 from jameshilliard/mbedtls-reset
Handle MBEDTLS_ERR_NET_CONN_RESET errors
2022-04-05 13:06:06 +01:00
Sergey Lyubka b20eded30e Fix ESP32 example 2022-04-05 12:54:20 +01:00
James Hilliard 3ec17c41be Handle MBEDTLS_ERR_NET_CONN_RESET errors 2022-04-04 17:17:17 -06:00
Sergey Lyubka 0ed64707f3 Introduce MG_ENABLE_CUSTOM_MILLIS 2022-04-04 18:14:59 +01:00
Sergey Lyubka 417533e90c Merge pull request #1509 from jameshilliard/wakeup-conflict
Fix mg_mgr_wakeup conflicting types error
2022-04-04 09:16:34 +01:00
James Hilliard e268affb5a Fix mg_mgr_wakeup conflicting types error 2022-04-02 22:10:17 -06:00
Sergey Lyubka 4474ada291 Fix #1505: let mg_wakeup() return status 2022-04-03 00:17:31 +01:00
Sergey Lyubka a65c3288d8 Merge pull request #1508 from jameshilliard/mqtt-debug
Print mqtt buffer on error in tests
2022-04-03 00:11:25 +01:00
Sergey Lyubka affcef7e37 Fix clean targets 2022-04-03 00:04:58 +01:00
James Hilliard 173c2ba338 Print mqtt buffer on error in tests
This test seems to fail randomly a lot, print the buffer on error to
make it easier to isolate issues.
2022-04-02 15:26:15 -06:00
Sergey Lyubka b5fcf95b88 Enable mbedTLS 2022-04-01 21:33:09 +01:00
Sergey Lyubka d954851d43 Drop CRL from mbedTLS integration - let customer do a custom init if required 2022-04-01 16:42:41 +01:00
Sergey Lyubka df602c27d5 Disable SSI on Zephyr by default 2022-04-01 16:38:37 +01:00
Sergey Lyubka 3d9fa10485 Increase sock limit, fix multiple connections issue 2022-04-01 15:42:15 +01:00
Sergey Lyubka 6990040626 Set DNS error on UDP send error 2022-04-01 15:41:23 +01:00
Sergey Lyubka b42ea6cd3c Better logging 2022-04-01 15:41:03 +01:00
Sergey Lyubka 8c7fd8b330 Set default socket in mg_connect() to -1 2022-04-01 15:40:53 +01:00
Sergey Lyubka 4e0039625e Remove cruft 2022-03-31 18:07:15 +01:00
Sergey Lyubka 6c909f8935 Disable SNTP 2022-03-31 18:02:29 +01:00
Sergey Lyubka 4f5d385e52 Enable SNTP in zephyr example 2022-03-31 17:42:09 +01:00
Sergey Lyubka 84fe1d87b3 Enable DHCP 2022-03-31 17:26:58 +01:00
Sergey Lyubka bb0bc41f99 Add zephyr example skeleton 2022-03-31 16:58:27 +01:00
Sergey Lyubka 1c2d67ded7 Add missing arch_zephyr.h, add musl target 2022-03-30 09:06:00 +01:00
Sergey Lyubka 7372a05ce5 Add initial Zephyr support 2022-03-30 07:56:44 +01:00
Sergey Lyubka 642befa8d5 Remove unused mg_hmac_sha1() 2022-03-22 13:30:25 +00:00
Sergey Lyubka 3b5a9e2d4b Add tests for mg_ws_wrap() and messages > 128 bytes 2022-03-22 12:46:55 +00:00
Sergey Lyubka 6b3d9d9baf Add failed TLS host validation 2022-03-22 10:51:37 +00:00
Sergey Lyubka 436852d077 Enable TLS host validation in test_http_client() 2022-03-22 10:48:50 +00:00
Sergey Lyubka b78ced4fa9 Honor hostname validation for openssl 2022-03-22 08:50:37 +00:00
Sergey Lyubka 1bc81df00e Fix mg_dns_parse_name_depth() 2022-03-21 16:29:27 +00:00
Sergey Lyubka bf025537b6 Merge pull request #1495 from jameshilliard/optimize-valgrind
Run valgrind tests with appropriate compiler optimizations.
2022-03-21 15:18:58 +00:00
Sergey Lyubka ad4812218d Handle #1497 - handshake format 2022-03-21 15:17:09 +00:00
Sergey Lyubka 0fbc4b8ff4 Reuse sock.c logic in mbedtls integration 2022-03-21 14:39:27 +00:00
Sergey Lyubka 7ef82f5caf Add vc2017 target 2022-03-21 14:38:42 +00:00
Sergey Lyubka 1141ea7933 Add connection timeout 2022-03-20 18:50:01 +00:00
James Hilliard 1e9344161b Run valgrind tests with appropriate compiler optimizations. 2022-03-18 13:51:05 -06:00
Sergey Lyubka 6c021765bb Merge pull request #1479 from zudwalex/infineon_rtx_support
Add RTX-RTOS support
2022-03-18 09:18:49 +00:00
Sergey Lyubka bc151af51d Set IPv6=0 for valgrind test 2022-03-18 09:10:24 +00:00
Sergey Lyubka 8bd09e0e80 Add valgrind test 2022-03-18 09:03:18 +00:00
Sergey Lyubka 5978cb9703 Add valgrind test 2022-03-18 08:56:12 +00:00
Sergey Lyubka 8c447a95dd Fix #1483 - add valgrind target, do not use stdlib strcasecmp() 2022-03-18 08:52:54 +00:00
Sergey Lyubka 870431dc1a Enhance HTTP client example 2022-03-15 14:14:36 +00:00
Sergey Lyubka 1125b1f545 Fix #1490 - add winerror.h to fix old mingw build 2022-03-15 10:10:08 +00:00
Sergey Lyubka d255fc737a Fix mongoose.exe target 2022-03-15 10:03:00 +00:00
Sergey Lyubka 37cb51ee94 Fix #1488 - properly set non-blocking flags on accepted connections 2022-03-14 13:26:00 +00:00
Sergey Lyubka 02cabcc0ec Fix busy loop in TLS case 2022-03-14 11:16:41 +00:00
Sergey Lyubka 3ffb6c046e Merge branch 'master' of github.com:cesanta/mongoose 2022-03-11 15:40:22 +00:00
Sergey Lyubka 7bf8b81995 Fix mg_http_delete_chunk() 2022-03-11 15:38:59 +00:00
Sergey Lyubka 0ddda4a267 Merge pull request #1487 from jameshilliard/update-actions
Update actions/checkout@v2 to actions/checkout@v3
2022-03-08 12:06:08 +00:00
James Hilliard c46ad970cb Update actions/checkout@v2 to actions/checkout@v3 2022-03-06 21:59:25 -07:00
Sergey Lyubka 733abcffbc Merge pull request #1485 from jameshilliard/fix-pack-rebuild
Regenerate pack file when dependencies are changed.
2022-03-06 16:50:03 +00:00
Sergey Lyubka 139f928731 Fix #1481 - use stricter compilation flags 2022-03-06 14:31:42 +00:00
James Hilliard 3da9986232 Regenerate pack file when dependencies are changed.
Should help prevent some spurious test failures due to stale
test/packed_fs.c file.
2022-03-02 10:12:48 -07:00
Sergey Lyubka a284df01d1 Fix #1480 - use memmove instead of memcpy in mg_iobuf_resize() 2022-02-28 16:51:46 +00:00
Alex Alashkin 412f5cf3e0 Add RTX-RTOS support 2022-02-23 19:56:37 +02:00
Sergey Lyubka 42ba1e4aed Fix #1473 - calm static analyzer 2022-02-23 13:34:20 +00:00
Sergey Lyubka 1893b06c13 Fix #1472 - calm static analyzer 2022-02-23 13:31:00 +00:00
Sergey Lyubka b0cbb70e53 Fix #1471 - calm static analyzer 2022-02-23 13:30:14 +00:00
Sergey Lyubka 78e81fef27 Fix #1471 - calm static analyzer 2022-02-23 13:28:56 +00:00
Sergey Lyubka dd08231a34 Fix #1470 - calm static analyzer 2022-02-23 13:25:25 +00:00
Sergey Lyubka 8c78b7ef53 Refactor mg_alloc_conn() 2022-02-23 10:51:01 +00:00
Sergey Lyubka c650fdcdfc Move mg_listen() to net.c 2022-02-23 03:06:02 +00:00
Sergey Lyubka e99973d4b7 Introduce local address 2022-02-22 22:00:55 +00:00
Sergey Lyubka 4012a86bd8 Remove gitter chat link 2022-02-22 21:46:07 +00:00
Sergey Lyubka 048fee5c88 Fix build after mg_connect() move 2022-02-22 21:35:03 +00:00
Sergey Lyubka a287fd97e4 Factor mg_connect() to net.c 2022-02-22 21:27:08 +00:00
Sergey Lyubka 3a249183f8 Factor out mg_alloc_conn() 2022-02-22 21:14:29 +00:00
Sergey Lyubka 393dad97e0 Get rid of ctype dependencies 2022-02-22 20:20:56 +00:00
Sergey Lyubka 113abc71d4 Add SPDX identifier. Fix #1387 2022-02-22 18:13:06 +00:00
Sergey Lyubka 63261ec0d5 Use mg_http_status() 2022-02-19 16:19:51 +00:00
Sergey Lyubka 507935d067 Tiny refactoring - move clearance of the c->is_resolving flag to the mg_connect_resolved() 2022-02-19 16:17:03 +00:00
Sergey Lyubka c579b73f75 Update docs 2022-02-19 15:31:22 +00:00
Sergey Lyubka e69ec148cc Better mbedtls debug 2022-02-19 13:46:07 +00:00
Sergey Lyubka a76ade9bb2 Update packed fs 2022-02-14 17:44:43 +00:00
Sergey Lyubka 4fec149345 Disable dirlist by default 2022-02-14 11:32:01 +00:00
Sergey Lyubka 5bc164de76 Remove MG_ENABLE_DIRLIST 2022-02-14 11:19:24 +00:00
Sergey Lyubka 969b84043c Fix #1465 - rename LL_* constants, introduce MG_* log macros 2022-02-12 18:17:25 +00:00
Sergey Lyubka da72dafbfb Fix #1462 - add helper function for getting HTTP response status code. 2022-02-12 16:51:37 +00:00
Sergey Lyubka 1adf7f6b2b Handle #1463 - add missing includes for windows 2022-02-12 16:43:08 +00:00
Sergey Lyubka 3e085ccbaa Eliminate explicit _WIN32 checks 2022-02-12 14:33:43 +00:00
Sergey Lyubka 3e8e2ab513 Add optional zip cmd to pack.c 2022-02-12 14:28:02 +00:00
Sergey Lyubka 4f802ece52 Enhance mg_snrpintf tests 2022-02-11 18:15:34 +00:00
Sergey Lyubka 313ac34e59 Handle minus in the fmt spec 2022-02-11 11:02:06 +00:00
Sergey Lyubka 54f75889fa Using mg_snprintf() everywhere 2022-02-10 17:11:03 +00:00
Sergey Lyubka f64c86d23f Use mg_snprintf() 2022-02-10 11:56:55 +00:00
Sergey Lyubka 9e634310b4 Use MG_EV_HTTP_CHUNK for streaming upload 2022-02-09 12:24:06 +00:00
Sergey Lyubka 33666e7d6b Fix ESP32 example 2022-02-08 14:13:47 +00:00
Sergey Lyubka 40d4c7e410 Fix ESP32 example 2022-02-08 13:43:34 +00:00
Sergey Lyubka 8cd4699dbc Rename FS API functions to avoid conflicts 2022-02-08 13:36:04 +00:00
Sergey Lyubka 49d5aff8f9 Call realpath() against root_dir only when there are no overrides 2022-02-01 11:10:01 +00:00
Sergey Lyubka b2ee9cb363 Update docs 2022-01-29 11:12:31 +00:00
Sergey Lyubka e9c20ad992 Update docs 2022-01-29 11:11:22 +00:00
Sergey Lyubka 63b880e624 Handle #1459 - deprecate mg_globmatch, introduce mg_match() 2022-01-29 11:03:54 +00:00
Sergey Lyubka 768f67ae0a Fix #1458 - fail on example build error, and fix examples/mqtt-server 2022-01-27 12:10:03 +00:00
Sergey Lyubka fbf5fa53c6 Refactor ff_open() 2022-01-25 10:50:08 +00:00
Sergey Lyubka 4a5824e7a7 Silence error in clean target 2022-01-24 13:07:56 +00:00
Sergey Lyubka 32f3c48b65 Fix FreeBSD build 2022-01-24 02:20:45 +00:00
Sergey Lyubka f7fbcd2451 Disable leaks detection on macos 2022-01-23 06:58:06 +00:00
Sergey Lyubka c6253854ba Disable leaks detection on macos 2022-01-23 06:12:36 +00:00
Sergey Lyubka 544644e94b Refactor - always enable LOG macro. Enhance iolog 2022-01-23 06:10:14 +00:00
Sergey Lyubka f6cebf6956 Listen on INADDR_ANY by default 2022-01-22 17:03:27 +00:00
Sergey Lyubka 1b82aa02aa 7.5 -> 7.6 2022-01-21 12:41:52 +00:00
Sergey Lyubka e565a7e73d Fix #1453 - do not allow empty wakeup buffer 2022-01-21 12:35:28 +00:00
Sergey Lyubka 59c4dfa33b Fix 1452 - byte order issue in mg_check_ip_acl 2022-01-21 12:24:58 +00:00
Sergey Lyubka 1a8e3c2e31 Decrese debug verbosity 2022-01-19 18:15:47 +00:00
Sergey Lyubka 226917e1cd Use mg_str, not pointers, in mg_mqtt_{sub,pub} 2022-01-19 09:57:27 +00:00
Sergey Lyubka fe813ee247 Add buf,len params to mg_mgr_wakeup() 2022-01-19 09:25:01 +00:00
Sergey Lyubka 4cd830a0bf Improve ff_write - save large blocks in chunks 2022-01-19 08:43:34 +00:00
Sergey Lyubka 1259ca9ac9 Add missing mkdir() on ARM targets 2022-01-18 19:31:10 +00:00
Sergey Lyubka d0857fe36c Add mkdir to the FS API 2022-01-18 19:19:34 +00:00
Sergey Lyubka 56a7438114 Add fs arg to mg_file* and mg_http_upload API 2022-01-18 17:11:02 +00:00
Sergey Lyubka 09df542db8 Fix unamalgamated build 2022-01-18 08:02:41 +00:00
Sergey Lyubka 1df102ab93 Add Fat FS support 2022-01-18 07:44:30 +00:00
Sergey Lyubka 02b3b780bb Add missing fs.c 2022-01-17 14:44:59 +00:00
Sergey Lyubka f84f3da012 Change in FS API for fs->open() and fs->close() 2022-01-17 14:42:41 +00:00
Sergey Lyubka c65c8fdaaa Protect against the directory traversal in mg_upload() 2022-01-17 12:08:23 +00:00
Sergey Lyubka b484123f1b gcc and clang 2022-01-15 01:45:31 +00:00
Sergey Lyubka b60119790e Fix wrong memcmp usage in mg_url_port() 2022-01-14 12:45:55 +00:00
Sergey Lyubka 216427cea0 Cleanup suseconds_t usage 2022-01-14 12:33:06 +00:00
Sergey Lyubka c53e2fcc14 Fix #1444 - clang warns on undefined behavior with null pointers 2022-01-14 09:55:15 +00:00
Sergey Lyubka 9fd80fd136 Fix #1445 - send error to a resolving connection when DNS error happens 2022-01-14 08:50:01 +00:00
Sergey Lyubka 5fc284722f Fix #1448 - fix MG_ENABLE_FILE build 2022-01-14 08:21:12 +00:00
Sergey Lyubka ad6e395da0 Fix #1447 - typo in mg_random() 2022-01-14 08:18:28 +00:00
Sergey Lyubka 6c79a406cd Fix localtime_r and gmtmime_r signatures 2022-01-11 19:15:38 +00:00
Sergey Lyubka 456e8bd249 Use 3 params for fcntl() 2022-01-10 18:21:13 +00:00
Sergey Lyubka de95f28598 Make stat and struct stat changeable 2022-01-10 18:07:45 +00:00
Sergey Lyubka 43405afdb2 Dont use ctype.h from str.h 2022-01-10 17:30:51 +00:00
Sergey Lyubka 97f95e17db Better strategy for restoring multicast address 2022-01-07 18:40:48 +00:00
Sergey Lyubka 6725d61b3f Re-init SSDP peer address before every search 2022-01-07 18:28:51 +00:00
Sergey Lyubka ebda2cb100 Use sendto on Mac too 2022-01-07 16:31:29 +00:00
Sergey Lyubka bb72b2494d Fix unamalgamated target 2022-01-07 15:48:09 +00:00
Sergey Lyubka 3b0a509cae Change mg_resolve() 2022-01-07 15:00:10 +00:00
Sergey Lyubka 2f981f9383 Skip connect() on UDP connection 2022-01-06 15:42:55 +00:00
Sergey Lyubka fc3c08e411 Change mg_straddr() API 2022-01-05 19:09:11 +00:00
Sergey Lyubka ff811f287a Squash warnings 2022-01-05 13:57:30 +00:00
Sergey Lyubka 8ebb3bc37b More detailed debug log - show ip/port for sender and receiver 2022-01-05 13:47:11 +00:00
Sergey Lyubka f7ff571c5f Added SSDP search example 2022-01-05 13:22:00 +00:00
Sergey Lyubka 6a82cbee71 Send MG_EV_WRITE for UDP messages 2022-01-05 13:03:56 +00:00
Sergey Lyubka b0585bd0a9 Do not set c->label for DNS connections - let labels be user-only-settable 2022-01-05 11:46:26 +00:00
Sergey Lyubka 6ad4d242c2 Better cleanup 2022-01-05 11:45:22 +00:00
Sergey Lyubka 030b1d9826 Remove wrong comment 2021-12-27 03:31:33 +00:00
Sergey Lyubka f6ffa7b3b9 Add huge-response example 2021-12-27 03:25:28 +00:00
Sergey Lyubka 137efe2233 Show requests in flight 2021-12-24 20:46:31 +00:00
Sergey Lyubka 62411d039e Increase mqtt timeouts 2021-12-24 12:14:06 +00:00
novlean 54b1716e77 Merge pull request #1436 from cesanta/novlean-patch-1
Update README.md
2021-12-23 17:18:55 +00:00
novlean 9f7aba8039 Update README.md 2021-12-23 17:18:45 +00:00
Sergey Lyubka a176b8d566 Upate docs w.r.t. mg_mqtt_opts 2021-12-23 11:12:10 +00:00
Sergey Lyubka 26a97ec36c Fix #1434 - set will QoS in CONNECT message correctly, and rename qos -> will_qos in struct mg_mqtt_opts 2021-12-23 11:00:18 +00:00
Sergey Lyubka f29807aad8 Resolve root path before serving to avoid double dots 2021-12-22 18:05:03 +00:00
Sergey Lyubka 92d3c7a4cd Add realpath() for Win32 2021-12-22 18:04:36 +00:00
Sergey Lyubka af3a0f6af0 Add realpath() for Win32 2021-12-22 18:04:27 +00:00
Sergey Lyubka ce6164ca20 Fail stat() if S_ISDIR is not defined 2021-12-22 18:04:16 +00:00
Sergey Lyubka e9fc3fcecf Update event enum 2021-12-21 22:20:42 +00:00
Sergey Lyubka 25a1a24890 Fallback to time(0) in mg_millis() 2021-12-21 22:04:03 +00:00
Sergey Lyubka bf7fc1d5a9 Amalgamate 2021-12-21 21:58:03 +00:00
Sergey Lyubka 2126b49ab3 Merge pull request #1433 from ryuukk/patch-2
Remove debugging left over
2021-12-21 21:56:41 +00:00
Sergey Lyubka ae6767b1d2 Use int64_t for timers and mg_millis() 2021-12-21 21:50:18 +00:00
ryuukk 133629dbee Remove debugging left over 2021-12-21 21:28:22 +01:00
Sergey Lyubka fb0a9bc7e3 Fix build 2021-12-21 18:16:12 +00:00
Sergey Lyubka 2a3492766f Remove mg_time() and mg_usleep() 2021-12-21 17:44:34 +00:00
Sergey Lyubka 2ec7e5653e Merge branch 'master' of github.com:cesanta/mongoose 2021-12-21 17:40:04 +00:00
Sergey Lyubka 30bfb245f5 Make mongoose more bare-metal Keil friendly. Declare settings after arch. Introduce MG_ENABLE_FILE 2021-12-21 17:39:55 +00:00
Sergey Lyubka 84d89bc4f5 Merge pull request #1431 from ryuukk/patch-1
Closed WS connection is not an error for the logger
2021-12-21 08:47:21 +00:00
ryuukk 7f55fabfec Closed WS connection is not an error for the logger 2021-12-21 01:34:48 +01:00
Sergey Lyubka 9e0f04dc8b Add -S option 2021-12-20 16:35:16 +00:00
Sergey Lyubka 574c2954b3 Docs nitpick 2021-12-19 10:53:17 +00:00
Sergey Lyubka 9d38e26b81 Add MG_HTTP_INDEX build var 2021-12-17 13:54:01 +00:00
Sergey Lyubka 4e75a0c81e 7.4 -> 7.5 2021-12-17 13:34:32 +00:00
Sergey Lyubka d311e967fa Add mingw target 2021-12-16 09:22:28 +00:00
Sergey Lyubka d0a6c111cb Call MG_EV_ERROR with c->is_closing set 2021-12-15 11:31:42 +00:00
Sergey Lyubka aa6d8b4662 Remove TLS custom_init() API 2021-12-15 11:04:43 +00:00
Sergey Lyubka 3f5fd89fd7 Pass more args to custom TLS init 2021-12-14 13:21:59 +00:00
Sergey Lyubka f43a5a005c Introduce custom TLS init function 2021-12-14 13:04:11 +00:00
Sergey Lyubka 755bf3c412 Split TLS files and introduce MG_ENABLE_CUSTOM_TLS 2021-12-14 12:42:41 +00:00
Sergey Lyubka 84f3fb0ba5 Move EXTERN_C to util.h 2021-12-14 12:11:05 +00:00
Sergey Lyubka f2df6c114d Example for mg_http_bauth 2021-12-13 15:14:29 +00:00
Sergey Lyubka f93659e303 Merge branch 'master' of github.com:cesanta/mongoose 2021-12-13 15:08:14 +00:00
Sergey Lyubka 226404d39c Example for mg_http_bauth 2021-12-13 15:08:05 +00:00
Sergey Lyubka c1bcc143b9 Merge pull request #1417 from DavidKorczynski/cifuzz-integration
Add CIFuzz integration
2021-12-13 11:35:31 +00:00
Sergey Lyubka 5ca9062d45 Less verbose serve_dir 2021-12-07 15:23:02 +00:00
Sergey Lyubka 0b209eaabb Add mg_http_get_header_var() 2021-12-03 09:54:03 +00:00
David Korczynski 47ef191ea2 Add CIFuzz integration 2021-11-30 22:48:20 +00:00
Sergey Lyubka 25e76d761e Fix usage message 2021-11-29 14:00:03 +00:00
Sergey Lyubka 9731a51dae Recognise :PORT as a valid URL 2021-11-29 13:57:33 +00:00
Sergey Lyubka 81220fa590 Remove noisy debug 2021-11-26 16:23:06 +00:00
Sergey Lyubka 24e8c4d81a Increase SNMP test timeout 2021-11-25 17:31:41 +00:00
Sergey Lyubka 013520b0c0 Add note for mg_http_serve_dir() 2021-11-25 12:29:45 +00:00
Sergey Lyubka f012bcb027 Update issue template 2021-11-25 11:47:26 +00:00
Sergey Lyubka 922e1ce529 Make captive portal work on Linux 2021-11-23 17:52:54 +00:00
Sergey Lyubka 7a707cc64b Add README 2021-11-23 15:08:34 +00:00
Sergey Lyubka 1967acfef2 Make it build on vc98 2021-11-23 13:24:21 +00:00
Sergey Lyubka e24cb04003 Update URL 2021-11-23 09:37:03 +00:00
Sergey Lyubka 67974746ff Add json-rpc-over-ws example 2021-11-22 16:45:14 +00:00
Sergey Lyubka 8d916d41d7 Fix #1409 - remove VLA which confuses MSVC 2021-11-22 12:24:58 +00:00
Sergey Lyubka 08444e3942 Fix links and typos 2021-11-16 12:30:40 +00:00
Sergey Lyubka 9c41a0cd26 Fix links 2021-11-16 09:54:36 +00:00
Sergey Lyubka 2c50069d98 Do not clean the recv buffer 2021-11-14 16:47:40 +00:00
Sergey Lyubka 31059f6728 Add test.html 2021-11-14 14:31:51 +00:00
Sergey Lyubka 2000752b2c Remove dead code 2021-11-11 15:15:47 +00:00
Sergey Lyubka dd34830443 Update README 2021-11-11 15:11:57 +00:00
Sergey Lyubka c58f4ee51b Add symlinks 2021-11-11 14:21:52 +00:00
Sergey Lyubka 00f7e8cb2a Support wildcards 2021-11-11 12:08:17 +00:00
Sergey Lyubka 439904e27f Add signal handler 2021-11-11 10:35:41 +00:00
Sergey Lyubka 4d6590ece2 Add readme 2021-11-11 10:29:16 +00:00
Sergey Lyubka 9b0c4b970d Add README.md 2021-11-11 08:26:31 +00:00
Sergey Lyubka a80ce1b12a Add signal handler 2021-11-11 07:36:25 +00:00
Sergey Lyubka 57dd74c180 Add reconnection logic 2021-11-10 12:57:02 +00:00
Sergey Lyubka 90c67e4226 Reformatting for better readability 2021-11-06 11:29:56 +00:00
Sergey Lyubka 093cd6a6a3 Fix FD_CLOEXEC setting 2021-11-05 13:30:12 +00:00
Sergey Lyubka 975db86306 Add READMEs 2021-11-05 13:02:36 +00:00
Sergey Lyubka eba688e11b Update docs 2021-11-04 23:07:47 +00:00
Sergey Lyubka 05b44204d5 Set FD_CLOEXEC properly 2021-11-04 18:28:32 +00:00
Sergey Lyubka 0bb017c1d5 Add examples/mqtt-client-aws-iot 2021-11-03 20:58:57 +00:00
Sergey Lyubka a6efd0b335 Merge branch 'master' into dec 2021-11-02 22:43:59 +00:00
Sergey Lyubka c3d237e9c6 More verbose log 2021-11-02 22:43:23 +00:00
Sergey Lyubka 09d28f5570 Print a clarifying message at start 2021-11-02 16:53:57 +00:00
Sergey Lyubka ba02937ec6 Squash warnings 2021-11-02 16:40:25 +00:00
Sergey Lyubka 3d78b14deb Fix #1396 - JS sorting code for dir listing 2021-11-02 16:01:06 +00:00
Sergey Lyubka f558551d1d Fix #1395 - show decoded URI in dir listing 2021-11-02 15:11:56 +00:00
Sergey Lyubka 12fe66370f Fix #1393 - cleanup mime types 2021-11-01 20:31:45 +00:00
Sergey Lyubka 423aaa492c Fix #1394 - mg_mqtt_sub(). Add MG_EV_OPEN, too. 2021-11-01 16:20:00 +00:00
Sergey Lyubka 2016586475 Clearer client init 2021-11-01 13:29:44 +00:00
Sergey Lyubka bf88167152 Add missing test files 2021-10-31 12:50:06 +00:00
Sergey Lyubka 52be84f04e Add user and pass to struct mg_mqtt_opts 2021-10-31 12:48:32 +00:00
Sergey Lyubka 03d187b147 Fix #1375 - change event processing order 2021-10-31 12:40:56 +00:00
Sergey Lyubka 3329047579 More explicit check on hm->uri.len 2021-10-31 12:37:40 +00:00
Sergey Lyubka 86c70e5abb Fix #1391 - make remove_double_dots less aggressive 2021-10-31 12:36:34 +00:00
Sergey Lyubka b17f4108ea Change MG_PATH_MAX for ESP8266 2021-10-30 19:34:53 +01:00
Sergey Lyubka 4e523cc455 Remove binary section 2021-10-27 20:13:05 +01:00
Sergey Lyubka 49c9be9159 Set MG_IO_SIZE -> 128 2021-10-27 19:20:52 +01:00
Sergey Lyubka f1bfe0760f Optimise TLS read 2021-10-27 19:18:44 +01:00
Sergey Lyubka 35f02fdaad Use small IO buffer 2021-10-27 19:18:19 +01:00
Sergey Lyubka 6ac09966ba Fix #1374 - fix ESP32 build 2021-10-22 22:06:10 +01:00
Sergey Lyubka af9a7617f5 Merge pull request #1381 from ptsolakos/master
Certificate revocation checking when using mbedtls
2021-10-22 20:30:47 +01:00
Sergey Lyubka 42c89732c8 Remove private.h 2021-10-22 19:41:26 +01:00
Sergey Lyubka 054475ddbf Fix #1376 - handle comma for unquoted header vars 2021-10-22 14:33:51 +01:00
Sergey Lyubka 126d556d6a Merge pull request #1380 from jameshilliard/fix-time
util: Actually return uptime on OSX/Linux for mg_millis
2021-10-22 14:06:44 +01:00
Sergey Lyubka 529a302e77 Merge pull request #1379 from jameshilliard/fix-sntp
sntp: Don't set Leap Indicator in client messages.
2021-10-22 14:04:54 +01:00
Sergey Lyubka acbf978452 Merge pull request #1378 from jameshilliard/undef
Fix some undefined macro warnings.
2021-10-22 14:03:42 +01:00
Sergey Lyubka 90b22716ea Merge branch 'master' into undef 2021-10-22 14:03:25 +01:00
Sergey Lyubka fb90c504a6 Remove private.h 2021-10-22 14:00:31 +01:00
Sergey Lyubka 65fd834654 Increate mqtt test timeout 2021-10-22 13:58:51 +01:00
Sergey Lyubka a84acf5b8b Merge pull request #1385 from zudwalex/move_stm32_examples
Move STM32 examples to stm32 folder
2021-10-22 13:39:13 +01:00
Sergey Lyubka e9505db146 Merge pull request #1384 from zudwalex/master
Add more NXP examples and move them to nxp folder
2021-10-22 13:39:02 +01:00
Sergey Lyubka 4fb49b011e Merge pull request #1377 from jameshilliard/missing-prototypes
Make private functions static and add missing prototypes.
2021-10-22 13:16:06 +01:00
Alex Alashkin 6742991469 Move STM32 examples to stm32 folder 2021-10-22 14:19:50 +03:00
Alex Alashkin 2430cc5aac Add more NXP examples and move them to nxp folder 2021-10-22 14:04:15 +03:00
Sergey Lyubka 54590aaf26 Use UDP sendto() on win32 only 2021-10-22 09:14:18 +01:00
Sergey Lyubka 7c5e67b272 Fix #1382 - use sendto for UDP send 2021-10-22 08:56:45 +01:00
Sergey Lyubka c62c8ea4dd Correct licensing URL 2021-10-20 10:04:49 +01:00
Panagiotis Tsolakos 383653d608 mbedtls CRL support
Certificate revocation checking when using mbedtls
2021-10-14 17:56:31 +02:00
James Hilliard 001dc3f33d util: Actually return uptime on OSX/Linux for mg_millis
The documentation for mg_millis indicates it will:
Return current uptime in milliseconds.

So we should use API's that actually return uptime.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2021-10-13 17:06:18 -06:00
James Hilliard 37d45b3fbb sntp: Don't set Leap Indicator in client messages.
Per RFC4330:
This field is significant only in server messages

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2021-10-13 16:48:33 -06:00
James Hilliard f72b8c6034 Fix some undefined macro warnings.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2021-10-13 13:06:00 -06:00
James Hilliard c11e5a9383 Make private functions static and add missing prototypes.
Fixes:
mongoose/mongoose.c:180:8: warning: no previous prototype for ‘mg_dns_parse_name’ [-Wmissing-prototypes]
  180 | size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, char *dst,
      |        ^~~~~~~~~~~~~~~~~
mongoose/mongoose.c:306:6: warning: no previous prototype for ‘mg_dns_send’ [-Wmissing-prototypes]
  306 | void mg_dns_send(struct mg_connection *c, const struct mg_str *name,
      |      ^~~~~~~~~~~
mongoose/mongoose.c:925:6: warning: no previous prototype for ‘mg_http_parse_headers’ [-Wmissing-prototypes]
  925 | void mg_http_parse_headers(const char *s, const char *end,
      |      ^~~~~~~~~~~~~~~~~~~~~
mongoose/mongoose.c:1125:7: warning: no previous prototype for ‘mg_http_etag’ [-Wmissing-prototypes]
 1125 | char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
      |       ^~~~~~~~~~~~
mongoose/mongoose.c:2578:6: warning: no previous prototype for ‘mg_sha1_transform’ [-Wmissing-prototypes]
 2578 | void mg_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
      |      ^~~~~~~~~~~~~~~~~
mongoose/mongoose.c:2976:8: warning: no previous prototype for ‘mg_open_listener’ [-Wmissing-prototypes]
 2976 | SOCKET mg_open_listener(const char *url, struct mg_addr *addr) {
      |        ^~~~~~~~~~~~~~~~

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
2021-10-12 20:18:11 -06:00
Sergey Lyubka b5147a149e Merge pull request #1373 from zudwalex/fix_board_ci
Use mongoose.[ch] from main repo
2021-10-06 12:31:27 +01:00
Alex Alashkin 655529f30b Use mongoose.[ch] from parent repo 2021-10-06 13:15:12 +03:00
novlean fd5319e0ce Merge pull request #1372 from cesanta/novlean-patch-1
license
2021-10-05 17:09:43 +01:00
novlean f8c84ca672 license 2021-10-05 17:09:35 +01:00
novlean 1b81ac8916 Merge pull request #1371 from cesanta/novlean-patch-1
mongoose.ws
2021-10-05 16:54:43 +01:00
novlean c64e2ebb16 mongoose.ws 2021-10-05 16:54:34 +01:00
Sergey Lyubka 3771f2c088 Merge branch 'master' of github.com:cesanta/mongoose 2021-10-04 19:47:34 +01:00
Sergey Lyubka 6d398bd5c7 Fix mg_random() for ESP32 2021-10-04 19:47:24 +01:00
Sergey Lyubka 5c355a5ff3 Merge pull request #1368 from zudwalex/nxp_examples
Add NXP examples
2021-10-04 12:04:00 +01:00
Alex Alashkin d2f8b346e6 Add nxp to actions 2021-10-04 13:15:32 +03:00
Alex Alashkin 5cdaa931f9 Add NXP examples 2021-10-04 12:54:15 +03:00
Sergey Lyubka 693863a51e Merge pull request #1366 from zudwalex/fix_rtos_lwip
Add missed include for FreeRTOS/LwIP arch
2021-10-02 22:45:08 +01:00
Alex Alashkin bc7178e5f7 Add missed include 2021-09-29 12:25:25 +03:00
272 changed files with 12715 additions and 5871 deletions

No files matched your search

+4 -6
View File
@@ -6,9 +6,7 @@
# Environment # Environment
- mongoose version (e.g. 7.3): _X.Y_ - mongoose version: _X.Y_
- Compiler/IDE (e.g. arm-none-eabi-gcc 9.3): _?_ - Compiler/IDE and SDK: ZZZ version _X.Y_
- Target OS (e.g. FreeRTOS 10.3.1, Linux 5.8): _?_ - Target hardware/board: _?_
- Target hardware/board (e.g. STM32F411): _?_ - Connectivity chip/module: _?_
- Connectivity chip/module (e.g. WizNet W5500): _?_
- Target SDK (e.g. STM32CubeF1 1.8.4): _?_
+26
View File
@@ -0,0 +1,26 @@
name: CIFuzz
on: [pull_request]
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'mongoose'
dry-run: false
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'mongoose'
fuzz-seconds: 600
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@v1
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
+86 -46
View File
@@ -3,20 +3,29 @@ on: [push, pull_request]
jobs: jobs:
linux: linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
target: [test, test++, valgrind]
ssl: [MBEDTLS, OPENSSL]
name: linux ${{ matrix.target }} CC=${{ matrix.cc }} SSL=${{ matrix.ssl }}
env:
IPV6: 0
CC: ${{ matrix.cc }}
SSL: ${{ matrix.ssl }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install packages - name: Install packages
run: sudo apt-get install libmbedtls-dev run: sudo apt-get update ; sudo apt-get install libmbedtls-dev valgrind
- name: make - name: test
run: make test test++ IPV6=0 run: make ${{ matrix.target }}
- name: openssl
run: make test SSL=OPENSSL IPV6=0
- name: unamalgamated - name: unamalgamated
run: make unamalgamated run: make unamalgamated
examples: examples:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install packages - name: Install packages
run: sudo apt-get install libmbedtls-dev run: sudo apt-get install libmbedtls-dev
- name: examples - name: examples
@@ -24,19 +33,19 @@ jobs:
macos: macos:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install packages - name: Install packages
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install jq mbedtls openssl run: HOMEBREW_NO_AUTO_UPDATE=1 brew install jq mbedtls openssl
- name: make - name: make
run: make test++ upload-coverage IPV6=0 run: make test++ upload-coverage IPV6=0 ASAN_OPTIONS=
- name: openssl - name: openssl
run: make test SSL=OPENSSL IPV6=0 OPENSSL=`echo /usr/local/Cellar/openssl*/*` run: make test SSL=OPENSSL IPV6=0 ASAN_OPTIONS= OPENSSL=`echo /usr/local/Cellar/openssl*/*`
- name: exports - name: exports
run: make mg_prefix run: make mg_prefix
windows: windows:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: vc98 - name: vc98
run: make vc98 run: make vc98
- name: vc2017 - name: vc2017
@@ -48,48 +57,79 @@ jobs:
arm: arm:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: arm - name: arm
run: make arm run: make arm
esp32: matrix_examples:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: strategy:
- uses: actions/checkout@v2 fail-fast: false
matrix:
example:
- name: esp32 - name: esp32
run: make -C examples/esp32 build path: esp32
esp8266:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: esp8266 - name: esp8266
run: make -C examples/esp8266 build path: esp8266
stm32-freertos-tcp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: stm32-freertos-tcp - name: stm32-freertos-tcp
run: make -C examples/stm32-freertos-tcp build path: stm32/stm32-freertos-tcp
stm32-nucleo-f746z:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: stm32-nucleo-f746z - name: stm32-nucleo-f746z
run: make -C examples/stm32-nucleo-f746z build path: stm32/stm32-nucleo-f746z
stm32-nucleo-f429z:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: stm32-nucleo-f429z - name: stm32-nucleo-f429z
run: make -C examples/stm32-nucleo-f429z build path: stm32/stm32-nucleo-f429z
stm32-nucleo-h743z:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: stm32-nucleo-h743z - name: stm32-nucleo-h743z
run: make -C examples/stm32-nucleo-h743z build path: stm32/stm32-nucleo-h743z
nxp-mimxrt1020-azurertos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: nxp-mimxrt1020-azurertos - name: nxp-mimxrt1020-azurertos
run: make -C examples/nxp-mimxrt1020-azurertos build path: nxp/nxp-mimxrt1020-azurertos
- name: nxp-frdmk66f-freertos
path: nxp/nxp-frdmk66f-freertos
- name: nxp-lpcxpresso54s018m-freertos
path: nxp/nxp-lpcxpresso54s018m-freertos
- name: nxp-mimxrt1020-freertos
path: nxp/nxp-mimxrt1020-freertos
- name: nxp-evkbimxrt1050-lwip-freertos
path: nxp/nxp-evkbimxrt1050-lwip-freertos
- name: nxp-evkmimxrt1020-lwip-freertos
path: nxp/nxp-evkmimxrt1020-lwip-freertos
- name: nxp-evkmimxrt1024-lwip-freertos
path: nxp/nxp-evkmimxrt1024-lwip-freertos
- name: nxp-evkmimxrt1060-lwip-freertos
path: nxp/nxp-evkmimxrt1060-lwip-freertos
- name: nxp-evkmimxrt1064-lwip-freertos
path: nxp/nxp-evkmimxrt1064-lwip-freertos
- name: nxp-evkmimxrt1160-cm7-lwip-freertos
path: nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
- name: nxp-evkmimxrt1170-cm7-lwip-freertos
path: nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
- name: nxp-frdmk64f-lwip-freertos
path: nxp/nxp-frdmk64f-lwip-freertos
- name: nxp-frdmk66f-lwip-freertos
path: nxp/nxp-frdmk66f-lwip-freertos
- name: nxp-lpcxpresso54018-lwip-freertos
path: nxp/nxp-lpcxpresso54018-lwip-freertos
- name: nxp-lpcxpresso54608-lwip-freertos
path: nxp/nxp-lpcxpresso54608-lwip-freertos
- name: nxp-lpcxpresso54618-lwip-freertos
path: nxp/nxp-lpcxpresso54618-lwip-freertos
- name: nxp-lpcxpresso54628-lwip-freertos
path: nxp/nxp-lpcxpresso54628-lwip-freertos
- name: nxp-twrk65f180m-lwip-freertos
path: nxp/nxp-twrk65f180m-lwip-freertos
- name: nxp-twrkv58f220m-lwip-freertos
path: nxp/nxp-twrkv58f220m-lwip-freertos
- name: infineon-xmc4700_4800-lwip-rtx-rtos
path: infineon/infineon-xmc4700_4800-lwip-rtx-rtos
- name: ti
path: ti/ti-ek-tm4c1294xl-http-server
- name: nucleo_f746zg-zephyr-http-client
path: zephyr/http-client
- name: nucleo_f746zg-zephyr-http-server
path: zephyr/http-server
- name: nucleo_f746zg-zephyr-mqtt-aws-client
path: zephyr/mqtt-aws-client
- name: nucleo_f746zg-zephyr-websocket-server
path: zephyr/websocket-server
name: ${{ matrix.example.name }}
steps:
- uses: actions/checkout@v3
- name: ${{ matrix.example.name }}
run: make -C examples/${{ matrix.example.path }} build
+1 -1
View File
@@ -13,4 +13,4 @@ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details. See the GNU General Public License for more details.
Alternatively, you can license this software under a commercial Alternatively, you can license this software under a commercial
license, as set out in <https://www.cesanta.com/license>. license, as set out in <https://mongoose.ws/licensing/>.
+50 -25
View File
@@ -1,40 +1,53 @@
SRCS = mongoose.c test/unit_test.c test/packed_fs.c SRCS = mongoose.c test/unit_test.c test/packed_fs.c
HDRS = $(wildcard src/*.h) HDRS = $(wildcard src/*.h)
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1 DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1 -DMG_ENABLE_SSI=1
WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion C_WARN ?= -Wmissing-prototypes -Wstrict-prototypes
WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef $(C_WARN)
OPTS ?= -O3 -g3 OPTS ?= -O3 -g3
VALGRIND_OPTS ?= -O0 -g3
INCS ?= -Isrc -I. INCS ?= -Isrc -I.
CFLAGS ?= $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) $(EXTRA)
SSL ?= MBEDTLS SSL ?= MBEDTLS
CWD ?= $(realpath $(CURDIR)) CWD ?= $(realpath $(CURDIR))
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(CWD):$(CWD) -w $(CWD) DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(CWD):$(CWD) -w $(CWD)
VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS) VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS)
IPV6 ?= 1 IPV6 ?= 1
ASAN_OPTIONS ?= ASAN ?= -fsanitize=address,undefined -fno-sanitize-recover=all
EXAMPLES := $(wildcard examples/*) ASAN_OPTIONS ?= detect_leaks=1
EXAMPLES := $(dir $(wildcard examples/*/Makefile))
PREFIX ?= /usr/local PREFIX ?= /usr/local
VERSION ?= $(shell cut -d'"' -f2 src/version.h) VERSION ?= $(shell cut -d'"' -f2 src/version.h)
.PHONY: examples test COMMON_CFLAGS ?= $(WARN) $(INCS) $(DEFS) -DMG_ENABLE_IPV6=$(IPV6) $(TFLAGS) $(EXTRA)
CFLAGS ?= $(OPTS) $(ASAN) $(COMMON_CFLAGS)
VALGRIND_CFLAGS ?= $(VALGRIND_OPTS) $(COMMON_CFLAGS)
VALGRIND_RUN ?= valgrind --tool=memcheck --gen-suppressions=all --leak-check=full --show-leak-kinds=all --leak-resolution=high --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes
.PHONY: examples test valgrind
ifeq "$(SSL)" "MBEDTLS" ifeq "$(SSL)" "MBEDTLS"
MBEDTLS ?= /usr MBEDTLS ?= /usr/local
CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS)/include -I/usr/include CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS)/include -I/usr/include
LDFLAGS ?= -L$(MBEDTLS)/lib -lmbedtls -lmbedcrypto -lmbedx509 LDFLAGS ?= -L$(MBEDTLS)/lib -lmbedtls -lmbedcrypto -lmbedx509
endif endif
ifeq "$(SSL)" "OPENSSL" ifeq "$(SSL)" "OPENSSL"
OPENSSL ?= /usr OPENSSL ?= /usr/local
CFLAGS += -DMG_ENABLE_OPENSSL=1 -I$(OPENSSL)/include CFLAGS += -DMG_ENABLE_OPENSSL=1 -I$(OPENSSL)/include
LDFLAGS ?= -L$(OPENSSL)/lib -lssl -lcrypto LDFLAGS ?= -L$(OPENSSL)/lib -lssl -lcrypto
endif endif
all: mg_prefix unpacked test test++ arm examples vc98 vc2017 mingw mingw++ linux linux++ fuzz all: mg_prefix unamalgamated unpacked test test++ valgrind arm examples vc98 vc2017 mingw mingw++ linux linux++ fuzz
examples: examples:
@for X in $(EXAMPLES); do $(MAKE) -C $$X example || break; done @for X in $(EXAMPLES); do test -f $$X/Makefile || continue; $(MAKE) -C $$X example || exit 1; done
test/packed_fs.c: Makefile src/fs.h src/ssi.h test/fuzz.c test/data/a.txt test/packed_fs.c: Makefile src/ssi.h test/fuzz.c test/data/a.txt
$(CC) $(CFLAGS) test/pack.c -o pack $(CC) $(CFLAGS) test/pack.c -o pack
./pack $? > $@ ./pack Makefile src/ssi.h test/fuzz.c test/data/a.txt test/data/range.txt > $@
DIR ?= test/data/
OUT ?= fs_packed.c
mkfs:
$(CC) $(CFLAGS) test/pack.c -o pack
./pack -s $(DIR) `find $(DIR) -type f` > $(OUT)
# find $(DIR) -type f | sed -e s,^$(DIR),,g -e s,^/,,g
# Check that all external (exported) symbols have "mg_" prefix # Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h mg_prefix: mongoose.c mongoose.h
@@ -42,43 +55,56 @@ mg_prefix: mongoose.c mongoose.h
# C++ build # C++ build
test++: CC = g++ test++: CC = g++
test++: C_WARN =
test++: WARN += -Wno-shadow -Wno-missing-field-initializers -Wno-deprecated test++: WARN += -Wno-shadow -Wno-missing-field-initializers -Wno-deprecated
test++: test test++: test
musl: test
musl: ASAN =
musl: WARN += -Wno-sign-conversion
musl: CC = $(DOCKER) mdashnet/cc1 gcc
# Make sure we can build from an unamalgamated sources # Make sure we can build from an unamalgamated sources
unamalgamated: $(HDRS) Makefile test/packed_fs.c unamalgamated: $(HDRS) Makefile test/packed_fs.c
$(CC) src/*.c test/packed_fs.c test/unit_test.c $(CFLAGS) $(LDFLAGS) -g -o unit_test $(CC) src/*.c test/packed_fs.c test/unit_test.c $(CFLAGS) $(LDFLAGS) -g -o unit_test
unpacked: unpacked:
$(CC) -I. mongoose.c test/unit_test.c -o unit_test $(CC) -I. mongoose.c test/unit_test.c -o $@
fuzzer: mongoose.c mongoose.h Makefile test/fuzz.c fuzzer: mongoose.c mongoose.h Makefile test/fuzz.c
clang mongoose.c test/fuzz.c $(WARN) $(INCS) -DMG_ENABLE_LINES -DMG_ENABLE_LOG=0 -fsanitize=fuzzer,signed-integer-overflow,address -g -o $@ clang++ mongoose.c test/fuzz.c $(WARN) $(INCS) $(TFLAGS) $(EXTRA) -DMG_ENABLE_LINES -fsanitize=fuzzer,signed-integer-overflow,address -Wno-deprecated -o $@
fuzz: fuzzer fuzz: fuzzer
$(RUN) ./fuzzer $(RUN) ./fuzzer
# make CC=/usr/local/opt/llvm\@8/bin/clang ASAN_OPTIONS=detect_leaks=1 unit_test: Makefile mongoose.h $(SRCS)
test: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6) -fsanitize=address#,undefined
test: mongoose.h Makefile $(SRCS)
$(CC) $(SRCS) $(CFLAGS) -coverage $(LDFLAGS) -g -o unit_test $(CC) $(SRCS) $(CFLAGS) -coverage $(LDFLAGS) -g -o unit_test
# make CC=/usr/local/opt/llvm\@8/bin/clang ASAN_OPTIONS=detect_leaks=1
test: unit_test
ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./unit_test
coverage: test coverage: test
gcov -l -n *.gcno | sed '/^$$/d' | sed 'N;s/\n/ /' gcov -l -n *.gcno | sed '/^$$/d' | sed 'N;s/\n/ /'
gcov mongoose.gcno >/dev/null gcov -t mongoose.c > mongoose.gcov
upload-coverage: coverage upload-coverage: coverage
curl -s https://codecov.io/bash | /bin/bash curl -s https://codecov.io/bash | /bin/bash
valgrind_unit_test: Makefile mongoose.h $(SRCS)
$(CC) $(SRCS) $(VALGRIND_CFLAGS) -coverage $(LDFLAGS) -g -o valgrind_unit_test
valgrind: valgrind_unit_test
$(VALGRIND_RUN) ./valgrind_unit_test
infer: infer:
infer run -- cc test/unit_test.c -c -W -Wall -Werror -Isrc -I. -O2 -DMG_ENABLE_MBEDTLS=1 -DMG_ENABLE_LINES -I/usr/local/Cellar/mbedtls/2.23.0/include -DMG_ENABLE_IPV6=1 -g -o /dev/null infer run -- cc test/unit_test.c -c -W -Wall -Werror -Isrc -I. -O2 -DMG_ENABLE_MBEDTLS=1 -DMG_ENABLE_LINES -I/usr/local/Cellar/mbedtls/2.23.0/include -DMG_ENABLE_IPV6=1 -g -o /dev/null
arm: mongoose.h $(SRCS) arm: mongoose.h $(SRCS)
$(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test -nostartfiles --specs nosys.specs -e 0 $(DOCKER) mdashnet/armgcc arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_NEWLIB $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test -nostartfiles --specs nosys.specs -e 0
riscv: mongoose.h $(SRCS) riscv: mongoose.h $(SRCS)
$(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_CUSTOM $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test $(DOCKER) mdashnet/riscv riscv-none-elf-gcc -march=rv32imc -mabi=ilp32 $(SRCS) test/mongoose_custom.c -Itest -DMG_ARCH=MG_ARCH_NEWLIB $(OPTS) $(WARN) $(INCS) $(DEFS) -o unit_test
#vc98: VCFLAGS += -DMG_ENABLE_IPV6=1 #vc98: VCFLAGS += -DMG_ENABLE_IPV6=1
vc98: Makefile mongoose.h $(SRCS) vc98: Makefile mongoose.h $(SRCS)
@@ -98,8 +124,7 @@ mingw++: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/mingw i686-w64-mingw32-g++ $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o test.exe $(DOCKER) mdashnet/mingw i686-w64-mingw32-g++ $(SRCS) -W -Wall -Werror -I. $(DEFS) -lwsock32 -o test.exe
# Note: for some reason, a binary built with mingw g++, fails to run # Note: for some reason, a binary built with mingw g++, fails to run
#linux: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6) linux: IPV6=0
linux: CFLAGS += -fsanitize=address,undefined
linux: Makefile mongoose.h $(SRCS) linux: Makefile mongoose.h $(SRCS)
$(DOCKER) mdashnet/cc2 gcc $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test_gcc $(DOCKER) mdashnet/cc2 gcc $(SRCS) $(CFLAGS) $(LDFLAGS) -o unit_test_gcc
$(DOCKER) mdashnet/cc2 ./unit_test_gcc $(DOCKER) mdashnet/cc2 ./unit_test_gcc
@@ -122,11 +147,11 @@ uninstall:
rm -rf $(DESTDIR)$(PREFIX)/lib/libmongoose.a $(DESTDIR)$(PREFIX)/lib/libmongoose.so.$(VERSION) $(DESTDIR)$(PREFIX)/include/mongoose.h $(DESTDIR)$(PREFIX)/lib/libmongoose.so rm -rf $(DESTDIR)$(PREFIX)/lib/libmongoose.a $(DESTDIR)$(PREFIX)/lib/libmongoose.so.$(VERSION) $(DESTDIR)$(PREFIX)/include/mongoose.h $(DESTDIR)$(PREFIX)/lib/libmongoose.so
mongoose.c: Makefile $(wildcard src/*) mongoose.c: Makefile $(wildcard src/*)
(cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/private.h src/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
mongoose.h: $(HDRS) Makefile mongoose.h: $(HDRS) Makefile
(cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/config.h src/arch.h src/arch_*.h src/str.h src/log.h src/timer.h src/util.h src/fs.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/ws.h src/sntp.h src/mqtt.h src/dns.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/config.h src/str.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/mip.h | sed -e 's,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
clean: clean:
rm -rf $(PROG) *.o *.dSYM unit_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack rm -rf $(PROG) *.o *.dSYM unit_test* valgrind_unit_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack unpacked
@for X in $(EXAMPLES); do $(MAKE) -C $$X clean; done @for X in $(EXAMPLES); do $(MAKE) -C $$X clean; done
+5 -24
View File
@@ -4,7 +4,6 @@
[![Build Status]( https://github.com/cesanta/mongoose/workflows/build/badge.svg)](https://github.com/cesanta/mongoose/actions) [![Build Status]( https://github.com/cesanta/mongoose/workflows/build/badge.svg)](https://github.com/cesanta/mongoose/actions)
[![Code Coverage](https://codecov.io/gh/cesanta/mongoose/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mongoose) [![Code Coverage](https://codecov.io/gh/cesanta/mongoose/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mongoose)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/mongoose.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/mongoose.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
[![Gitter Chat](https://badges.gitter.im/cesanta/mongoose.png)](https://gitter.im/cesanta/mongoose)
Mongoose is a networking library for C/C++. It implements event-driven Mongoose is a networking library for C/C++. It implements event-driven
non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It is designed for non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It is designed for
@@ -33,15 +32,11 @@ robust, and easy. Features include:
interface on devices, RESTful API services, telemetry data exchange, remote interface on devices, RESTful API services, telemetry data exchange, remote
control for a product, remote software updates, remote monitoring, and others control for a product, remote software updates, remote monitoring, and others
- Deployed to hundreds of millions devices in production environment worldwide - Deployed to hundreds of millions devices in production environment worldwide
- See [Case Studies](https://cesanta.com/case-studies.html) from our respected - See [Case Studies](https://mongoose.ws/case-studies/) from our respected
customers like Schneider Electric (industrial automation), Broadcom (semiconductors), Schenck Process customers like [Schneider Electric](https://mongoose.ws/case-studies/schneider-electric/) (industrial automation), [Broadcom](https://mongoose.ws/case-studies/broadcom/) (semiconductors), [Pilz](https://mongoose.ws/case-studies/pilz/) (industrial automation), and others
(industrial engineering), and others - See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products
- See [Testimonials](https://cesanta.com/testimonials.html) from engineers that - We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and integration
integrated Mongoose in their commercial products assistance - don't hesitate to [contact us](https://mongoose.ws/contact/)
- We provide [commercial licensing](https://cesanta.com/licensing.html),
[support](https://cesanta.com/support.html), consultancy and integration
assistance - don't hesitate to
[contact us](https://www.cesanta.com/contact.html)
# Security # Security
@@ -80,20 +75,6 @@ This software is often used together with Mongoose:
Used to implement scripting support Used to implement scripting support
# Precompiled web server binary
We have built a ready-to-go, precompiled web server executable application for
Windows and Mac. It requires no installation and no configuration to start.
Simply copy `mongoose.exe` to any directory you want to serve, and double-click
on `mongoose.exe` to start it. Mongoose will iconify to a system tray and
start serving your directory to a local network.
It is a great tool for sharing your files, or for local web or game
development. It has a unique feature - an ability to share your local files via
a global URL.
Visit [download page](https://mongoose.ws/) to try it out.
# Contributions # Contributions
Contributions are welcome! Please follow the guidelines below: Contributions are welcome! Please follow the guidelines below:
+374 -331
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
# This is a collection of low-level drivers for the experimental
# built-in TCP/IP stack
+140
View File
@@ -0,0 +1,140 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "mip_driver_stm32.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
struct eth {
uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
DMACHRBAR;
};
#define ETH ((struct eth *) 0x40028000)
#define BIT(x) (1UL << (x))
#define ETH_PKT_SIZE 1540 // Max frame size
#define ETH_DESC_CNT 4 // Descriptors count
#define ETH_DS 4 // Descriptor size (words)
static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors
static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors
static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers
static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers
static void (*s_rx)(void *, size_t, void *); // Recv callback
static void *s_rxdata; // Recv callback data
static void *s_userdata; // Driver data
enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 }; // PHY constants
static inline void spin(volatile uint32_t count) {
while (count--) asm("nop");
}
uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
ETH->MACMIIAR &= (7 << 2);
ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
ETH->MACMIIAR |= BIT(0);
while (ETH->MACMIIAR & BIT(0)) spin(1);
return ETH->MACMIIDR;
}
void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
ETH->MACMIIDR = val;
ETH->MACMIIAR &= (7 << 2);
ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
ETH->MACMIIAR |= BIT(0);
while (ETH->MACMIIAR & BIT(0)) spin(1);
}
void mip_driver_stm32_init(void *userdata) {
s_userdata = userdata;
// Init RX descriptors
for (int i = 0; i < ETH_DESC_CNT; i++) {
s_rxdesc[i][0] = BIT(31); // Own
s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14); // 2nd address chained
s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; // Point to data buffer
s_rxdesc[i][3] = (uint32_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain
}
// Init TX descriptors
for (int i = 0; i < ETH_DESC_CNT; i++) {
s_txdesc[i][2] = (uint32_t) s_txbuf[i]; // Buf pointer
s_txdesc[i][3] = (uint32_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain
}
ETH->DMABMR |= BIT(0); // Software reset
while ((ETH->DMABMR & BIT(0)) != 0) spin(1); // Wait until done
// NOTE(cpq): we do not use extended descriptor bit 7, and do not use
// hardware checksum. Therefore, descriptor size is 4, not 8
// ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT
ETH->MACMIIAR = 4 << 2; // MDC clock 150-216 MHz, 38.8.1
ETH->MACFCR = BIT(7); // Disable zero quarta pause
ETH->MACFFR = BIT(31); // Receive all
eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY
eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12)); // Set autonegotiation
ETH->DMARDLAR = (uint32_t) s_rxdesc; // RX descriptors
ETH->DMATDLAR = (uint32_t) s_txdesc; // RX descriptors
ETH->DMAIER = BIT(6) | BIT(16); // RIE, NISE
ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14); // RE, TE, Duplex, Fast
ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25); // SR, ST, TSF, RSF
}
void mip_driver_stm32_setrx(void (*rx)(void *, size_t, void *), void *rxdata) {
s_rx = rx;
s_rxdata = rxdata;
}
static uint32_t s_txno;
size_t mip_driver_stm32_tx(const void *buf, size_t len, void *userdata) {
if (len > sizeof(s_txbuf[s_txno])) {
printf("%s: frame too big, %ld\n", __func__, (long) len);
len = 0; // Frame is too big
} else if ((s_txdesc[s_txno][0] & BIT(31))) {
printf("%s: no free descr\n", __func__);
len = 0; // All descriptors are busy, fail
} else {
memcpy(s_txbuf[s_txno], buf, len); // Copy data
s_txdesc[s_txno][1] = (uint32_t) len; // Set data len
s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30); // Chain,FS,LS
s_txdesc[s_txno][0] |= BIT(31); // Set OWN bit - let DMA take over
if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
}
uint32_t sr = ETH->DMASR;
if (sr & BIT(2)) ETH->DMASR = BIT(2), ETH->DMATPDR = 0; // Resume
if (sr & BIT(5)) ETH->DMASR = BIT(5), ETH->DMATPDR = 0; // if busy
if (len == 0) printf("E: D0 %lx, DMASR %lx\n", s_txdesc[0][0], sr);
return len;
(void) userdata;
}
bool mip_driver_stm32_status(void *userdata) {
uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
return bsr & BIT(2) ? 1 : 0;
(void) userdata;
}
void ETH_IRQHandler(void) {
volatile uint32_t sr = ETH->DMASR;
if (sr & BIT(6)) { // Frame received, loop
for (uint32_t i = 0; i < ETH_DESC_CNT; i++) {
if (s_rxdesc[i][0] & BIT(31)) continue;
uint32_t len = ((s_rxdesc[i][0] >> 16) & (BIT(14) - 1));
// printf("%lx %lu %lx %lx\n", i, len, s_rxdesc[i][0], sr);
if (s_rx != NULL) s_rx(s_rxbuf[i], len > 4 ? len - 4 : len, s_rxdata);
s_rxdesc[i][0] = BIT(31);
}
}
if (sr & BIT(7)) ETH->DMARPDR = 0; // Resume RX
ETH->DMASR = sr & ~(BIT(2) | BIT(7)); // Clear status
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#pragma once
#include <stdbool.h>
#include <stddef.h>
void mip_driver_stm32_init(void *userdata);
bool mip_driver_stm32_status(void *);
void mip_driver_stm32_setrx(void (*rx)(void *, size_t, void *), void *);
size_t mip_driver_stm32_tx(const void *buf, size_t len, void *);
+99
View File
@@ -0,0 +1,99 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
//
// This software is dual-licensed: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3 as
// published by the Free Software Foundation. For the terms of this
// license, see http://www.fsf.org/licensing/licenses/agpl-3.0.html
//
// You are free to use this software under the terms of the GNU General
// Public License, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// Alternatively, you can license this software under a commercial
// license, please contact us at https://cesanta.com/contact.html
#pragma once
struct w5500 {
uint8_t mac[6]; // MAC address
void *spi; // Opaque SPI bus descriptor
uint8_t (*txn)(void *, uint8_t); // SPI transaction
void (*begin)(void *); // SPI begin
void (*end)(void *); // SPI end
};
enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
static inline void w5500_txn(struct w5500 *w, uint8_t block, uint16_t addr,
bool wr, void *buf, size_t len) {
uint8_t *p = buf, cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
(uint8_t) ((block << 3) | (wr ? 4 : 0))};
w->begin(w->spi);
for (size_t i = 0; i < sizeof(cmd); i++) w->txn(w->spi, cmd[i]);
for (size_t i = 0; i < len; i++) {
uint8_t r = w->txn(w->spi, p[i]);
if (!wr) p[i] = r;
}
w->end(w->spi);
}
// clang-format off
static inline void w5500_wn(struct w5500 *w, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(w, block, addr, true, buf, len); }
static inline void w5500_w1(struct w5500 *w, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(w, block, addr, &val, 1); }
static inline void w5500_w2(struct w5500 *w, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(w, block, addr, buf, sizeof(buf)); }
static inline void w5500_rn(struct w5500 *w, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(w, block, addr, false, buf, len); }
static inline uint8_t w5500_r1(struct w5500 *w, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(w, block, addr, &r, 1); return r; }
static inline uint16_t w5500_r2(struct w5500 *w, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(w, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
// clang-format on
static inline uint8_t w5500_status(struct w5500 *w) {
return w5500_r1(w, W5500_CR, 0x2e);
}
static inline uint16_t w5500_rx(struct w5500 *w, uint8_t *buf, uint16_t len) {
uint16_t r = 0, n = 0, n2; // Read recv len
while ((n2 = w5500_r2(w, W5500_S0, 0x26)) > n) n = n2; // Until it is stable
// printf("RSR: %d\n", (int) n);
if (n > 0) {
uint16_t ptr = w5500_r2(w, W5500_S0, 0x28); // Get read pointer
n = w5500_r2(w, W5500_RX0, ptr); // Read frame length
if (n <= len + 2) r = n - 2, w5500_rn(w, W5500_RX0, ptr + 2, buf, r);
w5500_w2(w, W5500_S0, 0x28, ptr + n); // Advance read pointer
w5500_w1(w, W5500_S0, 1, 0x40); // Sock0 CR -> RECV
// printf(" RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
}
return r;
}
static inline uint16_t w5500_tx(struct w5500 *w, void *buf, uint16_t len) {
uint16_t n = 0;
while (n < len) n = w5500_r2(w, W5500_S0, 0x20); // Wait for space
uint16_t ptr = w5500_r2(w, W5500_S0, 0x24); // Get write pointer
w5500_wn(w, W5500_TX0, ptr, buf, len); // Write data
w5500_w2(w, W5500_S0, 0x24, ptr + len); // Advance write pointer
w5500_w1(w, W5500_S0, 1, 0x20); // Sock0 CR -> SEND
for (int i = 0; i < 40; i++) {
uint8_t ir = w5500_r1(w, W5500_S0, 2); // Read S0 IR
if (ir == 0) continue;
// printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
w5500_w1(w, W5500_S0, 2, ir); // Write S0 IR: clear it!
if (ir & 8) len = 0; // Timeout. Report error
if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout
}
return len;
}
static inline bool w5500_init(struct w5500 *w) {
w->end(w->spi);
w5500_w1(w, W5500_CR, 0, 0x80); // Reset chip: CR -> 0x80
w5500_w1(w, W5500_CR, 0x2e, 0); // CR PHYCFGR -> reset
w5500_w1(w, W5500_CR, 0x2e, 0xf8); // CR PHYCFGR -> set
// w5500_wn(w, W5500_CR, 9, w->mac, 6); // Set source MAC
w5500_w1(w, W5500_S0, 0x1e, 16); // Sock0 RX buf size
w5500_w1(w, W5500_S0, 0x1f, 16); // Sock0 TX buf size
w5500_w1(w, W5500_S0, 0, 4); // Sock0 MR -> MACRAW
w5500_w1(w, W5500_S0, 1, 1); // Sock0 CR -> OPEN
return w5500_r1(w, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW
}
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/captive-dns-portal/
+6 -3
View File
@@ -21,11 +21,14 @@ uint8_t answer[] = {
}; };
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_READ) { if (ev == MG_EV_OPEN) {
c->is_hexdumping = 1;
} else if (ev == MG_EV_READ) {
struct mg_dns_rr rr; // Parse first question, offset 12 is header size struct mg_dns_rr rr; // Parse first question, offset 12 is header size
size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
MG_INFO(("DNS request parsed, result=%d", (int) n));
if (n > 0) { if (n > 0) {
char buf[12 + n + sizeof(answer)]; char buf[512];
struct mg_dns_header *h = (struct mg_dns_header *) buf; struct mg_dns_header *h = (struct mg_dns_header *) buf;
memset(buf, 0, sizeof(buf)); // Clear the whole datagram memset(buf, 0, sizeof(buf)); // Clear the whole datagram
h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid
@@ -34,7 +37,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
h->flags = mg_htons(0x8400); // Authoritative response h->flags = mg_htons(0x8400); // Authoritative response
memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question
memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer
mg_send(c, buf, sizeof(buf)); // And send it! mg_send(c, buf, 12 + n + sizeof(answer)); // And send it!
} }
mg_iobuf_del(&c->recv, 0, c->recv.len); mg_iobuf_del(&c->recv, 0, c->recv.len);
} }
-23
View File
@@ -1,23 +0,0 @@
PROG ?= example
SOURCES ?= ../../mongoose.c main.c mjson.c packed_fs.c
CFLAGS ?= -I../.. -DMG_ENABLE_PACKED_FS=1 $(EXTRA)
FILES_TO_EMBED ?= $(wildcard web_root/*.js) $(wildcard web_root/*.css) $(wildcard web_root/*.html) $(wildcard web_root/images/*.png) $(wildcard images/*.jpg)
all: $(PROG)
$(RUN) ./$(PROG)
$(PROG):
$(CC) ../../test/pack.c -o pack
./pack $(FILES_TO_EMBED) > packed_fs.c
$(CC) -W -Wall -Wextra -Os -g3 $(CFLAGS) -o $(PROG) $(SOURCES)
ROOT ?= $(realpath $(CURDIR)/../..)
DOCKER ?= docker run -it --rm -e Tmp=. -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/vc98 wine
windows:
$(DOCKER) cl.exe /nologo $(CFLAGS) ../../test/pack.c /Fepack.exe
$(DOCKER) cmd /c 'pack.exe $(FILES_TO_EMBED) > packed_fs.c'
$(DOCKER) cl.exe /nologo /O2 $(CFLAGS) $(SOURCES) ws2_32.lib /Fe$(PROG).exe
$(DOCKER) $(PROG).exe
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt packed_fs.c pack
-34
View File
@@ -1,34 +0,0 @@
# A complete device dashboard
This example is a demonstration of how Mongoose Library could be integrated
into an embedded device and provide a complete device dashboard with the
following features:
- Multiple logins with different permissions (admin and user)
- Login screen for non-authenticated connections
- A [preact](https://preactjs.com/)-based dashboard with multiple pages
- Internal variables
- A form that changes those variables
- All changes are propagates to all connected clients
- Live log stream
- Live video stream
In essence, this example is a combination of several other examples
pulled together (multiple-logins, dashboard, live-log, video-stream).
# Screenshots
This is a login screen that prompts for user/password
![](screenshots/login.png)
# Main dashboard
A main dashboard page shows internal data, live video stream, and a form
![](screenshots/dashboard.png)
A live log page shows live log stream coming from device
![](screenshots/logs.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

-207
View File
@@ -1,207 +0,0 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "mjson.h"
#include "mongoose.h"
// Authenticated user.
// A user can be authenticated by:
// - a name:pass pair
// - a token
// When a user is shown a login screen, she enters a user:pass. If successful,
// a server returns user info which includes token. From that point on,
// client can use token for authentication. Tokens could be refreshed/changed
// on a server side, forcing clients to re-login.
struct user {
const char *name, *pass, *token;
};
// This is a configuration structure we're going to show on a dashboard
static struct config {
int value1;
char *value2;
} s_config = {123, NULL};
// Stringifies the config. A caller must free() it.
static char *stringify_config(struct config *cfg) {
char *s = NULL;
mjson_printf(mjson_print_dynamic_buf, &s, "{%Q:%d,%Q:%Q}", "value1",
cfg->value1, "value2", cfg->value2);
return s;
}
// Update config structure. Return true if changed, false otherwise
static bool update_config(struct mg_http_message *hm, struct config *cfg) {
bool changed = false;
char buf[256];
double dv;
if (mjson_get_number(hm->body.ptr, hm->body.len, "$.value1", &dv)) {
cfg->value1 = dv;
changed = true;
}
if (mjson_get_string(hm->body.ptr, hm->body.len, "$.value2", buf,
sizeof(buf)) > 0) {
free(cfg->value2);
cfg->value2 = strdup(buf);
changed = true;
}
return changed;
}
// Parse HTTP requests, return authenticated user or NULL
static struct user *getuser(struct mg_http_message *hm) {
// In production, make passwords strong and tokens randomly generated
// In this example, user list is kept in RAM. In production, it can
// be backed by file, database, or some other method.
static struct user users[] = {
{"admin", "admin", "admin_token"},
{"user1", "pass1", "user1_token"},
{"user2", "pass2", "user2_token"},
{NULL, NULL, NULL},
};
char user[256], pass[256];
struct user *u;
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
if (user[0] != '\0' && pass[0] != '\0') {
// Both user and password is set, search by user/password
for (u = users; u->name != NULL; u++)
if (strcmp(user, u->name) == 0 && strcmp(pass, u->pass) == 0) return u;
} else if (user[0] == '\0') {
// Only password is set, search by token
for (u = users; u->name != NULL; u++)
if (strcmp(pass, u->token) == 0) return u;
}
return NULL;
}
// Notify all config watchers about the config change
static void notify_config_change(struct mg_mgr *mgr) {
struct mg_connection *c;
char *s = stringify_config(&s_config);
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W') mg_http_printf_chunk(c, "%s\n", s);
}
free(s);
}
// HTTP request handler function
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct user *u = getuser(hm);
// LOG(LL_INFO, ("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
// u ? u->name : "NULL"));
if (u == NULL && mg_http_match_uri(hm, "/api/#")) {
// All URIs starting with /api/ must be authenticated
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/config/get")) {
char *s = stringify_config(&s_config);
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s\n",
(int) strlen(s) + 1, s);
free(s);
} else if (mg_http_match_uri(hm, "/api/config/set")) {
// Admins only
if (strcmp(u->name, "admin") == 0) {
if (update_config(hm, &s_config)) notify_config_change(fn_data);
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
} else {
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
}
} else if (mg_http_match_uri(hm, "/api/config/watch")) {
c->label[0] = 'W'; // Mark ourselves as a config watcher
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/video1")) {
c->label[0] = 'S'; // Mark that connection as live streamer
mg_printf(
c, "%s",
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Pragma: no-cache\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
"Content-Type: multipart/x-mixed-replace; boundary=--foo\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/log/static")) {
struct mg_http_serve_opts opts;
memset(&opts, 0, sizeof(opts));
mg_http_serve_file(c, hm, "log.txt", &opts);
} else if (mg_http_match_uri(hm, "/api/log/live")) {
c->label[0] = 'L'; // Mark that connection as live log listener
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/login")) {
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_http_printf_chunk(c, "{\"user\":\"%s\",\"token\":\"%s\"}\n", u->name,
u->token);
mg_http_printf_chunk(c, "");
} else {
struct mg_http_serve_opts opts = {0};
opts.root_dir = "web_root";
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
// The image stream is simulated by sending MJPEG frames specified by the
// "files" array of file names.
static void broadcast_mjpeg_frame(struct mg_mgr *mgr) {
const char *files[] = {"images/1.jpg", "images/2.jpg", "images/3.jpg",
"images/4.jpg", "images/5.jpg", "images/6.jpg"};
size_t nfiles = sizeof(files) / sizeof(files[0]);
static size_t i;
const char *path = files[i++ % nfiles];
size_t size = 0;
char *data = mg_file_read(path, &size); // Read next file
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] != 'S') continue; // Skip non-stream connections
if (data == NULL || size == 0) continue; // Skip on file read error
mg_printf(c,
"--foo\r\nContent-Type: image/jpeg\r\n"
"Content-Length: %lu\r\n\r\n",
(unsigned long) size);
mg_send(c, data, size);
mg_send(c, "\r\n", 2);
}
free(data);
}
static void mjpeg_cb(void *arg) {
broadcast_mjpeg_frame(arg);
}
static void log_message(const char *filename, const char *message) {
FILE *fp = fopen(filename, "a");
if (fp != NULL) {
fprintf(fp, "%s", message);
fclose(fp);
}
}
static void broadcast_message(struct mg_mgr *mgr, const char *message) {
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'L') mg_http_printf_chunk(c, "%s", message);
}
}
// Timer function - called periodically.
// Prepare log message. Save it to a file, and broadcast.
static void log_cb(void *arg) {
char buf[64];
snprintf(buf, sizeof(buf), "Time is: %lu\n", (unsigned long) time(NULL));
log_message("log.txt", buf);
broadcast_message(arg, buf);
}
int main(void) {
struct mg_mgr mgr;
struct mg_timer t1, t2;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://localhost:8000", cb, &mgr);
mg_timer_init(&t1, 500, MG_TIMER_REPEAT, mjpeg_cb, &mgr);
mg_timer_init(&t2, 1000, MG_TIMER_REPEAT, log_cb, &mgr);
for (;;) mg_mgr_poll(&mgr, 50);
mg_timer_free(&t1);
mg_timer_free(&t2);
mg_mgr_free(&mgr);
return 0;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

-312
View File
@@ -1,312 +0,0 @@
const {h, render, Component} = preact;
const html = htm.bind(h);
class Login extends Component {
state = {user: '', pass: ''};
onclick = (app) => {
const authhdr = 'Basic ' + btoa(this.state.user + ':' + this.state.pass);
const headers = {Authorization: authhdr};
return axios.get('/api/login', {headers})
.then(res => app.setUser(res.data.token))
.catch(err => alert('Login failed'));
};
onpassinput = (ev) => this.setState({pass: ev.target.value});
onuserinput = (ev) => this.setState({user: ev.target.value});
render({app}, {user, pass, signup}) {
return html`
<div class='mx-auto bg-light rounded border my-5' style='max-width: 480px;'>
<div class='form p-5 rounded form-sm'>
<h4 class="text-muted mb-4">Login </h4>
<input type='email' placeholder='Email' class='my-2 form-control'
oninput=${this.onuserinput} value=${user} />
<input type="password" placeholder="Password" class="my-2 form-control"
oninput=${this.onpassinput} value=${pass}
onchange=${ev => this.onclick(app)} />
<div class="mb-4">
<button class="btn btn-info btn-block"
disabled=${!user || !pass} onclick="${ev => this.onclick(app)}"
> Login </button>
</div>
<div class="text-muted small">
Valid logins: admin:admin, user1:pass1, user2:pass2
</div>
</div>
</div>
`
}
};
class Dropdown extends Component {
state = {show: false};
render(props, state) {
const onclick = x => this.setState({show: x});
const show = state.show ? 'show' : '';
return html`
<div class="dropdown autoexpand ${props.cls}">
<div type="buttonx" onclick=${() => onclick(!state.show)}
class="dropdown-toggle my-0 ${props.bcls}"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
${props.title}
</div>
<div onclick=${() => onclick(false)} style=${props.dstyle}
class="dropdown-menu ${props.dcls} ${show}">
${props.children}
</div>
</div>`;
}
};
const NavLink = ({href, title, url}) => html`<a class="nav-item nav-link
${url == href ? 'active' : ''}"
target=${href.match(/^http/) ? '_blank' : ''}
href="${href.match(/^http/) ? '' : '#'}${href}">${title}</a>`;
class Header extends Component {
state = {expanded: false};
ontoggle = () => this.setState({expanded: !this.state.expanded});
render(props, state) {
const u = props.app.state.user || {};
return html`
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<a class="navbar-brand" href="#">
<img src="images/logo.png" width="26" height="26" alt="" class="mr-2" />
MyProduct
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"
onclick=${() => this.ontoggle()} >
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse ${state.expanded ? '' : 'collapse'}"
id="navbarNav">
<div class="nav navbar-nav mr-auto">
<${NavLink} href="/" title="Dashboard" url=${props.url} />
<${NavLink} href="/logs" title="Logs" url=${props.url} />
</div>
</div>
<form class="form-inline">
<${Dropdown} title="${u.user}" cls="mr-2"
bcls="btn btn-sm btn-outline-light pointer" dcls="m-0 dropdown-menu-right">
<div onclick=${() => props.app.setUser('')}
class="dropdown-item small pointer text-center">logout</div>
</${Dropdown}>
<img src="images/user.png" class="rounded-circle nav-item mr-2" width="30" />
</form>
</nav>
`
}
};
const Info = () => html`<div class="alert alert-secondary my-3">
This dashboard shows values kept in server memory. Many clients
can open this page. As soon as any client changes any value,
all clients update automatically.
The JS code that watches state changes, reconnects on network failures.
That means if server restarts, dashboard on all connected clients
refresh automatically.
You can use <code>curl</code> command-line utility:
<br/><code>curl localhost:8000/api/config/get</code>
<br/><code>curl localhost:8000/api/config/watch</code>
<br/><code>curl localhost:8000/api/config/set -d '{"a":123}'</code>
<br/><span class="badge badge-danger">NOTE:</span> administrators
can change settings values, whilst users cannot.
</div>`;
class DashVideo extends Component {
render(props, state) {
const onclick = ev => axios.post(
'/api/config/set', {value1: +state.value1, value2: state.value2});
// alert(JSON.stringify(state));
return html`<div id="form">
<div><b>Change values</b></div>
<table>
<tr><td>Value 1 (number):</td><td><input value=${state.value1}
onInput=${linkState(this, 'value1')} /></td></tr>
<tr><td>Value 2 (string):</td><td><input value=${state.value2}
onInput=${linkState(this, 'value2')}/></td></tr>
<tr><td></td>
<td><button onClick=${onclick}>Save values</button></td></tr>
</table>
</div>`
}
};
class DashForm extends Component {
render(props, state) {
const onclick = ev => axios.post(
'/api/config/set', {value1: +state.value1, value2: state.value2});
// alert(JSON.stringify(state));
return html`<div id="form">
<div><b>Change values</b></div>
<table>
<tr><td>Value 1 (number):</td><td><input class="form-control form-control-sm"
value=${state.value1}
onInput=${linkState(this, 'value1')} /></td></tr>
<tr><td>Value 2 (string):</td><td><input class="form-control form-control-sm"
value=${state.value2}
onInput=${linkState(this, 'value2')}/></td></tr>
<tr><td></td>
<td><button class="btn btn-primary btn-block"
onClick=${onclick}>Save values</button></td></tr>
</table>
</div>`
}
};
class DashSettings extends Component {
state = {value1: null, value2: null};
componentDidMount() {
axios.get('/api/config/get')
.then(r => this.setState(r.data))
.catch(e => console.log(e));
var self = this;
var f = function(reader) {
return reader.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
self.setState(JSON.parse(data));
// console.log(JSON.parse(data));
if (!result.done) return f(reader);
});
};
fetch('/api/config/watch')
.then(r => r.body.getReader())
.then(f)
.catch(e => setTimeout(x => self.componentDidMount(), 1000));
}
render(props, state) {
return html
`<div id="dashboard" style="display: flex; justify-content: flex-evenly;">
<div style="width:50%;text-align:center;">Value 1 (number):
<div style="font-size:140%;"><b>${state.value1}</b></div></div>
<div style="width:50%;text-align:center;">Value 2 (string):
<div style="font-size:140%;"><b>${state.value2}</b></div></div>
</div>`
}
};
class AdminDashboard extends Component {
render(props, state) {
return html`<div class="container-fluid">
<${Info} />
<div class="card-deck">
<div class="card">
<div class="card-header">Settings</div>
<div class="card-body"><${DashSettings} /></div>
</div>
<div class="card">
<div class="card-header">Video stream</div>
<div class="card-body"><img src="/api/video1" class="mw-100" /></div>
</div>
<div class="card">
<div class="card-header">Change settings</div>
<div class="card-body"><${DashForm} /></div>
</div>
</div>
</div>`
}
}
class UserDashboard extends Component {
render(props, state) {
return html`<div class="container-fluid">
<${Info} />
<div class="card-deck">
<div class="card">
<div class="card-header">Settings</div>
<div class="card-body"><${DashSettings} /></div>
</div>
<div class="card">
<div class="card-header">Video stream</div>
<div class="card-body"><img src="/api/video1" class="mw-100" /></div>
</div>
</div>
</div>`
}
}
class Logs extends Component {
state = {live: '', static: ''};
componentDidMount() {
var self = this;
var f = function(r) {
return r.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
var live = self.state.live + data;
self.setState({live}); // Append live log
if (!result.done) f(r); // Read next chunk
});
};
fetch('/api/log/static')
.then(r => r.text())
.then(r => {
self.setState({static: r});
})
.then(r => fetch('/api/log/live'))
.then(r => r.body.getReader())
.then(f);
}
render(props, state) {
return html`<div class="container-fluid">
<div class="alert alert-secondary my-3">
A div below shows file log.txt, which is produced by the server.
The server appends a new log message to that file every second,
and a div below also shows that, implementing a "live log" feature.
JS code on this page first fetches /api/log/static that returns
log.txt contents, then fetches /api/log/live that does not return,
but feeds chunks of data as they arrive (live log).
<br/><br/>
You can also use <code>curl</code> command-line utility to see
live logs:
<br/><code>curl localhost:8000/api/log/live</code>
</div>
<div class="card-deck">
<div class="card">
<div class="card-header">Static</div>
<div class="card-body"><pre class="log">${state.static}</pre></div>
</div>
<div class="card">
<div class="card-header">Live</div>
<div class="card-body"><pre class="log">${state.live}</pre></div>
</div>
</div>
</div>`
}
}
class App extends Component {
state = {user: null, url: '/'};
setUser(token) {
const maxAge = token ? 86400 : 0;
document.cookie = `access_token=${token};path=/;max-age=${maxAge}`;
// this.setState({token});
return axios.get('/api/login')
.then(res => this.setState({user: res.data}))
.catch(err => this.setState({user: {}}));
}
componentDidMount() {
const getCookie = name => {
var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
return v ? v[2] : '';
};
this.setUser(getCookie('access_token')); // Move from state1 to 2 or 3
}
render(props, state) {
// We have three states:
// 1. Beginning. We don't know if we have a valid auth token or not
// 2. We sent an /api/login request, and failed to login
// 3. We sent an /api/login request, and succeeded to login
if (!state.user) return ''; // State 1
if (!state.user.user) return h(Login, {app: this}); // State 2
return h( // State 3
'div', {}, h(Header, {url: state.url, app: this}),
h(preactRouter.Router, {
history: History.createHashHistory(),
onChange: ev => this.setState({url: ev.url}),
},
h(state.user.user == 'admin' ? AdminDashboard : UserDashboard,
{default: true, app: this}),
h(Logs, {path: 'logs', app: this})));
}
};
window.onload = () => render(h(App), document.body);
-3
View File
@@ -1,3 +0,0 @@
/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&t.call(null,e[s],s,e)}function E(){function e(e,n){d(t[n])&&d(e)?t[n]=E(t[n],e):d(e)?t[n]=E({},e):r(e)?t[n]=e.slice():t[n]=e}for(var t={},n=0,o=arguments.length;n<o;n++)b(arguments[n],e);return t}function j(e,t,n){return b(t,function(t,r){n&&"function"==typeof t?e[r]=S(t,n):e[r]=t}),e}function C(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}var S=n(3),R=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:i,isBuffer:s,isFormData:a,isArrayBufferView:u,isString:c,isNumber:f,isObject:p,isPlainObject:d,isUndefined:o,isDate:l,isFile:h,isBlob:m,isFunction:y,isStream:g,isURLSearchParams:v,isStandardBrowserEnv:w,forEach:b,merge:E,extend:j,trim:x,stripBOM:C}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new i,response:new i}}var o=n(2),s=n(5),i=n(6),a=n(7),u=n(22);r.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=u(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[a,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},r.prototype.getUri=function(e){return e=u(this.defaults,e),s(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},o.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(u(n||{},{method:e,url:t,data:(n||{}).data}))}}),o.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(u(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(o.isURLSearchParams(t))s=t.toString();else{var i=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),i.push(r(t)+"="+r(e))}))}),s=i.join("&")}if(s){var a=e.indexOf("#");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf("?")===-1?"?":"&")+s}return e}},function(e,t,n){"use stricLine truncated
//# sourceMappingURL=axios.min.map
-7
View File
@@ -1,7 +0,0 @@
/*!
* Bootstrap v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,Line truncated
/*# sourceMappingURL=bootstrap.min.css.map */
-1
View File
@@ -1 +0,0 @@
!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.History=n():t.History=n()}(this,function(){return function(t){function n(o){if(e[o])return e[o].exports;var r=e[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,n),r.loaded=!0,r.exports}var e={};return n.m=t,n.c=e,n.p="",n(0)}([function(t,n,e){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0,n.createPath=n.parsePath=n.locationsAreEqual=n.createLocation=n.createMemoryHistory=n.createHashHistory=n.createBrowserHistory=void 0;var r=e(2);Object.defineProperty(n,"createLocation",{enumerable:!0,get:function(){return r.createLocation}}),Object.defineProperty(n,"locationsAreEqual",{enumerable:!0,get:function(){return r.locationsAreEqual}});var i=e(1);Object.defineProperty(n,"parsePath",{enumerable:!0,get:function(){return i.parsePath}}),Object.defineProperty(n,"createPath",{enumerable:!0,get:function(){return i.createPath}});var a=e(7),c=o(a),u=e(8),s=o(u),f=e(9),l=o(f);n.createBrowserHistory=c.default,n.createHashHistory=s.default,n.createMemoryHistory=l.default},function(t,n){"use strict";n.__esModule=!0;n.addLeadingSlash=function(t){return"/"===t.charAt(0)?t:"/"+t},n.stripLeadingSlash=function(t){return"/"===t.charAt(0)?t.substr(1):t},n.stripPrefix=function(t,n){return 0===t.indexOf(n)?t.substr(n.length):t},n.stripTrailingSlash=function(t){return"/"===t.charAt(t.length-1)?t.slice(0,-1):t},n.parsePath=function(t){var n=t||"/",e="",o="",r=n.indexOf("#");r!==-1&&(o=n.substr(r),n=n.substr(0,r));var i=n.indexOf("?");return i!==-1&&(e=n.substr(i),n=n.substr(0,i)),n=decodeURI(n),{pathname:n,search:"?"===e?"":e,hash:"#"===o?"":o}},n.createPath=function(t){var n=t.pathname,e=t.search,o=t.hash,r=encodeURI(n||"/");return e&&"?"!==e&&(r+="?"===e.charAt(0)?e:"?"+e),o&&"#"!==o&&(r+="#"===o.charAt(0)?o:"#"+o),r}},function(t,n,e){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0,n.locationsAreEqual=n.createLocation=void 0;var r=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var e=arguments[n];for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o])}return t},i=e(10),a=o(i),c=e(11),u=o(c),s=e(1);n.createLocation=function(t,n,e,o){var i=void 0;return"string"==typeof t?(i=(0,s.parsePath)(t),i.state=n):(i=r({},t),void 0===i.pathname&&(i.pathname=""),i.search?"?"!==i.search.charAt(0)&&(i.search="?"+i.search):i.search="",i.hash?"#"!==i.hash.charAt(0)&&(i.hash="#"+i.hash):i.hash="",void 0!==n&&void 0===i.state&&(i.state=n)),i.key=e,o&&(i.pathname?"/"!==i.pathname.charAt(0)&&(i.pathname=(0,a.default)(i.pathname,o.pathname)):i.pathname=o.pathname),i},n.locationsAreEqual=function(t,n){return t.pathname===n.pathname&&t.search===n.search&&t.hash===n.hash&&t.key===n.key&&(0,u.default)(t.state,n.state)}},function(t,n,e){"use strict";var o=function(){};t.exports=o},function(t,n,e){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}n.__esModule=!0;var r=e(3),i=(o(r),function(){var t=null,n=function(n){return t=n,function(){t===n&&(t=null)}},e=function(n,e,o,r){if(null!=t){var i="function"==typeof t?t(n,e):t;"string"==typeof i?"function"==typeof o?o(i,r):r(!0):r(i!==!1)}else r(!0)},o=[],r=function(t){var n=!0,e=function(){n&&t.apply(void 0,arguments)};return o.push(e),function(){n=!1,o=o.filter(function(t){return t!==e})}},i=function(){for(var t=arguments.length,n=Array(t),e=0;e<t;e++)n[e]=arguments[e];o.forEach(function(t){return t.apply(void 0,n)})};return{setPrompt:n,confirmTransitionTo:e,appendListener:r,notifyListeners:i}});n.default=i},function(t,n){"use strict";n.__esModule=!0;n.canUseDOM=!("undefined"==typeof window||!window.document||!window.document.createElement),n.addEventListener=function(t,n,e){return t.addEventListener?t.addEventListener(n,e,!1):t.attachEvent("on"+n,e)},n.removeEventListener=function(t,n,e){return t.removeEventListener?t.removeEventListener(n,e,!1):t.detachEvent("on"+n,e)},n.getConfirmation=function(t,n){return n(window.confirm(t))},n.supportsHistory=function(){var t=window.navigator.userAgent;return(t.indexOf("Android 2.")===-1&&t.indexOf("Android 4.0")===-1||t.indexOf("Mobile Safari")===-1||t.indexOf("Chrome")!==-1||t.indexOf("Windows Phone")!==-1)&&(window.history&&"pushState"in window.history)},n.supportsPopStateOnHashChange=function(){return window.navigator.userAgent.indexOf("Trident")===-1},n.supportsGoWithoutReloadUsingHash=function(){return window.navigator.userAgent.indexOf("Firefox")===-1},n.isExtraneousPopstateEvent=function(t){return void 0===t.state&&navigator.userAgent.indexOf("CriOS")===-1}},function(t,n,e){"use strict";var o=function(t,n,e,o,r,i,a,c){if(!t){var u;if(void 0===n)u=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var s=[e,o,r,i,a,c],f=0;u=new Error(n.replace(/%s/g,function(){return s[f++]})),u.name="InLine truncated
-1
View File
@@ -1 +0,0 @@
!function(){function n(n){var t=e.get(this);return t||(t=new Map,e.set(this,t)),1<(t=a(this,t.get(n)||(t.set(n,t=function(n){function t(n){1===u&&(n||(r=r.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?p.push(0,n,r):3===u&&(n||r)?(p.push(3,n,r),u=2):2===u&&"..."===r&&n?p.push(4,n,0):2===u&&r&&!n?p.push(5,0,!0,r):5<=u&&((r||!n&&5===u)&&(p.push(u,0,r,s),u=6),n&&(p.push(u,n,0,s),u=6)),r=""}for(var e,s,u=1,r="",h="",p=[0],a=0;a<n.length;a++){a&&(1===u&&t(),t(a));for(var o=0;o<n[a].length;o++)e=n[a][o],1===u?"<"===e?(t(),p=[p],u=3):r+=e:4===u?r="--"===r&&">"===e?(u=1,""):e+r[0]:h?e===h?h="":r+=e:'"'===e||"'"===e?h=e:">"===e?(t(),u=1):u&&("="===e?(u=5,s=r,r=""):"/"===e&&(u<5||">"===n[a][o+1])?(t(),3===u&&(p=p[0]),(p=(u=p)[0]).push(2,0,u),u=0):" "===e||"\t"===e||"\n"===e||"\r"===e?(t(),u=2):r+=e),3===u&&"!--"===r&&(u=4,p=p[0])}return t(),p}(n)),t),arguments,[])).length?t:t[0]}var a=function(n,t,e,s){var u;t[0]=0;for(var r=1;r<t.length;r++){var h=t[r++],p=t[r]?(t[0]|=h?1:2,e[t[r++]]):t[++r];3===h?s[0]=p:4===h?s[1]=Object.assign(s[1]||{},p):5===h?(s[1]=s[1]||{})[t[++r]]=p:6===h?s[1][t[++r]]+=p+"":h?(u=n.apply(p,a(n,p,e,["",null])),s.push(u),p[0]?t[0]|=2:(t[r-2]=0,t[r]=u)):s.push(p)}return s},e=new Map;"undefined"!=typeof module?module.exports=n:self.htm=n}();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="bootstrap.min.css" />
<link rel="stylesheet" href="style.css" />
<script src="preact.min.js"></script>
<script src="preact-router.min.js"></script>
<script src="htm.min.js"></script>
<script src="history.min.js"></script>
<script src="linkstate.min.js"></script>
<script src="axios.min.js"></script>
<script src="app.js"></script>
</head>
<body></body>
</html>
-18
View File
@@ -1,18 +0,0 @@
function linkState(component, key, eventPath) {
let path = key.split('.'), cache = component.__lsc || (component.__lsc = {});
return cache[key + eventPath] || (cache[key + eventPath] = function(e) {
let t = e && e.target || this, state = {}, obj = state,
v = typeof eventPath === 'string' ?
delve(e, eventPath) :
t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) :
e,
i = 0;
for (; i < path.length - 1; i++) {
obj = obj[path[i]] ||
(obj[path[i]] = !i && component.state[path[i]] || {});
}
obj[path[i]] = v;
component.setState(state);
});
}
-1
View File
@@ -1 +0,0 @@
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("preact")):"function"==typeof define&&define.amd?define(["preact"],e):t.preactRouter=e(t.preact)}(this,function(p){function i(t,e){for(var n in e)t[n]=e[n];return t}function u(t,e,n){var r,o=/(?:\?([^#]*))?(#.*)?$/,i=t.match(o),u={};if(i&&i[1])for(var a=i[1].split("&"),p=0;p<a.length;p++){var c=a[p].split("=");u[decodeURIComponent(c[0])]=decodeURIComponent(c.slice(1).join("="))}t=y(t.replace(o,"")),e=y(e||"");for(var f=Math.max(t.length,e.length),l=0;l<f;l++)if(e[l]&&":"===e[l].charAt(0)){var s=e[l].replace(/(^:|[+*?]+$)/g,""),h=(e[l].match(/[+*?]+$/)||v)[0]||"",d=~h.indexOf("+"),g=~h.indexOf("*"),m=t[l]||"";if(!m&&!g&&(h.indexOf("?")<0||d)){r=!1;break}if(u[s]=decodeURIComponent(m),d||g){u[s]=t.slice(l).map(decodeURIComponent).join("/");break}}else if(e[l]!==t[l]){r=!1;break}return(!0===n.default||!1!==r)&&u}function e(t,e){return t.rank<e.rank?1:t.rank>e.rank?-1:t.index-e.index}function n(t,e){return t.index=e,t.rank=(n=t).props.default?0:function(t){return y(t).map(r).join("")}(n.props.path),t.props;var n}function y(t){return t.replace(/(^\/+|\/+$)/g,"").split("/")}function r(t){return":"==t.charAt(0)?1+"*+?".indexOf(t.charAt(t.length-1))||4:5}function o(){var t;return""+((t=g&&g.location?g.location:g&&g.getCurrentLocation?g.getCurrentLocation():"undefined"!=typeof location?location:b).pathname||"")+(t.search||"")}function a(t,e){return void 0===e&&(e=!1),"string"!=typeof t&&t.url&&(e=t.replace,t=t.url),function(t){for(var e=m.length;e--;)if(m[e].canRoute(t))return 1;return}(t)&&(n=t,void 0===(r=e?"replace":"push")&&(r="push"),g&&g[r]?g[r](n):"undefined"!=typeof history&&history[r+"State"]&&history[r+"State"](null,null,n)),c(t);var n,r}function c(t){for(var e=!1,n=0;n<m.length;n++)!0===m[n].routeTo(t)&&(e=!0);for(var r=C.length;r--;)C[r](t);return e}function f(t){if(t&&t.getAttribute){var e=t.getAttribute("href"),n=t.getAttribute("target");if(e&&e.match(/^\//g)&&(!n||n.match(/^_?self$/i)))return a(e)}}function l(t){if(!(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey||0!==t.button))return f(t.currentTarget||t.target||this),s(t)}function s(t){return t&&(t.stopImmediatePropagation&&t.stopImmediatePropagation(),t.stopPropagation&&t.stopPropagation(),t.preventDefault()),!1}function h(t){if(!(t.ctrlKey||t.metaKey||t.altKey||t.shiftKey||0!==t.button)){var e=t.target;do{if("A"===(e.nodeName+"").toUpperCase()&&e.getAttribute("href")){if(e.hasAttribute("native"))return;if(f(e))return s(t)}}while(e=e.parentNode)}}var d,v={},g=null,m=[],C=[],b={},U=!1,t=((d=p.Component)&&(k.__proto__=d),((k.prototype=Object.create(d&&d.prototype)).constructor=k).prototype.shouldComponentUpdate=function(t){return!0!==t.static||t.url!==this.props.url||t.onChange!==this.props.onChange},k.prototype.canRoute=function(t){return 0<this.getMatchingChildren(p.toChildArray(this.props.children),t,!1).length},k.prototype.routeTo=function(t){this.setState({url:t});var e=this.canRoute(t);return this.updating||this.forceUpdate(),e},k.prototype.componentWillMount=function(){m.push(this),this.updating=!0},k.prototype.componentDidMount=function(){var e=this;g&&(this.unlisten=g.listen(function(t){e.routeTo(""+(t.pathname||"")+(t.search||""))})),this.updating=!1},k.prototype.componentWillUnmount=function(){"function"==typeof this.unlisten&&this.unlisten(),m.splice(m.indexOf(this),1)},k.prototype.componentWillUpdate=function(){this.updating=!0},k.prototype.componentDidUpdate=function(){this.updating=!1},k.prototype.getMatchingChildren=function(t,r,o){return t.filter(n).sort(e).map(function(t){var e=u(r,t.props.path,t.props);if(e){if(!1===o)return t;var n={url:r,matches:e};return i(n,e),delete n.ref,delete n.key,p.cloneElement(t,n)}}).filter(Boolean)},k.prototype.render=function(t,e){var n=t.children,r=t.onChange,o=e.url,i=this.getMatchingChildren(p.toChildArray(n),o,!0),u=i[0]||null,a=this.previousUrl;return o!==a&&(this.previousUrl=o,"function"==typeof r&&r({router:this,url:o,previous:a,active:i,current:u})),u},k);function k(t){d.call(this,t),t.history&&(g=t.history),this.state={url:t.url||o()},U||("function"==typeof addEventListener&&(g||addEventListener("popstate",function(){c(o())}),addEventListener("click",h)),U=!0)}return t.subscribers=C,t.getCurrentUrl=o,t.route=a,(t.Router=t).Route=function(t){return p.createElement(t.component,t)},t.Link=function(t){return p.createElement("a",i({onClick:l},t))},t.exec=u,t});
-2
View File
@@ -1,2 +0,0 @@
!function(){var n,l,u,t,i,o,r,e,f={},c=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function v(n){var l=n.parentNode;l&&l.removeChild(n)}function h(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t<arguments.length;t++)u.push(i[t]);if(null!=u&&(o.children=u),"function"==typeof n&&null!=n.defaultProps)for(t in n.defaultProps)void 0===o[t]&&(o[t]=n.defaultProps[t]);return y(n,o,l&&l.key,l&&l.ref,null)}function y(l,u,t,i,o){var r={type:l,props:u,key:t,ref:i,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:o};return null==o&&(r.__v=r),n.vnode&&n.vnode(r),r}function p(n){return n.children}function d(n,l){this.props=n,this.context=l}function _(n,l){if(null==l)return n.__?_(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?_(n):null}function m(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return m(n)}}function k(l){(!l.__d&&(l.__d=!0)&&u.push(l)&&!w.__r++||i!==n.debounceRendering)&&((i=n.debounceRendering)||t)(w)}function w(){for(var n;w.__r=u.length;)n=u.sort(function(n,l){return n.__v.__b-l.__v.__b}),u=[],n.some(function(n){var l,u,t,i,o,r,e;n.__d&&(r=(o=(l=n).__v).__e,(e=l.__P)&&(u=[],(t=s({},o)).__v=t,i=N(e,o,t,l.__n,void 0!==e.ownerSVGElement,null,u,null==r?_(o):r),z(u,o),i!=r&&m(o)))})}function g(n,l,u,t,i,o,r,e,a,s){var h,d,m,k,w,g,x,C=t&&t.__k||c,A=C.length;for(a==f&&(a=null!=r?r[0]:A?_(t,0):null),u.__k=[],h=0;h<l.length;h++)if(null!=(k=u.__k[h]=null==(k=l[h])||"boolean"==typeof k?null:"string"==typeof k||"number"==typeof k?y(null,k,null,null,k):Array.isArray(k)?y(p,{children:k},null,null,null):null!=k.__e||null!=k.__c?y(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(m=C[h])||m&&k.key==m.key&&k.type===m.type)C[h]=void 0;else for(d=0;d<A;d++){if((m=C[d])&&k.key==m.key&&k.type===m.type){C[d]=void 0;break}m=null}w=N(n,k,m=m||f,i,o,r,e,a,s),(d=k.ref)&&m.ref!=d&&(x||(x=[]),m.ref&&x.push(m.ref,null,k),x.push(d,k.__c||w,k)),null!=w?(null==g&&(g=w),a=b(n,k,m,C,r,w,a),"option"==u.type?n.value="":"function"==typeof u.type&&(u.__d=a)):a&&m.__e==a&&a.parentNode!=n&&(a=_(m))}if(u.__e=g,null!=r&&"function"!=typeof u.type)for(h=r.length;h--;)null!=r[h]&&v(r[h]);for(h=A;h--;)null!=C[h]&&j(C[h],C[h]);if(x)for(h=0;h<x.length;h++)$(x[h],x[++h],x[++h])}function b(n,l,u,t,i,o,r){var e,f,c;if(void 0!==l.__d)e=l.__d,l.__d=void 0;else if(i==u||o!=r||null==o.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(o),e=null;else{for(f=r,c=0;(f=f.nextSibling)&&c<t.length;c+=2)if(f==o)break n;n.insertBefore(o,r),e=r}return void 0!==e?e:o.nextSibling}function x(n,l,u,t,i){var o;for(o in u)"children"===o||"key"===o||o in l||A(n,o,null,u[o],t);for(o in l)i&&"function"!=typeof l[o]||"children"===o||"key"===o||"value"===o||"checked"===o||u[o]===l[o]||A(n,o,l[o],u[o],t)}function C(n,l,u){"-"===l[0]?n.setProperty(l,u):n[l]="number"==typeof u&&!1===a.test(l)?u+"px":null==u?"":u}function A(n,l,u,t,i){var o,r,e,f,c;if(i?"className"===l&&(l="class"):"class"===l&&(l="className"),"style"===l)if(o=n.style,"string"==typeof u)o.cssText=u;else{if("string"==typeof t&&(o.cssText="",t=null),t)for(f in t)u&&f in u||C(o,f,"");if(u)for(c in u)t&&u[c]===t[c]||C(o,c,u[c])}else"o"===l[0]&&"n"===l[1]?(r=l!==(l=l.replace(/Capture$/,"")),e=l.toLowerCase(),l=(e in n?e:l).slice(2),u?(t||n.addEventListener(l,P,r),(n.l||(n.l={}))[l]=u):n.removeEventListener(l,P,r)):"list"!==l&&"tagName"!==l&&"form"!==l&&"type"!==l&&"size"!==l&&!i&&l in n?n[l]=null==u?"":u:"function"!=typeof u&&"dangerouslySetInnerHTML"!==l&&(l!==(l=l.replace(/^xlink:?/,""))?null==u||!1===u?n.removeAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase()):n.setAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase(),u):null==u||!1===u&&!/^ar/.test(l)?n.removeAttribute(l):n.setAttribute(l,u))}function P(l){this.l[l.type](n.event?n.event(l):l)}function E(n,l,u){var t,i;for(t=0;t<n.__k.length;t++)(i=n.__k[t])&&(i.__=n,i.__e&&("function"==typeof i.type&&i.__k.length>1&&E(i,l,u),l=b(u,i,i,n.__k,null,i.__e,l),"function"==typeof n.type&&(n.__d=l)))}function N(l,u,t,i,o,r,e,f,c){var a,v,h,y,_,m,k,w,b,x,C,A=u.type;if(void 0!==u.constructor)return null;(a=n.__b)&&a(u);try{n:if("function"==typeof A){if(w=u.props,b=(a=A.contextType)&&i[a.__c],x=a?b?b.props.value:a.__:i,t.__c?k=(v=u.__c=t.__c).__=v.__E:("prototype"in A&&A.prototype.render?u.__c=v=new A(w,x):(u.__c=v=new d(w,x),v.constructor=A,v.render=F),b&&b.sub(v),v.props=w,v.state||(v.state={}),v.context=x,v.__n=i,h=v.__d=!0,v.__h=[]),null==v.__s&&(v.__s=v.state),null!=A.getDerivedStateFromProps&&(v.__s==v.state&&(v.__s=s({},v.__s)),s(v.__s,A.getDerivedStateFromProps(w,v.__s))),y=v.props,_=v.state,h)null==A.getDerivedStateFromProps&&null!=v.componentWillMount&&v.componenLine truncated
//# sourceMappingURL=preact.min.js.map
-7
View File
@@ -1,7 +0,0 @@
html, body { margin: 0; padding: 0; height: 100%; }
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
.btn:focus,.btn:active:focus,.btn.active:focus,
.btn.focus,.btn:active.focus,.btn.active.focus { outline: none; box-shadow:none !important; }
.dropdown.autoexpand:hover>.dropdown-menu { display: block; }
.pointer { cursor: pointer; }
.log { max-height: 10em; overflow: auto; height: 100%; }
-10
View File
@@ -1,10 +0,0 @@
PROG ?= example
all: $(PROG)
$(DEBUGGER) ./$(PROG)
$(PROG):
$(CC) ../../mongoose.c -I../.. $(CFLAGS) $(EXTRA) -o $(PROG) main.c mjson.c
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt
-82
View File
@@ -1,82 +0,0 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "mjson.h" // JSON parsing and printing
#include "mongoose.h"
// This is a configuration structure we're going to show on a dashboard
static struct config {
int value1;
char *value2;
} s_config = {123, NULL};
// Stringifies the config. A caller must free() it.
static char *stringify_config(struct config *cfg) {
char *s = NULL;
mjson_printf(mjson_print_dynamic_buf, &s, "{%Q:%d,%Q:%Q}", "value1",
cfg->value1, "value2", cfg->value2);
return s;
}
// Update config structure. Return true if changed, false otherwise
static bool update_config(struct mg_http_message *hm, struct config *cfg) {
bool changed = false;
char buf[256];
double dv;
if (mjson_get_number(hm->body.ptr, hm->body.len, "$.value1", &dv)) {
s_config.value1 = dv;
changed = true;
}
if (mjson_get_string(hm->body.ptr, hm->body.len, "$.value2", buf,
sizeof(buf)) > 0) {
free(s_config.value2);
s_config.value2 = strdup(buf);
changed = true;
}
return changed;
}
// Notify all config watchers about the config change
static void notify_config_change(struct mg_mgr *mgr) {
struct mg_connection *c;
char *s = stringify_config(&s_config);
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W') mg_http_printf_chunk(c, "%s\n", s);
}
free(s);
}
// HTTP request handler function. It implements the following endpoints:
// /api/config/get - returns current config
// /api/config/set - updates current config
// /api/config/watch - does not return. Sends config as it changes in
// chunks all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/api/config/get")) {
char *s = stringify_config(&s_config);
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s\n",
(int) strlen(s) + 1, s);
free(s);
} else if (mg_http_match_uri(hm, "/api/config/set")) {
if (update_config(hm, &s_config)) notify_config_change(fn_data);
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/config/watch")) {
c->label[0] = 'W'; // Mark ourselves as a config watcher
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://localhost:8000", cb, &mgr);
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
return 0;
}
-76
View File
@@ -1,76 +0,0 @@
const {h, render, Component} = preact;
const html = htm.bind(h);
const Info = () => html`<div id="info">
This dashboard shows values kept in server memory. Many clients
can open this page. As soon as any client changes any value,
all clients update automatically.
<br/><br/>
The JS code that watches state changes, reconnects on network failures.
That means if server restarts, dashboard on all connected clients
refresh automatically.
<br/><br/>
You can use <code>curl</code> command-line utility:
<br/><code>curl localhost:8000/api/config/get</code>
<br/><code>curl localhost:8000/api/config/watch</code>
<br/><code>curl localhost:8000/api/config/set -d '{"a":123}'</code>
</div>`;
class Dashboard extends Component {
state = {value1: null, value2: null};
componentDidMount() {
axios.get('/api/config/get')
.then(r => this.setState(r.data))
.catch(e => console.log(e));
var self = this;
var f = function(reader) {
return reader.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
self.setState(JSON.parse(data));
// console.log(JSON.parse(data));
if (!result.done) return f(reader);
});
};
fetch('/api/config/watch')
.then(r => r.body.getReader())
.then(f)
.catch(e => setTimeout(x => self.componentDidMount(), 1000));
}
render(props, state) {
return html
`<div id="dashboard" style="display: flex; justify-content: flex-evenly;">
<div style="width:50%;text-align:center;">Value 1 (number):
<div style="font-size:140%;"><b>${state.value1}</b></div></div>
<div style="width:50%;text-align:center;">Value 2 (string):
<div style="font-size:140%;"><b>${state.value2}</b></div></div>
</div>`
}
};
class Form extends Component {
render(props, state) {
const onclick = ev => axios.post(
'/api/config/set', {value1: +state.value1, value2: state.value2});
// alert(JSON.stringify(state));
return html`<div id="form">
<div><b>Change values</b></div>
<table>
<tr><td>Value 1 (number):</td><td><input value=${state.value1}
onInput=${linkState(this, 'value1')} /></td></tr>
<tr><td>Value 2 (string):</td><td><input value=${state.value2}
onInput=${linkState(this, 'value2')}/></td></tr>
<tr><td></td>
<td><button onClick=${onclick}>Save values</button></td></tr>
</table>
</div>`
}
};
const App = () => html`<div id="container">
<${Info} />
<${Dashboard} />
<${Form} />
</div>`;
window.onload = () => render(h(App), document.body);
-3
View File
@@ -1,3 +0,0 @@
/* axios v0.21.1 | (c) 2020 by Matt Zabriskie */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(22),u=n(10),c=r(u);c.Axios=i,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),c.isAxiosError=n(26),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"undefined"==typeof e}function s(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function i(e){return"[object ArrayBuffer]"===R.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){if("[object Object]"!==R.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}function l(e){return"[object Date]"===R.call(e)}function h(e){return"[object File]"===R.call(e)}function m(e){return"[object Blob]"===R.call(e)}function y(e){return"[object Function]"===R.call(e)}function g(e){return p(e)&&y(e.pipe)}function v(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function x(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function b(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&t.call(null,e[s],s,e)}function E(){function e(e,n){d(t[n])&&d(e)?t[n]=E(t[n],e):d(e)?t[n]=E({},e):r(e)?t[n]=e.slice():t[n]=e}for(var t={},n=0,o=arguments.length;n<o;n++)b(arguments[n],e);return t}function j(e,t,n){return b(t,function(t,r){n&&"function"==typeof t?e[r]=S(t,n):e[r]=t}),e}function C(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e}var S=n(3),R=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:i,isBuffer:s,isFormData:a,isArrayBufferView:u,isString:c,isNumber:f,isObject:p,isPlainObject:d,isUndefined:o,isDate:l,isFile:h,isBlob:m,isFunction:y,isStream:g,isURLSearchParams:v,isStandardBrowserEnv:w,forEach:b,merge:E,extend:j,trim:x,stripBOM:C}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new i,response:new i}}var o=n(2),s=n(5),i=n(6),a=n(7),u=n(22);r.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=u(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[a,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},r.prototype.getUri=function(e){return e=u(this.defaults,e),s(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},o.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(u(n||{},{method:e,url:t,data:(n||{}).data}))}}),o.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(u(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(o.isURLSearchParams(t))s=t.toString();else{var i=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),i.push(r(t)+"="+r(e))}))}),s=i.join("&")}if(s){var a=e.indexOf("#");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf("?")===-1?"?":"&")+s}return e}},function(e,t,n){"use stricLine truncated
//# sourceMappingURL=axios.min.map
-1
View File
@@ -1 +0,0 @@
!function(){function n(n){var t=e.get(this);return t||(t=new Map,e.set(this,t)),1<(t=a(this,t.get(n)||(t.set(n,t=function(n){function t(n){1===u&&(n||(r=r.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?p.push(0,n,r):3===u&&(n||r)?(p.push(3,n,r),u=2):2===u&&"..."===r&&n?p.push(4,n,0):2===u&&r&&!n?p.push(5,0,!0,r):5<=u&&((r||!n&&5===u)&&(p.push(u,0,r,s),u=6),n&&(p.push(u,n,0,s),u=6)),r=""}for(var e,s,u=1,r="",h="",p=[0],a=0;a<n.length;a++){a&&(1===u&&t(),t(a));for(var o=0;o<n[a].length;o++)e=n[a][o],1===u?"<"===e?(t(),p=[p],u=3):r+=e:4===u?r="--"===r&&">"===e?(u=1,""):e+r[0]:h?e===h?h="":r+=e:'"'===e||"'"===e?h=e:">"===e?(t(),u=1):u&&("="===e?(u=5,s=r,r=""):"/"===e&&(u<5||">"===n[a][o+1])?(t(),3===u&&(p=p[0]),(p=(u=p)[0]).push(2,0,u),u=0):" "===e||"\t"===e||"\n"===e||"\r"===e?(t(),u=2):r+=e),3===u&&"!--"===r&&(u=4,p=p[0])}return t(),p}(n)),t),arguments,[])).length?t:t[0]}var a=function(n,t,e,s){var u;t[0]=0;for(var r=1;r<t.length;r++){var h=t[r++],p=t[r]?(t[0]|=h?1:2,e[t[r++]]):t[++r];3===h?s[0]=p:4===h?s[1]=Object.assign(s[1]||{},p):5===h?(s[1]=s[1]||{})[t[++r]]=p:6===h?s[1][t[++r]]+=p+"":h?(u=n.apply(p,a(n,p,e,["",null])),s.push(u),p[0]?t[0]|=2:(t[r-2]=0,t[r]=u)):s.push(p)}return s},e=new Map;"undefined"!=typeof module?module.exports=n:self.htm=n}();
-18
View File
@@ -1,18 +0,0 @@
function linkState(component, key, eventPath) {
let path = key.split('.'), cache = component.__lsc || (component.__lsc = {});
return cache[key + eventPath] || (cache[key + eventPath] = function(e) {
let t = e && e.target || this, state = {}, obj = state,
v = typeof eventPath === 'string' ?
delve(e, eventPath) :
t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) :
e,
i = 0;
for (; i < path.length - 1; i++) {
obj = obj[path[i]] ||
(obj[path[i]] = !i && component.state[path[i]] || {});
}
obj[path[i]] = v;
component.setState(state);
});
}
-2
View File
@@ -1,2 +0,0 @@
!function(){var n,l,u,t,i,o,r,e,f={},c=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function v(n){var l=n.parentNode;l&&l.removeChild(n)}function h(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t<arguments.length;t++)u.push(i[t]);if(null!=u&&(o.children=u),"function"==typeof n&&null!=n.defaultProps)for(t in n.defaultProps)void 0===o[t]&&(o[t]=n.defaultProps[t]);return y(n,o,l&&l.key,l&&l.ref,null)}function y(l,u,t,i,o){var r={type:l,props:u,key:t,ref:i,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,constructor:void 0,__v:o};return null==o&&(r.__v=r),n.vnode&&n.vnode(r),r}function p(n){return n.children}function d(n,l){this.props=n,this.context=l}function _(n,l){if(null==l)return n.__?_(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return"function"==typeof n.type?_(n):null}function m(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return m(n)}}function k(l){(!l.__d&&(l.__d=!0)&&u.push(l)&&!w.__r++||i!==n.debounceRendering)&&((i=n.debounceRendering)||t)(w)}function w(){for(var n;w.__r=u.length;)n=u.sort(function(n,l){return n.__v.__b-l.__v.__b}),u=[],n.some(function(n){var l,u,t,i,o,r,e;n.__d&&(r=(o=(l=n).__v).__e,(e=l.__P)&&(u=[],(t=s({},o)).__v=t,i=N(e,o,t,l.__n,void 0!==e.ownerSVGElement,null,u,null==r?_(o):r),z(u,o),i!=r&&m(o)))})}function g(n,l,u,t,i,o,r,e,a,s){var h,d,m,k,w,g,x,C=t&&t.__k||c,A=C.length;for(a==f&&(a=null!=r?r[0]:A?_(t,0):null),u.__k=[],h=0;h<l.length;h++)if(null!=(k=u.__k[h]=null==(k=l[h])||"boolean"==typeof k?null:"string"==typeof k||"number"==typeof k?y(null,k,null,null,k):Array.isArray(k)?y(p,{children:k},null,null,null):null!=k.__e||null!=k.__c?y(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(m=C[h])||m&&k.key==m.key&&k.type===m.type)C[h]=void 0;else for(d=0;d<A;d++){if((m=C[d])&&k.key==m.key&&k.type===m.type){C[d]=void 0;break}m=null}w=N(n,k,m=m||f,i,o,r,e,a,s),(d=k.ref)&&m.ref!=d&&(x||(x=[]),m.ref&&x.push(m.ref,null,k),x.push(d,k.__c||w,k)),null!=w?(null==g&&(g=w),a=b(n,k,m,C,r,w,a),"option"==u.type?n.value="":"function"==typeof u.type&&(u.__d=a)):a&&m.__e==a&&a.parentNode!=n&&(a=_(m))}if(u.__e=g,null!=r&&"function"!=typeof u.type)for(h=r.length;h--;)null!=r[h]&&v(r[h]);for(h=A;h--;)null!=C[h]&&j(C[h],C[h]);if(x)for(h=0;h<x.length;h++)$(x[h],x[++h],x[++h])}function b(n,l,u,t,i,o,r){var e,f,c;if(void 0!==l.__d)e=l.__d,l.__d=void 0;else if(i==u||o!=r||null==o.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(o),e=null;else{for(f=r,c=0;(f=f.nextSibling)&&c<t.length;c+=2)if(f==o)break n;n.insertBefore(o,r),e=r}return void 0!==e?e:o.nextSibling}function x(n,l,u,t,i){var o;for(o in u)"children"===o||"key"===o||o in l||A(n,o,null,u[o],t);for(o in l)i&&"function"!=typeof l[o]||"children"===o||"key"===o||"value"===o||"checked"===o||u[o]===l[o]||A(n,o,l[o],u[o],t)}function C(n,l,u){"-"===l[0]?n.setProperty(l,u):n[l]="number"==typeof u&&!1===a.test(l)?u+"px":null==u?"":u}function A(n,l,u,t,i){var o,r,e,f,c;if(i?"className"===l&&(l="class"):"class"===l&&(l="className"),"style"===l)if(o=n.style,"string"==typeof u)o.cssText=u;else{if("string"==typeof t&&(o.cssText="",t=null),t)for(f in t)u&&f in u||C(o,f,"");if(u)for(c in u)t&&u[c]===t[c]||C(o,c,u[c])}else"o"===l[0]&&"n"===l[1]?(r=l!==(l=l.replace(/Capture$/,"")),e=l.toLowerCase(),l=(e in n?e:l).slice(2),u?(t||n.addEventListener(l,P,r),(n.l||(n.l={}))[l]=u):n.removeEventListener(l,P,r)):"list"!==l&&"tagName"!==l&&"form"!==l&&"type"!==l&&"size"!==l&&!i&&l in n?n[l]=null==u?"":u:"function"!=typeof u&&"dangerouslySetInnerHTML"!==l&&(l!==(l=l.replace(/^xlink:?/,""))?null==u||!1===u?n.removeAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase()):n.setAttributeNS("http://www.w3.org/1999/xlink",l.toLowerCase(),u):null==u||!1===u&&!/^ar/.test(l)?n.removeAttribute(l):n.setAttribute(l,u))}function P(l){this.l[l.type](n.event?n.event(l):l)}function E(n,l,u){var t,i;for(t=0;t<n.__k.length;t++)(i=n.__k[t])&&(i.__=n,i.__e&&("function"==typeof i.type&&i.__k.length>1&&E(i,l,u),l=b(u,i,i,n.__k,null,i.__e,l),"function"==typeof n.type&&(n.__d=l)))}function N(l,u,t,i,o,r,e,f,c){var a,v,h,y,_,m,k,w,b,x,C,A=u.type;if(void 0!==u.constructor)return null;(a=n.__b)&&a(u);try{n:if("function"==typeof A){if(w=u.props,b=(a=A.contextType)&&i[a.__c],x=a?b?b.props.value:a.__:i,t.__c?k=(v=u.__c=t.__c).__=v.__E:("prototype"in A&&A.prototype.render?u.__c=v=new A(w,x):(u.__c=v=new d(w,x),v.constructor=A,v.render=F),b&&b.sub(v),v.props=w,v.state||(v.state={}),v.context=x,v.__n=i,h=v.__d=!0,v.__h=[]),null==v.__s&&(v.__s=v.state),null!=A.getDerivedStateFromProps&&(v.__s==v.state&&(v.__s=s({},v.__s)),s(v.__s,A.getDerivedStateFromProps(w,v.__s))),y=v.props,_=v.state,h)null==A.getDerivedStateFromProps&&null!=v.componentWillMount&&v.componenLine truncated
//# sourceMappingURL=preact.min.js.map
-6
View File
@@ -1,6 +0,0 @@
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #d0f0f0; border-radius: .5em; padding: 2em; }
#dashboard { background: #f0f0f0; border-radius: .5em; padding: 0.5em; max-height: 10em; overflow: auto; height: 100%; margin-top: 1em; }
#form { background: #d0c0f0; border-radius: .5em; padding: 0.5em; max-height: 10em; overflow: auto; height: 100%; margin-top: 1em; }
button { width: 100%; }
button, select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
+32
View File
@@ -0,0 +1,32 @@
PROG ?= example
SOURCES ?= ../../mongoose.c main.c web.c packed_fs.c
CFLAGS ?= -I../.. -DMG_ENABLE_PACKED_FS=1 $(EXTRA)
FILES_TO_EMBED ?= $(wildcard web_root/*)
ROOT ?= $(realpath $(CURDIR)/../../..)
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR)
VC98 ?= $(DOCKER) mdashnet/vc98 wine
MINGW ?= $(DOCKER) mdashnet/mingw
all: $(PROG)
$(RUN) ./$(PROG)
$(PROG): $(SOURCES) $(FILES_TO_EMBED)
$(CC) ../../test/pack.c -o pack
./pack $(FILES_TO_EMBED) > packed_fs.c
$(CC) -W -Wall -Wextra -O0 -g3 $(CFLAGS) -o $(PROG) $(SOURCES)
mingw: $(SOURCES) $(FILES_TO_EMBED)
$(MINGW) i686-w64-mingw32-gcc $(CFLAGS) ../../test/pack.c -o pack.exe
$(MINGW) wine cmd /c 'pack.exe $(FILES_TO_EMBED) > packed_fs.c'
$(MINGW) i686-w64-mingw32-gcc $(CFLAGS) $(SOURCES) -lws2_32 -o $(PROG).exe
$(MINGW) wine $(PROG).exe
vc98: $(SOURCES) $(FILES_TO_EMBED)
$(VC98) cl.exe /nologo $(CFLAGS) ../../test/pack.c /Fepack.exe
$(VC98) cmd /c 'pack.exe $(FILES_TO_EMBED) > packed_fs.c'
$(VC98) cl.exe /nologo /O2 $(CFLAGS) $(SOURCES) /Fe$(PROG).exe
$(VC98) $(PROG).exe
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb log.txt pack
+25
View File
@@ -0,0 +1,25 @@
# A complete device dashboard
This example is a demonstration of how Mongoose Library could be integrated
into an embedded device and provide a complete device dashboard with the
following features:
- Authentication: login-protected dashboard
- Multiple logins with different permissions (admin and user)
- Web UI is fully embedded into the server/firmware binary, and does not
need a filesystem to serve it. UI is resilient to FS problems
- View and change server settings
- All changes are propagates to all connected clients
- Live interactive chat that demonstrates bi-directional data exchange
# Screenshots
This is a login screen that prompts for user/password
![](screenshots/login.png)
# Main dashboard
A main dashboard page shows device settings form, and live chat
![](screenshots/dashboard.png)
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) 2020-2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
void device_dashboard_fn(struct mg_connection *, int, void *, void *);
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:8000", device_dashboard_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
return 0;
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

+130
View File
@@ -0,0 +1,130 @@
// Copyright (c) 2020-2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
// Authenticated user.
// A user can be authenticated by:
// - a name:pass pair
// - a token
// When a user is shown a login screen, she enters a user:pass. If successful,
// a server returns user info which includes token. From that point on,
// client can use token for authentication. Tokens could be refreshed/changed
// on a server side, forcing clients to re-login.
struct user {
const char *name, *pass, *token;
};
// This is a configuration structure we're going to show on a dashboard
static struct config {
int value1;
char *value2;
} s_config = {123, NULL};
// Update config structure. Return true if changed, false otherwise
static bool update_config(struct mg_http_message *hm, struct config *cfg) {
bool changed = false;
char buf[256];
if (mg_http_get_var(&hm->body, "value1", buf, sizeof(buf)) > 0) {
cfg->value1 = atoi(buf);
changed = true;
}
if (mg_http_get_var(&hm->body, "value2", buf, sizeof(buf)) > 0) {
free(cfg->value2);
cfg->value2 = malloc(strlen(buf) + 1);
strcpy(cfg->value2, buf);
changed = true;
}
return changed;
}
// Parse HTTP requests, return authenticated user or NULL
static struct user *getuser(struct mg_http_message *hm) {
// In production, make passwords strong and tokens randomly generated
// In this example, user list is kept in RAM. In production, it can
// be backed by file, database, or some other method.
static struct user users[] = {
{"admin", "pass0", "admin_token"},
{"user1", "pass1", "user1_token"},
{"user2", "pass2", "user2_token"},
{NULL, NULL, NULL},
};
char user[256], pass[256];
struct user *u;
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
if (user[0] != '\0' && pass[0] != '\0') {
// Both user and password is set, search by user/password
for (u = users; u->name != NULL; u++)
if (strcmp(user, u->name) == 0 && strcmp(pass, u->pass) == 0) return u;
} else if (user[0] == '\0') {
// Only password is set, search by token
for (u = users; u->name != NULL; u++)
if (strcmp(pass, u->token) == 0) return u;
}
return NULL;
}
// Notify all config watchers about the config change
static void send_notification(struct mg_mgr *mgr, const char *name,
const char *data) {
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W')
mg_http_printf_chunk(c, "{\"name\": \"%s\", \"data\": \"%s\"}", name,
data == NULL ? "" : data);
}
}
// HTTP request handler function
void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct user *u = getuser(hm);
// MG_INFO(("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
// u ? u->name : "NULL"));
if (mg_http_match_uri(hm, "/api/hi")) {
mg_http_reply(c, 200, "", "hi\n"); // Testing endpoint
} else if (u == NULL && mg_http_match_uri(hm, "/api/#")) {
// All URIs starting with /api/ must be authenticated
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/config/get")) {
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_http_printf_chunk(c, "{\"%s\":%d,\"%s\":\"%s\"}", "value1",
s_config.value1, "value2", s_config.value2);
mg_http_printf_chunk(c, "");
} else if (mg_http_match_uri(hm, "/api/config/set")) {
// Admins only
if (strcmp(u->name, "admin") == 0) {
if (update_config(hm, &s_config))
send_notification(fn_data, "config", NULL);
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
} else {
mg_printf(c, "%s", "HTTP/1.1 403 Denied\r\nContent-Length: 0\r\n\r\n");
}
} else if (mg_http_match_uri(hm, "/api/message/send")) {
char buf[256];
if (mg_http_get_var(&hm->body, "message", buf, sizeof(buf)) > 0) {
send_notification(fn_data, "message", buf);
}
mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/watch")) {
c->label[0] = 'W'; // Mark ourselves as a event listener
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else if (mg_http_match_uri(hm, "/api/login")) {
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_http_printf_chunk(c, "{\"user\":\"%s\",\"token\":\"%s\"}\n", u->name,
u->token);
mg_http_printf_chunk(c, "");
} else {
struct mg_http_serve_opts opts = {0};
#if 1
opts.root_dir = "/web_root";
opts.fs = &mg_fs_packed;
#else
opts.root_dir = "web_root";
#endif
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
@@ -1,16 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>example</title> <title>Device Dashboard</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
<script src="preact.min.js"></script>
<script src="htm.min.js"></script>
<script src="linkstate.min.js"></script>
<script src="axios.min.js"></script>
<script src="app.js"></script>
</head> </head>
<body></body> <body></body>
<script type="module" src="main.js"></script>
</html> </html>
+250
View File
@@ -0,0 +1,250 @@
'use strict';
import {Component, h, html, render, useEffect, useState} from './preact.min.js';
const Nav = props => html`
<div style="background: #333; padding: 0.5em; color: #fff;">
<div class="container d-flex">
<div style="flex: 1 1 auto; display: flex; align-items: center;">
<b>Your Product</b>
</div>
<div style="display: flex; align-items: center; flex: 0 0 auto; ">
<span>Logged in as:</span>
<span style="padding: 0 0.5em;"><img src="user.png" height="22" /></span>
<span>${props.user}</span>
<a class="btn" onclick=${props.logout}
style="margin-left: 1em; font-size: 0.8em; background: #8aa;">logout</a>
</div>
</div>
</div>`;
const Footer = props => html`
<div style="color: silver; margin-top: 2em; padding-top: 0.5em; border-top: 1px solid #ccc; ">
Copyright (c) Your Company
</div>
`;
const Hero = props => html`
<div style="margin-top: 1em; background: #eee; padding: 1em; border-radius: 0.5em; color: #777; ">
<h1 style="margin: 0.2em 0;">Interactive Device Dashboard</h1>
This device dashboard is developed with modern and compact Preact framework,
in order to fit on a very small devices. This is
a <a href="https://mongoose.ws/tutorials/http-server/">hybrid server</a> which
provides both static and dynamic content. Static files, like CSS/JS/HTML
or images, are compiled into the server binary. Dynamic content is
served via the REST API.
This dashboard shows values kept in server memory. Many clients can open
this page. The JS code that watches state changes, reconnects on network
failures. That means if server restarts, dashboard on all connected clients
refresh automatically.
NOTE: administrators can change settings values, whilst users cannot.
<p>
This UI uses the REST API implemented by the server, which you can examine
using <code>curl</code> command-line utility:
</p>
<div><code>curl localhost:8000/api/config/get</code> - get current device configuration</div>
<div><code>curl localhost:8000/api/config/set -d 'value1=7&value2=hello'</code> - set device configuration</div>
<div><code>curl localhost:8000/api/message/send -d 'msg=hello'</code> - send chat message</div>
<div><code>curl localhost:8000/api/watch</code> - get notifications on config changes or chat messages</div>
</div>`;
const ShowSettings = function(props) {
return html`
<div style="margin: 0 0.3em;">
<h3 style="background: #59d; color: #fff;padding: 0.4em;">Device configuration</h3>
<div style="margin: 0.5em 0;">
<span class="addon">value1:</span>
<input disabled type="text" class="smooth"
value=${(props.config || {}).value1 || ''} />
</div>
<div style="margin: 0.5em 0;">
<span class="addon">value2:</span>
<input disabled type="text" class="smooth"
value=${(props.config || {}).value2 || ''} />
</div>
<div class="msg">
Server's corresponding runtime configuration structure:
<pre>
struct config {
int value1;
char *value2;
} s_config;
</pre>
</div>
</div>`;
};
const ChangeSettings = function(props) {
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
useEffect(() => {
setValue1(props.config.value1);
setValue2(props.config.value2);
}, [props.config.value1, props.config.value2])
const update = (name, val) => fetch('/api/config/set', {
method: 'post',
body: `${name}=${encodeURIComponent(val)}`
}).catch(err => err);
const updatevalue1 = ev => update('value1', value1);
const updatevalue2 = ev => update('value2', value2);
return html`
<div style="margin: 0 0.3em;">
<h3 style="background: #c03434; color: #fff; padding: 0.4em;">
Change configuration</h3>
<div style="margin: 0.5em 0;">
<span class="addon">value1:</span>
<input type="text" value=${value1} onchange=${updatevalue1}
oninput=${ev => setValue1(ev.target.value)} />
<button class="btn" disabled=${!value1} onclick=${updatevalue1}
style="margin-left: 1em; background: #8aa;">Update</button>
</div>
<div style="margin: 0.5em 0;">
<span class="addon">value2:</span>
<input type="text" value=${value2} onchange=${updatevalue2}
oninput=${ev => setValue2(ev.target.value)} />
<button class="btn" disabled=${!value2} onclick=${updatevalue2}
style="margin-left: 1em; background: #8aa;">Update</button>
</div>
<div class="msg">
As soon as administrator updates configuration,
server iterates over all connected clients and sends update
notifications to all of them - so they update automatically.
</div>
</div>`;
};
const Login = function(props) {
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
const login = ev =>
fetch(
'/api/login',
{headers: {Authorization: 'Basic ' + btoa(user + ':' + pass)}})
.then(r => r.json())
.then(r => r && props.login(r))
.catch(err => err);
return html`
<div class="rounded border" style="max-width: 480px; margin: 0 auto; margin-top: 5em; background: #eee; ">
<div style="padding: 2em; ">
<h1 style="color: #666;">Device Dashboard Login </h1>
<div style="margin: 0.5em 0;">
<input type='text' placeholder='Name' style="width: 100%;"
oninput=${ev => setUser(ev.target.value)} value=${user} />
</div>
<div style="margin: 0.5em 0;">
<input type="password" placeholder="Password" style="width: 100%;"
oninput=${ev => setPass(ev.target.value)} value=${pass}
onchange=${login} />
</div>
<div style="margin: 1em 0;">
<button class="btn" style="width: 100%; background: #8aa;"
disabled=${!user || !pass} onclick=${login}> Login </button>
</div>
<div style="color: #777; margin-top: 2em;">
Valid logins: admin:pass0, user1:pass1, user2:pass2
</div>
</div>
</div>`;
};
const Message = text => html`<div style="margin: 0.5em 0;">${text}</div>`;
const Chat = function(props) {
const [message, setMessage] = useState('');
const sendmessage = ev => fetch('/api/message/send', {
method: 'post',
body: `message=${encodeURIComponent(message)}`
}).then(r => setMessage(''));
const messages = props.messages.map(
text => html`<div style="margin: 0.5em 0;">${text}</div>`);
return html`
<div style="margin: 0 0.3em;">
<h3 style="background: #30c040; color: #fff; padding: 0.4em;">User chat</h3>
<div style="height: 10em; overflow: auto; padding: 0.5em; " class="border">
${messages}
</div>
<div style="margin: 0.5em 0;">
<input placeholder="type message..." style="width: 100%" value=${message}
onchange=${sendmessage}
oninput=${ev => setMessage(ev.target.value)} />
</div>
<div class="msg">
Chat demonsrates
real-time bidirectional data exchange between many clients and a server.
</div>
</div>`;
};
const App = function(props) {
const [messages, setMessages] = useState([]);
const [user, setUser] = useState('');
const [config, setConfig] = useState({});
const refresh = () => fetch('/api/config/get', {headers: {Authorization: ''}})
.then(r => r.json())
.then(r => setConfig(r));
const login = function(u) {
document.cookie = `access_token=${u.token};path=/;max-age=3600`;
setUser(u.user);
refresh();
};
const logout = ev => {
document.cookie = `access_token=;path=/;max-age=0`;
setUser('');
};
const watch = function() {
var f = function(reader) {
return reader.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
var notification = JSON.parse(data);
if (notification.name == 'config') refresh();
if (notification.name == 'message') {
setMessages(m => m.concat([notification.data]))
}
// console.log(notification);
if (!result.done) return f(reader);
});
};
fetch('/api/watch', {headers: {Authorization: ''}})
.then(r => r.body.getReader())
.then(f)
.catch(e => setTimeout(watch, 1000));
};
useEffect(() => {
fetch('/api/login', {headers: {Authorization: ''}})
.then(r => r.json())
.then(r => login(r))
.catch(err => setUser(''));
refresh();
watch();
}, []);
if (!user) return html`<${Login} login=${login} />`;
const admin = user == 'admin';
const colsize = admin ? 'c4' : 'c6';
const cs = admin ? html`<${ChangeSettings} config=${config} />` : '';
return html`
<${Nav} user=${user} logout=${logout} />
<div class="container">
<${Hero} />
<div class="row">
<div class="col ${colsize}"><${ShowSettings} config=${config} /></div>
<div class="col ${colsize}">${cs}</div>
<div class="col ${colsize}"><${Chat} messages=${messages} /></div>
</div>
<${Footer} />
</div>`;
};
window.onload = () => render(h(App), document.body);
+1
View File
@@ -0,0 +1 @@
var e,n,_,t,o,r,u,l={},i=[],c=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(e,n){for(var _ in n)e[_]=n[_];return e}function f(e){var n=e.parentNode;n&&n.removeChild(e)}function a(n,_,t){var o,r,u,l={};for(u in _)"key"==u?o=_[u]:"ref"==u?r=_[u]:l[u]=_[u];if(arguments.length>2&&(l.children=arguments.length>3?e.call(arguments,2):t),"function"==typeof n&&null!=n.defaultProps)for(u in n.defaultProps)void 0===l[u]&&(l[u]=n.defaultProps[u]);return p(n,l,o,r,null)}function p(e,t,o,r,u){var l={type:e,props:t,key:o,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==u?++_:u};return null!=n.vnode&&n.vnode(l),l}function h(e){return e.children}function d(e,n){this.props=e,this.context=n}function v(e,n){if(null==n)return e.__?v(e.__,e.__.__k.indexOf(e)+1):null;for(var _;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e)return _.__e;return"function"==typeof e.type?v(e):null}function y(e){var n,_;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if(null!=(_=e.__k[n])&&null!=_.__e){e.__e=e.__c.base=_.__e;break}return y(e)}}function m(e){(!e.__d&&(e.__d=!0)&&t.push(e)&&!g.__r++||r!==n.debounceRendering)&&((r=n.debounceRendering)||o)(g)}function g(){for(var e;g.__r=t.length;)e=t.sort(function(e,n){return e.__v.__b-n.__v.__b}),t=[],e.some(function(e){var n,_,t,o,r,u;e.__d&&(r=(o=(n=e).__v).__e,(u=n.__P)&&(_=[],(t=s({},o)).__v=o.__v+1,P(u,o,t,n.__n,void 0!==u.ownerSVGElement,null!=o.__h?[r]:null,_,null==r?v(o):r,o.__h),D(_,o),o.__e!=r&&y(o)))})}function k(e,n,_,t,o,r,u,c,s,f){var a,d,y,m,g,k,x,H=t&&t.__k||i,E=H.length;for(_.__k=[],a=0;a<n.length;a++)if(null!=(m=_.__k[a]=null==(m=n[a])||"boolean"==typeof m?null:"string"==typeof m||"number"==typeof m||"bigint"==typeof m?p(null,m,null,null,m):Array.isArray(m)?p(h,{children:m},null,null,null):m.__b>0?p(m.type,m.props,m.key,null,m.__v):m)){if(m.__=_,m.__b=_.__b+1,null===(y=H[a])||y&&m.key==y.key&&m.type===y.type)H[a]=void 0;else for(d=0;d<E;d++){if((y=H[d])&&m.key==y.key&&m.type===y.type){H[d]=void 0;break}y=null}P(e,m,y=y||l,o,r,u,c,s,f),g=m.__e,(d=m.ref)&&y.ref!=d&&(x||(x=[]),y.ref&&x.push(y.ref,null,m),x.push(d,m.__c||g,m)),null!=g?(null==k&&(k=g),"function"==typeof m.type&&null!=m.__k&&m.__k===y.__k?m.__d=s=b(m,s,e):s=C(e,m,y,H,g,s),f||"option"!==_.type?"function"==typeof _.type&&(_.__d=s):e.value=""):s&&y.__e==s&&s.parentNode!=e&&(s=v(y))}for(_.__e=k,a=E;a--;)null!=H[a]&&("function"==typeof _.type&&null!=H[a].__e&&H[a].__e==_.__d&&(_.__d=v(t,a+1)),U(H[a],H[a]));if(x)for(a=0;a<x.length;a++)T(x[a],x[++a],x[++a])}function b(e,n,_){var t,o;for(t=0;t<e.__k.length;t++)(o=e.__k[t])&&(o.__=e,n="function"==typeof o.type?b(o,n,_):C(_,o,o,e.__k,o.__e,n));return n}function C(e,n,_,t,o,r){var u,l,i;if(void 0!==n.__d)u=n.__d,n.__d=void 0;else if(null==_||o!=r||null==o.parentNode)e:if(null==r||r.parentNode!==e)e.appendChild(o),u=null;else{for(l=r,i=0;(l=l.nextSibling)&&i<t.length;i+=2)if(l==o)break e;e.insertBefore(o,r),u=r}return void 0!==u?u:o.nextSibling}function x(e,n,_){"-"===n[0]?e.setProperty(n,_):e[n]=null==_?"":"number"!=typeof _||c.test(n)?_:_+"px"}function H(e,n,_,t,o){var r;e:if("style"===n)if("string"==typeof _)e.style.cssText=_;else{if("string"==typeof t&&(e.style.cssText=t=""),t)for(n in t)_&&n in _||x(e.style,n,"");if(_)for(n in _)t&&_[n]===t[n]||x(e.style,n,_[n])}else if("o"===n[0]&&"n"===n[1])r=n!==(n=n.replace(/Capture$/,"")),n=n.toLowerCase()in e?n.toLowerCase().slice(2):n.slice(2),e.l||(e.l={}),e.l[n+r]=_,_?t||e.addEventListener(n,r?S:E,r):e.removeEventListener(n,r?S:E,r);else if("dangerouslySetInnerHTML"!==n){if(o)n=n.replace(/xlink[H:h]/,"h").replace(/sName$/,"s");else if("href"!==n&&"list"!==n&&"form"!==n&&"tabIndex"!==n&&"download"!==n&&n in e)try{e[n]=null==_?"":_;break e}catch(e){}"function"==typeof _||(null!=_&&(!1!==_||"a"===n[0]&&"r"===n[1])?e.setAttribute(n,_):e.removeAttribute(n))}}function E(e){this.l[e.type+!1](n.event?n.event(e):e)}function S(e){this.l[e.type+!0](n.event?n.event(e):e)}function P(e,_,t,o,r,u,l,i,c){var f,a,p,v,y,m,g,b,C,x,H,E=_.type;if(void 0!==_.constructor)return null;null!=t.__h&&(c=t.__h,i=_.__e=t.__e,_.__h=null,u=[i]),(f=n.__b)&&f(_);try{e:if("function"==typeof E){if(b=_.props,C=(f=E.contextType)&&o[f.__c],x=f?C?C.props.value:f.__:o,t.__c?g=(a=_.__c=t.__c).__=a.__E:("prototype"in E&&E.prototype.render?_.__c=a=new E(b,x):(_.__c=a=new d(b,x),a.constructor=E,a.render=A),C&&C.sub(a),a.props=b,a.state||(a.state={}),a.context=x,a.__n=o,p=a.__d=!0,a.__h=[]),null==a.__s&&(a.__s=a.state),null!=E.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=s({},a.__s)),s(a.__s,E.getDerivedStateFromProps(b,a.__s))),v=a.props,y=a.state,p)null==E.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==E.getDerivedStateFromProps&&b!==v&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,x),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldCompLine truncated
@@ -0,0 +1,36 @@
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
code, pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 0.1em 0.3em; border-radius: 0.2em; }
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
a, a:visited, a:active { color: #55f; }
.addon { background: #eee; }
.btn {
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
}
.btn[disabled] { opacity: 0.5; cursor: auto;}
.smooth { transition: all .2s; }
.container { margin: 0 20px; width: auto; }
.d-flex { display: flex; }
.d-none { display: none; }
.border { border: 1px solid #ddd; }
.rounded { border-radius: 0.5em; }
.msg { background: #def; border-left: 5px solid #59d; padding: 1em; margin: 1em 0; }
.row { margin: 1% 0; overflow: auto; }
.col { float: left; }
.table, .c12 { width: 100%; }
.c11 { width: 91.66%; }
.c10 { width: 83.33%; }
.c9 { width: 75%; }
.c8 { width: 66.66%; }
.c7 { width: 58.33%; }
.c6 { width: 50%; }
.c5 { width: 41.66%; }
.c4 { width: 33.33%; }
.c3 { width: 25%; }
.c2 { width: 16.66%; }
.c1 { width: 8.33%; }
@media (min-width: 1310px) { .container { margin: auto; width: 1270px; } }
@media (max-width: 870px) { .row .col { width: 100%; } }
+1 -28
View File
@@ -1,28 +1 @@
# An example ESP32 application See detailed tutorial at https://mongoose.ws/tutorials/esp32/
To build this application, follow these steps:
1. Make sure you have Docker installed
2. Clone whole mongoose repository on your machine:
```sh
git clone --depth 1 https://github.com/cesanta/mongoose.git
```
3. Start command line or terminal, and go into the example's directory:
```sh
cd mongoose/examples/esp32
```
4. Run `make`. This invokes a Docker-based build. A firmware will be built
in the `build/` directory:
```sh
make
```
# Flashing built firmware
If you have a real board to flash to, you can flash a built firmware.
First, make sure to have `esptool.py` script installed on your workstation.
Then, connect the board to your workstation and run the following command:
```sh
make flash COMPORT=YOUR_USB_PORT
```
+1 -2
View File
@@ -1,5 +1,4 @@
idf_component_register(SRCS "main.c" idf_component_register(SRCS "main.c"
"wifi.c" "wifi.c"
"../../../mongoose.c" "mongoose.c")
INCLUDE_DIRS "../../..")
component_compile_options(-DMG_ENABLE_LINES) component_compile_options(-DMG_ENABLE_LINES)
+6 -12
View File
@@ -5,8 +5,8 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "mongoose.h" #include "mongoose.h"
#define WIFI_SSID "WIFI_NETWORK" // SET THIS! #define WIFI_SSID "VMDF554B9" // SET THIS!
#define WIFI_PASS "WIFI_PASSWORD" // SET THIS! #define WIFI_PASS "Mp7wjmamPafa" // SET THIS!
#define FS_ROOT "/spiffs" #define FS_ROOT "/spiffs"
// Event handler for an server (accepted) connection. Implemented endpoints: // Event handler for an server (accepted) connection. Implemented endpoints:
@@ -24,19 +24,13 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
} }
} }
// SPIFFS is flat, so tell Mongoose that the FS root is a directory
// This cludge is not required for filesystems with directory support
bool mg_is_dir(const char *path) {
return strcmp(path, FS_ROOT) == 0;
}
void app_main(void) { void app_main(void) {
// Mount filesystem // Mount filesystem
esp_vfs_spiffs_conf_t conf = { esp_vfs_spiffs_conf_t conf = {
.base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true}; .base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
int res = esp_vfs_spiffs_register(&conf); int res = esp_vfs_spiffs_register(&conf);
LOG(res == ESP_OK ? LL_INFO : LL_ERROR, ("FS %s, %d", conf.base_path, res)); MG_INFO(("FS %s, %d", conf.base_path, res));
mg_file_printf(FS_ROOT "/hello.txt", "%s", "hello from ESP"); mg_file_printf(&mg_fs_posix, FS_ROOT "/hello.txt", "%s", "hello from ESP");
// Setup wifi. This function is implemented in wifi.c // Setup wifi. This function is implemented in wifi.c
// It blocks until connected to the configured WiFi network // It blocks until connected to the configured WiFi network
@@ -45,9 +39,9 @@ void app_main(void) {
// Connected to WiFi, now start HTTP server // Connected to WiFi, now start HTTP server
struct mg_mgr mgr; struct mg_mgr mgr;
mg_log_set("3"); mg_log_set("4");
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0:80", cb, &mgr); // Listening server mg_http_listen(&mgr, "http://0.0.0.0:80", cb, &mgr); // Listening server
LOG(LL_INFO, ("Starting Mongoose web server v%s", MG_VERSION)); MG_INFO(("Starting Mongoose web server v%s", MG_VERSION));
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
} }
+1
View File
@@ -0,0 +1 @@
../../../mongoose.c
+1
View File
@@ -0,0 +1 @@
../../../mongoose.h
+7 -7
View File
@@ -35,14 +35,14 @@ static void event_handler(void *arg, esp_event_base_t event_base,
if (s_retry_num < 3) { if (s_retry_num < 3) {
esp_wifi_connect(); esp_wifi_connect();
s_retry_num++; s_retry_num++;
LOG(LL_INFO, ("retry to connect to the AP")); MG_INFO(("retry to connect to the AP"));
} else { } else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
} }
LOG(LL_ERROR, ("connect to the AP fail")); MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
LOG(LL_INFO, ("got ip:" IPSTR, IP2STR(&event->ip_info.ip))); MG_INFO(("got ip:" IPSTR, IP2STR(&event->ip_info.ip)));
s_retry_num = 0; s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} }
@@ -81,18 +81,18 @@ void wifi_init(const char *ssid, const char *pass) {
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &c)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &c));
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
LOG(LL_DEBUG, ("wifi_init_sta finished.")); MG_DEBUG(("wifi_init_sta finished."));
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY); pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) { if (bits & WIFI_CONNECTED_BIT) {
LOG(LL_INFO, ("connected to ap SSID:%s password:%s", ssid, pass)); MG_INFO(("connected to ap SSID:%s password:%s", ssid, pass));
} else if (bits & WIFI_FAIL_BIT) { } else if (bits & WIFI_FAIL_BIT) {
LOG(LL_ERROR, ("Failed to connect to SSID:%s, password:%s", ssid, pass)); MG_ERROR(("Failed to connect to SSID:%s, password:%s", ssid, pass));
} else { } else {
LOG(LL_ERROR, ("UNEXPECTED EVENT")); MG_ERROR(("UNEXPECTED EVENT"));
} }
/* The event will not be processed after unregister */ /* The event will not be processed after unregister */
+1
View File
@@ -1,3 +1,4 @@
CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
#CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+2 -2
View File
@@ -26,7 +26,7 @@ static void cb2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
mg_printf(c, "GET / HTTP/1.0\r\nHost: %.*s\r\n\r\n", (int) s.len, s.ptr); mg_printf(c, "GET / HTTP/1.0\r\nHost: %.*s\r\n\r\n", (int) s.len, s.ptr);
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data; // Print HTTP response struct mg_http_message *hm = ev_data; // Print HTTP response
LOG(LL_INFO, ("Fetched:\n%.*s", (int) hm->message.len, hm->message.ptr)); MG_INFO(("Fetched:\n%.*s", (int) hm->message.len, hm->message.ptr));
c->is_closing = 1; c->is_closing = 1;
} }
} }
@@ -38,7 +38,7 @@ static void run_mongoose(void) {
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_http_listen(&mgr, SERVER_URL, cb, &mgr); // Listening server mg_http_listen(&mgr, SERVER_URL, cb, &mgr); // Listening server
mg_http_connect(&mgr, CLIENT_URL, cb2, &mgr); // Example client mg_http_connect(&mgr, CLIENT_URL, cb2, &mgr); // Example client
LOG(LL_INFO, ("Starting Mongoose web server v%s", MG_VERSION)); MG_INFO(("Starting Mongoose web server v%s", MG_VERSION));
for (;;) mg_mgr_poll(&mgr, 1000); for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
} }
+6 -6
View File
@@ -33,14 +33,14 @@ static void event_handler(void *arg, esp_event_base_t event_base,
if (s_retry_num < 3) { if (s_retry_num < 3) {
esp_wifi_connect(); esp_wifi_connect();
s_retry_num++; s_retry_num++;
LOG(LL_DEBUG, ("retry to connect to the AP")); MG_DEBUG(("retry to connect to the AP"));
} else { } else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
} }
LOG(LL_ERROR, ("connect to the AP fail")); MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
LOG(LL_INFO, ("got ip:%s", ip4addr_ntoa(&event->ip_info.ip))); MG_INFO(("got ip:%s", ip4addr_ntoa(&event->ip_info.ip)));
s_retry_num = 0; s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
} }
@@ -71,11 +71,11 @@ void wifi_init(const char *ssid, const char *pass) {
pdFALSE, pdFALSE, portMAX_DELAY); pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) { if (bits & WIFI_CONNECTED_BIT) {
LOG(LL_INFO, ("connected to ap SSID:%s password:%s", ssid, pass)); MG_INFO(("connected to ap SSID:%s password:%s", ssid, pass));
} else if (bits & WIFI_FAIL_BIT) { } else if (bits & WIFI_FAIL_BIT) {
LOG(LL_ERROR, ("Failed to connect to SSID:%s, password:%s", ssid, pass)); MG_ERROR(("Failed to connect to SSID:%s, password:%s", ssid, pass));
} else { } else {
LOG(LL_ERROR, ("UNEXPECTED EVENT")); MG_ERROR(("UNEXPECTED EVENT"));
} }
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/
+21 -7
View File
@@ -1,5 +1,10 @@
// Copyright (c) 2020 Cesanta Software Limited // Copyright (c) 2020 Cesanta Software Limited
// All rights reserved // All rights reserved
//
// Streaming upload example. Demonstrates how to use chunked encoding
// to send large payload in smaller chunks. To test, use curl utility:
//
// curl http://localhost:8000/upload?name=a.txt --data-binary @large_file.txt
#include "mongoose.h" #include "mongoose.h"
@@ -7,20 +12,29 @@
// /upload - Saves the next file chunk // /upload - Saves the next file chunk
// all other URI - serves web_root/ directory // all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/upload")) { if (ev == MG_EV_HTTP_CHUNK && mg_http_match_uri(hm, "/upload")) {
mg_http_upload(c, hm, "/tmp"); MG_INFO(("Got chunk len %lu", (unsigned long) hm->chunk.len));
} else { MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
struct mg_http_serve_opts opts = {.root_dir = "web_root"}; // MG_INFO(("Chunk data:\n%.*s", (int) hm->chunk.len, hm->chunk.ptr));
mg_http_serve_dir(c, ev_data, &opts); mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) {
MG_INFO(("Last chunk received, sending response"));
mg_http_reply(c, 200, "", "ok (chunked)\n");
} }
} else if (ev == MG_EV_HTTP_MSG && mg_http_match_uri(hm, "/upload")) {
MG_INFO(("Got all %lu bytes!", (unsigned long) hm->body.len));
MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
// MG_INFO(("Body:\n%.*s", (int) hm->body.len, hm->body.ptr));
mg_http_reply(c, 200, "", "ok (%lu)\n", (unsigned long) hm->body.len);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, hm, &opts);
} }
} }
int main(void) { int main(void) {
struct mg_mgr mgr; struct mg_mgr mgr;
struct mg_timer t1;
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_log_set("3"); mg_log_set("3");
-41
View File
@@ -1,41 +0,0 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
// Helper function to display upload status
var setStatus = function(text) {
document.getElementById('el3').innerText = text;
};
// When user clicks on a button, trigger file selection dialog
var button = document.getElementById('el2');
button.onclick = function(ev) {
input.click();
};
// Send a large blob of data chunk by chunk
var sendFileData = function(name, data, chunkSize) {
var sendChunk = function(offset) {
var chunk = data.subarray(offset, offset + chunkSize) || '';
var opts = {method: 'POST', body: chunk};
var url = '/upload?offset=' + offset + '&name=' + encodeURIComponent(name);
setStatus(
'sending bytes ' + offset + '..' + (offset + chunk.length) + ' of ' +
data.length);
fetch(url, opts).then(function(res) {
if (chunk.length > 0) sendChunk(offset + chunk.length);
});
};
sendChunk(0);
};
// If user selected a file, read it into memory and trigger sendFileData()
var input = document.getElementById('el1');
input.onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
sendFileData(f.name, new Uint8Array(r.result), 4096);
};
};
+38 -10
View File
@@ -16,21 +16,49 @@
<div id="info"> <div id="info">
Mongoose always buffers a full HTTP message before invoking Mongoose always buffers a full HTTP message before invoking
MG_EV_HTTP_MSG event. Big POST request require of lot MG_EV_HTTP_MSG event. Big POST request require of lot
of RAM to buffer everything. Therefore, in order to upload large of RAM to buffer everything.
files on memory-constrained system, a large file should be send <br><br>
in small chunks. In order to upload large files to a memory-constrained system, use
<br/><br/> <code>MG_EV_HTTP_CHUNK</code> on a server side. It fires when
In this example, JavaScript code on this page sends uploaded a partial HTTP message has been received (or a chunk-encoded chunk).
file in 2k chunks using an <code>/upload</code> endpoint. Use <code>mg_http_delete_chunk()</code> to release chunk memory.
Uploaded file is stored in <code>/tmp</code> directory by the helper When 0-sized chunk is received, that's the end of the message.
API function <code>mg_http_upload()</code> Use <code>MG_MAX_RECV_BUF_SIZE</code> build constant to limit
maximum chunk size on a server side.
<br><br>
In this example, JavaScript code uses "fetch()" browser API.
Uploaded file is not saved, but rather printed by server side.
</div> </div>
<div id="wrapper"> <div id="wrapper">
<input type="file" id="el1" style="display: none"/> <input type="file" id="el1" style="display: none"/>
<button id="el2">choose file...</button> <button id="el2">choose file...</button>
<div id="el3"></div> <div id="el3" style="margin-top: 1em;"></div>
</div> </div>
</div> </div>
</body> </body>
<script src="app.js"></script> <script>
// When user clicks on a button, trigger file selection dialog
document.getElementById('el2').onclick = function(ev) {
document.getElementById('el1').click();
};
// If user selected a file, read it into memory and trigger sendFileData()
document.getElementById('el1').onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
document.getElementById('el3').innerText = 'Uploading...';
fetch('/upload?name=' + encodeURIComponent(f.name), {
method: 'POST',
body: r.result,
}).then(function(res) {
document.getElementById('el3').innerText = 'Uploaded ' + r.result.byteLength + ' bytes';
});
};
};
</script>
</html> </html>
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/
+2 -4
View File
@@ -19,14 +19,13 @@
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) { if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
LOG(LL_INFO, ("New request to: [%.*s], body size: %lu", (int) hm->uri.len, MG_INFO(("New request to: [%.*s], body size: %lu", (int) hm->uri.len,
hm->uri.ptr, (unsigned long) hm->body.len)); hm->uri.ptr, (unsigned long) hm->body.len));
if (mg_http_match_uri(hm, "/upload")) { if (mg_http_match_uri(hm, "/upload")) {
struct mg_http_part part; struct mg_http_part part;
size_t ofs = 0; size_t ofs = 0;
while ((ofs = mg_http_next_multipart(hm->body, ofs, &part)) > 0) { while ((ofs = mg_http_next_multipart(hm->body, ofs, &part)) > 0) {
LOG(LL_INFO, MG_INFO(("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
(int) part.name.len, part.name.ptr, (int) part.filename.len, (int) part.name.len, part.name.ptr, (int) part.filename.len,
part.filename.ptr, (unsigned long) part.body.len)); part.filename.ptr, (unsigned long) part.body.len));
} }
@@ -40,7 +39,6 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
int main(void) { int main(void) {
struct mg_mgr mgr; struct mg_mgr mgr;
struct mg_timer t1;
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_log_set("3"); mg_log_set("3");
+16 -2
View File
@@ -1,6 +1,6 @@
PROG ?= example PROG ?= example
CFLAGS ?= -DMG_ENABLE_LINES $(EXTRA)
ROOT ?= $(realpath $(CURDIR)/../..) ROOT ?= $(realpath $(CURDIR)/../..)
CFLAGS ?= -DMG_ENABLE_LINES $(EXTRA)
LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2 LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2
ifneq ($(MBEDTLS),) ifneq ($(MBEDTLS),)
@@ -24,5 +24,19 @@ linux: CFLAGS += -O2 -g -fsanitize=address,undefined,shift,null,return,bounds,al
linux: CC = $(LIN) cc linux: CC = $(LIN) cc
linux: RUN = $(LIN) linux: RUN = $(LIN)
ROOT ?= $(realpath $(CURDIR)/../..)
DOCKER = docker run -it --rm -e Tmp=. -e WINEDEBUG=-all -v $(ROOT):$(ROOT) -w $(CURDIR)
WDEFS = -DMG_ENABLE_MBEDTLS -DMG_ENABLE_LINES -Imbedtls/include -Imbedtls/library $(EXTRA)
WFLAGS = /nologo -O2 -I. -I../.. ws2_32.lib advapi32.lib /link /out:$(PROG).exe
WSRCS = ../../mongoose.c main.c mbedtls/library/*.c
$(PROG).exe: main.c mbedtls
$(DOCKER) mdashnet/vc2017 wine cl $(WSRCS) $(WDEFS) $(WFLAGS)
vc2017: ARGS = https://cesanta.com
vc2017: $(PROG).exe
$(DOCKER) mdashnet/vc2017 wine $(PROG).exe $(ARGS)
mbedtls:
git clone --depth 1 -b v3.1.0 https://github.com/ARMmbed/mbedtls
clean: clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb rm -rf $(PROG) _CL* *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb mbedtls
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/http-client/
+21 -6
View File
@@ -13,10 +13,20 @@
// The very first web page in history. You can replace it from command line // The very first web page in history. You can replace it from command line
static const char *s_url = "http://info.cern.ch/"; static const char *s_url = "http://info.cern.ch/";
static const char *s_post_data = NULL; // POST data
static const uint64_t s_timeout_ms = 1500; // Connect timeout in milliseconds
// Print HTTP response and signal that we're done // Print HTTP response and signal that we're done
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_CONNECT) { if (ev == MG_EV_OPEN) {
// Connection created. Store connect expiration time in c->label
*(uint64_t *) c->label = mg_millis() + s_timeout_ms;
} else if (ev == MG_EV_POLL) {
if (mg_millis() > *(uint64_t *) c->label &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Connect timeout");
}
} else if (ev == MG_EV_CONNECT) {
// Connected to server. Extract host name from URL // Connected to server. Extract host name from URL
struct mg_str host = mg_url_host(s_url); struct mg_str host = mg_url_host(s_url);
@@ -27,11 +37,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
} }
// Send request // Send request
int content_length = s_post_data ? strlen(s_post_data) : 0;
mg_printf(c, mg_printf(c,
"GET %s HTTP/1.0\r\n" "%s %s HTTP/1.0\r\n"
"Host: %.*s\r\n" "Host: %.*s\r\n"
"Content-Type: octet-stream\r\n"
"Content-Length: %d\r\n"
"\r\n", "\r\n",
mg_url_uri(s_url), (int) host.len, host.ptr); s_post_data ? "POST" : "GET", mg_url_uri(s_url), (int) host.len,
host.ptr, content_length);
mg_send(c, s_post_data, content_length);
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
// Response is received. Print it // Response is received. Print it
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
@@ -46,11 +61,11 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct mg_mgr mgr; // Event manager struct mg_mgr mgr; // Event manager
bool done = false; // Event handler flips it to true bool done = false; // Event handler flips it to true
if (argc > 1) s_url = argv[1]; // Use URL from command line if (argc > 1) s_url = argv[1]; // Use URL provided in the command line
mg_log_set("3"); // Set to 0 to disable debug mg_log_set("4"); // Set to 0 to disable debug
mg_mgr_init(&mgr); // Initialise event manager mg_mgr_init(&mgr); // Initialise event manager
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
while (!done) mg_mgr_poll(&mgr, 1000); // Infinite event loop while (!done) mg_mgr_poll(&mgr, 50); // Infinite event loop
mg_mgr_free(&mgr); // Free resources mg_mgr_free(&mgr); // Free resources
return 0; return 0;
} }
+1 -1
View File
@@ -37,7 +37,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_str host = mg_url_host(url); struct mg_str host = mg_url_host(url);
// CONNECT response - tunnel is established // CONNECT response - tunnel is established
connected = true; connected = true;
LOG(LL_DEBUG, MG_DEBUG(
("Connected to proxy, status: %.*s", (int) hm.uri.len, hm.uri.ptr)); ("Connected to proxy, status: %.*s", (int) hm.uri.len, hm.uri.ptr));
mg_iobuf_del(&c->recv, 0, n); mg_iobuf_del(&c->recv, 0, n);
// Send request to the target server // Send request to the target server
+2 -1
View File
@@ -1,5 +1,6 @@
PROG ?= example PROG ?= example
CFLAGS ?= -W -Wall -DMG_ENABLE_LINES=1
ifneq ($(MBEDTLS_DIR),) ifneq ($(MBEDTLS_DIR),)
CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS_DIR)/include -I/usr/include CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS_DIR)/include -I/usr/include
CFLAGS += -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedcrypto -lmbedx509 CFLAGS += -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedcrypto -lmbedx509
@@ -12,7 +13,7 @@ all: $(PROG)
$(DEBUGGER) ./$(PROG) $(ARGS) $(DEBUGGER) ./$(PROG) $(ARGS)
$(PROG): main.c $(PROG): main.c
$(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG) main.c $(CC) ../../mongoose.c -I../.. $(CFLAGS) $(EFLAGS) -o $(PROG) main.c
clean: clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
+16 -3
View File
@@ -4,7 +4,7 @@
// HTTP server example. This server serves both static and dynamic content. // HTTP server example. This server serves both static and dynamic content.
// It opens two ports: plain HTTP on port 8000 and HTTP on port 8443. // It opens two ports: plain HTTP on port 8000 and HTTP on port 8443.
// It implements the following endpoints: // It implements the following endpoints:
// /api/f1 - respond with JSON string {"result": 123} // /api/stats - respond with free-formatted stats on current connections
// /api/f2/:id - wildcard example, respond with JSON string {"result": "URI"} // /api/f2/:id - wildcard example, respond with JSON string {"result": "URI"}
// any other URI serves static files from s_root_dir // any other URI serves static files from s_root_dir
// //
@@ -30,8 +30,21 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
mg_tls_init(c, &opts); mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/api/f1")) { if (mg_http_match_uri(hm, "/api/stats")) {
mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); // Serve REST // Print some statistics about currently established connections
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_http_printf_chunk(c, "ID PROTO TYPE LOCAL REMOTE\n");
for (struct mg_connection *t = c->mgr->conns; t != NULL; t = t->next) {
char loc[40], rem[40];
mg_http_printf_chunk(c, "%-3lu %4s %s %-15s %s\n", t->id,
t->is_udp ? "UDP" : "TCP",
t->is_listening ? "LISTENING"
: t->is_accepted ? "ACCEPTED "
: "CONNECTED",
mg_straddr(&t->loc, loc, sizeof(loc)),
mg_straddr(&t->rem, rem, sizeof(rem)));
}
mg_http_printf_chunk(c, ""); // Don't forget the last empty chunk
} else if (mg_http_match_uri(hm, "/api/f2/*")) { } else if (mg_http_match_uri(hm, "/api/f2/*")) {
mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len,
hm->uri.ptr); hm->uri.ptr);
+1 -1
View File
@@ -1,5 +1,5 @@
PROG ?= example PROG ?= example
CFLAGS ?= -DMG_IO_SIZE=8192 CFLAGS ?= -DMG_IO_SIZE=128
MBEDTLS_DIR ?= MBEDTLS_DIR ?=
ifeq "$(MBEDTLS_DIR)" "" ifeq "$(MBEDTLS_DIR)" ""
+14 -20
View File
@@ -28,23 +28,18 @@ static void forward_request(struct mg_http_message *hm,
} }
mg_send(c, "\r\n", 2); mg_send(c, "\r\n", 2);
mg_send(c, hm->body.ptr, hm->body.len); mg_send(c, hm->body.ptr, hm->body.len);
LOG(LL_DEBUG, ("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr, MG_DEBUG(("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr)); (int) hm->uri.len, hm->uri.ptr));
} }
static void forward_fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_connection *c2 = fn_data; struct mg_connection *c2 = fn_data;
if (ev == MG_EV_READ) { if (ev == MG_EV_READ) {
if (c2 != NULL) {
// All incoming data from the backend, forward to the client // All incoming data from the backend, forward to the client
mg_send(c2, c->recv.buf, c->recv.len); if (c2 != NULL) mg_send(c2, c->recv.buf, c->recv.len);
mg_iobuf_del(&c->recv, 0, c->recv.len); mg_iobuf_del(&c->recv, 0, c->recv.len);
} } else if (ev == MG_EV_CLOSE) {
} else if (ev == MG_EV_CONNECT) { if (c2 != NULL) c2->fn_data = NULL;
if (mg_url_is_ssl(s_backend_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c, &opts);
}
} }
(void) ev_data; (void) ev_data;
} }
@@ -55,22 +50,21 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// Client request, create backend connection Note that we're passing // Client request, create backend connection Note that we're passing
// client connection `c` as fn_data for the created backend connection. // client connection `c` as fn_data for the created backend connection.
c2 = mg_connect(c->mgr, s_backend_url, forward_fn, c); c2 = mg_connect(c->mgr, s_backend_url, fn2, c);
c->fn_data = c2; if (c2 == NULL) {
mg_error(c, "Cannot create backend connection");
if (c2 != NULL) {
forward_request(hm, c2);
} else { } else {
c->is_closing = 1;
}
} else if (ev == MG_EV_CONNECT) {
if (mg_url_is_ssl(s_backend_url)) { if (mg_url_is_ssl(s_backend_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"}; struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c, &opts); mg_tls_init(c2, &opts);
}
c->fn_data = c2;
forward_request(hm, c2);
c2->is_hexdumping = 1;
} }
} else if (ev == MG_EV_CLOSE) { } else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->is_closing = 1; if (c2 != NULL) c2->is_closing = 1;
c->fn_data = NULL; if (c2 != NULL) c2->fn_data = NULL;
} }
} }
+5 -2
View File
@@ -1,7 +1,7 @@
PROG ?= example PROG ?= example
ROOT ?= $(realpath $(CURDIR)/../..) ROOT ?= $(realpath $(CURDIR)/../..)
DEFS ?= -DMG_ENABLE_LINES=1 DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_IPV6=1 -DMG_ENABLE_SSI=1
CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA) CFLAGS ?= -I../.. -W -Wall $(DEFS) $(EXTRA)
VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) /link /incremental:no /machine:IX86 VCFLAGS = /nologo /W3 /O2 /I../.. $(DEFS) $(EXTRA) /link /incremental:no /machine:IX86
VC98 = docker run -it --rm -e Tmp=. -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/vc98 VC98 = docker run -it --rm -e Tmp=. -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/vc98
LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2 LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2
@@ -15,6 +15,9 @@ $(PROG): main.c Makefile
mongoose.exe: main.c mongoose.exe: main.c
$(VC98) wine cl ../../mongoose.c main.c $(VCFLAGS) ws2_32.lib /out:$@ $(VC98) wine cl ../../mongoose.c main.c $(VCFLAGS) ws2_32.lib /out:$@
mingw:
gcc ../../mongoose.c main.c -I../.. -W -Wall -DMG_ENABLE_IPV6=1 -DMG_ENABLE_LINES=1 -D_POSIX_C_SOURCE=200000L -lws2_32 -o mongoose.exe
linux: all linux: all
linux: CFLAGS += -O2 -fsanitize=address,undefined,shift,null,return,bounds,alignment,object-size,bool,enum -static-libasan linux: CFLAGS += -O2 -fsanitize=address,undefined,shift,null,return,bounds,alignment,object-size,bool,enum -static-libasan
#linux: CC = $(LIN) cc #linux: CC = $(LIN) cc
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/http-server/
+30 -11
View File
@@ -6,9 +6,9 @@
static const char *s_debug_level = "2"; static const char *s_debug_level = "2";
static const char *s_root_dir = "."; static const char *s_root_dir = ".";
static const char *s_listening_address = "http://localhost:8000"; static const char *s_listening_address = "http://0.0.0.0:8000";
static const char *s_enable_hexdump = "no"; static const char *s_enable_hexdump = "no";
static const char *s_ssi_pattern = "#.shtml"; static const char *s_ssi_pattern = "#.html";
// Handle interrupts, like Ctrl-C // Handle interrupts, like Ctrl-C
static int s_signo; static int s_signo;
@@ -20,9 +20,18 @@ static void signal_handler(int signo) {
// Simply serve static files from `s_root_dir` // Simply serve static files from `s_root_dir`
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) { if (ev == MG_EV_HTTP_MSG) {
struct mg_http_serve_opts opts = {.root_dir = s_root_dir, struct mg_http_message *hm = ev_data, tmp = {0};
.ssi_pattern = s_ssi_pattern}; struct mg_str unknown = mg_str_n("?", 1), *cl;
mg_http_serve_dir(c, ev_data, &opts); struct mg_http_serve_opts opts = {0};
opts.root_dir = s_root_dir;
opts.ssi_pattern = s_ssi_pattern;
mg_http_serve_dir(c, hm, &opts);
mg_http_parse((char *) c->send.buf, c->send.len, &tmp);
cl = mg_http_get_header(&tmp, "Content-Length");
if (cl == NULL) cl = &unknown;
MG_INFO(("%.*s %.*s %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr, (int) tmp.uri.len, tmp.uri.ptr,
(int) cl->len, cl->ptr));
} }
(void) fn_data; (void) fn_data;
} }
@@ -32,16 +41,17 @@ static void usage(const char *prog) {
"Mongoose v.%s\n" "Mongoose v.%s\n"
"Usage: %s OPTIONS\n" "Usage: %s OPTIONS\n"
" -H yes|no - enable traffic hexdump, default: '%s'\n" " -H yes|no - enable traffic hexdump, default: '%s'\n"
" -S GLOB - glob pattern for SSI files, default: '%s'\n" " -S PAT - SSI filename pattern, default: '%s'\n"
" -d DIR - directory to serve, default: '%s'\n" " -d DIR - directory to serve, default: '%s'\n"
" -l ADDR - listening address, default: '%s'\n" " -l ADDR - listening address, default: '%s'\n"
" -v LEVEL - debug level, from 0 to 4, default: '%s'\n", " -v LEVEL - debug level, from 0 to 4, default: '%s'\n",
MG_VERSION, prog, s_debug_level, s_enable_hexdump, s_ssi_pattern, MG_VERSION, prog, s_enable_hexdump, s_ssi_pattern, s_root_dir,
s_root_dir, s_listening_address); s_listening_address, s_debug_level);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
char path[MG_PATH_MAX] = ".";
struct mg_mgr mgr; struct mg_mgr mgr;
struct mg_connection *c; struct mg_connection *c;
int i; int i;
@@ -63,22 +73,31 @@ int main(int argc, char *argv[]) {
} }
} }
// Root directory must not contain double dots. Make it absolute
// Do the conversion only if the root dir spec does not contain overrides
if (strchr(s_root_dir, ',') == NULL) {
realpath(s_root_dir, path);
s_root_dir = path;
}
// Initialise stuff // Initialise stuff
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
mg_log_set(s_debug_level); mg_log_set(s_debug_level);
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
if ((c = mg_http_listen(&mgr, s_listening_address, cb, &mgr)) == NULL) { if ((c = mg_http_listen(&mgr, s_listening_address, cb, &mgr)) == NULL) {
LOG(LL_ERROR, ("Cannot listen on %s. Use http://ADDR:PORT or :PORT", MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT",
s_listening_address)); s_listening_address));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1; if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1;
// Start infinite event loop // Start infinite event loop
LOG(LL_INFO, ("Starting Mongoose v%s, serving [%s]", MG_VERSION, s_root_dir)); MG_INFO(("Mongoose version : v%s", MG_VERSION));
MG_INFO(("Listening on : %s", s_listening_address));
MG_INFO(("Web root : [%s]", s_root_dir));
while (s_signo == 0) mg_mgr_poll(&mgr, 1000); while (s_signo == 0) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
LOG(LL_INFO, ("Exiting on signal %d", s_signo)); MG_INFO(("Exiting on signal %d", s_signo));
return 0; return 0;
} }
+9 -4
View File
@@ -32,19 +32,22 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
// Send request // Send request
mg_printf(c, mg_printf(c,
"GET %s HTTP/1.0\r\n" "GET %s HTTP/1.1\r\n"
"Connection: keep-alive\r\n"
"Keep-Alive: timeout=60\r\n"
"Host: %.*s\r\n" "Host: %.*s\r\n"
"\r\n", "\r\n",
mg_url_uri(s_url), (int) host.len, host.ptr); mg_url_uri(s_url), (int) host.len, host.ptr);
} else if (ev == MG_EV_HTTP_CHUNK) { } else if (ev == MG_EV_HTTP_CHUNK) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
LOG(LL_INFO, ("%.*s", (int) hm->chunk.len, hm->chunk.ptr)); fwrite(hm->chunk.ptr, 1, hm->chunk.len, stdout);
// fprintf(stderr, "c %u\n", (unsigned) hm->chunk.len);
mg_http_delete_chunk(c, hm); mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) *(bool *) fn_data = true; // Last chunk if (hm->chunk.len == 0) *(bool *) fn_data = true; // Last chunk
} else if (ev == MG_EV_HTTP_MSG) { } else if (ev == MG_EV_HTTP_MSG) {
// Response is received. Print it // Response is received. Print it
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
LOG(LL_INFO, ("%.*s", (int) hm->body.len, hm->body.ptr)); fwrite(hm->body.ptr, 1, hm->body.len, stdout);
c->is_closing = 1; // Tell mongoose to close this connection c->is_closing = 1; // Tell mongoose to close this connection
*(bool *) fn_data = true; // Tell event loop to stop *(bool *) fn_data = true; // Tell event loop to stop
} else if (ev == MG_EV_ERROR) { } else if (ev == MG_EV_ERROR) {
@@ -55,8 +58,10 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct mg_mgr mgr; // Event manager struct mg_mgr mgr; // Event manager
bool done = false; // Event handler flips it to true bool done = false; // Event handler flips it to true
const char *log_level = getenv("V"); // Log level
if (log_level == NULL) log_level = "3"; // If not set, set to DEBUG
mg_log_set(log_level); // Set to 0 to disable debug log
if (argc > 1) s_url = argv[1]; // Use URL from command line if (argc > 1) s_url = argv[1]; // Use URL from command line
mg_log_set("3"); // Set to 0 to disable debug
mg_mgr_init(&mgr); // Initialise event manager mg_mgr_init(&mgr); // Initialise event manager
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
while (!done) mg_mgr_poll(&mgr, 1000); // Infinite event loop while (!done) mg_mgr_poll(&mgr, 1000); // Infinite event loop
+11
View File
@@ -0,0 +1,11 @@
PROG ?= example
CFLAGS ?= -W -Wall -Wextra -O0 -g
all: $(PROG)
$(RUN) ./$(PROG) $(ARGS)
$(PROG): main.c
$(CC) ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG) main.c mjson.c
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
+87
View File
@@ -0,0 +1,87 @@
// Copyright (c) 2021 Cesanta Software Limited
// All rights reserved
//
// Example that demonstrates how to send a large responses with limited memory.
// We're going to send a JSON array of many integer values, s_data.
// The idea is to send a response in small chunks, and let the client request
// the next chunk.
// Periodically, s_data changes, which is tracked by s_version.
// Client requests a range and a version, to ensure data integrity.
//
// 1. Start this server, type `make`
// 2. Open http://localhost:8000 in your browser
#include "mjson.h"
#include "mongoose.h"
static const char *s_listen_on = "http://localhost:8000";
static const char *s_root_dir = "web_root";
#define DATA_SIZE 10000 // Total number of elements
#define CHUNK_SIZE 1000 // Max number returned in one API call
static int s_data[DATA_SIZE]; // Simulate some complex big data
static unsigned s_version = 0; // Data "version"
static double getparam(struct mg_http_message *hm, const char *json_path) {
double dv = 0;
mjson_get_number(hm->body.ptr, hm->body.len, json_path, &dv);
return dv;
}
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data;
if (mg_http_match_uri(hm, "/api/data")) {
unsigned start = getparam(hm, "$.start");
unsigned version = getparam(hm, "$.version");
char *response = NULL;
if (version > 0 && version != s_version) {
// Version mismatch: s_data has changed while client fetches it
// Tell client to restart
response = mjson_aprintf("{%Q:%Q, %Q:%u}", "error", "wrong version",
"version", version);
} else {
// Return data, up to CHUNK_SIZE elements
unsigned max = start + CHUNK_SIZE;
const char *comma = "";
if (max > DATA_SIZE) max = DATA_SIZE;
mjson_printf(mjson_print_dynamic_buf, &response, "{%Q:%u,%Q:%u,%Q:[",
"version", s_version, "start", start, "data");
while (start < max) {
mjson_printf(mjson_print_dynamic_buf, &response, "%s%d", comma,
s_data[start]);
comma = ",";
start++;
}
mjson_printf(mjson_print_dynamic_buf, &response, "]}");
}
mg_http_reply(c, 200, "Content-Type: text/json\r\n", "%s", response);
free(response);
} else {
struct mg_http_serve_opts opts = {0};
opts.root_dir = s_root_dir;
mg_http_serve_dir(c, hm, &opts);
}
}
(void) fn_data;
}
static void timer_fn(void *arg) {
for (int i = 0; i < DATA_SIZE; i++) {
s_data[i] = rand();
}
s_version++;
(void) arg;
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
srand(time(NULL));
mg_timer_add(&mgr, 1000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, NULL);
mg_http_listen(&mgr, s_listen_on, fn, NULL);
MG_INFO(("Listening on %s", s_listen_on));
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
return 0;
}
@@ -19,15 +19,23 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#include <float.h>
#include <math.h>
#include "mjson.h" #include "mjson.h"
#if !defined(_MSC_VER) || _MSC_VER >= 1700 #if defined(_MSC_VER)
#else #define alloca(x) _alloca(x)
#define va_copy(x, y) (x) = (y)
#define snprintf _snprintf
#define alloca _alloca
#endif #endif
#if defined(_MSC_VER) && _MSC_VER < 1700
#define va_copy(x, y) (x) = (y)
#define isinf(x) !_finite(x)
#define isnan(x) _isnan(x)
#endif
static double mystrtod(const char *str, char **end);
static int mjson_esc(int c, int esc) { static int mjson_esc(int c, int esc) {
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
for (p = esc ? esc1 : esc2; *p != '\0'; p++) { for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
@@ -36,10 +44,14 @@ static int mjson_esc(int c, int esc) {
return 0; return 0;
} }
static int mjson_escape(int c) {
return mjson_esc(c, 1);
}
static int mjson_pass_string(const char *s, int len) { static int mjson_pass_string(const char *s, int len) {
int i; int i;
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
if (s[i] == '\\' && i + 1 < len && mjson_esc(s[i + 1], 1)) { if (s[i] == '\\' && i + 1 < len && mjson_escape(s[i + 1])) {
i++; i++;
} else if (s[i] == '\0') { } else if (s[i] == '\0') {
return MJSON_ERROR_INVALID_INPUT; return MJSON_ERROR_INVALID_INPUT;
@@ -99,8 +111,8 @@ int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
tok = MJSON_TOK_FALSE; tok = MJSON_TOK_FALSE;
} else if (c == '-' || ((c >= '0' && c <= '9'))) { } else if (c == '-' || ((c >= '0' && c <= '9'))) {
char *end = NULL; char *end = NULL;
strtod(&s[i], &end); mystrtod(&s[i], &end);
if (end != NULL) i += end - &s[i] - 1; if (end != NULL) i += (int) (end - &s[i] - 1);
tok = MJSON_TOK_NUMBER; tok = MJSON_TOK_NUMBER;
} else if (c == '"') { } else if (c == '"') {
int n = mjson_pass_string(&s[i + 1], len - i - 1); int n = mjson_pass_string(&s[i + 1], len - i - 1);
@@ -158,7 +170,7 @@ int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
struct msjon_get_data { struct msjon_get_data {
const char *path; // Lookup json path const char *path; // Lookup json path
int pos; // Current path index int pos; // Current path position
int d1; // Current depth of traversal int d1; // Current depth of traversal
int d2; // Expected depth of traversal int d2; // Expected depth of traversal
int i1; // Index in an array int i1; // Index in an array
@@ -169,86 +181,111 @@ struct msjon_get_data {
int tok; // Returned token int tok; // Returned token
}; };
static int mjson_plen(const char *s) { #include <stdio.h>
int i = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++; static int plen1(const char *s) {
int i = 0, n = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
n++, i += s[i] == '\\' ? 2 : 1;
// printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
return n;
}
static int plen2(const char *s) {
int i = 0, n = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
n++, i += s[i] == '\\' ? 2 : 1;
// printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
return i; return i;
} }
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) { static int kcmp(const char *a, const char *b, int n) {
struct msjon_get_data *data = (struct msjon_get_data *) ud; int i = 0, j = 0, r = 0;
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1, for (i = 0, j = 0; j < n; i++, j++) {
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len, if (b[i] == '\\') i++;
// s + off); if ((r = a[j] - b[i]) != 0) return r;
if (data->tok != MJSON_TOK_INVALID) return 1; // Found }
// printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r);
return r;
}
if (tok == '{') { static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off; struct msjon_get_data *d = (struct msjon_get_data *) ud;
data->d1++; #if 0
} else if (tok == '[') { printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1,
if (data->d1 == data->d2 && data->path[data->pos] == '[') { d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off,
data->i1 = 0; d->path + d->pos);
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL); #endif
if (data->i1 == data->i2) { if (d->tok != MJSON_TOK_INVALID) return 1; // Found
data->d2++; if (tok == '{' || tok == '[') {
data->pos += 3; if (d->d1 < d->d2) d->obj = -1;
if (d->d1 == d->d2) d->obj = off;
if (d->d1 == d->d2 && tok == '[' && d->path[d->pos] == '[') {
d->i1 = 0;
d->i2 = (int) mystrtod(&d->path[d->pos + 1], NULL);
if (d->i1 == d->i2) {
while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++;
if (d->path[d->pos] == ']') d->pos++;
d->d2++;
} }
} }
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off; d->d1++;
data->d1++;
} else if (tok == ',') {
if (data->d1 == data->d2 + 1) {
data->i1++;
if (data->i1 == data->i2) {
while (data->path[data->pos] != ']') data->pos++;
data->pos++;
data->d2++;
}
}
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2 + 1 &&
data->path[data->pos] == '.' && s[off] == '"' &&
s[off + len - 1] == '"' &&
mjson_plen(&data->path[data->pos + 1]) == len - 2 &&
!memcmp(s + off + 1, &data->path[data->pos + 1], len - 2)) {
data->d2++;
data->pos += len - 1;
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2) {
return 1; // Exhausted path, not found
} else if (tok == '}' || tok == ']') { } else if (tok == '}' || tok == ']') {
data->d1--; if (tok == ']' && d->d1 == d->d2) d->i1 = 0;
// data->d2--; d->d1--;
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) { // printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1,
data->tok = tok - 2; // d->i2, d->obj);
if (data->tokptr) *data->tokptr = s + data->obj; if (!d->path[d->pos] && d->d1 == d->d2 && d->obj != -1) {
if (data->toklen) *data->toklen = off - data->obj + 1; d->tok = tok - 2;
if (d->tokptr) *d->tokptr = s + d->obj;
if (d->toklen) *d->toklen = off - d->obj + 1;
return 1; return 1;
} }
} else if (tok == ',' && d->d1 == d->d2 && d->pos &&
d->path[d->pos - 1] == ']') {
return 1; // Not found in the current elem array
} else if (tok == ',' && d->d1 == d->d2 + 1 && d->path[d->pos] == '[') {
// printf("GG '%s' '%s'\n", d->path, &d->path[d->pos]);
d->i1++;
if (d->i1 == d->i2) {
while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++;
if (d->path[d->pos] == ']') d->pos++;
d->d2++;
}
} else if (tok == MJSON_TOK_KEY && d->d1 == d->d2 + 1 &&
d->path[d->pos] == '.' && s[off] == '"' &&
s[off + len - 1] == '"' &&
plen1(&d->path[d->pos + 1]) == len - 2 &&
kcmp(s + off + 1, &d->path[d->pos + 1], len - 2) == 0) {
d->d2++;
d->pos += plen2(&d->path[d->pos + 1]) + 1;
} else if (tok == MJSON_TOK_KEY && d->d1 == d->d2) {
return 1; // Exhausted path, not found
} else if (MJSON_TOK_IS_VALUE(tok)) { } else if (MJSON_TOK_IS_VALUE(tok)) {
// printf("TOK --> %d\n", tok); // printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2);
if (data->d1 == data->d2 && !data->path[data->pos]) { if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) {
data->tok = tok; d->tok = tok;
if (data->tokptr) *data->tokptr = s + off; if (d->tokptr) *d->tokptr = s + off;
if (data->toklen) *data->toklen = len; if (d->toklen) *d->toklen = len;
return 1; return 1;
} }
} }
return 0; return 0;
} }
enum mjson_tok mjson_find(const char *s, int len, const char *jp, int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) {
const char **tokptr, int *toklen) {
struct msjon_get_data data = {jp, 1, 0, 0, 0, struct msjon_get_data data = {jp, 1, 0, 0, 0,
0, -1, tokptr, toklen, MJSON_TOK_INVALID}; 0, -1, tp, tl, MJSON_TOK_INVALID};
if (jp[0] != '$') return MJSON_TOK_INVALID; if (jp[0] != '$') return MJSON_TOK_INVALID;
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID; if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
return (enum mjson_tok) data.tok; return data.tok;
} }
int mjson_get_number(const char *s, int len, const char *path, double *v) { int mjson_get_number(const char *s, int len, const char *path, double *v) {
const char *p; const char *p;
int tok, n; int tok, n;
if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) { if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) {
if (v != NULL) *v = strtod(p, NULL); if (v != NULL) *v = mystrtod(p, NULL);
} }
return tok == MJSON_TOK_NUMBER ? 1 : 0; return tok == MJSON_TOK_NUMBER ? 1 : 0;
} }
@@ -260,13 +297,31 @@ int mjson_get_bool(const char *s, int len, const char *path, int *v) {
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0; return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
} }
static unsigned char unhex(unsigned char c) {
return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
: (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
: (unsigned char) (c - 'W');
}
static unsigned char mjson_unhex_nimble(const char *s) {
return (unsigned char) (unhex(((unsigned char *) s)[0]) << 4) |
unhex(((unsigned char *) s)[1]);
}
static int mjson_unescape(const char *s, int len, char *to, int n) { static int mjson_unescape(const char *s, int len, char *to, int n) {
int i, j; int i, j;
for (i = 0, j = 0; i < len && j < n; i++, j++) { for (i = 0, j = 0; i < len && j < n; i++, j++) {
if (s[i] == '\\' && i + 1 < len) { if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
// \uXXXX escape. We could process a simple one-byte chars
// \u00xx from the ASCII range. More complex chars would require
// dragging in a UTF8 library, which is too much for us
if (s[i + 2] != '0' || s[i + 3] != '0') return -1; // Too much, give up
((unsigned char *) to)[j] = mjson_unhex_nimble(s + i + 4);
i += 5;
} else if (s[i] == '\\' && i + 1 < len) {
int c = mjson_esc(s[i + 1], 0); int c = mjson_esc(s[i + 1], 0);
if (c == 0) return -1; if (c == 0) return -1;
to[j] = c; to[j] = (char) (unsigned char) c;
i++; i++;
} else { } else {
to[j] = s[i]; to[j] = s[i];
@@ -290,23 +345,20 @@ int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
int i, j, sz; int i, j, sz;
if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1; if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1;
for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) { for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) {
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') ((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1);
unsigned char a = *(const unsigned char *) (p + i + 1);
unsigned char b = *(const unsigned char *) (p + i + 2);
((unsigned char *) to)[j] = (HEXTOI(a) << 4) | HEXTOI(b);
} }
if (j < n) to[j] = '\0'; if (j < n) to[j] = '\0';
return j; return j;
} }
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
static int mjson_base64rev(int c) { static unsigned char mjson_base64rev(int c) {
if (c >= 'A' && c <= 'Z') { if (c >= 'A' && c <= 'Z') {
return c - 'A'; return (unsigned char) (c - 'A');
} else if (c >= 'a' && c <= 'z') { } else if (c >= 'a' && c <= 'z') {
return c + 26 - 'a'; return (unsigned char) (c + 26 - 'a');
} else if (c >= '0' && c <= '9') { } else if (c >= '0' && c <= '9') {
return c + 52 - '0'; return (unsigned char) (c + 52 - '0');
} else if (c == '+') { } else if (c == '+') {
return 62; return 62;
} else if (c == '/') { } else if (c == '/') {
@@ -320,13 +372,13 @@ int mjson_base64_dec(const char *src, int n, char *dst, int dlen) {
const char *end = src + n; const char *end = src + n;
int len = 0; int len = 0;
while (src + 3 < end && len < dlen) { while (src + 3 < end && len < dlen) {
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]), unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]); c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
dst[len++] = (a << 2) | (b >> 4); dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4));
if (src[2] != '=' && len < dlen) { if (src[2] != '=' && len < dlen) {
dst[len++] = (b << 4) | (c >> 2); dst[len++] = (char) (unsigned char) ((b << 4) | (c >> 2));
if (src[3] != '=' && len < dlen) { if (src[3] != '=' && len < dlen) {
dst[len++] = (c << 6) | d; dst[len++] = (char) (unsigned char) ((c << 6) | d);
} }
} }
src += 4; src += 4;
@@ -428,92 +480,177 @@ int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
return len; return len;
} }
// This function allocates memory in chunks of size MJSON_DYNBUF_CHUNK
// to decrease memory fragmentation, when many calls are executed to
// print e.g. a base64 string or a hex string.
int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) { int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) {
char *s, *buf = *(char **) fndata; char *s, *buf = *(char **) fndata;
int curlen = buf == NULL ? 0 : strlen(buf); size_t curlen = buf == NULL ? 0 : strlen(buf);
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) { size_t new_size = curlen + (size_t) len + 1 + MJSON_DYNBUF_CHUNK;
new_size -= new_size % MJSON_DYNBUF_CHUNK;
if ((s = (char *) realloc(buf, new_size)) == NULL) {
return 0; return 0;
} else { } else {
memcpy(s + curlen, ptr, len); memcpy(s + curlen, ptr, (size_t) len);
s[curlen + len] = '\0'; s[curlen + (size_t) len] = '\0';
*(char **) fndata = s; *(char **) fndata = s;
return len; return len;
} }
} }
int mjson_snprintf(char *buf, size_t len, const char *fmt, ...) {
va_list ap;
struct mjson_fixedbuf fb = {buf, (int) len, 0};
va_start(ap, fmt);
mjson_vprintf(mjson_print_fixed_buf, &fb, fmt, &ap);
va_end(ap);
return fb.len;
}
char *mjson_aprintf(const char *fmt, ...) {
va_list ap;
char *result = NULL;
va_start(ap, fmt);
mjson_vprintf(mjson_print_dynamic_buf, &result, fmt, &ap);
va_end(ap);
return result;
}
int mjson_print_null(const char *ptr, int len, void *userdata) { int mjson_print_null(const char *ptr, int len, void *userdata) {
(void) ptr; (void) ptr;
(void) userdata; (void) userdata;
return len; return len;
} }
int mjson_print_file(const char *ptr, int len, void *userdata) { int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) {
return fwrite(ptr, 1, len, (FILE *) userdata); return fn(buf, len, fnd);
} }
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf, int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) {
int len) { unsigned long v = (unsigned long) val, s = 0, n, i;
return fn(buf, len, fndata); char buf[20], t;
if (is_signed && val < 0) buf[s++] = '-', v = (unsigned long) (-val);
// This loop prints a number in reverse order. I guess this is because we
// write numbers from right to left: least significant digit comes last.
// Maybe because we use Arabic numbers, and Arabs write RTL?
for (n = 0; v > 0; v /= 10) buf[s + n++] = "0123456789"[v % 10];
// Reverse a string
for (i = 0; i < n / 2; i++)
t = buf[s + i], buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
if (val == 0) buf[n++] = '0'; // Handle special case
return fn(buf, (int) (s + n), fnd);
} }
int mjson_print_int(mjson_print_fn_t fn, void *fndata, int value, int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) {
int is_signed) { return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s);
char buf[20];
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
return fn(buf, len, fndata);
} }
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value, static int addexp(char *buf, int e, int sign) {
int is_signed) { int n = 0;
char buf[20]; buf[n++] = 'e';
const char *fmt = (is_signed ? "%ld" : "%lu"); buf[n++] = (char) sign;
int len = snprintf(buf, sizeof(buf), fmt, value); if (e > 400) return 0;
return fn(buf, len, fndata); if (e < 10) buf[n++] = '0';
if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
buf[n++] = (char) (e + '0');
return n;
} }
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d, int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) {
const char *fmt) {
char buf[40]; char buf[40];
int n = snprintf(buf, sizeof(buf), fmt, d); int i, s = 0, n = 0, e = 0;
return fn(buf, n, fndata); double t, mul, saved;
if (d == 0.0) return fn("0", 1, fnd);
if (isinf(d)) return fn(d > 0 ? "inf" : "-inf", d > 0 ? 3 : 4, fnd);
if (isnan(d)) return fn("nan", 3, fnd);
if (d < 0.0) d = -d, buf[s++] = '-';
// Round
saved = d;
mul = 1.0;
while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
d += t;
// Calculate exponent, and 'mul' for scientific representation
mul = 1.0;
while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
// printf(" --> %g %d %g %g\n", saved, e, t, mul);
if (e >= width) {
struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0};
n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
// printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, fb.len, fb.ptr);
n += addexp(buf + s + n, e, '+');
return fn(buf, s + n, fnd);
} else if (e <= -width) {
struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0};
n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
// printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, fb.len, fb.ptr);
n += addexp(buf + s + n, -e, '-');
return fn(buf, s + n, fnd);
} else {
for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
int ch = (int) (d / t);
if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
d -= ch * t;
t /= 10.0;
}
// printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
if (n == 0) buf[s++] = '0';
while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
// printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
int ch = (int) (d / t);
buf[s + n++] = (char) (ch + '0');
d -= ch * t;
t /= 10.0;
}
}
while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
buf[s + n] = '\0';
return fn(buf, s + n, fnd);
} }
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) { int mjson_print_str(mjson_print_fn_t fn, void *fnd, const char *s, int len) {
int i, n = fn("\"", 1, fndata); int i, n = fn("\"", 1, fnd);
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
char c = mjson_esc(s[i], 1); char c = (char) (unsigned char) mjson_escape(s[i]);
if (c) { if (c) {
n += fn("\\", 1, fndata); n += fn("\\", 1, fnd);
n += fn(&c, 1, fndata); n += fn(&c, 1, fnd);
} else { } else {
n += fn(&s[i], 1, fndata); n += fn(&s[i], 1, fnd);
} }
} }
return n + fn("\"", 1, fndata); return n + fn("\"", 1, fnd);
} }
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s, int mjson_print_b64(mjson_print_fn_t fn, void *fnd, const unsigned char *s,
int n) { int n) {
const char *t = const char *t =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i, len = fn("\"", 1, fndata); int i, len = fn("\"", 1, fnd);
for (i = 0; i < n; i += 3) { for (i = 0; i < n; i += 3) {
int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0; int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0;
char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='}; char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='};
if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)]; if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)];
if (i + 2 < n) buf[3] = t[c & 63]; if (i + 2 < n) buf[3] = t[c & 63];
len += fn(buf, sizeof(buf), fndata); len += fn(buf, sizeof(buf), fnd);
} }
return len + fn("\"", 1, fndata); return len + fn("\"", 1, fnd);
} }
#endif /* MJSON_ENABLE_BASE64 */ #endif /* MJSON_ENABLE_BASE64 */
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt, int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt,
va_list xap) { va_list *ap) {
int i = 0, n = 0; int i = 0, n = 0;
va_list ap;
va_copy(ap, xap);
while (fmt[i] != '\0') { while (fmt[i] != '\0') {
if (fmt[i] == '%') { if (fmt[i] == '%') {
char fc = fmt[++i]; char fc = fmt[++i];
@@ -523,85 +660,85 @@ int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
fc = fmt[i + 1]; fc = fmt[i + 1];
} }
if (fc == 'Q') { if (fc == 'Q') {
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0); n += mjson_print_str(fn, fnd, buf ? buf : "",
buf ? (int) strlen(buf) : 0);
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) { } else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_str(fn, fndata, buf, len); n += mjson_print_str(fn, fnd, buf, len);
i += 2; i += 2;
} else if (fc == 'd' || fc == 'u') { } else if (fc == 'd' || fc == 'u') {
int is_signed = (fc == 'd'); int is_signed = (fc == 'd');
if (is_long) { if (is_long) {
long val = va_arg(ap, long); long val = va_arg(*ap, long);
n += mjson_print_long(fn, fndata, val, is_signed); n += mjson_print_long(fn, fnd, val, is_signed);
i++; i++;
} else { } else {
int val = va_arg(ap, int); int val = va_arg(*ap, int);
n += mjson_print_int(fn, fndata, val, is_signed); n += mjson_print_int(fn, fnd, val, is_signed);
} }
} else if (fc == 'B') { } else if (fc == 'B') {
const char *s = va_arg(ap, int) ? "true" : "false"; const char *s = va_arg(*ap, int) ? "true" : "false";
n += mjson_print_buf(fn, fndata, s, strlen(s)); n += mjson_print_buf(fn, fnd, s, (int) strlen(s));
} else if (fc == 's') { } else if (fc == 's') {
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_buf(fn, fndata, buf, strlen(buf)); n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf));
} else if (strncmp(&fmt[i], ".*s", 3) == 0) { } else if (strncmp(&fmt[i], ".*s", 3) == 0) {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_buf(fn, fndata, buf, len); n += mjson_print_buf(fn, fnd, buf, len);
i += 2; i += 2;
} else if (fc == 'g') { } else if (fc == 'g') {
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g"); n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6);
} else if (fc == 'f') { } else if (strncmp(&fmt[i], ".*g", 3) == 0) {
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f"); int width = va_arg(*ap, int);
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width);
i += 2;
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
} else if (fc == 'V') { } else if (fc == 'V') {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
const char *buf = va_arg(ap, const char *); const char *buf = va_arg(*ap, const char *);
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len); n += mjson_print_b64(fn, fnd, (unsigned char *) buf, len);
#endif #endif
} else if (fc == 'H') { } else if (fc == 'H') {
const char *hex = "0123456789abcdef"; const char *hex = "0123456789abcdef";
int i, len = va_arg(ap, int); int j, len = va_arg(*ap, int);
const unsigned char *p = va_arg(ap, const unsigned char *); const unsigned char *p = va_arg(*ap, const unsigned char *);
n += fn("\"", 1, fndata); n += fn("\"", 1, fnd);
for (i = 0; i < len; i++) { for (j = 0; j < len; j++) {
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata); n += fn(&hex[(p[j] >> 4) & 15], 1, fnd);
n += fn(&hex[p[i] & 15], 1, fndata); n += fn(&hex[p[j] & 15], 1, fnd);
} }
n += fn("\"", 1, fndata); n += fn("\"", 1, fnd);
} else if (fc == 'M') { } else if (fc == 'M') {
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t); mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t);
n += vfn(fn, fndata, &ap); n += vfn(fn, fnd, ap);
} }
i++; i++;
} else { } else {
n += mjson_print_buf(fn, fndata, &fmt[i++], 1); n += mjson_print_buf(fn, fnd, &fmt[i++], 1);
} }
} }
va_end(xap);
va_end(ap);
return n; return n;
} }
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) { int mjson_printf(mjson_print_fn_t fn, void *fnd, const char *fmt, ...) {
va_list ap; va_list ap;
int len; int len;
va_start(ap, fmt); va_start(ap, fmt);
len = mjson_vprintf(fn, fndata, fmt, ap); len = mjson_vprintf(fn, fnd, fmt, &ap);
va_end(ap); va_end(ap);
return len; return len;
} }
#endif /* MJSON_ENABLE_PRINT */ #endif /* MJSON_ENABLE_PRINT */
#if MJSON_IMPLEMENT_STRTOD static int is_digit(int c) {
static inline int is_digit(int c) {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */ /* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
double strtod(const char *str, char **end) { static double mystrtod(const char *str, char **end) {
double d = 0.0; double d = 0.0;
int sign = 1, n = 0; int sign = 1, n = 0;
const char *p = str, *a = str; const char *p = str, *a = str;
@@ -610,8 +747,9 @@ double strtod(const char *str, char **end) {
if (*p == '-') { if (*p == '-') {
sign = -1; sign = -1;
++p; ++p;
} else if (*p == '+') } else if (*p == '+') {
++p; ++p;
}
if (is_digit(*p)) { if (is_digit(*p)) {
d = (double) (*p++ - '0'); d = (double) (*p++ - '0');
while (*p && is_digit(*p)) { while (*p && is_digit(*p)) {
@@ -620,8 +758,9 @@ double strtod(const char *str, char **end) {
++n; ++n;
} }
a = p; a = p;
} else if (*p != '.') } else if (*p != '.') {
goto done; goto done;
}
d *= sign; d *= sign;
/* fraction part */ /* fraction part */
@@ -644,30 +783,13 @@ double strtod(const char *str, char **end) {
/* exponential part */ /* exponential part */
if ((*p == 'E') || (*p == 'e')) { if ((*p == 'E') || (*p == 'e')) {
int e = 0; int i, e = 0, neg = 0;
++p; p++;
if (*p == '-') p++, neg++;
sign = 1; if (*p == '+') p++;
if (*p == '-') { while (is_digit(*p)) e = e * 10 + *p++ - '0';
sign = -1; if (neg) e = -e;
++p; #if 0
} else if (*p == '+')
++p;
if (is_digit(*p)) {
while (*p == '0') ++p;
e = (int) (*p++ - '0');
while (*p && is_digit(*p)) {
e = e * 10 + (int) (*p - '0');
++p;
}
e *= sign;
} else if (!is_digit(*(a - 1))) {
a = str;
goto done;
} else if (*p == 0)
goto done;
if (d == 2.2250738585072011 && e == -308) { if (d == 2.2250738585072011 && e == -308) {
d = 0.0; d = 0.0;
a = p; a = p;
@@ -678,10 +800,9 @@ double strtod(const char *str, char **end) {
a = p; a = p;
goto done; goto done;
} }
{ #endif
int i; for (i = 0; i < e; i++) d *= 10;
for (i = 0; i < 10; i++) d *= 10; for (i = 0; i < -e; i++) d /= 10;
}
a = p; a = p;
} else if (p > str && !is_digit(*(p - 1))) { } else if (p > str && !is_digit(*(p - 1))) {
a = str; a = str;
@@ -692,7 +813,6 @@ done:
if (end) *end = (char *) a; if (end) *end = (char *) a;
return d; return d;
} }
#endif
#if MJSON_ENABLE_MERGE #if MJSON_ENABLE_MERGE
int mjson_merge(const char *s, int n, const char *s2, int n2, int mjson_merge(const char *s, int n, const char *s2, int n2,
@@ -701,14 +821,10 @@ int mjson_merge(const char *s, int n, const char *s2, int n2,
if (n < 2) return len; if (n < 2) return len;
len += fn("{", 1, userdata); len += fn("{", 1, userdata);
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) { while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
#if !defined(_MSC_VER) || _MSC_VER >= 1700 char *path = (char *) alloca((size_t) klen + 1);
char path[klen + 1];
#else
char *path = (char *) alloca(klen + 1);
#endif
const char *val; const char *val;
memcpy(path, "$.", 2); memcpy(path, "$.", 2);
memcpy(path + 2, s + koff + 1, klen - 2); memcpy(path + 2, s + koff + 1, (size_t) (klen - 2));
path[klen] = '\0'; path[klen] = '\0';
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) { if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
if (t2 == MJSON_TOK_NULL) continue; // null deletes the key if (t2 == MJSON_TOK_NULL) continue; // null deletes the key
@@ -729,15 +845,11 @@ int mjson_merge(const char *s, int n, const char *s2, int n2,
// Add missing keys // Add missing keys
off = 0; off = 0;
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) { while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
#if !defined(_MSC_VER) || _MSC_VER >= 1700 char *path = (char *) alloca((size_t) klen + 1);
char path[klen + 1];
#else
char *path = (char *) alloca(klen + 1);
#endif
const char *val; const char *val;
if (t == MJSON_TOK_NULL) continue; if (t == MJSON_TOK_NULL) continue;
memcpy(path, "$.", 2); memcpy(path, "$.", 2);
memcpy(path + 2, s2 + koff + 1, klen - 2); memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2));
path[klen] = '\0'; path[klen] = '\0';
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue; if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
if (comma) len += fn(",", 1, userdata); if (comma) len += fn(",", 1, userdata);
@@ -816,7 +928,7 @@ static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn, int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
void *userdata) { void *userdata) {
struct prettydata d = {0, 0, 0, pad, strlen(pad), fn, userdata}; struct prettydata d = {0, 0, 0, pad, (int) strlen(pad), fn, userdata};
if (mjson(s, n, pretty_cb, &d) < 0) return -1; if (mjson(s, n, pretty_cb, &d) < 0) return -1;
return d.len; return d.len;
} }
@@ -824,10 +936,6 @@ int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
#if MJSON_ENABLE_RPC #if MJSON_ENABLE_RPC
struct jsonrpc_ctx jsonrpc_default_context; struct jsonrpc_ctx jsonrpc_default_context;
struct jsonrpc_userdata {
mjson_print_fn_t fn;
void *fndata;
};
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) { int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
int i = 0, j = 0, ni = 0, nj = 0; int i = 0, j = 0, ni = 0, nj = 0;
@@ -845,14 +953,9 @@ int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
return 1; return 1;
} }
static int jsonrpc_printer(const char *buf, int len, void *userdata) {
struct jsonrpc_userdata *u = (struct jsonrpc_userdata *) userdata;
return u->fn(buf, len, u->fndata);
}
void jsonrpc_return_errorv(struct jsonrpc_request *r, int code, void jsonrpc_return_errorv(struct jsonrpc_request *r, int code,
const char *message, const char *data_fmt, const char *message, const char *data_fmt,
va_list ap) { va_list *ap) {
if (r->id_len == 0) return; if (r->id_len == 0) return;
mjson_printf(r->fn, r->fndata, mjson_printf(r->fn, r->fndata,
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len, "{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
@@ -868,12 +971,12 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
const char *message, const char *data_fmt, ...) { const char *message, const char *data_fmt, ...) {
va_list ap; va_list ap;
va_start(ap, data_fmt); va_start(ap, data_fmt);
jsonrpc_return_errorv(r, code, message, data_fmt, ap); jsonrpc_return_errorv(r, code, message, data_fmt, &ap);
va_end(ap); va_end(ap);
} }
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt, void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
va_list ap) { va_list *ap) {
if (r->id_len == 0) return; if (r->id_len == 0) return;
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id); mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
if (result_fmt != NULL) { if (result_fmt != NULL) {
@@ -888,49 +991,41 @@ void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
...) { ...) {
va_list ap; va_list ap;
va_start(ap, result_fmt); va_start(ap, result_fmt);
jsonrpc_return_successv(r, result_fmt, ap); jsonrpc_return_successv(r, result_fmt, &ap);
va_end(ap); va_end(ap);
} }
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len,
mjson_print_fn_t fn, void *fndata) { mjson_print_fn_t fn, void *fndata, void *ud) {
const char *result = NULL, *error = NULL; const char *result = NULL, *error = NULL;
int result_sz = 0, error_sz = 0; int result_sz = 0, error_sz = 0;
struct jsonrpc_method *m = NULL; struct jsonrpc_method *m = NULL;
struct jsonrpc_userdata d; struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fndata, ud};
struct jsonrpc_request r = {req, req_sz, 0, 0, 0, 0, 0,
0, &jsonrpc_printer, NULL, NULL};
d.fn = fn;
d.fndata = fndata;
r.fndata = &d;
// Is is a response frame? // Is is a response frame?
mjson_find(req, req_sz, "$.result", &result, &result_sz); mjson_find(buf, len, "$.result", &result, &result_sz);
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz); if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz);
if (result_sz > 0 || error_sz > 0) { if (result_sz > 0 || error_sz > 0) {
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata); if (ctx->response_cb) ctx->response_cb(buf, len, ctx->response_cb_data);
return; return;
} }
// Method must exist and must be a string // Method must exist and must be a string
if (mjson_find(req, req_sz, "$.method", &r.method, &r.method_len) != if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) !=
MJSON_TOK_STRING) { MJSON_TOK_STRING) {
mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n", mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
req_sz, req); len, buf);
ctx->in_len = 0;
return; return;
} }
// id and params are optional // id and params are optional
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len); mjson_find(buf, len, "$.id", &r.id, &r.id_len);
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len); mjson_find(buf, len, "$.params", &r.params, &r.params_len);
for (m = ctx->methods; m != NULL; m = m->next) { for (m = ctx->methods; m != NULL; m = m->next) {
if (mjson_globmatch(m->method, m->method_sz, r.method + 1, if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
r.method_len - 2) > 0) { r.method_len - 2) > 0) {
if (r.params == NULL) r.params = ""; if (r.params == NULL) r.params = "";
r.userdata = m->cbdata;
m->cb(&r); m->cb(&r);
break; break;
} }
@@ -947,35 +1042,25 @@ static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
int len = 0; int len = 0;
for (m = ctx->methods; m != NULL; m = m->next) { for (m = ctx->methods; m != NULL; m = m->next) {
if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1); if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1);
len += mjson_print_str(fn, fndata, m->method, strlen(m->method)); len += mjson_print_str(fn, fndata, m->method, (int) strlen(m->method));
} }
return len; return len;
} }
static void rpclist(struct jsonrpc_request *r) { void jsonrpc_list(struct jsonrpc_request *r) {
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata); jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->ctx);
} }
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb, void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
void *userdata) { void *response_cb_data) {
ctx->methods = NULL;
ctx->response_cb = response_cb; ctx->response_cb = response_cb;
ctx->userdata = userdata; ctx->response_cb_data = response_cb_data;
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, rpclist, ctx);
}
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
mjson_print_fn_t fn, void *p) {
if (ctx->in_len >= (int) sizeof(ctx->in)) ctx->in_len = 0; // Overflow
if (ch == '\n') { // If new line, parse frame
if (ctx->in_len > 1) jsonrpc_ctx_process(ctx, ctx->in, ctx->in_len, fn, p);
ctx->in_len = 0;
} else {
ctx->in[ctx->in_len] = ch; // Append to the buffer
ctx->in_len++;
}
} }
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) { void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata); struct jsonrpc_ctx *ctx = &jsonrpc_default_context;
jsonrpc_ctx_init(ctx, response_cb, userdata);
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, jsonrpc_list);
} }
#endif // MJSON_ENABLE_RPC #endif // MJSON_ENABLE_RPC
@@ -23,7 +23,6 @@
#define MJSON_H #define MJSON_H
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -40,50 +39,40 @@
#endif #endif
#ifndef MJSON_ENABLE_MERGE #ifndef MJSON_ENABLE_MERGE
#define MJSON_ENABLE_MERGE 0 #define MJSON_ENABLE_MERGE 1
#elif MJSON_ENABLE_MERGE
#define MJSON_ENABLE_NEXT 1
#endif #endif
#ifndef MJSON_ENABLE_PRETTY #ifndef MJSON_ENABLE_PRETTY
#define MJSON_ENABLE_PRETTY 0 #define MJSON_ENABLE_PRETTY 1
#elif MJSON_ENABLE_PRETTY
#define MJSON_ENABLE_NEXT 1
#endif #endif
#ifndef MJSON_ENABLE_NEXT #ifndef MJSON_ENABLE_NEXT
#define MJSON_ENABLE_NEXT 0 #define MJSON_ENABLE_NEXT 1
#endif
#ifndef MJSON_RPC_IN_BUF_SIZE
#define MJSON_RPC_IN_BUF_SIZE 256
#endif #endif
#ifndef MJSON_RPC_LIST_NAME #ifndef MJSON_RPC_LIST_NAME
#define MJSON_RPC_LIST_NAME "rpc.list" #define MJSON_RPC_LIST_NAME "rpc.list"
#endif #endif
#ifndef MJSON_DYNBUF_CHUNK
#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
enum { #define MJSON_ERROR_INVALID_INPUT (-1)
MJSON_ERROR_INVALID_INPUT = -1, #define MJSON_ERROR_TOO_DEEP (-2)
MJSON_ERROR_TOO_DEEP = -2, #define MJSON_TOK_INVALID 0
}; #define MJSON_TOK_KEY 1
#define MJSON_TOK_STRING 11
enum mjson_tok { #define MJSON_TOK_NUMBER 12
MJSON_TOK_INVALID = 0, #define MJSON_TOK_TRUE 13
MJSON_TOK_KEY = 1, #define MJSON_TOK_FALSE 14
MJSON_TOK_STRING = 11, #define MJSON_TOK_NULL 15
MJSON_TOK_NUMBER = 12, #define MJSON_TOK_ARRAY 91
MJSON_TOK_TRUE = 13, #define MJSON_TOK_OBJECT 123
MJSON_TOK_FALSE = 14,
MJSON_TOK_NULL = 15,
MJSON_TOK_ARRAY = 91,
MJSON_TOK_OBJECT = 123,
};
#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20) #define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud); typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
@@ -93,8 +82,7 @@ typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
#endif #endif
int mjson(const char *s, int len, mjson_cb_t cb, void *ud); int mjson(const char *s, int len, mjson_cb_t cb, void *ud);
enum mjson_tok mjson_find(const char *s, int len, const char *jp, int mjson_find(const char *s, int len, const char *jp, const char **, int *);
const char **tokptr, int *toklen);
int mjson_get_number(const char *s, int len, const char *path, double *v); int mjson_get_number(const char *s, int len, const char *path, double *v);
int mjson_get_bool(const char *s, int len, const char *path, int *v); int mjson_get_bool(const char *s, int len, const char *path, int *v);
int mjson_get_string(const char *s, int len, const char *path, char *to, int n); int mjson_get_string(const char *s, int len, const char *path, char *to, int n);
@@ -120,17 +108,20 @@ struct mjson_fixedbuf {
}; };
int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...); int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...);
int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list ap); int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list *ap);
int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len); int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len);
int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed); int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed);
int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed); int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed);
int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len); int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len);
int mjson_print_dbl(mjson_print_fn_t fn, void *, double, int width);
int mjson_print_null(const char *ptr, int len, void *userdata); int mjson_print_null(const char *ptr, int len, void *userdata);
int mjson_print_file(const char *ptr, int len, void *userdata);
int mjson_print_fixed_buf(const char *ptr, int len, void *userdata); int mjson_print_fixed_buf(const char *ptr, int len, void *userdata);
int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata); int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata);
int mjson_snprintf(char *buf, size_t len, const char *fmt, ...);
char *mjson_aprintf(const char *fmt, ...);
#if MJSON_ENABLE_PRETTY #if MJSON_ENABLE_PRETTY
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *); int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
#endif #endif
@@ -147,6 +138,7 @@ void jsonrpc_init(mjson_print_fn_t, void *userdata);
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2); int mjson_globmatch(const char *s1, int n1, const char *s2, int n2);
struct jsonrpc_request { struct jsonrpc_request {
struct jsonrpc_ctx *ctx;
const char *frame; // Points to the whole frame const char *frame; // Points to the whole frame
int frame_len; // Frame length int frame_len; // Frame length
const char *params; // Points to the "params" in the request frame const char *params; // Points to the "params" in the request frame
@@ -164,7 +156,6 @@ struct jsonrpc_method {
const char *method; const char *method;
int method_sz; int method_sz;
void (*cb)(struct jsonrpc_request *); void (*cb)(struct jsonrpc_request *);
void *cbdata;
struct jsonrpc_method *next; struct jsonrpc_method *next;
}; };
@@ -172,17 +163,14 @@ struct jsonrpc_method {
// exported RPC methods. // exported RPC methods.
struct jsonrpc_ctx { struct jsonrpc_ctx {
struct jsonrpc_method *methods; struct jsonrpc_method *methods;
void *userdata;
mjson_print_fn_t response_cb; mjson_print_fn_t response_cb;
int in_len; void *response_cb_data;
char in[MJSON_RPC_IN_BUF_SIZE];
}; };
// Registers function fn under the given name within the given RPC context // Registers function fn under the given name within the given RPC context
#define jsonrpc_ctx_export(ctx, name, fn, ud) \ #define jsonrpc_ctx_export(ctx, name, fn) \
do { \ do { \
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \ static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \
m.cbdata = (ud); \
m.next = (ctx)->methods; \ m.next = (ctx)->methods; \
(ctx)->methods = &m; \ (ctx)->methods = &m; \
} while (0) } while (0)
@@ -193,20 +181,16 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
...); ...);
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
mjson_print_fn_t fn, void *fndata); mjson_print_fn_t fn, void *fndata, void *userdata);
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
mjson_print_fn_t fn, void *fndata);
extern struct jsonrpc_ctx jsonrpc_default_context; extern struct jsonrpc_ctx jsonrpc_default_context;
extern void jsonrpc_list(struct jsonrpc_request *r);
#define jsonrpc_export(name, fn, ud) \ #define jsonrpc_export(name, fn) \
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud)) jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn))
#define jsonrpc_process(buf, len, fn, data) \ #define jsonrpc_process(buf, len, fn, fnd, ud) \
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data)) jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud))
#define jsonrpc_process_byte(x, fn, data) \
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */ #define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */ #define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; margin-bottom: 1em; }
#result { margin-top: 1em; }
</style>
</head>
<body>
<div id="container">
<div id="info">
On devices with limited RAM, it is important to limit the response size
of API calls. This example demonstrates how to fetch a large
data in smaller chunks, and guarantee its integrity.
Data gets returned in a series of request/response transactions,
where each response is small enough to fit into available device RAM.
<br/><br/>
Data integrity is implemented by versioning.
The idea is that the first response includes the current "version" of the
data, and that version is passed to all subsequent requests.
If data version changes in the middle of the request series,
client fails with 'wrong version' error.
</div>
<button id="btn">fetch data</button>
<div id="result"></div>
</div>
</body>
<script>
const getchunk = (start, version) =>
fetch('/api/data', {method: 'POST', body:JSON.stringify({start, version})})
.then(r => r.json());
document.getElementById('btn').onclick = function() {
var data = [], version = 0;
const load = offset => getchunk(offset, version)
.then(r => {
// console.log(r);
if (r.error) {
document.getElementById('result').innerText = 'Error: ' + r.error;
} else {
version = r.version;
data = data.concat(r.data);
if (r.data.length == 0) {
document.getElementById('result').innerText = 'Version: ' +
version + ', data: \n' +
JSON.stringify(data, null, 2);
} else {
load(offset + r.data.length, version);
}
}
});
load(0);
};
</script>
</html>
@@ -2,6 +2,7 @@ NAME ?= $(notdir $(CURDIR))
build: build:
git clone --depth 1 https://github.com/mongoose-examples/$(NAME) git clone --depth 1 https://github.com/mongoose-examples/$(NAME)
cp ./../../../mongoose.[ch] $(NAME)
make -C $(NAME) build make -C $(NAME) build
clean: clean:
+10
View File
@@ -0,0 +1,10 @@
PROG ?= example
all: $(PROG)
$(DEBUGGER) ./$(PROG) $(ARGS)
$(PROG): main.c
$(CC) main.c mjson.c ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 $(CFLAGS) -o $(PROG)
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/json-rpc-over-websocket/
+84
View File
@@ -0,0 +1,84 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// See https://mongoose.ws/tutorials/json-rpc-over-websocket/
#include "mjson.h"
#include "mongoose.h"
static const char *s_listen_on = "ws://localhost:8000";
static const char *s_web_root = "web_root";
static void sum(struct jsonrpc_request *r) {
double a = 0.0, b = 0.0;
mjson_get_number(r->params, r->params_len, "$[0]", &a);
mjson_get_number(r->params, r->params_len, "$[1]", &b);
jsonrpc_return_success(r, "%g", a + b);
}
static void multiply(struct jsonrpc_request *r) {
double a = 0.0, b = 0.0;
mjson_get_number(r->params, r->params_len, "$[0]", &a);
mjson_get_number(r->params, r->params_len, "$[1]", &b);
jsonrpc_return_success(r, "%g", a * b);
}
// This RESTful server implements the following endpoints:
// /websocket - upgrade to Websocket, and implement websocket echo server
// any other URI serves static files from s_web_root
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN) {
// c->is_hexdumping = 1;
} else if (ev == MG_EV_WS_OPEN) {
c->label[0] = 'W'; // Mark this connection as an established WS client
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/websocket")) {
// Upgrade to websocket. From now on, a connection is a full-duplex
// Websocket connection, which will receive MG_EV_WS_MSG events.
mg_ws_upgrade(c, hm, NULL);
} else {
// Serve static files
struct mg_http_serve_opts opts = {.root_dir = s_web_root};
mg_http_serve_dir(c, ev_data, &opts);
}
} else if (ev == MG_EV_WS_MSG) {
// Got websocket frame. Received data is wm->data
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
struct mg_str req = wm->data;
char *response = NULL;
jsonrpc_process(req.ptr, req.len, mjson_print_dynamic_buf, &response, NULL);
mg_ws_send(c, response, strlen(response), WEBSOCKET_OP_TEXT);
MG_INFO(("[%.*s] -> [%s]", (int) req.len, req.ptr, response));
free(response);
}
(void) fn_data;
}
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
// Broadcast "hi" message to all connected websocket clients.
// Traverse over all connections
for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
// Send JSON-RPC notifications to marked connections
const char *msg = "{\"method\":\"hiya!!\",\"params\":[1,2,3]}";
if (c->label[0] == 'W') mg_ws_send(c, msg, strlen(msg), WEBSOCKET_OP_TEXT);
}
}
int main(void) {
struct mg_mgr mgr; // Event manager
mg_mgr_init(&mgr); // Init event manager
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT, timer_fn, &mgr); // Init timer
jsonrpc_init(NULL, NULL); // Init JSON-RPC instance
jsonrpc_export("sum", sum); // And export a couple
jsonrpc_export("mul", multiply); // of RPC functions
printf("Starting WS listener on %s/websocket\n", s_listen_on);
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr); // Deallocate event manager
return 0;
}
@@ -19,15 +19,23 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#include <float.h>
#include <math.h>
#include "mjson.h" #include "mjson.h"
#if !defined(_MSC_VER) || _MSC_VER >= 1700 #if defined(_MSC_VER)
#else #define alloca(x) _alloca(x)
#define va_copy(x, y) (x) = (y)
#define snprintf _snprintf
#define alloca _alloca
#endif #endif
#if defined(_MSC_VER) && _MSC_VER < 1700
#define va_copy(x, y) (x) = (y)
#define isinf(x) !_finite(x)
#define isnan(x) _isnan(x)
#endif
static double mystrtod(const char *str, char **end);
static int mjson_esc(int c, int esc) { static int mjson_esc(int c, int esc) {
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
for (p = esc ? esc1 : esc2; *p != '\0'; p++) { for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
@@ -36,10 +44,14 @@ static int mjson_esc(int c, int esc) {
return 0; return 0;
} }
static int mjson_escape(int c) {
return mjson_esc(c, 1);
}
static int mjson_pass_string(const char *s, int len) { static int mjson_pass_string(const char *s, int len) {
int i; int i;
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
if (s[i] == '\\' && i + 1 < len && mjson_esc(s[i + 1], 1)) { if (s[i] == '\\' && i + 1 < len && mjson_escape(s[i + 1])) {
i++; i++;
} else if (s[i] == '\0') { } else if (s[i] == '\0') {
return MJSON_ERROR_INVALID_INPUT; return MJSON_ERROR_INVALID_INPUT;
@@ -99,8 +111,8 @@ int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
tok = MJSON_TOK_FALSE; tok = MJSON_TOK_FALSE;
} else if (c == '-' || ((c >= '0' && c <= '9'))) { } else if (c == '-' || ((c >= '0' && c <= '9'))) {
char *end = NULL; char *end = NULL;
strtod(&s[i], &end); mystrtod(&s[i], &end);
if (end != NULL) i += end - &s[i] - 1; if (end != NULL) i += (int) (end - &s[i] - 1);
tok = MJSON_TOK_NUMBER; tok = MJSON_TOK_NUMBER;
} else if (c == '"') { } else if (c == '"') {
int n = mjson_pass_string(&s[i + 1], len - i - 1); int n = mjson_pass_string(&s[i + 1], len - i - 1);
@@ -158,7 +170,7 @@ int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
struct msjon_get_data { struct msjon_get_data {
const char *path; // Lookup json path const char *path; // Lookup json path
int pos; // Current path index int pos; // Current path position
int d1; // Current depth of traversal int d1; // Current depth of traversal
int d2; // Expected depth of traversal int d2; // Expected depth of traversal
int i1; // Index in an array int i1; // Index in an array
@@ -169,86 +181,111 @@ struct msjon_get_data {
int tok; // Returned token int tok; // Returned token
}; };
static int mjson_plen(const char *s) { #include <stdio.h>
int i = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++; static int plen1(const char *s) {
int i = 0, n = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
n++, i += s[i] == '\\' ? 2 : 1;
// printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
return n;
}
static int plen2(const char *s) {
int i = 0, n = 0;
while (s[i] != '\0' && s[i] != '.' && s[i] != '[')
n++, i += s[i] == '\\' ? 2 : 1;
// printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n);
return i; return i;
} }
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) { static int kcmp(const char *a, const char *b, int n) {
struct msjon_get_data *data = (struct msjon_get_data *) ud; int i = 0, j = 0, r = 0;
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1, for (i = 0, j = 0; j < n; i++, j++) {
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len, if (b[i] == '\\') i++;
// s + off); if ((r = a[j] - b[i]) != 0) return r;
if (data->tok != MJSON_TOK_INVALID) return 1; // Found }
// printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r);
return r;
}
if (tok == '{') { static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off; struct msjon_get_data *d = (struct msjon_get_data *) ud;
data->d1++; #if 0
} else if (tok == '[') { printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1,
if (data->d1 == data->d2 && data->path[data->pos] == '[') { d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off,
data->i1 = 0; d->path + d->pos);
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL); #endif
if (data->i1 == data->i2) { if (d->tok != MJSON_TOK_INVALID) return 1; // Found
data->d2++; if (tok == '{' || tok == '[') {
data->pos += 3; if (d->d1 < d->d2) d->obj = -1;
if (d->d1 == d->d2) d->obj = off;
if (d->d1 == d->d2 && tok == '[' && d->path[d->pos] == '[') {
d->i1 = 0;
d->i2 = (int) mystrtod(&d->path[d->pos + 1], NULL);
if (d->i1 == d->i2) {
while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++;
if (d->path[d->pos] == ']') d->pos++;
d->d2++;
} }
} }
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off; d->d1++;
data->d1++;
} else if (tok == ',') {
if (data->d1 == data->d2 + 1) {
data->i1++;
if (data->i1 == data->i2) {
while (data->path[data->pos] != ']') data->pos++;
data->pos++;
data->d2++;
}
}
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2 + 1 &&
data->path[data->pos] == '.' && s[off] == '"' &&
s[off + len - 1] == '"' &&
mjson_plen(&data->path[data->pos + 1]) == len - 2 &&
!memcmp(s + off + 1, &data->path[data->pos + 1], len - 2)) {
data->d2++;
data->pos += len - 1;
} else if (tok == MJSON_TOK_KEY && data->d1 == data->d2) {
return 1; // Exhausted path, not found
} else if (tok == '}' || tok == ']') { } else if (tok == '}' || tok == ']') {
data->d1--; if (tok == ']' && d->d1 == d->d2) d->i1 = 0;
// data->d2--; d->d1--;
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) { // printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1,
data->tok = tok - 2; // d->i2, d->obj);
if (data->tokptr) *data->tokptr = s + data->obj; if (!d->path[d->pos] && d->d1 == d->d2 && d->obj != -1) {
if (data->toklen) *data->toklen = off - data->obj + 1; d->tok = tok - 2;
if (d->tokptr) *d->tokptr = s + d->obj;
if (d->toklen) *d->toklen = off - d->obj + 1;
return 1; return 1;
} }
} else if (tok == ',' && d->d1 == d->d2 && d->pos &&
d->path[d->pos - 1] == ']') {
return 1; // Not found in the current elem array
} else if (tok == ',' && d->d1 == d->d2 + 1 && d->path[d->pos] == '[') {
// printf("GG '%s' '%s'\n", d->path, &d->path[d->pos]);
d->i1++;
if (d->i1 == d->i2) {
while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++;
if (d->path[d->pos] == ']') d->pos++;
d->d2++;
}
} else if (tok == MJSON_TOK_KEY && d->d1 == d->d2 + 1 &&
d->path[d->pos] == '.' && s[off] == '"' &&
s[off + len - 1] == '"' &&
plen1(&d->path[d->pos + 1]) == len - 2 &&
kcmp(s + off + 1, &d->path[d->pos + 1], len - 2) == 0) {
d->d2++;
d->pos += plen2(&d->path[d->pos + 1]) + 1;
} else if (tok == MJSON_TOK_KEY && d->d1 == d->d2) {
return 1; // Exhausted path, not found
} else if (MJSON_TOK_IS_VALUE(tok)) { } else if (MJSON_TOK_IS_VALUE(tok)) {
// printf("TOK --> %d\n", tok); // printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2);
if (data->d1 == data->d2 && !data->path[data->pos]) { if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) {
data->tok = tok; d->tok = tok;
if (data->tokptr) *data->tokptr = s + off; if (d->tokptr) *d->tokptr = s + off;
if (data->toklen) *data->toklen = len; if (d->toklen) *d->toklen = len;
return 1; return 1;
} }
} }
return 0; return 0;
} }
enum mjson_tok mjson_find(const char *s, int len, const char *jp, int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) {
const char **tokptr, int *toklen) {
struct msjon_get_data data = {jp, 1, 0, 0, 0, struct msjon_get_data data = {jp, 1, 0, 0, 0,
0, -1, tokptr, toklen, MJSON_TOK_INVALID}; 0, -1, tp, tl, MJSON_TOK_INVALID};
if (jp[0] != '$') return MJSON_TOK_INVALID; if (jp[0] != '$') return MJSON_TOK_INVALID;
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID; if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
return (enum mjson_tok) data.tok; return data.tok;
} }
int mjson_get_number(const char *s, int len, const char *path, double *v) { int mjson_get_number(const char *s, int len, const char *path, double *v) {
const char *p; const char *p;
int tok, n; int tok, n;
if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) { if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) {
if (v != NULL) *v = strtod(p, NULL); if (v != NULL) *v = mystrtod(p, NULL);
} }
return tok == MJSON_TOK_NUMBER ? 1 : 0; return tok == MJSON_TOK_NUMBER ? 1 : 0;
} }
@@ -260,13 +297,31 @@ int mjson_get_bool(const char *s, int len, const char *path, int *v) {
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0; return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
} }
static unsigned char unhex(unsigned char c) {
return (c >= '0' && c <= '9') ? (unsigned char) (c - '0')
: (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
: (unsigned char) (c - 'W');
}
static unsigned char mjson_unhex_nimble(const char *s) {
return (unsigned char) (unhex(((unsigned char *) s)[0]) << 4) |
unhex(((unsigned char *) s)[1]);
}
static int mjson_unescape(const char *s, int len, char *to, int n) { static int mjson_unescape(const char *s, int len, char *to, int n) {
int i, j; int i, j;
for (i = 0, j = 0; i < len && j < n; i++, j++) { for (i = 0, j = 0; i < len && j < n; i++, j++) {
if (s[i] == '\\' && i + 1 < len) { if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
// \uXXXX escape. We could process a simple one-byte chars
// \u00xx from the ASCII range. More complex chars would require
// dragging in a UTF8 library, which is too much for us
if (s[i + 2] != '0' || s[i + 3] != '0') return -1; // Too much, give up
((unsigned char *) to)[j] = mjson_unhex_nimble(s + i + 4);
i += 5;
} else if (s[i] == '\\' && i + 1 < len) {
int c = mjson_esc(s[i + 1], 0); int c = mjson_esc(s[i + 1], 0);
if (c == 0) return -1; if (c == 0) return -1;
to[j] = c; to[j] = (char) (unsigned char) c;
i++; i++;
} else { } else {
to[j] = s[i]; to[j] = s[i];
@@ -290,23 +345,20 @@ int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
int i, j, sz; int i, j, sz;
if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1; if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1;
for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) { for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) {
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') ((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1);
unsigned char a = *(const unsigned char *) (p + i + 1);
unsigned char b = *(const unsigned char *) (p + i + 2);
((unsigned char *) to)[j] = (HEXTOI(a) << 4) | HEXTOI(b);
} }
if (j < n) to[j] = '\0'; if (j < n) to[j] = '\0';
return j; return j;
} }
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
static int mjson_base64rev(int c) { static unsigned char mjson_base64rev(int c) {
if (c >= 'A' && c <= 'Z') { if (c >= 'A' && c <= 'Z') {
return c - 'A'; return (unsigned char) (c - 'A');
} else if (c >= 'a' && c <= 'z') { } else if (c >= 'a' && c <= 'z') {
return c + 26 - 'a'; return (unsigned char) (c + 26 - 'a');
} else if (c >= '0' && c <= '9') { } else if (c >= '0' && c <= '9') {
return c + 52 - '0'; return (unsigned char) (c + 52 - '0');
} else if (c == '+') { } else if (c == '+') {
return 62; return 62;
} else if (c == '/') { } else if (c == '/') {
@@ -320,13 +372,13 @@ int mjson_base64_dec(const char *src, int n, char *dst, int dlen) {
const char *end = src + n; const char *end = src + n;
int len = 0; int len = 0;
while (src + 3 < end && len < dlen) { while (src + 3 < end && len < dlen) {
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]), unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]); c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
dst[len++] = (a << 2) | (b >> 4); dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4));
if (src[2] != '=' && len < dlen) { if (src[2] != '=' && len < dlen) {
dst[len++] = (b << 4) | (c >> 2); dst[len++] = (char) (unsigned char) ((b << 4) | (c >> 2));
if (src[3] != '=' && len < dlen) { if (src[3] != '=' && len < dlen) {
dst[len++] = (c << 6) | d; dst[len++] = (char) (unsigned char) ((c << 6) | d);
} }
} }
src += 4; src += 4;
@@ -428,92 +480,177 @@ int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
return len; return len;
} }
// This function allocates memory in chunks of size MJSON_DYNBUF_CHUNK
// to decrease memory fragmentation, when many calls are executed to
// print e.g. a base64 string or a hex string.
int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) { int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) {
char *s, *buf = *(char **) fndata; char *s, *buf = *(char **) fndata;
int curlen = buf == NULL ? 0 : strlen(buf); size_t curlen = buf == NULL ? 0 : strlen(buf);
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) { size_t new_size = curlen + (size_t) len + 1 + MJSON_DYNBUF_CHUNK;
new_size -= new_size % MJSON_DYNBUF_CHUNK;
if ((s = (char *) realloc(buf, new_size)) == NULL) {
return 0; return 0;
} else { } else {
memcpy(s + curlen, ptr, len); memcpy(s + curlen, ptr, (size_t) len);
s[curlen + len] = '\0'; s[curlen + (size_t) len] = '\0';
*(char **) fndata = s; *(char **) fndata = s;
return len; return len;
} }
} }
int mjson_snprintf(char *buf, size_t len, const char *fmt, ...) {
va_list ap;
struct mjson_fixedbuf fb = {buf, (int) len, 0};
va_start(ap, fmt);
mjson_vprintf(mjson_print_fixed_buf, &fb, fmt, &ap);
va_end(ap);
return fb.len;
}
char *mjson_aprintf(const char *fmt, ...) {
va_list ap;
char *result = NULL;
va_start(ap, fmt);
mjson_vprintf(mjson_print_dynamic_buf, &result, fmt, &ap);
va_end(ap);
return result;
}
int mjson_print_null(const char *ptr, int len, void *userdata) { int mjson_print_null(const char *ptr, int len, void *userdata) {
(void) ptr; (void) ptr;
(void) userdata; (void) userdata;
return len; return len;
} }
int mjson_print_file(const char *ptr, int len, void *userdata) { int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) {
return fwrite(ptr, 1, len, (FILE *) userdata); return fn(buf, len, fnd);
} }
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf, int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) {
int len) { unsigned long v = (unsigned long) val, s = 0, n, i;
return fn(buf, len, fndata); char buf[20], t;
if (is_signed && val < 0) buf[s++] = '-', v = (unsigned long) (-val);
// This loop prints a number in reverse order. I guess this is because we
// write numbers from right to left: least significant digit comes last.
// Maybe because we use Arabic numbers, and Arabs write RTL?
for (n = 0; v > 0; v /= 10) buf[s + n++] = "0123456789"[v % 10];
// Reverse a string
for (i = 0; i < n / 2; i++)
t = buf[s + i], buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
if (val == 0) buf[n++] = '0'; // Handle special case
return fn(buf, (int) (s + n), fnd);
} }
int mjson_print_int(mjson_print_fn_t fn, void *fndata, int value, int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) {
int is_signed) { return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s);
char buf[20];
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
return fn(buf, len, fndata);
} }
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value, static int addexp(char *buf, int e, int sign) {
int is_signed) { int n = 0;
char buf[20]; buf[n++] = 'e';
const char *fmt = (is_signed ? "%ld" : "%lu"); buf[n++] = (char) sign;
int len = snprintf(buf, sizeof(buf), fmt, value); if (e > 400) return 0;
return fn(buf, len, fndata); if (e < 10) buf[n++] = '0';
if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
buf[n++] = (char) (e + '0');
return n;
} }
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d, int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) {
const char *fmt) {
char buf[40]; char buf[40];
int n = snprintf(buf, sizeof(buf), fmt, d); int i, s = 0, n = 0, e = 0;
return fn(buf, n, fndata); double t, mul, saved;
if (d == 0.0) return fn("0", 1, fnd);
if (isinf(d)) return fn(d > 0 ? "inf" : "-inf", d > 0 ? 3 : 4, fnd);
if (isnan(d)) return fn("nan", 3, fnd);
if (d < 0.0) d = -d, buf[s++] = '-';
// Round
saved = d;
mul = 1.0;
while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
d += t;
// Calculate exponent, and 'mul' for scientific representation
mul = 1.0;
while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
// printf(" --> %g %d %g %g\n", saved, e, t, mul);
if (e >= width) {
struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0};
n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
// printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, fb.len, fb.ptr);
n += addexp(buf + s + n, e, '+');
return fn(buf, s + n, fnd);
} else if (e <= -width) {
struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0};
n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width);
// printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, fb.len, fb.ptr);
n += addexp(buf + s + n, -e, '-');
return fn(buf, s + n, fnd);
} else {
for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
int ch = (int) (d / t);
if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
d -= ch * t;
t /= 10.0;
}
// printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
if (n == 0) buf[s++] = '0';
while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
// printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
int ch = (int) (d / t);
buf[s + n++] = (char) (ch + '0');
d -= ch * t;
t /= 10.0;
}
}
while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros
if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot
buf[s + n] = '\0';
return fn(buf, s + n, fnd);
} }
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) { int mjson_print_str(mjson_print_fn_t fn, void *fnd, const char *s, int len) {
int i, n = fn("\"", 1, fndata); int i, n = fn("\"", 1, fnd);
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
char c = mjson_esc(s[i], 1); char c = (char) (unsigned char) mjson_escape(s[i]);
if (c) { if (c) {
n += fn("\\", 1, fndata); n += fn("\\", 1, fnd);
n += fn(&c, 1, fndata); n += fn(&c, 1, fnd);
} else { } else {
n += fn(&s[i], 1, fndata); n += fn(&s[i], 1, fnd);
} }
} }
return n + fn("\"", 1, fndata); return n + fn("\"", 1, fnd);
} }
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s, int mjson_print_b64(mjson_print_fn_t fn, void *fnd, const unsigned char *s,
int n) { int n) {
const char *t = const char *t =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i, len = fn("\"", 1, fndata); int i, len = fn("\"", 1, fnd);
for (i = 0; i < n; i += 3) { for (i = 0; i < n; i += 3) {
int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0; int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0;
char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='}; char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='};
if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)]; if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)];
if (i + 2 < n) buf[3] = t[c & 63]; if (i + 2 < n) buf[3] = t[c & 63];
len += fn(buf, sizeof(buf), fndata); len += fn(buf, sizeof(buf), fnd);
} }
return len + fn("\"", 1, fndata); return len + fn("\"", 1, fnd);
} }
#endif /* MJSON_ENABLE_BASE64 */ #endif /* MJSON_ENABLE_BASE64 */
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt, int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt,
va_list xap) { va_list *ap) {
int i = 0, n = 0; int i = 0, n = 0;
va_list ap;
va_copy(ap, xap);
while (fmt[i] != '\0') { while (fmt[i] != '\0') {
if (fmt[i] == '%') { if (fmt[i] == '%') {
char fc = fmt[++i]; char fc = fmt[++i];
@@ -523,85 +660,85 @@ int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
fc = fmt[i + 1]; fc = fmt[i + 1];
} }
if (fc == 'Q') { if (fc == 'Q') {
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0); n += mjson_print_str(fn, fnd, buf ? buf : "",
buf ? (int) strlen(buf) : 0);
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) { } else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_str(fn, fndata, buf, len); n += mjson_print_str(fn, fnd, buf, len);
i += 2; i += 2;
} else if (fc == 'd' || fc == 'u') { } else if (fc == 'd' || fc == 'u') {
int is_signed = (fc == 'd'); int is_signed = (fc == 'd');
if (is_long) { if (is_long) {
long val = va_arg(ap, long); long val = va_arg(*ap, long);
n += mjson_print_long(fn, fndata, val, is_signed); n += mjson_print_long(fn, fnd, val, is_signed);
i++; i++;
} else { } else {
int val = va_arg(ap, int); int val = va_arg(*ap, int);
n += mjson_print_int(fn, fndata, val, is_signed); n += mjson_print_int(fn, fnd, val, is_signed);
} }
} else if (fc == 'B') { } else if (fc == 'B') {
const char *s = va_arg(ap, int) ? "true" : "false"; const char *s = va_arg(*ap, int) ? "true" : "false";
n += mjson_print_buf(fn, fndata, s, strlen(s)); n += mjson_print_buf(fn, fnd, s, (int) strlen(s));
} else if (fc == 's') { } else if (fc == 's') {
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_buf(fn, fndata, buf, strlen(buf)); n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf));
} else if (strncmp(&fmt[i], ".*s", 3) == 0) { } else if (strncmp(&fmt[i], ".*s", 3) == 0) {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
char *buf = va_arg(ap, char *); char *buf = va_arg(*ap, char *);
n += mjson_print_buf(fn, fndata, buf, len); n += mjson_print_buf(fn, fnd, buf, len);
i += 2; i += 2;
} else if (fc == 'g') { } else if (fc == 'g') {
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g"); n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6);
} else if (fc == 'f') { } else if (strncmp(&fmt[i], ".*g", 3) == 0) {
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f"); int width = va_arg(*ap, int);
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width);
i += 2;
#if MJSON_ENABLE_BASE64 #if MJSON_ENABLE_BASE64
} else if (fc == 'V') { } else if (fc == 'V') {
int len = va_arg(ap, int); int len = va_arg(*ap, int);
const char *buf = va_arg(ap, const char *); const char *buf = va_arg(*ap, const char *);
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len); n += mjson_print_b64(fn, fnd, (unsigned char *) buf, len);
#endif #endif
} else if (fc == 'H') { } else if (fc == 'H') {
const char *hex = "0123456789abcdef"; const char *hex = "0123456789abcdef";
int i, len = va_arg(ap, int); int j, len = va_arg(*ap, int);
const unsigned char *p = va_arg(ap, const unsigned char *); const unsigned char *p = va_arg(*ap, const unsigned char *);
n += fn("\"", 1, fndata); n += fn("\"", 1, fnd);
for (i = 0; i < len; i++) { for (j = 0; j < len; j++) {
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata); n += fn(&hex[(p[j] >> 4) & 15], 1, fnd);
n += fn(&hex[p[i] & 15], 1, fndata); n += fn(&hex[p[j] & 15], 1, fnd);
} }
n += fn("\"", 1, fndata); n += fn("\"", 1, fnd);
} else if (fc == 'M') { } else if (fc == 'M') {
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t); mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t);
n += vfn(fn, fndata, &ap); n += vfn(fn, fnd, ap);
} }
i++; i++;
} else { } else {
n += mjson_print_buf(fn, fndata, &fmt[i++], 1); n += mjson_print_buf(fn, fnd, &fmt[i++], 1);
} }
} }
va_end(xap);
va_end(ap);
return n; return n;
} }
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) { int mjson_printf(mjson_print_fn_t fn, void *fnd, const char *fmt, ...) {
va_list ap; va_list ap;
int len; int len;
va_start(ap, fmt); va_start(ap, fmt);
len = mjson_vprintf(fn, fndata, fmt, ap); len = mjson_vprintf(fn, fnd, fmt, &ap);
va_end(ap); va_end(ap);
return len; return len;
} }
#endif /* MJSON_ENABLE_PRINT */ #endif /* MJSON_ENABLE_PRINT */
#if MJSON_IMPLEMENT_STRTOD static int is_digit(int c) {
static inline int is_digit(int c) {
return c >= '0' && c <= '9'; return c >= '0' && c <= '9';
} }
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */ /* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
double strtod(const char *str, char **end) { static double mystrtod(const char *str, char **end) {
double d = 0.0; double d = 0.0;
int sign = 1, n = 0; int sign = 1, n = 0;
const char *p = str, *a = str; const char *p = str, *a = str;
@@ -610,8 +747,9 @@ double strtod(const char *str, char **end) {
if (*p == '-') { if (*p == '-') {
sign = -1; sign = -1;
++p; ++p;
} else if (*p == '+') } else if (*p == '+') {
++p; ++p;
}
if (is_digit(*p)) { if (is_digit(*p)) {
d = (double) (*p++ - '0'); d = (double) (*p++ - '0');
while (*p && is_digit(*p)) { while (*p && is_digit(*p)) {
@@ -620,8 +758,9 @@ double strtod(const char *str, char **end) {
++n; ++n;
} }
a = p; a = p;
} else if (*p != '.') } else if (*p != '.') {
goto done; goto done;
}
d *= sign; d *= sign;
/* fraction part */ /* fraction part */
@@ -644,30 +783,13 @@ double strtod(const char *str, char **end) {
/* exponential part */ /* exponential part */
if ((*p == 'E') || (*p == 'e')) { if ((*p == 'E') || (*p == 'e')) {
int e = 0; int i, e = 0, neg = 0;
++p; p++;
if (*p == '-') p++, neg++;
sign = 1; if (*p == '+') p++;
if (*p == '-') { while (is_digit(*p)) e = e * 10 + *p++ - '0';
sign = -1; if (neg) e = -e;
++p; #if 0
} else if (*p == '+')
++p;
if (is_digit(*p)) {
while (*p == '0') ++p;
e = (int) (*p++ - '0');
while (*p && is_digit(*p)) {
e = e * 10 + (int) (*p - '0');
++p;
}
e *= sign;
} else if (!is_digit(*(a - 1))) {
a = str;
goto done;
} else if (*p == 0)
goto done;
if (d == 2.2250738585072011 && e == -308) { if (d == 2.2250738585072011 && e == -308) {
d = 0.0; d = 0.0;
a = p; a = p;
@@ -678,10 +800,9 @@ double strtod(const char *str, char **end) {
a = p; a = p;
goto done; goto done;
} }
{ #endif
int i; for (i = 0; i < e; i++) d *= 10;
for (i = 0; i < 10; i++) d *= 10; for (i = 0; i < -e; i++) d /= 10;
}
a = p; a = p;
} else if (p > str && !is_digit(*(p - 1))) { } else if (p > str && !is_digit(*(p - 1))) {
a = str; a = str;
@@ -692,7 +813,6 @@ done:
if (end) *end = (char *) a; if (end) *end = (char *) a;
return d; return d;
} }
#endif
#if MJSON_ENABLE_MERGE #if MJSON_ENABLE_MERGE
int mjson_merge(const char *s, int n, const char *s2, int n2, int mjson_merge(const char *s, int n, const char *s2, int n2,
@@ -701,14 +821,10 @@ int mjson_merge(const char *s, int n, const char *s2, int n2,
if (n < 2) return len; if (n < 2) return len;
len += fn("{", 1, userdata); len += fn("{", 1, userdata);
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) { while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
#if !defined(_MSC_VER) || _MSC_VER >= 1700 char *path = (char *) alloca((size_t) klen + 1);
char path[klen + 1];
#else
char *path = (char *) alloca(klen + 1);
#endif
const char *val; const char *val;
memcpy(path, "$.", 2); memcpy(path, "$.", 2);
memcpy(path + 2, s + koff + 1, klen - 2); memcpy(path + 2, s + koff + 1, (size_t) (klen - 2));
path[klen] = '\0'; path[klen] = '\0';
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) { if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
if (t2 == MJSON_TOK_NULL) continue; // null deletes the key if (t2 == MJSON_TOK_NULL) continue; // null deletes the key
@@ -729,15 +845,11 @@ int mjson_merge(const char *s, int n, const char *s2, int n2,
// Add missing keys // Add missing keys
off = 0; off = 0;
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) { while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
#if !defined(_MSC_VER) || _MSC_VER >= 1700 char *path = (char *) alloca((size_t) klen + 1);
char path[klen + 1];
#else
char *path = (char *) alloca(klen + 1);
#endif
const char *val; const char *val;
if (t == MJSON_TOK_NULL) continue; if (t == MJSON_TOK_NULL) continue;
memcpy(path, "$.", 2); memcpy(path, "$.", 2);
memcpy(path + 2, s2 + koff + 1, klen - 2); memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2));
path[klen] = '\0'; path[klen] = '\0';
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue; if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
if (comma) len += fn(",", 1, userdata); if (comma) len += fn(",", 1, userdata);
@@ -816,7 +928,7 @@ static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn, int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
void *userdata) { void *userdata) {
struct prettydata d = {0, 0, 0, pad, strlen(pad), fn, userdata}; struct prettydata d = {0, 0, 0, pad, (int) strlen(pad), fn, userdata};
if (mjson(s, n, pretty_cb, &d) < 0) return -1; if (mjson(s, n, pretty_cb, &d) < 0) return -1;
return d.len; return d.len;
} }
@@ -824,10 +936,6 @@ int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn,
#if MJSON_ENABLE_RPC #if MJSON_ENABLE_RPC
struct jsonrpc_ctx jsonrpc_default_context; struct jsonrpc_ctx jsonrpc_default_context;
struct jsonrpc_userdata {
mjson_print_fn_t fn;
void *fndata;
};
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) { int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
int i = 0, j = 0, ni = 0, nj = 0; int i = 0, j = 0, ni = 0, nj = 0;
@@ -845,14 +953,9 @@ int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
return 1; return 1;
} }
static int jsonrpc_printer(const char *buf, int len, void *userdata) {
struct jsonrpc_userdata *u = (struct jsonrpc_userdata *) userdata;
return u->fn(buf, len, u->fndata);
}
void jsonrpc_return_errorv(struct jsonrpc_request *r, int code, void jsonrpc_return_errorv(struct jsonrpc_request *r, int code,
const char *message, const char *data_fmt, const char *message, const char *data_fmt,
va_list ap) { va_list *ap) {
if (r->id_len == 0) return; if (r->id_len == 0) return;
mjson_printf(r->fn, r->fndata, mjson_printf(r->fn, r->fndata,
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len, "{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
@@ -868,12 +971,12 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
const char *message, const char *data_fmt, ...) { const char *message, const char *data_fmt, ...) {
va_list ap; va_list ap;
va_start(ap, data_fmt); va_start(ap, data_fmt);
jsonrpc_return_errorv(r, code, message, data_fmt, ap); jsonrpc_return_errorv(r, code, message, data_fmt, &ap);
va_end(ap); va_end(ap);
} }
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt, void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
va_list ap) { va_list *ap) {
if (r->id_len == 0) return; if (r->id_len == 0) return;
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id); mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
if (result_fmt != NULL) { if (result_fmt != NULL) {
@@ -888,49 +991,41 @@ void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
...) { ...) {
va_list ap; va_list ap;
va_start(ap, result_fmt); va_start(ap, result_fmt);
jsonrpc_return_successv(r, result_fmt, ap); jsonrpc_return_successv(r, result_fmt, &ap);
va_end(ap); va_end(ap);
} }
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len,
mjson_print_fn_t fn, void *fndata) { mjson_print_fn_t fn, void *fndata, void *ud) {
const char *result = NULL, *error = NULL; const char *result = NULL, *error = NULL;
int result_sz = 0, error_sz = 0; int result_sz = 0, error_sz = 0;
struct jsonrpc_method *m = NULL; struct jsonrpc_method *m = NULL;
struct jsonrpc_userdata d; struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fndata, ud};
struct jsonrpc_request r = {req, req_sz, 0, 0, 0, 0, 0,
0, &jsonrpc_printer, NULL, NULL};
d.fn = fn;
d.fndata = fndata;
r.fndata = &d;
// Is is a response frame? // Is is a response frame?
mjson_find(req, req_sz, "$.result", &result, &result_sz); mjson_find(buf, len, "$.result", &result, &result_sz);
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz); if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz);
if (result_sz > 0 || error_sz > 0) { if (result_sz > 0 || error_sz > 0) {
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata); if (ctx->response_cb) ctx->response_cb(buf, len, ctx->response_cb_data);
return; return;
} }
// Method must exist and must be a string // Method must exist and must be a string
if (mjson_find(req, req_sz, "$.method", &r.method, &r.method_len) != if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) !=
MJSON_TOK_STRING) { MJSON_TOK_STRING) {
mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n", mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
req_sz, req); len, buf);
ctx->in_len = 0;
return; return;
} }
// id and params are optional // id and params are optional
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len); mjson_find(buf, len, "$.id", &r.id, &r.id_len);
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len); mjson_find(buf, len, "$.params", &r.params, &r.params_len);
for (m = ctx->methods; m != NULL; m = m->next) { for (m = ctx->methods; m != NULL; m = m->next) {
if (mjson_globmatch(m->method, m->method_sz, r.method + 1, if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
r.method_len - 2) > 0) { r.method_len - 2) > 0) {
if (r.params == NULL) r.params = ""; if (r.params == NULL) r.params = "";
r.userdata = m->cbdata;
m->cb(&r); m->cb(&r);
break; break;
} }
@@ -947,35 +1042,25 @@ static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
int len = 0; int len = 0;
for (m = ctx->methods; m != NULL; m = m->next) { for (m = ctx->methods; m != NULL; m = m->next) {
if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1); if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1);
len += mjson_print_str(fn, fndata, m->method, strlen(m->method)); len += mjson_print_str(fn, fndata, m->method, (int) strlen(m->method));
} }
return len; return len;
} }
static void rpclist(struct jsonrpc_request *r) { void jsonrpc_list(struct jsonrpc_request *r) {
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata); jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->ctx);
} }
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb, void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
void *userdata) { void *response_cb_data) {
ctx->methods = NULL;
ctx->response_cb = response_cb; ctx->response_cb = response_cb;
ctx->userdata = userdata; ctx->response_cb_data = response_cb_data;
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, rpclist, ctx);
}
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
mjson_print_fn_t fn, void *p) {
if (ctx->in_len >= (int) sizeof(ctx->in)) ctx->in_len = 0; // Overflow
if (ch == '\n') { // If new line, parse frame
if (ctx->in_len > 1) jsonrpc_ctx_process(ctx, ctx->in, ctx->in_len, fn, p);
ctx->in_len = 0;
} else {
ctx->in[ctx->in_len] = ch; // Append to the buffer
ctx->in_len++;
}
} }
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) { void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata); struct jsonrpc_ctx *ctx = &jsonrpc_default_context;
jsonrpc_ctx_init(ctx, response_cb, userdata);
jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, jsonrpc_list);
} }
#endif // MJSON_ENABLE_RPC #endif // MJSON_ENABLE_RPC
@@ -23,7 +23,6 @@
#define MJSON_H #define MJSON_H
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -40,50 +39,40 @@
#endif #endif
#ifndef MJSON_ENABLE_MERGE #ifndef MJSON_ENABLE_MERGE
#define MJSON_ENABLE_MERGE 0 #define MJSON_ENABLE_MERGE 1
#elif MJSON_ENABLE_MERGE
#define MJSON_ENABLE_NEXT 1
#endif #endif
#ifndef MJSON_ENABLE_PRETTY #ifndef MJSON_ENABLE_PRETTY
#define MJSON_ENABLE_PRETTY 0 #define MJSON_ENABLE_PRETTY 1
#elif MJSON_ENABLE_PRETTY
#define MJSON_ENABLE_NEXT 1
#endif #endif
#ifndef MJSON_ENABLE_NEXT #ifndef MJSON_ENABLE_NEXT
#define MJSON_ENABLE_NEXT 0 #define MJSON_ENABLE_NEXT 1
#endif
#ifndef MJSON_RPC_IN_BUF_SIZE
#define MJSON_RPC_IN_BUF_SIZE 256
#endif #endif
#ifndef MJSON_RPC_LIST_NAME #ifndef MJSON_RPC_LIST_NAME
#define MJSON_RPC_LIST_NAME "rpc.list" #define MJSON_RPC_LIST_NAME "rpc.list"
#endif #endif
#ifndef MJSON_DYNBUF_CHUNK
#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
enum { #define MJSON_ERROR_INVALID_INPUT (-1)
MJSON_ERROR_INVALID_INPUT = -1, #define MJSON_ERROR_TOO_DEEP (-2)
MJSON_ERROR_TOO_DEEP = -2, #define MJSON_TOK_INVALID 0
}; #define MJSON_TOK_KEY 1
#define MJSON_TOK_STRING 11
enum mjson_tok { #define MJSON_TOK_NUMBER 12
MJSON_TOK_INVALID = 0, #define MJSON_TOK_TRUE 13
MJSON_TOK_KEY = 1, #define MJSON_TOK_FALSE 14
MJSON_TOK_STRING = 11, #define MJSON_TOK_NULL 15
MJSON_TOK_NUMBER = 12, #define MJSON_TOK_ARRAY 91
MJSON_TOK_TRUE = 13, #define MJSON_TOK_OBJECT 123
MJSON_TOK_FALSE = 14,
MJSON_TOK_NULL = 15,
MJSON_TOK_ARRAY = 91,
MJSON_TOK_OBJECT = 123,
};
#define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20) #define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud); typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
@@ -93,8 +82,7 @@ typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
#endif #endif
int mjson(const char *s, int len, mjson_cb_t cb, void *ud); int mjson(const char *s, int len, mjson_cb_t cb, void *ud);
enum mjson_tok mjson_find(const char *s, int len, const char *jp, int mjson_find(const char *s, int len, const char *jp, const char **, int *);
const char **tokptr, int *toklen);
int mjson_get_number(const char *s, int len, const char *path, double *v); int mjson_get_number(const char *s, int len, const char *path, double *v);
int mjson_get_bool(const char *s, int len, const char *path, int *v); int mjson_get_bool(const char *s, int len, const char *path, int *v);
int mjson_get_string(const char *s, int len, const char *path, char *to, int n); int mjson_get_string(const char *s, int len, const char *path, char *to, int n);
@@ -120,17 +108,20 @@ struct mjson_fixedbuf {
}; };
int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...); int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...);
int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list ap); int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list *ap);
int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len); int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len);
int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed); int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed);
int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed); int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed);
int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len); int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len);
int mjson_print_dbl(mjson_print_fn_t fn, void *, double, int width);
int mjson_print_null(const char *ptr, int len, void *userdata); int mjson_print_null(const char *ptr, int len, void *userdata);
int mjson_print_file(const char *ptr, int len, void *userdata);
int mjson_print_fixed_buf(const char *ptr, int len, void *userdata); int mjson_print_fixed_buf(const char *ptr, int len, void *userdata);
int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata); int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata);
int mjson_snprintf(char *buf, size_t len, const char *fmt, ...);
char *mjson_aprintf(const char *fmt, ...);
#if MJSON_ENABLE_PRETTY #if MJSON_ENABLE_PRETTY
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *); int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
#endif #endif
@@ -147,6 +138,7 @@ void jsonrpc_init(mjson_print_fn_t, void *userdata);
int mjson_globmatch(const char *s1, int n1, const char *s2, int n2); int mjson_globmatch(const char *s1, int n1, const char *s2, int n2);
struct jsonrpc_request { struct jsonrpc_request {
struct jsonrpc_ctx *ctx;
const char *frame; // Points to the whole frame const char *frame; // Points to the whole frame
int frame_len; // Frame length int frame_len; // Frame length
const char *params; // Points to the "params" in the request frame const char *params; // Points to the "params" in the request frame
@@ -164,7 +156,6 @@ struct jsonrpc_method {
const char *method; const char *method;
int method_sz; int method_sz;
void (*cb)(struct jsonrpc_request *); void (*cb)(struct jsonrpc_request *);
void *cbdata;
struct jsonrpc_method *next; struct jsonrpc_method *next;
}; };
@@ -172,17 +163,14 @@ struct jsonrpc_method {
// exported RPC methods. // exported RPC methods.
struct jsonrpc_ctx { struct jsonrpc_ctx {
struct jsonrpc_method *methods; struct jsonrpc_method *methods;
void *userdata;
mjson_print_fn_t response_cb; mjson_print_fn_t response_cb;
int in_len; void *response_cb_data;
char in[MJSON_RPC_IN_BUF_SIZE];
}; };
// Registers function fn under the given name within the given RPC context // Registers function fn under the given name within the given RPC context
#define jsonrpc_ctx_export(ctx, name, fn, ud) \ #define jsonrpc_ctx_export(ctx, name, fn) \
do { \ do { \
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \ static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \
m.cbdata = (ud); \
m.next = (ctx)->methods; \ m.next = (ctx)->methods; \
(ctx)->methods = &m; \ (ctx)->methods = &m; \
} while (0) } while (0)
@@ -193,20 +181,16 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
...); ...);
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
mjson_print_fn_t fn, void *fndata); mjson_print_fn_t fn, void *fndata, void *userdata);
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
mjson_print_fn_t fn, void *fndata);
extern struct jsonrpc_ctx jsonrpc_default_context; extern struct jsonrpc_ctx jsonrpc_default_context;
extern void jsonrpc_list(struct jsonrpc_request *r);
#define jsonrpc_export(name, fn, ud) \ #define jsonrpc_export(name, fn) \
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud)) jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn))
#define jsonrpc_process(buf, len, fn, data) \ #define jsonrpc_process(buf, len, fn, fnd, ud) \
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data)) jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud))
#define jsonrpc_process_byte(x, fn, data) \
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */ #define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */ #define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<body>
<h1>JSON-RPC over Websocket demo</h1>
<input id="url" type="text" placeholder="Type URL" value="ws://localhost:8000/websocket" style="width:20em;" />
<button id="connect">connect</button>
<div style="height: 0.3em;">&nbsp;</div>
<button id="btn1">Calculate 1 + 2</button>
<button id="btn2">Calculate 2 * 3</button>
<div style="margin-top: 1em;">Event log:</div>
<div id="log" style="background: #eee; height: 10em; padding: 0.5em; overflow:auto;"><div>
</body>
<script src="rpc-over-websocket.js"></script>
<script>
var rpc, E = function(id) { return document.getElementById(id); };
var url = E('url'), connect = E('connect'), btn1 = E('btn1'), btn2 = E('btn2'), msglog = E('log');
var enable = function(en) { btn1.disabled = btn2.disabled = !en; url.disabled = en; connect.innerHTML = en ? 'disconnect' : 'connect'; };
var log = text => msglog.innerHTML += text + '<br/>\n';
enable(false);
connect.onclick = function() {
console.log(rpc);
if (rpc) { rpc.close(); rpc = null; return; }
rpc = jsonrpc(url.value,
() => enable(true),
() => enable(false),
msg => log('NOTIFICATION: ' + JSON.stringify(msg)));
};
btn1.onclick = ev => rpc.call('sum', [1, 2]).then(res => log('SUM:' + JSON.stringify(res)));
btn2.onclick = ev => rpc.call('mul', [2, 3]).then(res => log('MUL:' + JSON.stringify(res)));
</script>
</html>
@@ -0,0 +1,36 @@
// JSON-RPC over Websocket implementation
var JSONRPC_TIMEOUT_MS = 1000;
var jsonrpc = function(url, onopen, onclose, onnotification) {
var rpcid = 0, pending = {}, ws = new WebSocket(url);
if (!ws) return null;
ws.onclose = onclose;
ws.onmessage = function(ev) {
const frame = JSON.parse(ev.data);
console.log('rcvd', frame, 'pending:', pending);
if (frame.id !== undefined) {
if (pending[frame.id] !== undefined) pending[frame.id](frame); // Resolve
delete (pending[frame.id]);
} else {
if (onnotification) onnotification(frame);
}
};
if (onopen) onopen();
return {
close: () => ws.close(),
call: function(method, params) {
const id = rpcid++, request = {id, method, params};
ws.send(JSON.stringify(request));
console.log('sent', request);
return new Promise(function(resolve, reject) {
setTimeout(JSONRPC_TIMEOUT_MS, function() {
if (pending[id] === undefined) return;
log('Timing out frame ', JSON.stringify(request));
delete (pending[id]);
reject();
});
pending[id] = x => resolve(x);
});
},
};
};
+1 -3
View File
@@ -49,14 +49,12 @@ static void timer_fn(void *arg) {
int main(void) { int main(void) {
struct mg_mgr mgr; struct mg_mgr mgr;
struct mg_timer t1;
mg_mgr_init(&mgr); mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://localhost:8000", cb, NULL); mg_http_listen(&mgr, "http://localhost:8000", cb, NULL);
mg_timer_init(&t1, 1000, MG_TIMER_REPEAT, timer_fn, &mgr); mg_timer_add(&mgr, 1000, MG_TIMER_REPEAT, timer_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 50); for (;;) mg_mgr_poll(&mgr, 50);
mg_timer_free(&t1);
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
return 0; return 0;
+11
View File
@@ -0,0 +1,11 @@
PROG ?= example
CFLAGS ?= -W -Wall -O2 -DMG_ENABLE_MBEDTLS=1 -lmbedtls -lmbedcrypto -lmbedx509
all: $(PROG)
$(RUN) ./$(PROG) $(ARGS)
$(PROG): main.c
$(CC) main.c ../../mongoose.c -I../.. $(CFLAGS) -o $(PROG)
clean:
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
+1
View File
@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/aws-iot/
+1
View File
@@ -0,0 +1 @@
../../test/data/ca.pem
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff. Show more