Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5993ba27e | ||
|
|
ef563603a9 | ||
|
|
7e2ca72310 | ||
|
|
7c17c806ad | ||
|
|
8e0f529a00 | ||
|
|
47e15a3d47 | ||
|
|
cac7007069 | ||
|
|
ac55c900e7 | ||
|
|
25806e48dc | ||
|
|
3871f4b6ab | ||
|
|
27c892806b | ||
|
|
2b9f0480a1 | ||
|
|
72cc433265 | ||
|
|
7721b074e6 | ||
|
|
f5fd176b35 | ||
|
|
6db1d7925e | ||
|
|
0ed7545947 | ||
|
|
95e22e8d4f | ||
|
|
cf7d1d8041 | ||
|
|
443d0f0c6c | ||
|
|
c20e48179f | ||
|
|
3fbb7de712 | ||
|
|
0294603dfc | ||
|
|
5458252e4a | ||
|
|
31ce219544 | ||
|
|
86cd567968 | ||
|
|
5c66d32bc0 | ||
|
|
5ac5c93c90 | ||
|
|
84480f4072 | ||
|
|
b82953405d | ||
|
|
5271626947 | ||
|
|
bc16854ccb | ||
|
|
6db0485c7b | ||
|
|
b5fc0d30a8 | ||
|
|
38557aac7f | ||
|
|
d621fbfab1 | ||
|
|
f19eec7fae | ||
|
|
8174842b05 | ||
|
|
ff07d261b2 | ||
|
|
c55db8a895 | ||
|
|
70a9554b72 | ||
|
|
bdd7d92108 | ||
|
|
a7cbc19755 | ||
|
|
93ff3e4d1f | ||
|
|
1dfc748e87 | ||
|
|
e1f17a7ff1 | ||
|
|
56c40ff0fd | ||
|
|
9dd3b75165 | ||
|
|
20fc8f0279 | ||
|
|
944704a12d | ||
|
|
fba9f14b2c | ||
|
|
a7c24aeeee | ||
|
|
186eb998ba | ||
|
|
d630ba743d | ||
|
|
e9fabdcc96 | ||
|
|
31698678fb | ||
|
|
ebd2e89faa | ||
|
|
17e0fb1c15 | ||
|
|
6e3e77a1a0 | ||
|
|
ef44f90491 | ||
|
|
66376885e9 | ||
|
|
db941e2627 | ||
|
|
5f78d34f4d | ||
|
|
85281ae128 | ||
|
|
e5d1b213c9 | ||
|
|
e4ab9a4130 | ||
|
|
a1ec179229 | ||
|
|
2f77855ec1 | ||
|
|
d11173fe90 | ||
|
|
fde8b82b78 | ||
|
|
b20eded30e | ||
|
|
3ec17c41be | ||
|
|
0ed64707f3 | ||
|
|
417533e90c | ||
|
|
e268affb5a | ||
|
|
4474ada291 | ||
|
|
a65c3288d8 | ||
|
|
affcef7e37 | ||
|
|
173c2ba338 | ||
|
|
b5fcf95b88 | ||
|
|
d954851d43 | ||
|
|
df602c27d5 | ||
|
|
3d9fa10485 | ||
|
|
6990040626 | ||
|
|
b42ea6cd3c | ||
|
|
8c7fd8b330 | ||
|
|
4e0039625e | ||
|
|
6c909f8935 | ||
|
|
4f5d385e52 | ||
|
|
84fe1d87b3 | ||
|
|
bb0bc41f99 | ||
|
|
1c2d67ded7 | ||
|
|
7372a05ce5 | ||
|
|
642befa8d5 | ||
|
|
3b5a9e2d4b | ||
|
|
6b3d9d9baf | ||
|
|
436852d077 | ||
|
|
b78ced4fa9 | ||
|
|
1bc81df00e | ||
|
|
bf025537b6 | ||
|
|
ad4812218d | ||
|
|
0fbc4b8ff4 | ||
|
|
7ef82f5caf | ||
|
|
1141ea7933 | ||
|
|
1e9344161b | ||
|
|
6c021765bb | ||
|
|
bc151af51d | ||
|
|
8bd09e0e80 | ||
|
|
5978cb9703 | ||
|
|
8c447a95dd | ||
|
|
870431dc1a | ||
|
|
1125b1f545 | ||
|
|
d255fc737a | ||
|
|
37cb51ee94 | ||
|
|
02cabcc0ec | ||
|
|
3ffb6c046e | ||
|
|
7bf8b81995 | ||
|
|
0ddda4a267 | ||
|
|
c46ad970cb | ||
|
|
733abcffbc | ||
|
|
139f928731 | ||
|
|
3da9986232 | ||
|
|
a284df01d1 | ||
|
|
412f5cf3e0 | ||
|
|
42ba1e4aed | ||
|
|
1893b06c13 | ||
|
|
b0cbb70e53 | ||
|
|
78e81fef27 | ||
|
|
dd08231a34 | ||
|
|
8c78b7ef53 | ||
|
|
c650fdcdfc | ||
|
|
e99973d4b7 | ||
|
|
4012a86bd8 | ||
|
|
048fee5c88 | ||
|
|
a287fd97e4 | ||
|
|
3a249183f8 | ||
|
|
393dad97e0 | ||
|
|
113abc71d4 | ||
|
|
63261ec0d5 | ||
|
|
507935d067 | ||
|
|
c579b73f75 | ||
|
|
e69ec148cc | ||
|
|
a76ade9bb2 | ||
|
|
4fec149345 | ||
|
|
5bc164de76 | ||
|
|
969b84043c | ||
|
|
da72dafbfb | ||
|
|
1adf7f6b2b | ||
|
|
3e085ccbaa | ||
|
|
3e8e2ab513 | ||
|
|
4f802ece52 | ||
|
|
313ac34e59 | ||
|
|
54f75889fa | ||
|
|
f64c86d23f | ||
|
|
9e634310b4 | ||
|
|
33666e7d6b | ||
|
|
40d4c7e410 | ||
|
|
8cd4699dbc | ||
|
|
49d5aff8f9 | ||
|
|
b2ee9cb363 | ||
|
|
e9c20ad992 | ||
|
|
63b880e624 | ||
|
|
768f67ae0a | ||
|
|
fbf5fa53c6 | ||
|
|
4a5824e7a7 | ||
|
|
32f3c48b65 | ||
|
|
f7fbcd2451 | ||
|
|
c6253854ba | ||
|
|
544644e94b | ||
|
|
f6cebf6956 | ||
|
|
1b82aa02aa | ||
|
|
e565a7e73d | ||
|
|
59c4dfa33b | ||
|
|
1a8e3c2e31 | ||
|
|
226917e1cd | ||
|
|
fe813ee247 | ||
|
|
4cd830a0bf | ||
|
|
1259ca9ac9 | ||
|
|
d0857fe36c | ||
|
|
56a7438114 | ||
|
|
09df542db8 | ||
|
|
1df102ab93 | ||
|
|
02b3b780bb | ||
|
|
f84f3da012 | ||
|
|
c65c8fdaaa | ||
|
|
b484123f1b | ||
|
|
b60119790e | ||
|
|
216427cea0 | ||
|
|
c53e2fcc14 | ||
|
|
9fd80fd136 | ||
|
|
5fc284722f | ||
|
|
ad6e395da0 | ||
|
|
6c79a406cd | ||
|
|
456e8bd249 | ||
|
|
de95f28598 | ||
|
|
43405afdb2 | ||
|
|
97f95e17db | ||
|
|
6725d61b3f | ||
|
|
ebda2cb100 | ||
|
|
bb72b2494d | ||
|
|
3b0a509cae | ||
|
|
2f981f9383 | ||
|
|
fc3c08e411 | ||
|
|
ff811f287a | ||
|
|
8ebb3bc37b | ||
|
|
f7ff571c5f | ||
|
|
6a82cbee71 | ||
|
|
b0585bd0a9 | ||
|
|
6ad4d242c2 | ||
|
|
030b1d9826 | ||
|
|
f6ffa7b3b9 | ||
|
|
137efe2233 | ||
|
|
62411d039e | ||
|
|
54b1716e77 | ||
|
|
9f7aba8039 | ||
|
|
a176b8d566 | ||
|
|
26a97ec36c | ||
|
|
f29807aad8 | ||
|
|
92d3c7a4cd | ||
|
|
af3a0f6af0 | ||
|
|
ce6164ca20 | ||
|
|
e9fc3fcecf | ||
|
|
25a1a24890 | ||
|
|
bf7fc1d5a9 | ||
|
|
2126b49ab3 | ||
|
|
ae6767b1d2 | ||
|
|
133629dbee | ||
|
|
fb0a9bc7e3 | ||
|
|
2a3492766f | ||
|
|
2ec7e5653e | ||
|
|
30bfb245f5 | ||
|
|
84d89bc4f5 | ||
|
|
7f55fabfec | ||
|
|
9e0f04dc8b | ||
|
|
574c2954b3 | ||
|
|
9d38e26b81 | ||
|
|
4e75a0c81e | ||
|
|
d311e967fa | ||
|
|
d0a6c111cb | ||
|
|
aa6d8b4662 | ||
|
|
3f5fd89fd7 | ||
|
|
f43a5a005c | ||
|
|
755bf3c412 | ||
|
|
84f3fb0ba5 | ||
|
|
f2df6c114d | ||
|
|
f93659e303 | ||
|
|
226404d39c | ||
|
|
c1bcc143b9 | ||
|
|
5ca9062d45 | ||
|
|
0b209eaabb | ||
|
|
47ef191ea2 | ||
|
|
25e76d761e | ||
|
|
9731a51dae | ||
|
|
81220fa590 | ||
|
|
24e8c4d81a | ||
|
|
013520b0c0 | ||
|
|
f012bcb027 | ||
|
|
922e1ce529 | ||
|
|
7a707cc64b | ||
|
|
1967acfef2 | ||
|
|
e24cb04003 | ||
|
|
67974746ff | ||
|
|
8d916d41d7 | ||
|
|
08444e3942 | ||
|
|
9c41a0cd26 | ||
|
|
2c50069d98 | ||
|
|
31059f6728 | ||
|
|
2000752b2c | ||
|
|
dd34830443 | ||
|
|
c58f4ee51b | ||
|
|
00f7e8cb2a | ||
|
|
439904e27f | ||
|
|
4d6590ece2 | ||
|
|
9b0c4b970d | ||
|
|
a80ce1b12a | ||
|
|
57dd74c180 | ||
|
|
90c67e4226 | ||
|
|
093cd6a6a3 | ||
|
|
975db86306 | ||
|
|
eba688e11b | ||
|
|
05b44204d5 | ||
|
|
0bb017c1d5 | ||
|
|
a6efd0b335 | ||
|
|
c3d237e9c6 | ||
|
|
09d28f5570 | ||
|
|
ba02937ec6 | ||
|
|
3d78b14deb | ||
|
|
f558551d1d | ||
|
|
12fe66370f | ||
|
|
423aaa492c | ||
|
|
2016586475 | ||
|
|
bf88167152 | ||
|
|
52be84f04e | ||
|
|
03d187b147 | ||
|
|
3329047579 | ||
|
|
86c70e5abb | ||
|
|
b17f4108ea | ||
|
|
4e523cc455 | ||
|
|
49c9be9159 | ||
|
|
f1bfe0760f | ||
|
|
35f02fdaad | ||
|
|
6ac09966ba | ||
|
|
af9a7617f5 | ||
|
|
42c89732c8 | ||
|
|
054475ddbf | ||
|
|
126d556d6a | ||
|
|
529a302e77 | ||
|
|
acbf978452 | ||
|
|
90b22716ea | ||
|
|
fb90c504a6 | ||
|
|
65fd834654 | ||
|
|
a84acf5b8b | ||
|
|
e9505db146 | ||
|
|
4fb49b011e | ||
|
|
6742991469 | ||
|
|
2430cc5aac | ||
|
|
54590aaf26 | ||
|
|
7c5e67b272 | ||
|
|
c62c8ea4dd | ||
|
|
383653d608 | ||
|
|
001dc3f33d | ||
|
|
37d45b3fbb | ||
|
|
f72b8c6034 | ||
|
|
c11e5a9383 | ||
|
|
b5147a149e | ||
|
|
655529f30b | ||
|
|
fd5319e0ce | ||
|
|
f8c84ca672 | ||
|
|
1b81ac8916 | ||
|
|
c64e2ebb16 | ||
|
|
3771f2c088 | ||
|
|
6d398bd5c7 | ||
|
|
5c355a5ff3 | ||
|
|
d2f8b346e6 | ||
|
|
5cdaa931f9 | ||
|
|
693863a51e | ||
|
|
bc7178e5f7 |
No files matched your search
@@ -6,9 +6,7 @@
|
||||
|
||||
# Environment
|
||||
|
||||
- mongoose version (e.g. 7.3): _X.Y_
|
||||
- Compiler/IDE (e.g. arm-none-eabi-gcc 9.3): _?_
|
||||
- Target OS (e.g. FreeRTOS 10.3.1, Linux 5.8): _?_
|
||||
- Target hardware/board (e.g. STM32F411): _?_
|
||||
- Connectivity chip/module (e.g. WizNet W5500): _?_
|
||||
- Target SDK (e.g. STM32CubeF1 1.8.4): _?_
|
||||
- mongoose version: _X.Y_
|
||||
- Compiler/IDE and SDK: ZZZ version _X.Y_
|
||||
- Target hardware/board: _?_
|
||||
- Connectivity chip/module: _?_
|
||||
@@ -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
|
||||
@@ -3,20 +3,29 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
linux:
|
||||
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:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install packages
|
||||
run: sudo apt-get install libmbedtls-dev
|
||||
- name: make
|
||||
run: make test test++ IPV6=0
|
||||
- name: openssl
|
||||
run: make test SSL=OPENSSL IPV6=0
|
||||
run: sudo apt-get update ; sudo apt-get install libmbedtls-dev valgrind
|
||||
- name: test
|
||||
run: make ${{ matrix.target }}
|
||||
- name: unamalgamated
|
||||
run: make unamalgamated
|
||||
examples:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install packages
|
||||
run: sudo apt-get install libmbedtls-dev
|
||||
- name: examples
|
||||
@@ -24,19 +33,19 @@ jobs:
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install packages
|
||||
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install jq mbedtls openssl
|
||||
- name: make
|
||||
run: make test++ upload-coverage IPV6=0
|
||||
run: make test++ upload-coverage IPV6=0 ASAN_OPTIONS=
|
||||
- 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
|
||||
run: make mg_prefix
|
||||
windows:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: vc98
|
||||
run: make vc98
|
||||
- name: vc2017
|
||||
@@ -48,48 +57,79 @@ jobs:
|
||||
arm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: arm
|
||||
run: make arm
|
||||
esp32:
|
||||
matrix_examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- name: esp32
|
||||
path: esp32
|
||||
- name: esp8266
|
||||
path: esp8266
|
||||
- name: stm32-freertos-tcp
|
||||
path: stm32/stm32-freertos-tcp
|
||||
- name: stm32-nucleo-f746z
|
||||
path: stm32/stm32-nucleo-f746z
|
||||
- name: stm32-nucleo-f429z
|
||||
path: stm32/stm32-nucleo-f429z
|
||||
- name: stm32-nucleo-h743z
|
||||
path: stm32/stm32-nucleo-h743z
|
||||
- name: nxp-mimxrt1020-azurertos
|
||||
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@v2
|
||||
- name: esp32
|
||||
run: make -C examples/esp32 build
|
||||
esp8266:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: esp8266
|
||||
run: make -C examples/esp8266 build
|
||||
stm32-freertos-tcp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-freertos-tcp
|
||||
run: make -C examples/stm32-freertos-tcp build
|
||||
stm32-nucleo-f746z:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-f746z
|
||||
run: make -C examples/stm32-nucleo-f746z build
|
||||
stm32-nucleo-f429z:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-f429z
|
||||
run: make -C examples/stm32-nucleo-f429z build
|
||||
stm32-nucleo-h743z:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: stm32-nucleo-h743z
|
||||
run: make -C examples/stm32-nucleo-h743z build
|
||||
nxp-mimxrt1020-azurertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-mimxrt1020-azurertos
|
||||
run: make -C examples/nxp-mimxrt1020-azurertos build
|
||||
- uses: actions/checkout@v3
|
||||
- name: ${{ matrix.example.name }}
|
||||
run: make -C examples/${{ matrix.example.path }} build
|
||||
@@ -13,4 +13,4 @@ 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, as set out in <https://www.cesanta.com/license>.
|
||||
license, as set out in <https://mongoose.ws/licensing/>.
|
||||
@@ -1,40 +1,53 @@
|
||||
SRCS = mongoose.c test/unit_test.c test/packed_fs.c
|
||||
HDRS = $(wildcard src/*.h)
|
||||
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1
|
||||
WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion
|
||||
DEFS ?= -DMG_MAX_HTTP_HEADERS=7 -DMG_ENABLE_LINES -DMG_ENABLE_PACKED_FS=1 -DMG_ENABLE_SSI=1
|
||||
C_WARN ?= -Wmissing-prototypes -Wstrict-prototypes
|
||||
WARN ?= -W -Wall -Werror -Wshadow -Wdouble-promotion -fno-common -Wconversion -Wundef $(C_WARN)
|
||||
OPTS ?= -O3 -g3
|
||||
VALGRIND_OPTS ?= -O0 -g3
|
||||
INCS ?= -Isrc -I.
|
||||
CFLAGS ?= $(OPTS) $(WARN) $(INCS) $(DEFS) $(TFLAGS) $(EXTRA)
|
||||
SSL ?= MBEDTLS
|
||||
CWD ?= $(realpath $(CURDIR))
|
||||
DOCKER ?= docker run --rm -e Tmp=. -e WINEDEBUG=-all -v $(CWD):$(CWD) -w $(CWD)
|
||||
VCFLAGS = /nologo /W3 /O2 /I. $(DEFS) $(TFLAGS)
|
||||
IPV6 ?= 1
|
||||
ASAN_OPTIONS ?=
|
||||
EXAMPLES := $(wildcard examples/*)
|
||||
ASAN ?= -fsanitize=address,undefined -fno-sanitize-recover=all
|
||||
ASAN_OPTIONS ?= detect_leaks=1
|
||||
EXAMPLES := $(dir $(wildcard examples/*/Makefile))
|
||||
PREFIX ?= /usr/local
|
||||
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"
|
||||
MBEDTLS ?= /usr
|
||||
MBEDTLS ?= /usr/local
|
||||
CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS)/include -I/usr/include
|
||||
LDFLAGS ?= -L$(MBEDTLS)/lib -lmbedtls -lmbedcrypto -lmbedx509
|
||||
endif
|
||||
ifeq "$(SSL)" "OPENSSL"
|
||||
OPENSSL ?= /usr
|
||||
OPENSSL ?= /usr/local
|
||||
CFLAGS += -DMG_ENABLE_OPENSSL=1 -I$(OPENSSL)/include
|
||||
LDFLAGS ?= -L$(OPENSSL)/lib -lssl -lcrypto
|
||||
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:
|
||||
@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
|
||||
./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
|
||||
mg_prefix: mongoose.c mongoose.h
|
||||
@@ -42,43 +55,56 @@ mg_prefix: mongoose.c mongoose.h
|
||||
|
||||
# C++ build
|
||||
test++: CC = g++
|
||||
test++: C_WARN =
|
||||
test++: WARN += -Wno-shadow -Wno-missing-field-initializers -Wno-deprecated
|
||||
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
|
||||
unamalgamated: $(HDRS) Makefile test/packed_fs.c
|
||||
$(CC) src/*.c test/packed_fs.c test/unit_test.c $(CFLAGS) $(LDFLAGS) -g -o unit_test
|
||||
|
||||
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
|
||||
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
|
||||
$(RUN) ./fuzzer
|
||||
|
||||
# make CC=/usr/local/opt/llvm\@8/bin/clang ASAN_OPTIONS=detect_leaks=1
|
||||
test: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6) -fsanitize=address#,undefined
|
||||
test: mongoose.h Makefile $(SRCS)
|
||||
unit_test: Makefile mongoose.h $(SRCS)
|
||||
$(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
|
||||
|
||||
coverage: test
|
||||
gcov -l -n *.gcno | sed '/^$$/d' | sed 'N;s/\n/ /'
|
||||
gcov mongoose.gcno >/dev/null
|
||||
gcov -t mongoose.c > mongoose.gcov
|
||||
|
||||
upload-coverage: coverage
|
||||
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 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)
|
||||
$(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)
|
||||
$(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: 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
|
||||
# Note: for some reason, a binary built with mingw g++, fails to run
|
||||
|
||||
#linux: CFLAGS += -DMG_ENABLE_IPV6=$(IPV6)
|
||||
linux: CFLAGS += -fsanitize=address,undefined
|
||||
linux: IPV6=0
|
||||
linux: Makefile mongoose.h $(SRCS)
|
||||
$(DOCKER) mdashnet/cc2 gcc $(SRCS) $(CFLAGS) $(LDFLAGS) -o 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
|
||||
|
||||
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
|
||||
(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:
|
||||
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
|
||||
@@ -4,7 +4,6 @@
|
||||
[](https://github.com/cesanta/mongoose/actions)
|
||||
[](https://codecov.io/gh/cesanta/mongoose)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
|
||||
[](https://gitter.im/cesanta/mongoose)
|
||||
|
||||
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
|
||||
@@ -33,15 +32,11 @@ robust, and easy. Features include:
|
||||
interface on devices, RESTful API services, telemetry data exchange, remote
|
||||
control for a product, remote software updates, remote monitoring, and others
|
||||
- Deployed to hundreds of millions devices in production environment worldwide
|
||||
- See [Case Studies](https://cesanta.com/case-studies.html) from our respected
|
||||
customers like Schneider Electric (industrial automation), Broadcom (semiconductors), Schenck Process
|
||||
(industrial engineering), and others
|
||||
- See [Testimonials](https://cesanta.com/testimonials.html) from engineers that
|
||||
integrated Mongoose in their commercial products
|
||||
- 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)
|
||||
- See [Case Studies](https://mongoose.ws/case-studies/) from our respected
|
||||
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
|
||||
- See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products
|
||||
- We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and integration
|
||||
assistance - don't hesitate to [contact us](https://mongoose.ws/contact/)
|
||||
|
||||
|
||||
# Security
|
||||
@@ -80,20 +75,6 @@ This software is often used together with Mongoose:
|
||||
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 are welcome! Please follow the guidelines below:
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# This is a collection of low-level drivers for the experimental
|
||||
# built-in TCP/IP stack
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 *);
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/captive-dns-portal/
|
||||
@@ -21,11 +21,14 @@ uint8_t answer[] = {
|
||||
};
|
||||
|
||||
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
|
||||
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) {
|
||||
char buf[12 + n + sizeof(answer)];
|
||||
char buf[512];
|
||||
struct mg_dns_header *h = (struct mg_dns_header *) buf;
|
||||
memset(buf, 0, sizeof(buf)); // Clear the whole datagram
|
||||
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
|
||||
memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
# Main dashboard
|
||||
|
||||
A main dashboard page shows internal data, live video stream, and a form
|
||||
|
||||

|
||||
|
||||
A live log page shows live log stream coming from device
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 122 KiB |
@@ -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);
|
||||
@@ -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,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 +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 +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}();
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -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>
|
||||
@@ -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 +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});
|
||||
@@ -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
|
||||
@@ -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%; }
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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 +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}();
|
||||
@@ -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,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
|
||||
@@ -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; }
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
# Main dashboard
|
||||
|
||||
A main dashboard page shows device settings form, and live chat
|
||||
|
||||

|
||||
@@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 309 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -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>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>example</title>
|
||||
<title>Device Dashboard</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="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>
|
||||
<body></body>
|
||||
<script type="module" src="main.js"></script>
|
||||
</html>
|
||||
@@ -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);
|
||||
@@ -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 +1 @@
|
||||
# An example ESP32 application
|
||||
|
||||
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
|
||||
```
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/esp32/
|
||||
@@ -1,5 +1,4 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
"wifi.c"
|
||||
"../../../mongoose.c"
|
||||
INCLUDE_DIRS "../../..")
|
||||
"mongoose.c")
|
||||
component_compile_options(-DMG_ENABLE_LINES)
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define WIFI_SSID "WIFI_NETWORK" // SET THIS!
|
||||
#define WIFI_PASS "WIFI_PASSWORD" // SET THIS!
|
||||
#define WIFI_SSID "VMDF554B9" // SET THIS!
|
||||
#define WIFI_PASS "Mp7wjmamPafa" // SET THIS!
|
||||
#define FS_ROOT "/spiffs"
|
||||
|
||||
// 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) {
|
||||
// Mount filesystem
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
|
||||
int res = esp_vfs_spiffs_register(&conf);
|
||||
LOG(res == ESP_OK ? LL_INFO : LL_ERROR, ("FS %s, %d", conf.base_path, res));
|
||||
mg_file_printf(FS_ROOT "/hello.txt", "%s", "hello from ESP");
|
||||
MG_INFO(("FS %s, %d", conf.base_path, res));
|
||||
mg_file_printf(&mg_fs_posix, FS_ROOT "/hello.txt", "%s", "hello from ESP");
|
||||
|
||||
// Setup wifi. This function is implemented in wifi.c
|
||||
// It blocks until connected to the configured WiFi network
|
||||
@@ -45,9 +39,9 @@ void app_main(void) {
|
||||
|
||||
// Connected to WiFi, now start HTTP server
|
||||
struct mg_mgr mgr;
|
||||
mg_log_set("3");
|
||||
mg_log_set("4");
|
||||
mg_mgr_init(&mgr);
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../mongoose.c
|
||||
@@ -0,0 +1 @@
|
||||
../../../mongoose.h
|
||||
@@ -35,14 +35,14 @@ static void event_handler(void *arg, esp_event_base_t event_base,
|
||||
if (s_retry_num < 3) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
LOG(LL_INFO, ("retry to connect to the AP"));
|
||||
MG_INFO(("retry to connect to the AP"));
|
||||
} else {
|
||||
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) {
|
||||
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;
|
||||
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_config(ESP_IF_WIFI_STA, &c));
|
||||
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,
|
||||
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||
pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
|
||||
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) {
|
||||
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 {
|
||||
LOG(LL_ERROR, ("UNEXPECTED EVENT"));
|
||||
MG_ERROR(("UNEXPECTED EVENT"));
|
||||
}
|
||||
|
||||
/* The event will not be processed after unregister */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
#CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
@@ -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);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ static void run_mongoose(void) {
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, SERVER_URL, cb, &mgr); // Listening server
|
||||
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);
|
||||
mg_mgr_free(&mgr);
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ static void event_handler(void *arg, esp_event_base_t event_base,
|
||||
if (s_retry_num < 3) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
LOG(LL_DEBUG, ("retry to connect to the AP"));
|
||||
MG_DEBUG(("retry to connect to the AP"));
|
||||
} else {
|
||||
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) {
|
||||
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;
|
||||
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);
|
||||
|
||||
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) {
|
||||
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 {
|
||||
LOG(LL_ERROR, ("UNEXPECTED EVENT"));
|
||||
MG_ERROR(("UNEXPECTED EVENT"));
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/
|
||||
@@ -1,5 +1,10 @@
|
||||
// Copyright (c) 2020 Cesanta Software Limited
|
||||
// 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"
|
||||
|
||||
@@ -7,20 +12,29 @@
|
||||
// /upload - Saves the next file chunk
|
||||
// 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, "/upload")) {
|
||||
mg_http_upload(c, hm, "/tmp");
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (ev == MG_EV_HTTP_CHUNK && mg_http_match_uri(hm, "/upload")) {
|
||||
MG_INFO(("Got chunk len %lu", (unsigned long) hm->chunk.len));
|
||||
MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
|
||||
// MG_INFO(("Chunk data:\n%.*s", (int) hm->chunk.len, hm->chunk.ptr));
|
||||
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) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer t1;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_log_set("3");
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -16,21 +16,49 @@
|
||||
<div id="info">
|
||||
Mongoose always buffers a full HTTP message before invoking
|
||||
MG_EV_HTTP_MSG event. Big POST request require of lot
|
||||
of RAM to buffer everything. Therefore, in order to upload large
|
||||
files on memory-constrained system, a large file should be send
|
||||
in small chunks.
|
||||
<br/><br/>
|
||||
In this example, JavaScript code on this page sends uploaded
|
||||
file in 2k chunks using an <code>/upload</code> endpoint.
|
||||
Uploaded file is stored in <code>/tmp</code> directory by the helper
|
||||
API function <code>mg_http_upload()</code>
|
||||
of RAM to buffer everything.
|
||||
<br><br>
|
||||
In order to upload large files to a memory-constrained system, use
|
||||
<code>MG_EV_HTTP_CHUNK</code> on a server side. It fires when
|
||||
a partial HTTP message has been received (or a chunk-encoded chunk).
|
||||
Use <code>mg_http_delete_chunk()</code> to release chunk memory.
|
||||
When 0-sized chunk is received, that's the end of the message.
|
||||
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 id="wrapper">
|
||||
<input type="file" id="el1" style="display: none"/>
|
||||
<button id="el2">choose file...</button>
|
||||
<div id="el3"></div>
|
||||
<div id="el3" style="margin-top: 1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/
|
||||
@@ -19,16 +19,15 @@
|
||||
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;
|
||||
LOG(LL_INFO, ("New request to: [%.*s], body size: %lu", (int) hm->uri.len,
|
||||
hm->uri.ptr, (unsigned long) hm->body.len));
|
||||
MG_INFO(("New request to: [%.*s], body size: %lu", (int) hm->uri.len,
|
||||
hm->uri.ptr, (unsigned long) hm->body.len));
|
||||
if (mg_http_match_uri(hm, "/upload")) {
|
||||
struct mg_http_part part;
|
||||
size_t ofs = 0;
|
||||
while ((ofs = mg_http_next_multipart(hm->body, ofs, &part)) > 0) {
|
||||
LOG(LL_INFO,
|
||||
("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
|
||||
(int) part.name.len, part.name.ptr, (int) part.filename.len,
|
||||
part.filename.ptr, (unsigned long) part.body.len));
|
||||
MG_INFO(("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
|
||||
(int) part.name.len, part.name.ptr, (int) part.filename.len,
|
||||
part.filename.ptr, (unsigned long) part.body.len));
|
||||
}
|
||||
mg_http_reply(c, 200, "", "Thank you!");
|
||||
} else {
|
||||
@@ -40,7 +39,6 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer t1;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
mg_log_set("3");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PROG ?= example
|
||||
CFLAGS ?= -DMG_ENABLE_LINES $(EXTRA)
|
||||
ROOT ?= $(realpath $(CURDIR)/../..)
|
||||
CFLAGS ?= -DMG_ENABLE_LINES $(EXTRA)
|
||||
LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2
|
||||
|
||||
ifneq ($(MBEDTLS),)
|
||||
@@ -24,5 +24,19 @@ linux: CFLAGS += -O2 -g -fsanitize=address,undefined,shift,null,return,bounds,al
|
||||
linux: CC = $(LIN) cc
|
||||
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:
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/http-client/
|
||||
@@ -13,10 +13,20 @@
|
||||
|
||||
// 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_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
|
||||
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
|
||||
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
|
||||
int content_length = s_post_data ? strlen(s_post_data) : 0;
|
||||
mg_printf(c,
|
||||
"GET %s HTTP/1.0\r\n"
|
||||
"%s %s HTTP/1.0\r\n"
|
||||
"Host: %.*s\r\n"
|
||||
"Content-Type: octet-stream\r\n"
|
||||
"Content-Length: %d\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) {
|
||||
// Response is received. Print it
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
@@ -44,13 +59,13 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr; // Event manager
|
||||
bool done = false; // Event handler flips it to true
|
||||
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
|
||||
struct mg_mgr mgr; // Event manager
|
||||
bool done = false; // Event handler flips it to true
|
||||
if (argc > 1) s_url = argv[1]; // Use URL provided in the command line
|
||||
mg_log_set("4"); // Set to 0 to disable debug
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
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
|
||||
return 0;
|
||||
}
|
||||
@@ -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);
|
||||
// CONNECT response - tunnel is established
|
||||
connected = true;
|
||||
LOG(LL_DEBUG,
|
||||
MG_DEBUG(
|
||||
("Connected to proxy, status: %.*s", (int) hm.uri.len, hm.uri.ptr));
|
||||
mg_iobuf_del(&c->recv, 0, n);
|
||||
// Send request to the target server
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
PROG ?= example
|
||||
|
||||
CFLAGS ?= -W -Wall -DMG_ENABLE_LINES=1
|
||||
ifneq ($(MBEDTLS_DIR),)
|
||||
CFLAGS += -DMG_ENABLE_MBEDTLS=1 -I$(MBEDTLS_DIR)/include -I/usr/include
|
||||
CFLAGS += -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedcrypto -lmbedx509
|
||||
@@ -12,7 +13,7 @@ all: $(PROG)
|
||||
$(DEBUGGER) ./$(PROG) $(ARGS)
|
||||
|
||||
$(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:
|
||||
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
|
||||
@@ -4,7 +4,7 @@
|
||||
// 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 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"}
|
||||
// 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);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_http_match_uri(hm, "/api/f1")) {
|
||||
mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); // Serve REST
|
||||
if (mg_http_match_uri(hm, "/api/stats")) {
|
||||
// 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/*")) {
|
||||
mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len,
|
||||
hm->uri.ptr);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PROG ?= example
|
||||
CFLAGS ?= -DMG_IO_SIZE=8192
|
||||
CFLAGS ?= -DMG_IO_SIZE=128
|
||||
MBEDTLS_DIR ?=
|
||||
|
||||
ifeq "$(MBEDTLS_DIR)" ""
|
||||
|
||||
@@ -28,25 +28,20 @@ static void forward_request(struct mg_http_message *hm,
|
||||
}
|
||||
mg_send(c, "\r\n", 2);
|
||||
mg_send(c, hm->body.ptr, hm->body.len);
|
||||
LOG(LL_DEBUG, ("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr,
|
||||
(int) hm->uri.len, hm->uri.ptr));
|
||||
MG_DEBUG(("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.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;
|
||||
if (ev == MG_EV_READ) {
|
||||
if (c2 != NULL) {
|
||||
// All incoming data from the backend, forward to the client
|
||||
mg_send(c2, c->recv.buf, c->recv.len);
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
}
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
if (mg_url_is_ssl(s_backend_url)) {
|
||||
struct mg_tls_opts opts = {.ca = "ca.pem"};
|
||||
mg_tls_init(c, &opts);
|
||||
}
|
||||
// All incoming data from the backend, forward to the client
|
||||
if (c2 != NULL) mg_send(c2, c->recv.buf, c->recv.len);
|
||||
mg_iobuf_del(&c->recv, 0, c->recv.len);
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
if (c2 != NULL) c2->fn_data = NULL;
|
||||
}
|
||||
(void)ev_data;
|
||||
(void) ev_data;
|
||||
}
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_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;
|
||||
// Client request, create backend connection Note that we're passing
|
||||
// client connection `c` as fn_data for the created backend connection.
|
||||
c2 = mg_connect(c->mgr, s_backend_url, forward_fn, c);
|
||||
c->fn_data = c2;
|
||||
|
||||
if (c2 != NULL) {
|
||||
forward_request(hm, c2);
|
||||
c2 = mg_connect(c->mgr, s_backend_url, fn2, c);
|
||||
if (c2 == NULL) {
|
||||
mg_error(c, "Cannot create backend connection");
|
||||
} else {
|
||||
c->is_closing = 1;
|
||||
}
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
if (mg_url_is_ssl(s_backend_url)) {
|
||||
struct mg_tls_opts opts = {.ca = "ca.pem"};
|
||||
mg_tls_init(c, &opts);
|
||||
if (mg_url_is_ssl(s_backend_url)) {
|
||||
struct mg_tls_opts opts = {.ca = "ca.pem"};
|
||||
mg_tls_init(c2, &opts);
|
||||
}
|
||||
c->fn_data = c2;
|
||||
forward_request(hm, c2);
|
||||
c2->is_hexdumping = 1;
|
||||
}
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
if (c2 != NULL) c2->is_closing = 1;
|
||||
c->fn_data = NULL;
|
||||
if (c2 != NULL) c2->fn_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PROG ?= example
|
||||
ROOT ?= $(realpath $(CURDIR)/../..)
|
||||
DEFS ?= -DMG_ENABLE_LINES=1
|
||||
CFLAGS ?= -I../.. -W -Wall -DMG_ENABLE_IPV6=1 $(DEFS) $(EXTRA)
|
||||
DEFS ?= -DMG_ENABLE_LINES=1 -DMG_ENABLE_IPV6=1 -DMG_ENABLE_SSI=1
|
||||
CFLAGS ?= -I../.. -W -Wall $(DEFS) $(EXTRA)
|
||||
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
|
||||
LIN = docker run -it --rm -v $(ROOT):$(ROOT) -w $(CURDIR) mdashnet/cc2
|
||||
@@ -15,6 +15,9 @@ $(PROG): main.c Makefile
|
||||
mongoose.exe: main.c
|
||||
$(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: CFLAGS += -O2 -fsanitize=address,undefined,shift,null,return,bounds,alignment,object-size,bool,enum -static-libasan
|
||||
#linux: CC = $(LIN) cc
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/http-server/
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
static const char *s_debug_level = "2";
|
||||
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_ssi_pattern = "#.shtml";
|
||||
static const char *s_ssi_pattern = "#.html";
|
||||
|
||||
// Handle interrupts, like Ctrl-C
|
||||
static int s_signo;
|
||||
@@ -20,9 +20,18 @@ static void signal_handler(int signo) {
|
||||
// Simply serve static files from `s_root_dir`
|
||||
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_serve_opts opts = {.root_dir = s_root_dir,
|
||||
.ssi_pattern = s_ssi_pattern};
|
||||
mg_http_serve_dir(c, ev_data, &opts);
|
||||
struct mg_http_message *hm = ev_data, tmp = {0};
|
||||
struct mg_str unknown = mg_str_n("?", 1), *cl;
|
||||
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;
|
||||
}
|
||||
@@ -32,16 +41,17 @@ static void usage(const char *prog) {
|
||||
"Mongoose v.%s\n"
|
||||
"Usage: %s OPTIONS\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"
|
||||
" -l ADDR - listening address, 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,
|
||||
s_root_dir, s_listening_address);
|
||||
MG_VERSION, prog, s_enable_hexdump, s_ssi_pattern, s_root_dir,
|
||||
s_listening_address, s_debug_level);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char path[MG_PATH_MAX] = ".";
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *c;
|
||||
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
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
mg_log_set(s_debug_level);
|
||||
mg_mgr_init(&mgr);
|
||||
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",
|
||||
s_listening_address));
|
||||
MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT",
|
||||
s_listening_address));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1;
|
||||
|
||||
// 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);
|
||||
mg_mgr_free(&mgr);
|
||||
LOG(LL_INFO, ("Exiting on signal %d", s_signo));
|
||||
MG_INFO(("Exiting on signal %d", s_signo));
|
||||
return 0;
|
||||
}
|
||||
@@ -32,19 +32,22 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
|
||||
// Send request
|
||||
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"
|
||||
"\r\n",
|
||||
mg_url_uri(s_url), (int) host.len, host.ptr);
|
||||
} else if (ev == MG_EV_HTTP_CHUNK) {
|
||||
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);
|
||||
if (hm->chunk.len == 0) *(bool *) fn_data = true; // Last chunk
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
// Response is received. Print it
|
||||
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
|
||||
*(bool *) fn_data = true; // Tell event loop to stop
|
||||
} 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[]) {
|
||||
struct mg_mgr mgr; // Event manager
|
||||
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
|
||||
mg_log_set("3"); // Set to 0 to disable debug
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
|
||||
while (!done) mg_mgr_poll(&mgr, 1000); // Infinite event loop
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
// SOFTWARE.
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#if defined(_MSC_VER)
|
||||
#define alloca(x) _alloca(x)
|
||||
#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) {
|
||||
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
|
||||
for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
|
||||
@@ -36,10 +44,14 @@ static int mjson_esc(int c, int esc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mjson_escape(int c) {
|
||||
return mjson_esc(c, 1);
|
||||
}
|
||||
|
||||
static int mjson_pass_string(const char *s, int len) {
|
||||
int 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++;
|
||||
} else if (s[i] == '\0') {
|
||||
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;
|
||||
} else if (c == '-' || ((c >= '0' && c <= '9'))) {
|
||||
char *end = NULL;
|
||||
strtod(&s[i], &end);
|
||||
if (end != NULL) i += end - &s[i] - 1;
|
||||
mystrtod(&s[i], &end);
|
||||
if (end != NULL) i += (int) (end - &s[i] - 1);
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
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 {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int pos; // Current path position
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
@@ -169,86 +181,111 @@ struct msjon_get_data {
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *data = (struct msjon_get_data *) ud;
|
||||
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1,
|
||||
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len,
|
||||
// s + off);
|
||||
if (data->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
static int kcmp(const char *a, const char *b, int n) {
|
||||
int i = 0, j = 0, r = 0;
|
||||
for (i = 0, j = 0; j < n; i++, j++) {
|
||||
if (b[i] == '\\') i++;
|
||||
if ((r = a[j] - b[i]) != 0) return r;
|
||||
}
|
||||
// printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (tok == '{') {
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == '[') {
|
||||
if (data->d1 == data->d2 && data->path[data->pos] == '[') {
|
||||
data->i1 = 0;
|
||||
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL);
|
||||
if (data->i1 == data->i2) {
|
||||
data->d2++;
|
||||
data->pos += 3;
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *d = (struct msjon_get_data *) ud;
|
||||
#if 0
|
||||
printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1,
|
||||
d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off,
|
||||
d->path + d->pos);
|
||||
#endif
|
||||
if (d->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
if (tok == '{' || tok == '[') {
|
||||
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;
|
||||
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
|
||||
d->d1++;
|
||||
} else if (tok == '}' || tok == ']') {
|
||||
data->d1--;
|
||||
// data->d2--;
|
||||
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) {
|
||||
data->tok = tok - 2;
|
||||
if (data->tokptr) *data->tokptr = s + data->obj;
|
||||
if (data->toklen) *data->toklen = off - data->obj + 1;
|
||||
if (tok == ']' && d->d1 == d->d2) d->i1 = 0;
|
||||
d->d1--;
|
||||
// printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1,
|
||||
// d->i2, d->obj);
|
||||
if (!d->path[d->pos] && d->d1 == d->d2 && d->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;
|
||||
}
|
||||
} 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)) {
|
||||
// printf("TOK --> %d\n", tok);
|
||||
if (data->d1 == data->d2 && !data->path[data->pos]) {
|
||||
data->tok = tok;
|
||||
if (data->tokptr) *data->tokptr = s + off;
|
||||
if (data->toklen) *data->toklen = len;
|
||||
// printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2);
|
||||
if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) {
|
||||
d->tok = tok;
|
||||
if (d->tokptr) *d->tokptr = s + off;
|
||||
if (d->toklen) *d->toklen = len;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tokptr, toklen, MJSON_TOK_INVALID};
|
||||
int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tp, tl, MJSON_TOK_INVALID};
|
||||
if (jp[0] != '$') return MJSON_TOK_INVALID;
|
||||
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return (enum mjson_tok) data.tok;
|
||||
if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return data.tok;
|
||||
}
|
||||
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v) {
|
||||
const char *p;
|
||||
int tok, n;
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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) {
|
||||
int 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);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
to[j] = (char) (unsigned char) c;
|
||||
i++;
|
||||
} else {
|
||||
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;
|
||||
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++) {
|
||||
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
|
||||
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);
|
||||
((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
static unsigned char mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
return (unsigned char) (c - 'A');
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
return (unsigned char) (c + 26 - 'a');
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
return (unsigned char) (c + 52 - '0');
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} 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;
|
||||
int len = 0;
|
||||
while (src + 3 < end && len < dlen) {
|
||||
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (a << 2) | (b >> 4);
|
||||
unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4));
|
||||
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) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
dst[len++] = (char) (unsigned char) ((c << 6) | d);
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
@@ -428,92 +480,177 @@ int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
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) {
|
||||
char *s, *buf = *(char **) fndata;
|
||||
int curlen = buf == NULL ? 0 : strlen(buf);
|
||||
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) {
|
||||
size_t curlen = buf == NULL ? 0 : strlen(buf);
|
||||
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;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
memcpy(s + curlen, ptr, (size_t) len);
|
||||
s[curlen + (size_t) len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
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) {
|
||||
(void) ptr;
|
||||
(void) userdata;
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata) {
|
||||
return fwrite(ptr, 1, len, (FILE *) userdata);
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) {
|
||||
return fn(buf, len, fnd);
|
||||
}
|
||||
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) {
|
||||
unsigned long v = (unsigned long) val, s = 0, n, i;
|
||||
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 is_signed) {
|
||||
char buf[20];
|
||||
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
|
||||
return fn(buf, len, fndata);
|
||||
int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) {
|
||||
return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s);
|
||||
}
|
||||
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
const char *fmt = (is_signed ? "%ld" : "%lu");
|
||||
int len = snprintf(buf, sizeof(buf), fmt, value);
|
||||
return fn(buf, len, fndata);
|
||||
static int addexp(char *buf, int e, int sign) {
|
||||
int n = 0;
|
||||
buf[n++] = 'e';
|
||||
buf[n++] = (char) sign;
|
||||
if (e > 400) return 0;
|
||||
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,
|
||||
const char *fmt) {
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
int i, s = 0, n = 0, e = 0;
|
||||
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++] = '-';
|
||||
|
||||
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = mjson_esc(s[i], 1);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fndata);
|
||||
n += fn(&c, 1, fndata);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fndata);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
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 *fnd, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fnd);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = (char) (unsigned char) mjson_escape(s[i]);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fnd);
|
||||
n += fn(&c, 1, fnd);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fnd);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fnd);
|
||||
}
|
||||
|
||||
#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) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
int i, len = fn("\"", 1, fnd);
|
||||
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;
|
||||
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 + 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 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt,
|
||||
va_list *ap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (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];
|
||||
}
|
||||
if (fc == 'Q') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_str(fn, fnd, buf ? buf : "",
|
||||
buf ? (int) strlen(buf) : 0);
|
||||
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_str(fn, fnd, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'd' || fc == 'u') {
|
||||
int is_signed = (fc == 'd');
|
||||
if (is_long) {
|
||||
long val = va_arg(ap, long);
|
||||
n += mjson_print_long(fn, fndata, val, is_signed);
|
||||
long val = va_arg(*ap, long);
|
||||
n += mjson_print_long(fn, fnd, val, is_signed);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, val, is_signed);
|
||||
int val = va_arg(*ap, int);
|
||||
n += mjson_print_int(fn, fnd, val, is_signed);
|
||||
}
|
||||
} else if (fc == 'B') {
|
||||
const char *s = va_arg(ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fndata, s, strlen(s));
|
||||
const char *s = va_arg(*ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fnd, s, (int) strlen(s));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, strlen(buf));
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf));
|
||||
} else if (strncmp(&fmt[i], ".*s", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_buf(fn, fnd, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'g') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g");
|
||||
} else if (fc == 'f') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f");
|
||||
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6);
|
||||
} else if (strncmp(&fmt[i], ".*g", 3) == 0) {
|
||||
int width = va_arg(*ap, int);
|
||||
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width);
|
||||
i += 2;
|
||||
#if MJSON_ENABLE_BASE64
|
||||
} else if (fc == 'V') {
|
||||
int len = va_arg(ap, int);
|
||||
const char *buf = va_arg(ap, const char *);
|
||||
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
const char *buf = va_arg(*ap, const char *);
|
||||
n += mjson_print_b64(fn, fnd, (unsigned char *) buf, len);
|
||||
#endif
|
||||
} else if (fc == 'H') {
|
||||
const char *hex = "0123456789abcdef";
|
||||
int i, len = va_arg(ap, int);
|
||||
const unsigned char *p = va_arg(ap, const unsigned char *);
|
||||
n += fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata);
|
||||
n += fn(&hex[p[i] & 15], 1, fndata);
|
||||
int j, len = va_arg(*ap, int);
|
||||
const unsigned char *p = va_arg(*ap, const unsigned char *);
|
||||
n += fn("\"", 1, fnd);
|
||||
for (j = 0; j < len; j++) {
|
||||
n += fn(&hex[(p[j] >> 4) & 15], 1, fnd);
|
||||
n += fn(&hex[p[j] & 15], 1, fnd);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
n += fn("\"", 1, fnd);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fnd, ap);
|
||||
}
|
||||
i++;
|
||||
} 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;
|
||||
}
|
||||
|
||||
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;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
len = mjson_vprintf(fn, fnd, fmt, &ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
static int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* 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;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
@@ -610,8 +747,9 @@ double strtod(const char *str, char **end) {
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
} else if (*p == '+') {
|
||||
++p;
|
||||
}
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
@@ -620,8 +758,9 @@ double strtod(const char *str, char **end) {
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
} else if (*p != '.') {
|
||||
goto done;
|
||||
}
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
@@ -644,30 +783,13 @@ double strtod(const char *str, char **end) {
|
||||
|
||||
/* exponential part */
|
||||
if ((*p == 'E') || (*p == 'e')) {
|
||||
int e = 0;
|
||||
++p;
|
||||
|
||||
sign = 1;
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} 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;
|
||||
|
||||
int i, e = 0, neg = 0;
|
||||
p++;
|
||||
if (*p == '-') p++, neg++;
|
||||
if (*p == '+') p++;
|
||||
while (is_digit(*p)) e = e * 10 + *p++ - '0';
|
||||
if (neg) e = -e;
|
||||
#if 0
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
@@ -678,10 +800,9 @@ double strtod(const char *str, char **end) {
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
#endif
|
||||
for (i = 0; i < e; i++) d *= 10;
|
||||
for (i = 0; i < -e; i++) d /= 10;
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
@@ -692,7 +813,6 @@ done:
|
||||
if (end) *end = (char *) a;
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
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;
|
||||
len += fn("{", 1, userdata);
|
||||
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
char *path = (char *) alloca((size_t) klen + 1);
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, klen - 2);
|
||||
memcpy(path + 2, s + koff + 1, (size_t) (klen - 2));
|
||||
path[klen] = '\0';
|
||||
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
|
||||
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
|
||||
off = 0;
|
||||
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
char *path = (char *) alloca((size_t) klen + 1);
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2));
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
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,
|
||||
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;
|
||||
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
|
||||
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 i = 0, j = 0, ni = 0, nj = 0;
|
||||
@@ -845,20 +953,15 @@ int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
|
||||
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,
|
||||
const char *message, const char *data_fmt,
|
||||
va_list ap) {
|
||||
va_list *ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata,
|
||||
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
|
||||
r->id, code, message == NULL ? "" : message);
|
||||
if (data_fmt != NULL) {
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
@@ -868,12 +971,12 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...) {
|
||||
va_list ap;
|
||||
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);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
va_list ap) {
|
||||
va_list *ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
|
||||
if (result_fmt != NULL) {
|
||||
@@ -888,49 +991,41 @@ void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, result_fmt);
|
||||
jsonrpc_return_successv(r, result_fmt, ap);
|
||||
jsonrpc_return_successv(r, result_fmt, &ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len,
|
||||
mjson_print_fn_t fn, void *fndata, void *ud) {
|
||||
const char *result = NULL, *error = NULL;
|
||||
int result_sz = 0, error_sz = 0;
|
||||
struct jsonrpc_method *m = NULL;
|
||||
struct jsonrpc_userdata d;
|
||||
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;
|
||||
struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fndata, ud};
|
||||
|
||||
// Is is a response frame?
|
||||
mjson_find(req, req_sz, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz);
|
||||
mjson_find(buf, len, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
|
||||
req_sz, req);
|
||||
ctx->in_len = 0;
|
||||
len, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// id and params are optional
|
||||
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len);
|
||||
mjson_find(buf, len, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(buf, len, "$.params", &r.params, &r.params_len);
|
||||
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
|
||||
r.method_len - 2) > 0) {
|
||||
if (r.params == NULL) r.params = "";
|
||||
r.userdata = m->cbdata;
|
||||
m->cb(&r);
|
||||
break;
|
||||
}
|
||||
@@ -947,35 +1042,25 @@ static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
int len = 0;
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
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;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
void jsonrpc_list(struct jsonrpc_request *r) {
|
||||
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 *userdata) {
|
||||
void *response_cb_data) {
|
||||
ctx->methods = NULL;
|
||||
ctx->response_cb = response_cb;
|
||||
ctx->userdata = userdata;
|
||||
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++;
|
||||
}
|
||||
ctx->response_cb_data = response_cb_data;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -23,7 +23,6 @@
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -40,50 +39,40 @@
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#define MJSON_ENABLE_MERGE 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#define MJSON_ENABLE_PRETTY 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_NEXT
|
||||
#define MJSON_ENABLE_NEXT 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MJSON_RPC_IN_BUF_SIZE
|
||||
#define MJSON_RPC_IN_BUF_SIZE 256
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_DYNBUF_CHUNK
|
||||
#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MJSON_ERROR_INVALID_INPUT = -1,
|
||||
MJSON_ERROR_TOO_DEEP = -2,
|
||||
};
|
||||
|
||||
enum mjson_tok {
|
||||
MJSON_TOK_INVALID = 0,
|
||||
MJSON_TOK_KEY = 1,
|
||||
MJSON_TOK_STRING = 11,
|
||||
MJSON_TOK_NUMBER = 12,
|
||||
MJSON_TOK_TRUE = 13,
|
||||
MJSON_TOK_FALSE = 14,
|
||||
MJSON_TOK_NULL = 15,
|
||||
MJSON_TOK_ARRAY = 91,
|
||||
MJSON_TOK_OBJECT = 123,
|
||||
};
|
||||
#define MJSON_ERROR_INVALID_INPUT (-1)
|
||||
#define MJSON_ERROR_TOO_DEEP (-2)
|
||||
#define MJSON_TOK_INVALID 0
|
||||
#define MJSON_TOK_KEY 1
|
||||
#define MJSON_TOK_STRING 11
|
||||
#define MJSON_TOK_NUMBER 12
|
||||
#define MJSON_TOK_TRUE 13
|
||||
#define MJSON_TOK_FALSE 14
|
||||
#define MJSON_TOK_NULL 15
|
||||
#define MJSON_TOK_ARRAY 91
|
||||
#define MJSON_TOK_OBJECT 123
|
||||
#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);
|
||||
@@ -93,8 +82,7 @@ typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
#endif
|
||||
|
||||
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,
|
||||
const char **tokptr, int *toklen);
|
||||
int mjson_find(const char *s, int len, const char *jp, const char **, int *);
|
||||
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_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_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_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_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_file(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_snprintf(char *buf, size_t len, const char *fmt, ...);
|
||||
char *mjson_aprintf(const char *fmt, ...);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#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);
|
||||
|
||||
struct jsonrpc_request {
|
||||
struct jsonrpc_ctx *ctx;
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
@@ -164,7 +156,6 @@ struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
@@ -172,19 +163,16 @@ struct jsonrpc_method {
|
||||
// exported RPC methods.
|
||||
struct jsonrpc_ctx {
|
||||
struct jsonrpc_method *methods;
|
||||
void *userdata;
|
||||
mjson_print_fn_t response_cb;
|
||||
int in_len;
|
||||
char in[MJSON_RPC_IN_BUF_SIZE];
|
||||
void *response_cb_data;
|
||||
};
|
||||
|
||||
// Registers function fn under the given name within the given RPC context
|
||||
#define jsonrpc_ctx_export(ctx, name, fn, ud) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \
|
||||
m.cbdata = (ud); \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
#define jsonrpc_ctx_export(ctx, name, fn) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
@@ -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_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
mjson_print_fn_t fn, void *fndata, void *userdata);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
extern void jsonrpc_list(struct jsonrpc_request *r);
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
#define jsonrpc_export(name, fn) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn))
|
||||
|
||||
#define jsonrpc_process(buf, len, fn, data) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data))
|
||||
|
||||
#define jsonrpc_process_byte(x, fn, data) \
|
||||
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
|
||||
#define jsonrpc_process(buf, len, fn, fnd, ud) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud))
|
||||
|
||||
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#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:
|
||||
git clone --depth 1 https://github.com/mongoose-examples/$(NAME)
|
||||
cp ./../../../mongoose.[ch] $(NAME)
|
||||
make -C $(NAME) build
|
||||
|
||||
clean:
|
||||
@@ -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/
|
||||
@@ -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
|
||||
// SOFTWARE.
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#if defined(_MSC_VER)
|
||||
#define alloca(x) _alloca(x)
|
||||
#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) {
|
||||
const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
|
||||
for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
|
||||
@@ -36,10 +44,14 @@ static int mjson_esc(int c, int esc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mjson_escape(int c) {
|
||||
return mjson_esc(c, 1);
|
||||
}
|
||||
|
||||
static int mjson_pass_string(const char *s, int len) {
|
||||
int 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++;
|
||||
} else if (s[i] == '\0') {
|
||||
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;
|
||||
} else if (c == '-' || ((c >= '0' && c <= '9'))) {
|
||||
char *end = NULL;
|
||||
strtod(&s[i], &end);
|
||||
if (end != NULL) i += end - &s[i] - 1;
|
||||
mystrtod(&s[i], &end);
|
||||
if (end != NULL) i += (int) (end - &s[i] - 1);
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
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 {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int pos; // Current path position
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
@@ -169,86 +181,111 @@ struct msjon_get_data {
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *data = (struct msjon_get_data *) ud;
|
||||
// printf("--> %2x %2d %2d %2d %2d\t'%s'\t'%.*s'\t\t'%.*s'\n", tok, data->d1,
|
||||
// data->d2, data->i1, data->i2, data->path + data->pos, off, s, len,
|
||||
// s + off);
|
||||
if (data->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
static int kcmp(const char *a, const char *b, int n) {
|
||||
int i = 0, j = 0, r = 0;
|
||||
for (i = 0, j = 0; j < n; i++, j++) {
|
||||
if (b[i] == '\\') i++;
|
||||
if ((r = a[j] - b[i]) != 0) return r;
|
||||
}
|
||||
// printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (tok == '{') {
|
||||
if (!data->path[data->pos] && data->d1 == data->d2) data->obj = off;
|
||||
data->d1++;
|
||||
} else if (tok == '[') {
|
||||
if (data->d1 == data->d2 && data->path[data->pos] == '[') {
|
||||
data->i1 = 0;
|
||||
data->i2 = (int) strtod(&data->path[data->pos + 1], NULL);
|
||||
if (data->i1 == data->i2) {
|
||||
data->d2++;
|
||||
data->pos += 3;
|
||||
static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct msjon_get_data *d = (struct msjon_get_data *) ud;
|
||||
#if 0
|
||||
printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1,
|
||||
d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off,
|
||||
d->path + d->pos);
|
||||
#endif
|
||||
if (d->tok != MJSON_TOK_INVALID) return 1; // Found
|
||||
if (tok == '{' || tok == '[') {
|
||||
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;
|
||||
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
|
||||
d->d1++;
|
||||
} else if (tok == '}' || tok == ']') {
|
||||
data->d1--;
|
||||
// data->d2--;
|
||||
if (!data->path[data->pos] && data->d1 == data->d2 && data->obj != -1) {
|
||||
data->tok = tok - 2;
|
||||
if (data->tokptr) *data->tokptr = s + data->obj;
|
||||
if (data->toklen) *data->toklen = off - data->obj + 1;
|
||||
if (tok == ']' && d->d1 == d->d2) d->i1 = 0;
|
||||
d->d1--;
|
||||
// printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1,
|
||||
// d->i2, d->obj);
|
||||
if (!d->path[d->pos] && d->d1 == d->d2 && d->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;
|
||||
}
|
||||
} 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)) {
|
||||
// printf("TOK --> %d\n", tok);
|
||||
if (data->d1 == data->d2 && !data->path[data->pos]) {
|
||||
data->tok = tok;
|
||||
if (data->tokptr) *data->tokptr = s + off;
|
||||
if (data->toklen) *data->toklen = len;
|
||||
// printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2);
|
||||
if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) {
|
||||
d->tok = tok;
|
||||
if (d->tokptr) *d->tokptr = s + off;
|
||||
if (d->toklen) *d->toklen = len;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum mjson_tok mjson_find(const char *s, int len, const char *jp,
|
||||
const char **tokptr, int *toklen) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tokptr, toklen, MJSON_TOK_INVALID};
|
||||
int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) {
|
||||
struct msjon_get_data data = {jp, 1, 0, 0, 0,
|
||||
0, -1, tp, tl, MJSON_TOK_INVALID};
|
||||
if (jp[0] != '$') return MJSON_TOK_INVALID;
|
||||
if (mjson(s, len, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return (enum mjson_tok) data.tok;
|
||||
if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID;
|
||||
return data.tok;
|
||||
}
|
||||
|
||||
int mjson_get_number(const char *s, int len, const char *path, double *v) {
|
||||
const char *p;
|
||||
int tok, n;
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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) {
|
||||
int 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);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
to[j] = (char) (unsigned char) c;
|
||||
i++;
|
||||
} else {
|
||||
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;
|
||||
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++) {
|
||||
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
|
||||
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);
|
||||
((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
static unsigned char mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
return (unsigned char) (c - 'A');
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
return (unsigned char) (c + 26 - 'a');
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
return (unsigned char) (c + 52 - '0');
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} 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;
|
||||
int len = 0;
|
||||
while (src + 3 < end && len < dlen) {
|
||||
int a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (a << 2) | (b >> 4);
|
||||
unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]),
|
||||
c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]);
|
||||
dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4));
|
||||
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) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
dst[len++] = (char) (unsigned char) ((c << 6) | d);
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
@@ -428,92 +480,177 @@ int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
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) {
|
||||
char *s, *buf = *(char **) fndata;
|
||||
int curlen = buf == NULL ? 0 : strlen(buf);
|
||||
if ((s = (char *) realloc(buf, curlen + len + 1)) == NULL) {
|
||||
size_t curlen = buf == NULL ? 0 : strlen(buf);
|
||||
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;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
memcpy(s + curlen, ptr, (size_t) len);
|
||||
s[curlen + (size_t) len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
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) {
|
||||
(void) ptr;
|
||||
(void) userdata;
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_print_file(const char *ptr, int len, void *userdata) {
|
||||
return fwrite(ptr, 1, len, (FILE *) userdata);
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) {
|
||||
return fn(buf, len, fnd);
|
||||
}
|
||||
|
||||
int mjson_print_buf(mjson_print_fn_t fn, void *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) {
|
||||
unsigned long v = (unsigned long) val, s = 0, n, i;
|
||||
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 is_signed) {
|
||||
char buf[20];
|
||||
int len = snprintf(buf, sizeof(buf), is_signed ? "%d" : "%u", value);
|
||||
return fn(buf, len, fndata);
|
||||
int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) {
|
||||
return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s);
|
||||
}
|
||||
|
||||
int mjson_print_long(mjson_print_fn_t fn, void *fndata, long value,
|
||||
int is_signed) {
|
||||
char buf[20];
|
||||
const char *fmt = (is_signed ? "%ld" : "%lu");
|
||||
int len = snprintf(buf, sizeof(buf), fmt, value);
|
||||
return fn(buf, len, fndata);
|
||||
static int addexp(char *buf, int e, int sign) {
|
||||
int n = 0;
|
||||
buf[n++] = 'e';
|
||||
buf[n++] = (char) sign;
|
||||
if (e > 400) return 0;
|
||||
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,
|
||||
const char *fmt) {
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
int i, s = 0, n = 0, e = 0;
|
||||
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++] = '-';
|
||||
|
||||
int mjson_print_str(mjson_print_fn_t fn, void *fndata, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = mjson_esc(s[i], 1);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fndata);
|
||||
n += fn(&c, 1, fndata);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fndata);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
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 *fnd, const char *s, int len) {
|
||||
int i, n = fn("\"", 1, fnd);
|
||||
for (i = 0; i < len; i++) {
|
||||
char c = (char) (unsigned char) mjson_escape(s[i]);
|
||||
if (c) {
|
||||
n += fn("\\", 1, fnd);
|
||||
n += fn(&c, 1, fnd);
|
||||
} else {
|
||||
n += fn(&s[i], 1, fnd);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fnd);
|
||||
}
|
||||
|
||||
#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) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
int i, len = fn("\"", 1, fnd);
|
||||
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;
|
||||
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 + 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 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt,
|
||||
va_list *ap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (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];
|
||||
}
|
||||
if (fc == 'Q') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf ? buf : "", buf ? strlen(buf) : 0);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_str(fn, fnd, buf ? buf : "",
|
||||
buf ? (int) strlen(buf) : 0);
|
||||
} else if (strncmp(&fmt[i], ".*Q", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_str(fn, fndata, buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_str(fn, fnd, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'd' || fc == 'u') {
|
||||
int is_signed = (fc == 'd');
|
||||
if (is_long) {
|
||||
long val = va_arg(ap, long);
|
||||
n += mjson_print_long(fn, fndata, val, is_signed);
|
||||
long val = va_arg(*ap, long);
|
||||
n += mjson_print_long(fn, fnd, val, is_signed);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, val, is_signed);
|
||||
int val = va_arg(*ap, int);
|
||||
n += mjson_print_int(fn, fnd, val, is_signed);
|
||||
}
|
||||
} else if (fc == 'B') {
|
||||
const char *s = va_arg(ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fndata, s, strlen(s));
|
||||
const char *s = va_arg(*ap, int) ? "true" : "false";
|
||||
n += mjson_print_buf(fn, fnd, s, (int) strlen(s));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, strlen(buf));
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf));
|
||||
} else if (strncmp(&fmt[i], ".*s", 3) == 0) {
|
||||
int len = va_arg(ap, int);
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
char *buf = va_arg(*ap, char *);
|
||||
n += mjson_print_buf(fn, fnd, buf, len);
|
||||
i += 2;
|
||||
} else if (fc == 'g') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%g");
|
||||
} else if (fc == 'f') {
|
||||
n += mjson_print_dbl(fn, fndata, va_arg(ap, double), "%f");
|
||||
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6);
|
||||
} else if (strncmp(&fmt[i], ".*g", 3) == 0) {
|
||||
int width = va_arg(*ap, int);
|
||||
n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width);
|
||||
i += 2;
|
||||
#if MJSON_ENABLE_BASE64
|
||||
} else if (fc == 'V') {
|
||||
int len = va_arg(ap, int);
|
||||
const char *buf = va_arg(ap, const char *);
|
||||
n += mjson_print_b64(fn, fndata, (unsigned char *) buf, len);
|
||||
int len = va_arg(*ap, int);
|
||||
const char *buf = va_arg(*ap, const char *);
|
||||
n += mjson_print_b64(fn, fnd, (unsigned char *) buf, len);
|
||||
#endif
|
||||
} else if (fc == 'H') {
|
||||
const char *hex = "0123456789abcdef";
|
||||
int i, len = va_arg(ap, int);
|
||||
const unsigned char *p = va_arg(ap, const unsigned char *);
|
||||
n += fn("\"", 1, fndata);
|
||||
for (i = 0; i < len; i++) {
|
||||
n += fn(&hex[(p[i] >> 4) & 15], 1, fndata);
|
||||
n += fn(&hex[p[i] & 15], 1, fndata);
|
||||
int j, len = va_arg(*ap, int);
|
||||
const unsigned char *p = va_arg(*ap, const unsigned char *);
|
||||
n += fn("\"", 1, fnd);
|
||||
for (j = 0; j < len; j++) {
|
||||
n += fn(&hex[(p[j] >> 4) & 15], 1, fnd);
|
||||
n += fn(&hex[p[j] & 15], 1, fnd);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
n += fn("\"", 1, fnd);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fnd, ap);
|
||||
}
|
||||
i++;
|
||||
} 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;
|
||||
}
|
||||
|
||||
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;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
len = mjson_vprintf(fn, fnd, fmt, &ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
static int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* 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;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
@@ -610,8 +747,9 @@ double strtod(const char *str, char **end) {
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
} else if (*p == '+') {
|
||||
++p;
|
||||
}
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
@@ -620,8 +758,9 @@ double strtod(const char *str, char **end) {
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
} else if (*p != '.') {
|
||||
goto done;
|
||||
}
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
@@ -644,30 +783,13 @@ double strtod(const char *str, char **end) {
|
||||
|
||||
/* exponential part */
|
||||
if ((*p == 'E') || (*p == 'e')) {
|
||||
int e = 0;
|
||||
++p;
|
||||
|
||||
sign = 1;
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} 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;
|
||||
|
||||
int i, e = 0, neg = 0;
|
||||
p++;
|
||||
if (*p == '-') p++, neg++;
|
||||
if (*p == '+') p++;
|
||||
while (is_digit(*p)) e = e * 10 + *p++ - '0';
|
||||
if (neg) e = -e;
|
||||
#if 0
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
@@ -678,10 +800,9 @@ double strtod(const char *str, char **end) {
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
#endif
|
||||
for (i = 0; i < e; i++) d *= 10;
|
||||
for (i = 0; i < -e; i++) d /= 10;
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
@@ -692,7 +813,6 @@ done:
|
||||
if (end) *end = (char *) a;
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
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;
|
||||
len += fn("{", 1, userdata);
|
||||
while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
char *path = (char *) alloca((size_t) klen + 1);
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, klen - 2);
|
||||
memcpy(path + 2, s + koff + 1, (size_t) (klen - 2));
|
||||
path[klen] = '\0';
|
||||
if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) {
|
||||
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
|
||||
off = 0;
|
||||
while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) {
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
char path[klen + 1];
|
||||
#else
|
||||
char *path = (char *) alloca(klen + 1);
|
||||
#endif
|
||||
char *path = (char *) alloca((size_t) klen + 1);
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2));
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
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,
|
||||
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;
|
||||
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
|
||||
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 i = 0, j = 0, ni = 0, nj = 0;
|
||||
@@ -845,20 +953,15 @@ int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) {
|
||||
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,
|
||||
const char *message, const char *data_fmt,
|
||||
va_list ap) {
|
||||
va_list *ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata,
|
||||
"{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len,
|
||||
r->id, code, message == NULL ? "" : message);
|
||||
if (data_fmt != NULL) {
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_printf(r->fn, r->fndata, ",\"data\":");
|
||||
mjson_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
@@ -868,12 +971,12 @@ void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...) {
|
||||
va_list ap;
|
||||
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);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
va_list ap) {
|
||||
va_list *ap) {
|
||||
if (r->id_len == 0) return;
|
||||
mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id);
|
||||
if (result_fmt != NULL) {
|
||||
@@ -888,49 +991,41 @@ void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...) {
|
||||
va_list ap;
|
||||
va_start(ap, result_fmt);
|
||||
jsonrpc_return_successv(r, result_fmt, ap);
|
||||
jsonrpc_return_successv(r, result_fmt, &ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len,
|
||||
mjson_print_fn_t fn, void *fndata, void *ud) {
|
||||
const char *result = NULL, *error = NULL;
|
||||
int result_sz = 0, error_sz = 0;
|
||||
struct jsonrpc_method *m = NULL;
|
||||
struct jsonrpc_userdata d;
|
||||
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;
|
||||
struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fndata, ud};
|
||||
|
||||
// Is is a response frame?
|
||||
mjson_find(req, req_sz, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(req, req_sz, "$.error", &error, &error_sz);
|
||||
mjson_find(buf, len, "$.result", &result, &result_sz);
|
||||
if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n",
|
||||
req_sz, req);
|
||||
ctx->in_len = 0;
|
||||
len, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// id and params are optional
|
||||
mjson_find(req, req_sz, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(req, req_sz, "$.params", &r.params, &r.params_len);
|
||||
mjson_find(buf, len, "$.id", &r.id, &r.id_len);
|
||||
mjson_find(buf, len, "$.params", &r.params, &r.params_len);
|
||||
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
if (mjson_globmatch(m->method, m->method_sz, r.method + 1,
|
||||
r.method_len - 2) > 0) {
|
||||
if (r.params == NULL) r.params = "";
|
||||
r.userdata = m->cbdata;
|
||||
m->cb(&r);
|
||||
break;
|
||||
}
|
||||
@@ -947,35 +1042,25 @@ static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
int len = 0;
|
||||
for (m = ctx->methods; m != NULL; m = m->next) {
|
||||
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;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
void jsonrpc_list(struct jsonrpc_request *r) {
|
||||
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 *userdata) {
|
||||
void *response_cb_data) {
|
||||
ctx->methods = NULL;
|
||||
ctx->response_cb = response_cb;
|
||||
ctx->userdata = userdata;
|
||||
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++;
|
||||
}
|
||||
ctx->response_cb_data = response_cb_data;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -23,7 +23,6 @@
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -40,50 +39,40 @@
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#define MJSON_ENABLE_MERGE 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#define MJSON_ENABLE_PRETTY 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_NEXT
|
||||
#define MJSON_ENABLE_NEXT 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef MJSON_RPC_IN_BUF_SIZE
|
||||
#define MJSON_RPC_IN_BUF_SIZE 256
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_DYNBUF_CHUNK
|
||||
#define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MJSON_ERROR_INVALID_INPUT = -1,
|
||||
MJSON_ERROR_TOO_DEEP = -2,
|
||||
};
|
||||
|
||||
enum mjson_tok {
|
||||
MJSON_TOK_INVALID = 0,
|
||||
MJSON_TOK_KEY = 1,
|
||||
MJSON_TOK_STRING = 11,
|
||||
MJSON_TOK_NUMBER = 12,
|
||||
MJSON_TOK_TRUE = 13,
|
||||
MJSON_TOK_FALSE = 14,
|
||||
MJSON_TOK_NULL = 15,
|
||||
MJSON_TOK_ARRAY = 91,
|
||||
MJSON_TOK_OBJECT = 123,
|
||||
};
|
||||
#define MJSON_ERROR_INVALID_INPUT (-1)
|
||||
#define MJSON_ERROR_TOO_DEEP (-2)
|
||||
#define MJSON_TOK_INVALID 0
|
||||
#define MJSON_TOK_KEY 1
|
||||
#define MJSON_TOK_STRING 11
|
||||
#define MJSON_TOK_NUMBER 12
|
||||
#define MJSON_TOK_TRUE 13
|
||||
#define MJSON_TOK_FALSE 14
|
||||
#define MJSON_TOK_NULL 15
|
||||
#define MJSON_TOK_ARRAY 91
|
||||
#define MJSON_TOK_OBJECT 123
|
||||
#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);
|
||||
@@ -93,8 +82,7 @@ typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
#endif
|
||||
|
||||
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,
|
||||
const char **tokptr, int *toklen);
|
||||
int mjson_find(const char *s, int len, const char *jp, const char **, int *);
|
||||
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_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_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_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_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_file(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_snprintf(char *buf, size_t len, const char *fmt, ...);
|
||||
char *mjson_aprintf(const char *fmt, ...);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#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);
|
||||
|
||||
struct jsonrpc_request {
|
||||
struct jsonrpc_ctx *ctx;
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
@@ -164,7 +156,6 @@ struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
@@ -172,19 +163,16 @@ struct jsonrpc_method {
|
||||
// exported RPC methods.
|
||||
struct jsonrpc_ctx {
|
||||
struct jsonrpc_method *methods;
|
||||
void *userdata;
|
||||
mjson_print_fn_t response_cb;
|
||||
int in_len;
|
||||
char in[MJSON_RPC_IN_BUF_SIZE];
|
||||
void *response_cb_data;
|
||||
};
|
||||
|
||||
// Registers function fn under the given name within the given RPC context
|
||||
#define jsonrpc_ctx_export(ctx, name, fn, ud) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0, 0}; \
|
||||
m.cbdata = (ud); \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
#define jsonrpc_ctx_export(ctx, name, fn) \
|
||||
do { \
|
||||
static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \
|
||||
m.next = (ctx)->methods; \
|
||||
(ctx)->methods = &m; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
@@ -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_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
void jsonrpc_ctx_process_byte(struct jsonrpc_ctx *ctx, unsigned char ch,
|
||||
mjson_print_fn_t fn, void *fndata);
|
||||
mjson_print_fn_t fn, void *fndata, void *userdata);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
extern void jsonrpc_list(struct jsonrpc_request *r);
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
#define jsonrpc_export(name, fn) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn))
|
||||
|
||||
#define jsonrpc_process(buf, len, fn, data) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (data))
|
||||
|
||||
#define jsonrpc_process_byte(x, fn, data) \
|
||||
jsonrpc_ctx_process_byte(&jsonrpc_default_context, (x), (fn), (data))
|
||||
#define jsonrpc_process(buf, len, fn, fnd, ud) \
|
||||
jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud))
|
||||
|
||||
#define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#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;"> </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);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -49,14 +49,12 @@ static void timer_fn(void *arg) {
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer t1;
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
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);
|
||||
mg_timer_free(&t1);
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
See detailed tutorial at https://mongoose.ws/tutorials/aws-iot/
|
||||
@@ -0,0 +1 @@
|
||||
../../test/data/ca.pem
|
||||