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 |
No files matched your search
@@ -3,22 +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: gcc
|
||||
run: make test test++ IPV6=0 CC=gcc
|
||||
- name: clang
|
||||
run: make test test++ IPV6=0 CC=clang
|
||||
- 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
|
||||
@@ -26,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
|
||||
@@ -50,162 +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/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/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/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/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/nxp-mimxrt1020-azurertos build
|
||||
nxp-frdmk66f-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-frdmk66f-freertos
|
||||
run: make -C examples/nxp/nxp-frdmk66f-freertos build
|
||||
nxp-lpcxpresso54s018m-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-lpcxpresso54s018m-freertos
|
||||
run: make -C examples/nxp/nxp-lpcxpresso54s018m-freertos build
|
||||
nxp-mimxrt1020-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-mimxrt1020-freertos
|
||||
run: make -C examples/nxp/nxp-mimxrt1020-freertos build
|
||||
nxp-evkbimxrt1050-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkbimxrt1050-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkbimxrt1050-lwip-freertos build
|
||||
nxp-evkmimxrt1020-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1020-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1020-lwip-freertos build
|
||||
nxp-evkmimxrt1024-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1024-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1024-lwip-freertos build
|
||||
nxp-evkmimxrt1060-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1060-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1060-lwip-freertos build
|
||||
nxp-evkmimxrt1064-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1064-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1064-lwip-freertos build
|
||||
nxp-evkmimxrt1160-cm7-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1160-cm7-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1160-cm7-lwip-freertos build
|
||||
nxp-evkmimxrt1170-cm4-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1170-cm4-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1170-cm4-lwip-freertos build
|
||||
nxp-evkmimxrt1170-cm7-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-evkmimxrt1170-cm7-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-evkmimxrt1170-cm7-lwip-freertos build
|
||||
nxp-frdmk64f-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-frdmk64f-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-frdmk64f-lwip-freertos build
|
||||
nxp-frdmk66f-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-frdmk66f-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-frdmk66f-lwip-freertos build
|
||||
nxp-lpcxpresso54018-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-lpcxpresso54018-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-lpcxpresso54018-lwip-freertos build
|
||||
nxp-lpcxpresso54608-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-lpcxpresso54608-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-lpcxpresso54608-lwip-freertos build
|
||||
nxp-lpcxpresso54618-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-lpcxpresso54618-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-lpcxpresso54618-lwip-freertos build
|
||||
nxp-lpcxpresso54628-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-lpcxpresso54628-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-lpcxpresso54628-lwip-freertos build
|
||||
nxp-twrk65f180m-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-twrk65f180m-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-twrk65f180m-lwip-freertos build
|
||||
nxp-twrkv58f220m-lwip-freertos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: nxp-twrkv58f220m-lwip-freertos
|
||||
run: make -C examples/nxp/nxp-twrkv58f220m-lwip-freertos build
|
||||
- uses: actions/checkout@v3
|
||||
- name: ${{ matrix.example.name }}
|
||||
run: make -C examples/${{ matrix.example.path }} build
|
||||
@@ -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 -Wundef
|
||||
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 unamalgamated 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
|
||||
@@ -125,8 +150,8 @@ mongoose.c: Makefile $(wildcard src/*)
|
||||
(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/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 | 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
|
||||
|
||||
@@ -134,7 +134,7 @@ to an event handler:
|
||||
enum {
|
||||
MG_EV_ERROR, // Error char *error_message
|
||||
MG_EV_OPEN, // Connection created NULL
|
||||
MG_EV_POLL, // mg_mgr_poll iteration int64_t *milliseconds
|
||||
MG_EV_POLL, // mg_mgr_poll iteration uint64_t *milliseconds
|
||||
MG_EV_RESOLVE, // Host name is resolved NULL
|
||||
MG_EV_CONNECT, // Connection established NULL
|
||||
MG_EV_ACCEPT, // Connection accepted NULL
|
||||
@@ -149,7 +149,7 @@ enum {
|
||||
MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message *
|
||||
MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message *
|
||||
MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code
|
||||
MG_EV_SNTP_TIME, // SNTP time received int64_t *milliseconds
|
||||
MG_EV_SNTP_TIME, // SNTP time received uint64_t *milliseconds
|
||||
MG_EV_USER, // Starting ID for user events
|
||||
};
|
||||
```
|
||||
@@ -188,6 +188,68 @@ struct mg_connection {
|
||||
};
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
- If you need to perform any sort of initialisation of your connection,
|
||||
do it by catching `MG_EV_OPEN` event. That event is sent immediately
|
||||
after a connection has been allocated and added to the event manager,
|
||||
but before anything else:
|
||||
```c
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
... // Do your initialisation
|
||||
}
|
||||
```
|
||||
- If you need to keep some connection-specific data, you have two options.
|
||||
First, use `c->fn_data` pointer. That pointer is passed to the event handler
|
||||
as last parameter:
|
||||
```c
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
c->fn_data = malloc(123); // Change our fn_data
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
free(fn_data); // Don't forget to free!
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
// Every accepted connection inherit NULL pointer as c->fn_data, but we change
|
||||
// it per-connection to something else
|
||||
mg_http_listen(&mgr, "http://localhost:1234", fn, NULL);
|
||||
```
|
||||
Another option is to use `c->label` buffer, which can
|
||||
hold some amount of connection-specific data without extra memory allocation:
|
||||
```c
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_WS_OPEN) {
|
||||
c->label[0] = 'W'; // Established websocket connection, store something
|
||||
...
|
||||
```
|
||||
- If you need to close the connection, set `c->is_draining = 1;` in your
|
||||
event handler function. That tells the event manager to send all remaining
|
||||
data in a send buffer ("drain" the connection), then close the connection.
|
||||
If you need to close
|
||||
the connection immediately without draining, use `c->is_closing = 1;`
|
||||
- Use `mg_http_reply()` function to create HTTP responses. That function
|
||||
properly sets the `Content-Length` header, which is important. Of course
|
||||
you can create responses manually, e.g. with `mg_printf()` function,
|
||||
but be sure to set the `Content-Length` header:
|
||||
```c
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\Content-Length: %d\r\n\r\n%s", 2, "hi");
|
||||
```
|
||||
Alternatively, use chunked transfer enconding:
|
||||
```c
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
mg_http_printf_chunk(c, "%s", "foo");
|
||||
mg_http_printf_chunk(c, "%s", "bar");
|
||||
mg_http_printf_chunk(c, ""); // Don't forget the last empty chunk
|
||||
```
|
||||
- On embedded environment, make sure that serving task has enough stack:
|
||||
give it 2k for simple RESTful serving, or 4-8k for complex dynamic/static
|
||||
serving. In certain environments, it is necessary to adjust heap size, too.
|
||||
By default, IO buffer allocation size `MG_IO_SIZE` is 2048: change it to 512
|
||||
to trim run-time per-connection memory consumption.
|
||||
|
||||
## Build options
|
||||
|
||||
Mongoose source code ships in two files:
|
||||
@@ -207,7 +269,7 @@ option during build time, use the `-D OPTION` compiler flag:
|
||||
$ cc app0.c mongoose.c # Use defaults!
|
||||
$ cc app1.c mongoose.c -D MG_ENABLE_IPV6=1 # Build with IPv6 enabled
|
||||
$ cc app2.c mongoose.c -D MG_ARCH=MG_ARCH_FREERTOS_LWIP # Set architecture
|
||||
$ cc app3.c mongoose.c -D MG_ENABLE_SSI=0 -D MG_ENABLE_LOG=0 # Multiple options
|
||||
$ cc app3.c mongoose.c -D MG_ENABLE_SSI=0 -D MG_IO_SIZE=8192 # Multiple options
|
||||
```
|
||||
|
||||
The list of supported architectures is defined in the [arch.h](https://github.com/cesanta/mongoose/blob/master/src/arch.h)
|
||||
@@ -236,21 +298,21 @@ Here is a list of build constants and their default values:
|
||||
|MG_ENABLE_SOCKET | 1 | Use BSD socket low-level API |
|
||||
|MG_ENABLE_MBEDTLS | 0 | Enable mbedTLS library |
|
||||
|MG_ENABLE_OPENSSL | 0 | Enable OpenSSL library |
|
||||
|MG_ENABLE_CUSTOM_TLS | 0 | Enable custom TLS library |
|
||||
|MG_ENABLE_IPV6 | 0 | Enable IPv6 |
|
||||
|MG_ENABLE_LOG | 1 | Enable `LOG()` macro |
|
||||
|MG_ENABLE_MD5 | 0 | Use native MD5 implementation |
|
||||
|MG_ENABLE_SSI | 1 | Enable serving SSI files by `mg_http_serve_dir()` |
|
||||
|MG_ENABLE_DIRLIST | 0 | Enable directory listing |
|
||||
|MG_ENABLE_CUSTOM_RANDOM | 0 | Provide custom RNG function `mg_random()` |
|
||||
|MG_ENABLE_CUSTOM_TLS | 0 | Enable custom TLS library |
|
||||
|MG_ENABLE_CUSTOM_MILLIS | 0 | Enable custom `mg_millis()` function |
|
||||
|MG_ENABLE_PACKED_FS | 0 | Enable embedded FS support |
|
||||
|MG_ENABLE_FATFS | 0 | Enable embedded FAT FS support |
|
||||
|MG_ENABLE_LINES | undefined | If defined, show source file names in logs |
|
||||
|MG_IO_SIZE | 2048 | Granularity of the send/recv IO buffer growth |
|
||||
|MG_MAX_RECV_BUF_SIZE | (3 * 1024 * 1024) | Maximum recv buffer size |
|
||||
|MG_MAX_HTTP_HEADERS | 40 | Maximum number of HTTP headers |
|
||||
|MG_HTTP_INDEX | "index.html" | Index file for HTML directory |
|
||||
|MG_FATFS_ROOT | "/" | FAT FS root directory |
|
||||
|MG_ENABLE_LINES | undefined | If defined, show source file names in logs |
|
||||
|MG_PUTCHAR | putchar | Character output function, used by logging |
|
||||
|
||||
<span class="badge bg-danger">NOTE:</span> the `MG_IO_SIZE` constant also sets
|
||||
maximum UDP message size, see
|
||||
@@ -286,18 +348,11 @@ to fail due to several undefined symbols. Create `mongoose_custom.c` and impleme
|
||||
the following functions (take a look at `src/sock.c` for the reference implementation):
|
||||
|
||||
```c
|
||||
struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
|
||||
mg_event_handler_t fn, void *fn_data) {
|
||||
// implement this!
|
||||
}
|
||||
|
||||
void mg_connect_resolved(struct mg_connection *c) {
|
||||
// implement this!
|
||||
}
|
||||
|
||||
|
||||
struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
|
||||
mg_event_handler_t fn, void *fn_data) {
|
||||
bool mg_open_listener(struct mg_connection *c, const char *url) {
|
||||
// implement this!
|
||||
}
|
||||
|
||||
@@ -313,22 +368,29 @@ int mg_send(struct mg_connection *c, const void *buf, size_t len) {
|
||||
|
||||
## Minimal HTTP server
|
||||
|
||||
This example is a simple static HTTP server that serves current directory:
|
||||
This example is a simple HTTP server that serves both static and dynamic content:
|
||||
|
||||
```c
|
||||
#include "mongoose.h"
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "."}; // Serve local dir
|
||||
if (ev == MG_EV_HTTP_MSG) mg_http_serve_dir(c, ev_data, &opts);
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_http_match_uri(hm, "/api/hello")) {
|
||||
mg_http_reply(c, 200, "", "%s\n", "hi"); // Serve dynamic content
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "."}; // Serve
|
||||
mg_http_serve_dir(c, ev_data, &opts); // static content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr); // Init manager
|
||||
mg_http_listen(&mgr, "http://localhost:8000", fn, &mgr); // Setup listener
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
|
||||
mg_mgr_free(&mgr); // Cleanup
|
||||
mg_mgr_init(&mgr); // Init manager
|
||||
mg_http_listen(&mgr, "http://0.0.0.0:8000", fn, &mgr); // Setup listener
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
|
||||
mg_mgr_free(&mgr); // Cleanup
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -397,7 +459,8 @@ with some housekeeping information.
|
||||
struct mg_connection {
|
||||
struct mg_connection *next; // Linkage in struct mg_mgr :: connections
|
||||
struct mg_mgr *mgr; // Our container
|
||||
struct mg_addr peer; // Remote address. For listeners, local address
|
||||
struct mg_addr loc; // Local address
|
||||
struct mg_addr rem; // Remote address
|
||||
void *fd; // Connected socket, or LWIP data
|
||||
unsigned long id; // Auto-incrementing unique connection ID
|
||||
struct mg_iobuf recv; // Incoming data
|
||||
@@ -580,14 +643,17 @@ Usage example:
|
||||
mg_send(c, "hi", 2); // Append string "hi" to the output buffer
|
||||
```
|
||||
|
||||
### mg\_printf()
|
||||
### mg\_printf(), mg\_vprintf()
|
||||
|
||||
```c
|
||||
int mg_printf(struct mg_connection *, const char *fmt, ...);
|
||||
int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
|
||||
```
|
||||
|
||||
Same as `mg_send()`, but formats data using `printf()` semantics. Return
|
||||
number of bytes appended to the output buffer.
|
||||
<span class="badge bg-danger">NOTE: </span> See [mg\_snprintf](#mg_snprintf-mg_vsnprintf)
|
||||
for the list of supported format specifiers
|
||||
|
||||
Parameters:
|
||||
- `c` - a connection pointer
|
||||
@@ -601,32 +667,6 @@ Usage example:
|
||||
mg_printf(c, "Hello, %s!", "world"); // Add "Hello, world!" to output buffer
|
||||
```
|
||||
|
||||
### mg\_vprintf()
|
||||
|
||||
```c
|
||||
int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
|
||||
```
|
||||
|
||||
Same as `mg_printf()`, but takes `va_list` argument as a parameter.
|
||||
|
||||
Parameters:
|
||||
- `c` - A connection pointer
|
||||
- `fmt` - A format string in `printf` semantics
|
||||
- `ap` - An arguments list
|
||||
|
||||
Return value: Number of bytes appended to the output buffer.
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
void foo(struct mg_connection *c, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
mg_vprintf(c, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
```
|
||||
|
||||
### mg\_straddr
|
||||
|
||||
```c
|
||||
@@ -649,57 +689,68 @@ char buf[100];
|
||||
LOG(LL_INFO, ("%s", mg_straddr(&c->peer, buf, sizeof(buf))));
|
||||
```
|
||||
|
||||
### mg\_mkpipe()
|
||||
### mg\_wrapfd()
|
||||
|
||||
```c
|
||||
struct mg_connection *mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *);
|
||||
struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
|
||||
mg_event_handler_t fn, void *fn_data);
|
||||
```
|
||||
|
||||
Create a "pipe" connection which is safe to pass to a different task/thread
|
||||
and used to wake up event manager from a different task. These
|
||||
functions are designed to implement multi-threaded support, to handle two
|
||||
common use cases:
|
||||
|
||||
- There are multiple consumer connections, e.g. connected websocket clients.
|
||||
A server constantly pushes some data to all of them. In this case, a data
|
||||
producer task should call `mg_mgr_wakeup()` as soon as more data is produced.
|
||||
A pipe's event handler should push data to all client connection.
|
||||
Use `c->label` to mark client connections.
|
||||
- In order to serve a request, a long blocking operation should be performed.
|
||||
In this case, request handler assigns some marker to `c->label` and then
|
||||
spawns a handler task and gives a pipe to a
|
||||
handler task. A handler does its job, and when data is ready, wakes up a
|
||||
manager. A pipe's event handler pushes data to a marked connection.
|
||||
|
||||
Another task can wake up a sleeping event manager (in `mg_mgr_poll()` call)
|
||||
using `mg_mgr_wakeup()`. When an event manager is woken up, a pipe
|
||||
connection event handler function receives `MG_EV_READ` event.
|
||||
Wrap a given file descriptor `fd` into a connection, and add that connection
|
||||
to the event manager. An `fd` descriptor must suport `send()`, `recv()`,
|
||||
`select()` syscalls, and be non-blocking. Mongoose will treat it as a TCP
|
||||
socket. The `c->rem` and `c->loc` addresses will be empty.
|
||||
|
||||
Parameters:
|
||||
- `fd` - A file descriptor to wrap
|
||||
- `mgr` - An event manager
|
||||
- `fn` - A pointer to event handler function
|
||||
- `ud` - A user data pointer. It will be passed to `fn` as `fn_data` parameter
|
||||
|
||||
Return value: Pointer to created connection or `NULL` in case of error
|
||||
Return value: Pointer to the created connection or `NULL` in case of error
|
||||
|
||||
### mg\_mkpipe()
|
||||
|
||||
```c
|
||||
int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data);
|
||||
```
|
||||
|
||||
Create two interconnected sockets for inter-thread communication. One socket
|
||||
is wrapped into a Mongoose connection and is added to the event manager.
|
||||
Another socket is returned, and supposed to be passed to a worker thread.
|
||||
When a worker thread `send()`s to socket any data, that wakes up `mgr` and
|
||||
`fn` event handler reveives `MG_EV_READ` event. Also, `fn` can send any
|
||||
data to a worker thread, which can be `recv()`ed by a worker thread.
|
||||
|
||||
Parameters:
|
||||
- `mgr` - An event manager
|
||||
- `fn` - A pointer to event handler function
|
||||
- `fn_data` - A user data pointer. It will be passed to `fn` as `fn_data` parameter
|
||||
|
||||
Return value: created socket, or `-1` on error
|
||||
|
||||
Usage example: see [examples/multi-threaded](https://github.com/cesanta/mongoose/tree/master/examples/multi-threaded).
|
||||
|
||||
### mg\_mgr\_wakeup()
|
||||
|
||||
```c
|
||||
void mg_mgr_wakeup(struct mg_connection *pipe, const void *buf, size_len len);
|
||||
bool mg_mgr_wakeup(struct mg_connection *pipe, const void *buf, size_len len);
|
||||
```
|
||||
|
||||
Wake up an event manager that sleeps in `mg_mgr_poll()` call. This function
|
||||
must be called from a separate task/thread. A calling thread can pass
|
||||
some specific data to the IO thread via `buf`, `len`. Parameters:
|
||||
some specific data to the IO thread via `buf`, `len`. The maximum value
|
||||
of `len` is limited by a maximum UDP datagram size, which is 64KiB. If you need
|
||||
to send a large data to the Mongoose thread, `malloc()` the data and send
|
||||
a pointer to it, not the data itself. The receiving event handler can receive
|
||||
a pointer, send a response, and call `free()`. Parameters:
|
||||
|
||||
Parameters:
|
||||
- `pipe` - a special connection created by the `mg_mkpipe()` call
|
||||
- `buf` - a data to send to the pipe connection. Use `""` if there is no data
|
||||
- `len` - a data length
|
||||
|
||||
Return value: None
|
||||
Return value: `true` if data has been sent, `false` otherwise
|
||||
|
||||
Usage example: see [examples/multi-threaded](https://github.com/cesanta/mongoose/tree/master/examples/multi-threaded).
|
||||
|
||||
@@ -789,6 +840,17 @@ struct mg_connection *c = mg_http_connect(&mgr, "http://google.com", fn, NULL);
|
||||
if (c == NULL) fatal_error("Cannot create connection");
|
||||
```
|
||||
|
||||
### mg\_http\_status()
|
||||
|
||||
```c
|
||||
int mg_http_status(const struct mg_http_message *hm);
|
||||
```
|
||||
|
||||
Get status code of the HTTP response.
|
||||
Parameters:
|
||||
- `hm` - Parsed HTTP response
|
||||
|
||||
Return value: status code, e.g. `200` for success.
|
||||
|
||||
### mg\_http\_get\_request\_len()
|
||||
|
||||
@@ -1176,61 +1238,6 @@ void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
}
|
||||
```
|
||||
|
||||
### mg\_http\_upload()
|
||||
|
||||
```c
|
||||
int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||||
struct mg_fs *fs, const char *dir);
|
||||
```
|
||||
|
||||
Handle file upload. See [file upload example](https://github.com/cesanta/mongoose/tree/master/examples/file-uploads/).
|
||||
|
||||
This function expects a series of POST requests with file data. POST requests
|
||||
should have `name` and `offset` query string parameters set:
|
||||
|
||||
```text
|
||||
POST /whatever_uri?name=myfile.txt&offset=1234 HTTP/1.0
|
||||
Content-Length: 5
|
||||
|
||||
hello
|
||||
```
|
||||
|
||||
- `name` - A mandatory query string parameter, specifies a file name. It it
|
||||
created in the `dir` directory
|
||||
- `offset` - An optional parameter, default `0`. If it set to `0`, or omitted,
|
||||
then a file gets truncated before write. Otherwise, the body of
|
||||
the POST request gets appended to the file
|
||||
- Server must call `mg_http_upload()` when `/whatever_uri` is hit
|
||||
|
||||
The expected usage of this API function follows:
|
||||
- A client splits a file into small enough chunks, to ensure that a chunk
|
||||
fits into the server's RAM
|
||||
- Then, each chunk is POST-ed to the server with using URI, like
|
||||
`/some_uri?name=FILENAME&offset=OFFSET`
|
||||
- Initial OFFSET is `0`, and subsequent offsets are non-zero
|
||||
- Each chunk gets appended to the file
|
||||
- When the last chunk is POST-ed, upload finishes
|
||||
- POST data must not be encoded in any way; it it saved as-is
|
||||
|
||||
Parameters:
|
||||
- `c` - Connection to use
|
||||
- `hm` - POST message, containing parameters described above
|
||||
- `fs` - Filesystem to use
|
||||
- `dir` - Path to directory
|
||||
|
||||
Return value: Request body length or negative value on error
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
// Mongoose events handler
|
||||
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 = (struct mg_http_message *) ev_data;
|
||||
mg_http_upload(c, hm, &mg_fs_posix, "."); // Upload to current folder
|
||||
}
|
||||
```
|
||||
|
||||
### mg\_http\_bauth()
|
||||
|
||||
```c
|
||||
@@ -1475,24 +1482,23 @@ static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
|
||||
mg_sntp_connect(mgr&, NULL /* connect to time.google.com */, sntp_cb, NULL);
|
||||
```
|
||||
|
||||
### mg_sntp_send()
|
||||
### mg_sntp_request()
|
||||
|
||||
```c
|
||||
void mg_sntp_send(struct mg_connection *c, unsigned long utc)
|
||||
void mg_sntp_request(struct mg_connection *c)
|
||||
```
|
||||
|
||||
Send time request to SNTP server. Note: This app can't send SNTP request more often than every one hour.
|
||||
Send time request to SNTP server
|
||||
|
||||
Parameters:
|
||||
- `c` - Connection to use
|
||||
- `utc` - Current time, used to verify if new request is possible.
|
||||
|
||||
Return value: None
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
mg_sntp_send(c, (unsigned long) time(NULL));
|
||||
mg_sntp_request(c);
|
||||
```
|
||||
|
||||
## MQTT
|
||||
@@ -1904,18 +1910,53 @@ mg_tls_init(c, &opts);
|
||||
|
||||
## Timer
|
||||
|
||||
### mg\_timer\_add()
|
||||
|
||||
```c
|
||||
struct timer *mg_timer_add(struct mg_mgr *mgr,
|
||||
uint64_t period_ms, unsigned flags,
|
||||
void (*fn)(void *), void *fn_data);
|
||||
```
|
||||
|
||||
Setup a timer. This is a high-level timer API that allows to add a software
|
||||
timer to the event manager. This function `calloc()`s a new timer and
|
||||
adds it to the `mgr->timers` list. All added timers are polled when
|
||||
`mg_mgr_poll()` is called, and called if expired.
|
||||
|
||||
<span class="badge bg-danger">NOTE: </span> Make sure that the timer
|
||||
interval is equal or more to the `mg_mgr_poll()` timeout.
|
||||
|
||||
Parameters:
|
||||
- `mgr` - Pointer to `mg_mgr` event manager structure
|
||||
- `ms` - An interval in milliseconds
|
||||
- `flags` - Timer flags bitmask, `MG_TIMER_REPEAT` and `MG_TIMER_RUN_NOW`
|
||||
- `fn` - Function to invoke
|
||||
- `fn_data` - Function argument
|
||||
|
||||
Return value: None
|
||||
|
||||
Usage example:
|
||||
```c
|
||||
void timer_fn(void *data) {
|
||||
// ...
|
||||
}
|
||||
|
||||
mg_timer_add(mgr, 1000, MG_TIMER_REPEAT, timer_fn, NULL);
|
||||
```
|
||||
|
||||
### struct mg\_timer
|
||||
|
||||
```c
|
||||
struct mg_timer {
|
||||
int64_t period_ms; // Timer period in milliseconds
|
||||
int64_t expire; // Expiration timestamp in milliseconds
|
||||
uint64_t period_ms; // Timer period in milliseconds
|
||||
uint64_t expire; // Expiration timestamp in milliseconds
|
||||
unsigned flags; // Possible flags values below
|
||||
#define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once
|
||||
#define MG_TIMER_ONCE 0 // Call function once
|
||||
#define MG_TIMER_REPEAT 1 // Call function periodically
|
||||
#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set
|
||||
void (*fn)(void *); // Function to call
|
||||
void *arg; // Function argument
|
||||
struct mg_timer *next; // Linkage in g_timers list
|
||||
struct mg_timer *next; // Linkage
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1925,13 +1966,15 @@ as the `mg_mgr_poll()` timeout argument in the main event loop.
|
||||
### mg\_timer\_init()
|
||||
|
||||
```c
|
||||
void mg_timer_init(struct mg_timer *t, int64_t period_ms, unsigned flags,
|
||||
void mg_timer_init(struct mg_timer **head,
|
||||
struct mg_timer *t, uint64_t period_ms, unsigned flags,
|
||||
void (*fn)(void *), void *fn_data);
|
||||
```
|
||||
|
||||
Setup a timer.
|
||||
|
||||
Parameters:
|
||||
- `head` - Pointer to `mg_timer` list head
|
||||
- `t` - Pointer to `mg_timer` that should be initialized
|
||||
- `ms` - An interval in milliseconds
|
||||
- `flags` - Timer flags bitmask, `MG_TIMER_REPEAT` and `MG_TIMER_RUN_NOW`
|
||||
@@ -1946,19 +1989,19 @@ void timer_fn(void *data) {
|
||||
// ...
|
||||
}
|
||||
|
||||
struct mg_timer timer;
|
||||
mg_timer_init(&timer, 1000 /* 1sec */, MG_TIMER_REPEAT, timer_fn, NULL);
|
||||
// A timer gets initialized and linked into the internal timers list
|
||||
struct mg_timer timer, *head = NULL;
|
||||
mg_timer_init(&head, &timer, 1000, MG_TIMER_REPEAT, timer_fn, NULL);
|
||||
```
|
||||
|
||||
### mg\_timer\_free()
|
||||
|
||||
```c
|
||||
void mg_timer_free(struct mg_timer *t);
|
||||
void mg_timer_free(struct mg_timer **head, struct mg_timer *t);
|
||||
```
|
||||
Free timer, remove it from the internal timers list.
|
||||
|
||||
Parameters:
|
||||
- `head` - Pointer to `mg_timer` list head
|
||||
- `t` - Timer to free
|
||||
|
||||
Return value: None
|
||||
@@ -1973,7 +2016,7 @@ mg_timer_free(&timer);
|
||||
### mg\_timer\_poll()
|
||||
|
||||
```c
|
||||
void mg_timer_poll(int64_t uptime_ms);
|
||||
void mg_timer_poll(struct mg_timer **head, uint64_t uptime_ms);
|
||||
```
|
||||
|
||||
Traverse list of timers and call them if current timestamp `uptime_ms` is
|
||||
@@ -1983,6 +2026,7 @@ Note, that `mg_mgr_poll` function internally calls `mg_timer_poll`; therefore,
|
||||
in most cases it is unnecessary to call it explicitly.
|
||||
|
||||
Parameters:
|
||||
- `head` - Pointer to `mg_timer` list head
|
||||
- `uptime_ms` - current timestamp
|
||||
|
||||
Return value: None
|
||||
@@ -1990,8 +2034,7 @@ Return value: None
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
int64_t now = mg_millis();
|
||||
mg_timer_poll(now);
|
||||
mg_timer_poll(mg_millis());
|
||||
```
|
||||
|
||||
## Time
|
||||
@@ -2221,7 +2264,7 @@ Usage example:
|
||||
struct mg_str str1 = mg_str("hello");
|
||||
struct mg_str str2 = mg_strdup(str1);
|
||||
//...
|
||||
free(str1.ptr);
|
||||
free(str2.ptr);
|
||||
```
|
||||
|
||||
|
||||
@@ -2273,13 +2316,19 @@ if (mg_vcmp(str, "Hello, world") == 0) {
|
||||
}
|
||||
```
|
||||
|
||||
### mg\_globmatch()
|
||||
### mg\_match()
|
||||
|
||||
```c
|
||||
bool mg_globmatch(const char *pattern, size_t p_len, const char *s, size_t s_len);
|
||||
bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps);
|
||||
```
|
||||
|
||||
Check if string `s` (limited to `s_len` symbols) matches glob pattern `pattern`, (limited to `p_len` symbols).
|
||||
Check if string `str` matches glob pattern `pattern`, and optionally capture
|
||||
wildcards into the provided array `caps`.
|
||||
|
||||
<span class="badge bg-danger">NOTE: </span> If `caps` is not NULL, then the
|
||||
`caps` array size must be at least the number of wildcard symbols in `pattern`
|
||||
plus 1. The last cap will be initialized to an empty string.
|
||||
|
||||
The glob pattern matching rules are as follows:
|
||||
|
||||
- `?` matches any single character
|
||||
@@ -2288,21 +2337,19 @@ The glob pattern matching rules are as follows:
|
||||
- any other character matches itself
|
||||
|
||||
Parameters:
|
||||
- `pattern` - Pattern to match for
|
||||
- `p_len` - Pattern length
|
||||
- `s` - String to match
|
||||
- `s_len` - String length
|
||||
- `str` - a string to match
|
||||
- `pattern` - a pattern to match against
|
||||
- `caps` - an optional array of captures for wildcard symbols `?`, `*`, '#'
|
||||
|
||||
Return value: `true` if matches, `false` otherwise
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
struct mg_str pattern = mg_str("#, ?????");
|
||||
struct mg_str s = mg_str("Hello, world");
|
||||
|
||||
if (mg_globmatch(pattern.ptr, pattern.len, s.otr, s.len)) {
|
||||
// Match
|
||||
// Assume that hm->uri holds /foo/bar. Then we can match the requested URI:
|
||||
struct mg_str caps[3]; // Two wildcard symbols '*' plus 1
|
||||
if (mg_match(hm->uri, mg_str("/*/*"))) {
|
||||
// caps[0] holds `foo`, caps[1] holds `bar`.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2336,30 +2383,6 @@ while (mg_commalist(&s, &k, &v)) // This loop output:
|
||||
(int) k.len, k.ptr, (int) v.len, v.ptr); // [b] set to [777]
|
||||
```
|
||||
|
||||
### mg\_hexdump()
|
||||
|
||||
```c
|
||||
char *mg_hexdump(const void *buf, int len);
|
||||
```
|
||||
|
||||
Hexdump binary data `buf`, `len` into malloc-ed buffer and return it.
|
||||
It is a caller's responsibility to free() returned pointer.
|
||||
|
||||
Parameters:
|
||||
- `buf` - Data to hexdump
|
||||
- `len` - Data length
|
||||
|
||||
Return value: malloc-ed buffer with hexdumped data
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
char arr[] = "\0x1\0x2\0x3";
|
||||
char *hex = mg_hexdump(arr, sizeof(arr));
|
||||
LOG(LL_INFO, ("%s", hex)); // Output "0000 01 02 03 00";
|
||||
free(hex);
|
||||
```
|
||||
|
||||
### mg\_hex()
|
||||
|
||||
```c
|
||||
@@ -2436,10 +2459,11 @@ unsigned long val = mg_unhex(data, sizeof(data) - 1); // val is now 123
|
||||
```
|
||||
|
||||
|
||||
### mg\_asprintf()
|
||||
### mg\_asprintf(), mg\_vasprintf()
|
||||
|
||||
```c
|
||||
int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
|
||||
int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap);
|
||||
```
|
||||
|
||||
Print message specified by printf-like format string `fmt` into a buffer
|
||||
@@ -2462,44 +2486,52 @@ char buf[1024], *pbuf = &buf;
|
||||
mg_asprintf(&pbuf, sizeof(buf), "Hello, %s!", "world"); // buf is now "Hello, world!"
|
||||
```
|
||||
|
||||
### mg\_vasprintf()
|
||||
|
||||
### mg\_snprintf(), mg\_vsnprintf()
|
||||
```c
|
||||
int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap);
|
||||
size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...);
|
||||
size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list ap);
|
||||
size_t mg_asprintf(char **buf, size_t len, const char *fmt, ...);
|
||||
size_t mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap);
|
||||
```
|
||||
|
||||
Same as `mg_asprintf()` but uses `va_list` argument.
|
||||
Print formatted string into a string buffer, just like `snprintf()`
|
||||
standard function does, but in a predictable way that does not depend on
|
||||
the C library or the build environment. The return value can be larger
|
||||
than the buffer length `len`, in which case the overflow bytes are not printed.
|
||||
|
||||
Parameters:
|
||||
- `buf` - Pointer to pointer to output buffer
|
||||
- `size` - Pre-allocated buffer size
|
||||
- `len` - Buffer size
|
||||
- `fmt` - printf-like format string
|
||||
- `ap` - Parameters list
|
||||
|
||||
Supported format specifiers:
|
||||
- `hhd`, `hd`, `d`, `ld`, `lld` - for `char`, `short`, `int`, `long`, `int64_t`
|
||||
- `hhu`, `hu`, `u`, `lu`, `llu` - same but for unsigned variants
|
||||
- `hhx`, `hx`, `x`, `lx`, `llx` - same, unsigned and hex output
|
||||
- `s` - `for char *`
|
||||
- `c` - `for char`
|
||||
- `%` - `the `%` character itself
|
||||
- `p` - for any pointer, prints `0x.....` hex value
|
||||
- `%X.Y` - optional width and precision modifiers
|
||||
- `%.*` - optional precision modifier specified as `int` argument
|
||||
|
||||
Return value: Number of bytes printed
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
void foo(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
char buf[1024], *pbuf = buf;
|
||||
mg_vasprintf(&pbuf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
printf("%s\n", buf);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
foo("Hello, %s!", "world"); // Print "Hello, world!
|
||||
|
||||
mg_snprintf(buf, sizeof(buf), "%lld", (int64_t) 123); // 123
|
||||
mg_snprintf(buf, sizeof(buf), "%.2s", "abcdef"); // ab
|
||||
mg_snprintf(buf, sizeof(buf), "%.*s", 2, "abcdef"); // ab
|
||||
mg_snprintf(buf, sizeof(buf), "%05x", 123); // 00123
|
||||
mg_snprintf(buf, sizeof(buf), "%%-%3s", "a"); // %- a
|
||||
```
|
||||
|
||||
### mg\_to64()
|
||||
|
||||
```c
|
||||
int64_t mg_to64(struct mg_str str);
|
||||
uint64_t mg_tou64(struct mg_str str);
|
||||
```
|
||||
|
||||
Parse 64-bit integer value held by string `s`.
|
||||
@@ -3394,27 +3426,17 @@ use these functions for its own purposes as well as the rest of Mongoose API.
|
||||
|
||||
```c
|
||||
#define LOG(level, args)
|
||||
#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args)
|
||||
#define MG_INFO(args) MG_LOG(MG_LL_INFO, args)
|
||||
#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args)
|
||||
#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args)
|
||||
```
|
||||
|
||||
General way to log is using `LOG` macro.
|
||||
`LOG` prints to log only is `MG_ENABLE_LOG` macro defined, otherwise is does nothing.
|
||||
|
||||
This macro has two arguments: log level and information to log. The second argument is a printf-alike format string.
|
||||
|
||||
Log levels defined as:
|
||||
```c
|
||||
enum { LL_NONE, LL_ERROR, LL_INFO, LL_DEBUG, LL_VERBOSE_DEBUG };
|
||||
```
|
||||
|
||||
Parameters:
|
||||
- `level` - Log level, see levels above
|
||||
- `args` - Information to log
|
||||
|
||||
Return value: None
|
||||
|
||||
Logging macros.
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
LOG(LL_ERROR, ("Hello %s!", "world")); // Output "Hello, world"
|
||||
MG_INFO(("Hello %s!", "world")); // Output "Hello, world"
|
||||
```
|
||||
|
||||
### mg\_log\_set()
|
||||
@@ -3447,32 +3469,24 @@ mg_log_set("2"); // Set log level to info
|
||||
mg_log_set("2,foo.c=3,bar.c=0"); // Set log level to info, with overrides
|
||||
```
|
||||
|
||||
### mg\_log\_set\_callback()
|
||||
### mg\_hexdump()
|
||||
|
||||
```c
|
||||
void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *fnd);
|
||||
void mg_hexdump(const void *buf, int len);
|
||||
```
|
||||
|
||||
By default, `LOG` writes to standard output stream (aka `stdout`), but this behaviour
|
||||
can be changes via `mg_log_set_callback`. This function allows to set callback,
|
||||
which called once mongoose (or host application) calls `LOG`
|
||||
Log a hex dump of binary data `buf`, `len`.
|
||||
|
||||
Parameters:
|
||||
- `fn` - callback function, should be called on logging
|
||||
- `fnd` - user parameter to pass to `fn`
|
||||
- `buf` - Data pointer
|
||||
- `len` - Data length
|
||||
|
||||
Return value: none
|
||||
|
||||
Usage example:
|
||||
|
||||
```c
|
||||
void log_via_printf(const void *buf, size_t len, void *userdata) {
|
||||
(void) userdata;
|
||||
printf("*.%s", buf, len);
|
||||
}
|
||||
|
||||
// ...
|
||||
mg_log_set_callback(&log_via_printf, NULL);
|
||||
mg_hexdump(c->recv.buf, c->recv.len); // Hex dump incoming data
|
||||
```
|
||||
|
||||
## Filesystem
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -26,7 +26,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
} 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);
|
||||
LOG(LL_INFO, ("DNS request parsed, result=%d", (int) n));
|
||||
MG_INFO(("DNS request parsed, result=%d", (int) n));
|
||||
if (n > 0) {
|
||||
char buf[512];
|
||||
struct mg_dns_header *h = (struct mg_dns_header *) buf;
|
||||
|
||||
@@ -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(&mg_fs_posix, 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;
|
||||
}
|
||||
@@ -1,981 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
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++) {
|
||||
if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)) {
|
||||
i++;
|
||||
} else if (s[i] == '\0') {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
} else if (s[i] == '"') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
|
||||
enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
|
||||
unsigned char nesting[MJSON_MAX_DEPTH];
|
||||
int i, depth = 0;
|
||||
#define MJSONCALL(ev) \
|
||||
if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1;
|
||||
|
||||
// In the ascii table, the distance between `[` and `]` is 2.
|
||||
// Ditto for `{` and `}`. Hence +2 in the code below.
|
||||
#define MJSONEOO() \
|
||||
do { \
|
||||
if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \
|
||||
depth--; \
|
||||
if (depth == 0) { \
|
||||
MJSONCALL(tok); \
|
||||
return i + 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
int start = i;
|
||||
unsigned char c = ((unsigned char *) s)[i];
|
||||
int tok = c;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
|
||||
// printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting);
|
||||
switch (expecting) {
|
||||
case S_VALUE:
|
||||
if (c == '{') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
expecting = S_KEY;
|
||||
break;
|
||||
} else if (c == '[') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
break;
|
||||
} else if (c == ']' && depth > 0) { // Empty array
|
||||
MJSONEOO();
|
||||
} else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_TRUE;
|
||||
} else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_NULL;
|
||||
} else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
|
||||
i += 4;
|
||||
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;
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_STRING;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
if (depth == 0) {
|
||||
MJSONCALL(tok);
|
||||
return i + 1;
|
||||
}
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
break;
|
||||
|
||||
case S_KEY:
|
||||
if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_KEY;
|
||||
expecting = S_COLON;
|
||||
} else if (c == '}') { // Empty object
|
||||
MJSONEOO();
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COLON:
|
||||
if (c == ':') {
|
||||
expecting = S_VALUE;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COMMA_OR_EOO:
|
||||
if (depth <= 0) return MJSON_ERROR_INVALID_INPUT;
|
||||
if (c == ',') {
|
||||
expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
|
||||
} else if (c == ']' || c == '}') {
|
||||
MJSONEOO();
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
MJSONCALL(tok);
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
struct msjon_get_data {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
int i2; // Expected index in an array
|
||||
int obj; // If the value is array/object, offset where it starts
|
||||
const char **tokptr; // Destination
|
||||
int *toklen; // Destination length
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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
|
||||
} 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;
|
||||
return 1;
|
||||
}
|
||||
} 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;
|
||||
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};
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return tok == MJSON_TOK_NUMBER ? 1 : 0;
|
||||
}
|
||||
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v) {
|
||||
int tok = mjson_find(s, len, path, NULL, NULL);
|
||||
if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1;
|
||||
if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0;
|
||||
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
int c = mjson_esc(s[i + 1], 0);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s[i];
|
||||
}
|
||||
}
|
||||
if (j >= n) return -1;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
return mjson_unescape(p + 1, sz - 2, to, n);
|
||||
}
|
||||
|
||||
int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
|
||||
const char *p;
|
||||
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);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} else if (c == '/') {
|
||||
return 63;
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (src[2] != '=' && len < dlen) {
|
||||
dst[len++] = (b << 4) | (c >> 2);
|
||||
if (src[3] != '=' && len < dlen) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
}
|
||||
if (len < dlen) dst[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0;
|
||||
return mjson_base64_dec(p + 1, sz - 2, to, n);
|
||||
}
|
||||
#endif // MJSON_ENABLE_BASE64
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
struct nextdata {
|
||||
int off, len, depth, t, vo, arrayindex;
|
||||
int *koff, *klen, *voff, *vlen, *vtype;
|
||||
};
|
||||
|
||||
static int next_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct nextdata *d = (struct nextdata *) ud;
|
||||
// int i;
|
||||
switch (tok) {
|
||||
case '{':
|
||||
case '[':
|
||||
if (d->depth == 0 && tok == '[') d->arrayindex = 0;
|
||||
if (d->depth == 1 && off > d->off) {
|
||||
d->vo = off;
|
||||
d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY;
|
||||
if (d->voff) *d->voff = off;
|
||||
if (d->vtype) *d->vtype = d->t;
|
||||
}
|
||||
d->depth++;
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->depth--;
|
||||
if (d->depth == 1 && d->vo) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = d->len - d->vo;
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
case ',':
|
||||
case ':':
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->depth == 1 && d->off < off) {
|
||||
if (d->koff) *d->koff = off; // And report back to the user
|
||||
if (d->klen) *d->klen = len; // If we have to
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (d->depth != 1) break;
|
||||
// If we're iterating over the array
|
||||
if (off > d->off) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = len; // value length
|
||||
if (d->voff) *d->voff = off; // value offset
|
||||
if (d->vtype) *d->vtype = tok; // value type
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
}
|
||||
(void) s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype) {
|
||||
struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype};
|
||||
mjson(s, n, next_cb, &d);
|
||||
return d.len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fndata;
|
||||
int i, left = fb->size - 1 - fb->len;
|
||||
if (left < len) len = left;
|
||||
for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i];
|
||||
fb->len += len;
|
||||
fb->ptr[fb->len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
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) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
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 *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d,
|
||||
const char *fmt) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s,
|
||||
int n) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
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);
|
||||
}
|
||||
return len + fn("\"", 1, fndata);
|
||||
}
|
||||
#endif /* MJSON_ENABLE_BASE64 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (fmt[i] == '%') {
|
||||
char fc = fmt[++i];
|
||||
int is_long = 0;
|
||||
if (fc == 'l') {
|
||||
is_long = 1;
|
||||
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);
|
||||
} 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);
|
||||
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);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, 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));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, 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);
|
||||
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");
|
||||
#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);
|
||||
#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);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
n += mjson_print_buf(fn, fndata, &fmt[i++], 1);
|
||||
}
|
||||
}
|
||||
va_end(xap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
|
||||
double strtod(const char *str, char **end) {
|
||||
double d = 0.0;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
|
||||
/* decimal part */
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
d = d * 10.0 + (double) (*p - '0');
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
goto done;
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
if (*p == '.') {
|
||||
double f = 0.0;
|
||||
double base = 0.1;
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p && is_digit(*p)) {
|
||||
f += base * (*p - '0');
|
||||
base /= 10.0;
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
d += f * sign;
|
||||
a = p;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
if (d == 2.2250738585072012 && e <= -308) {
|
||||
d *= 1.0e-308;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
}
|
||||
|
||||
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,
|
||||
mjson_print_fn_t fn, void *userdata) {
|
||||
int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0;
|
||||
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
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, 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
|
||||
} else {
|
||||
val = s + voff; // Key is not found in the update. Copy the old value.
|
||||
}
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) {
|
||||
len += mjson_merge(s + voff, vlen, val, k, fn, userdata);
|
||||
} else {
|
||||
if (t2 != MJSON_TOK_INVALID) vlen = k;
|
||||
len += fn(val, vlen, userdata);
|
||||
}
|
||||
comma = 1;
|
||||
}
|
||||
// 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
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s2 + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
len += fn(s2 + voff, vlen, userdata);
|
||||
comma = 1;
|
||||
}
|
||||
len += fn("}", 1, userdata);
|
||||
return len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_MERGE
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
struct prettydata {
|
||||
int level;
|
||||
int len;
|
||||
int prev;
|
||||
const char *pad;
|
||||
int padlen;
|
||||
mjson_print_fn_t fn;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
|
||||
struct prettydata *d = (struct prettydata *) ud;
|
||||
int i;
|
||||
switch (ev) {
|
||||
case '{':
|
||||
case '[':
|
||||
d->level++;
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->level--;
|
||||
if (d->prev != '[' && d->prev != '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case ',':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata);
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->prev == '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
default:
|
||||
if (d->prev == '[' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
}
|
||||
d->prev = ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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};
|
||||
if (mjson(s, n, pretty_cb, &d) < 0) return -1;
|
||||
return d.len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_PRETTY
|
||||
|
||||
#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;
|
||||
while (i < n1 || j < n2) {
|
||||
if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
|
||||
i++, j++;
|
||||
} else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
|
||||
ni = i, nj = j + 1, i++;
|
||||
} else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
|
||||
i = ni, j = nj;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
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) {
|
||||
mjson_vprintf(r->fn, r->fndata, result_fmt, ap);
|
||||
} else {
|
||||
mjson_printf(r->fn, r->fndata, "%s", "null");
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}\n");
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
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;
|
||||
|
||||
// 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);
|
||||
if (result_sz > 0 || error_sz > 0) {
|
||||
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Method must exist and must be a string
|
||||
if (mjson_find(req, req_sz, "$.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;
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (m == NULL) {
|
||||
jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
va_list *ap) {
|
||||
struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *);
|
||||
struct jsonrpc_method *m;
|
||||
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));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
|
||||
void *userdata) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
|
||||
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata);
|
||||
}
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef MJSON_H
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MJSON_ENABLE_PRINT
|
||||
#define MJSON_ENABLE_PRINT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_RPC
|
||||
#define MJSON_ENABLE_RPC 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_BASE64
|
||||
#define MJSON_ENABLE_BASE64 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 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
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#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_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
|
||||
|
||||
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
|
||||
#ifndef MJSON_MAX_DEPTH
|
||||
#define MJSON_MAX_DEPTH 20
|
||||
#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_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);
|
||||
int mjson_get_hex(const char *s, int len, const char *path, char *to, int n);
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
typedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata);
|
||||
typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t, void *, va_list *);
|
||||
|
||||
struct mjson_fixedbuf {
|
||||
char *ptr;
|
||||
int size, len;
|
||||
};
|
||||
|
||||
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_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_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);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *, int, const char *, int, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#endif // MJSON_ENABLE_PRINT
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
|
||||
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 {
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
int params_len; // Length of the "params"
|
||||
const char *id; // Points to the "id" in the request frame
|
||||
int id_len; // Length of the "id"
|
||||
const char *method; // Points to the "method" in the request frame
|
||||
int method_len; // Length of the "method"
|
||||
mjson_print_fn_t fn; // Printer function
|
||||
void *fndata; // Printer function data
|
||||
void *userdata; // Callback's user data as specified at export time
|
||||
};
|
||||
|
||||
struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
// Main RPC context, stores current request information and a list of
|
||||
// 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];
|
||||
};
|
||||
|
||||
// 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; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...);
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...);
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
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);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
|
||||
#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_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
|
||||
#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */
|
||||
#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */
|
||||
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // MJSON_H
|
||||
|
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,981 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include "mjson.h"
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#else
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define snprintf _snprintf
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
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++) {
|
||||
if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)) {
|
||||
i++;
|
||||
} else if (s[i] == '\0') {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
} else if (s[i] == '"') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int mjson(const char *s, int len, mjson_cb_t cb, void *ud) {
|
||||
enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
|
||||
unsigned char nesting[MJSON_MAX_DEPTH];
|
||||
int i, depth = 0;
|
||||
#define MJSONCALL(ev) \
|
||||
if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1;
|
||||
|
||||
// In the ascii table, the distance between `[` and `]` is 2.
|
||||
// Ditto for `{` and `}`. Hence +2 in the code below.
|
||||
#define MJSONEOO() \
|
||||
do { \
|
||||
if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \
|
||||
depth--; \
|
||||
if (depth == 0) { \
|
||||
MJSONCALL(tok); \
|
||||
return i + 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
int start = i;
|
||||
unsigned char c = ((unsigned char *) s)[i];
|
||||
int tok = c;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
|
||||
// printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting);
|
||||
switch (expecting) {
|
||||
case S_VALUE:
|
||||
if (c == '{') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
expecting = S_KEY;
|
||||
break;
|
||||
} else if (c == '[') {
|
||||
if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP;
|
||||
nesting[depth++] = c;
|
||||
break;
|
||||
} else if (c == ']' && depth > 0) { // Empty array
|
||||
MJSONEOO();
|
||||
} else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_TRUE;
|
||||
} else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
|
||||
i += 3;
|
||||
tok = MJSON_TOK_NULL;
|
||||
} else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
|
||||
i += 4;
|
||||
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;
|
||||
tok = MJSON_TOK_NUMBER;
|
||||
} else if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_STRING;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
if (depth == 0) {
|
||||
MJSONCALL(tok);
|
||||
return i + 1;
|
||||
}
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
break;
|
||||
|
||||
case S_KEY:
|
||||
if (c == '"') {
|
||||
int n = mjson_pass_string(&s[i + 1], len - i - 1);
|
||||
if (n < 0) return n;
|
||||
i += n + 1;
|
||||
tok = MJSON_TOK_KEY;
|
||||
expecting = S_COLON;
|
||||
} else if (c == '}') { // Empty object
|
||||
MJSONEOO();
|
||||
expecting = S_COMMA_OR_EOO;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COLON:
|
||||
if (c == ':') {
|
||||
expecting = S_VALUE;
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_COMMA_OR_EOO:
|
||||
if (depth <= 0) return MJSON_ERROR_INVALID_INPUT;
|
||||
if (c == ',') {
|
||||
expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
|
||||
} else if (c == ']' || c == '}') {
|
||||
MJSONEOO();
|
||||
} else {
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
MJSONCALL(tok);
|
||||
}
|
||||
return MJSON_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
struct msjon_get_data {
|
||||
const char *path; // Lookup json path
|
||||
int pos; // Current path index
|
||||
int d1; // Current depth of traversal
|
||||
int d2; // Expected depth of traversal
|
||||
int i1; // Index in an array
|
||||
int i2; // Expected index in an array
|
||||
int obj; // If the value is array/object, offset where it starts
|
||||
const char **tokptr; // Destination
|
||||
int *toklen; // Destination length
|
||||
int tok; // Returned token
|
||||
};
|
||||
|
||||
static int mjson_plen(const char *s) {
|
||||
int i = 0;
|
||||
while (s[i] != '\0' && s[i] != '.' && s[i] != '[') i++;
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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
|
||||
} 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;
|
||||
return 1;
|
||||
}
|
||||
} 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;
|
||||
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};
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return tok == MJSON_TOK_NUMBER ? 1 : 0;
|
||||
}
|
||||
|
||||
int mjson_get_bool(const char *s, int len, const char *path, int *v) {
|
||||
int tok = mjson_find(s, len, path, NULL, NULL);
|
||||
if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1;
|
||||
if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0;
|
||||
return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
int c = mjson_esc(s[i + 1], 0);
|
||||
if (c == 0) return -1;
|
||||
to[j] = c;
|
||||
i++;
|
||||
} else {
|
||||
to[j] = s[i];
|
||||
}
|
||||
}
|
||||
if (j >= n) return -1;
|
||||
if (n > 0) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
int mjson_get_string(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1;
|
||||
return mjson_unescape(p + 1, sz - 2, to, n);
|
||||
}
|
||||
|
||||
int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) {
|
||||
const char *p;
|
||||
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);
|
||||
}
|
||||
if (j < n) to[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
static int mjson_base64rev(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c - 'A';
|
||||
} else if (c >= 'a' && c <= 'z') {
|
||||
return c + 26 - 'a';
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
return c + 52 - '0';
|
||||
} else if (c == '+') {
|
||||
return 62;
|
||||
} else if (c == '/') {
|
||||
return 63;
|
||||
} else {
|
||||
return 64;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (src[2] != '=' && len < dlen) {
|
||||
dst[len++] = (b << 4) | (c >> 2);
|
||||
if (src[3] != '=' && len < dlen) {
|
||||
dst[len++] = (c << 6) | d;
|
||||
}
|
||||
}
|
||||
src += 4;
|
||||
}
|
||||
if (len < dlen) dst[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to,
|
||||
int n) {
|
||||
const char *p;
|
||||
int sz;
|
||||
if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0;
|
||||
return mjson_base64_dec(p + 1, sz - 2, to, n);
|
||||
}
|
||||
#endif // MJSON_ENABLE_BASE64
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
struct nextdata {
|
||||
int off, len, depth, t, vo, arrayindex;
|
||||
int *koff, *klen, *voff, *vlen, *vtype;
|
||||
};
|
||||
|
||||
static int next_cb(int tok, const char *s, int off, int len, void *ud) {
|
||||
struct nextdata *d = (struct nextdata *) ud;
|
||||
// int i;
|
||||
switch (tok) {
|
||||
case '{':
|
||||
case '[':
|
||||
if (d->depth == 0 && tok == '[') d->arrayindex = 0;
|
||||
if (d->depth == 1 && off > d->off) {
|
||||
d->vo = off;
|
||||
d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY;
|
||||
if (d->voff) *d->voff = off;
|
||||
if (d->vtype) *d->vtype = d->t;
|
||||
}
|
||||
d->depth++;
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->depth--;
|
||||
if (d->depth == 1 && d->vo) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = d->len - d->vo;
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
case ',':
|
||||
case ':':
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->depth == 1 && d->off < off) {
|
||||
if (d->koff) *d->koff = off; // And report back to the user
|
||||
if (d->klen) *d->klen = len; // If we have to
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (d->depth != 1) break;
|
||||
// If we're iterating over the array
|
||||
if (off > d->off) {
|
||||
d->len = off + len;
|
||||
if (d->vlen) *d->vlen = len; // value length
|
||||
if (d->voff) *d->voff = off; // value offset
|
||||
if (d->vtype) *d->vtype = tok; // value type
|
||||
if (d->arrayindex >= 0) {
|
||||
if (d->koff) *d->koff = d->arrayindex; // koff holds array index
|
||||
if (d->klen) *d->klen = 0; // klen holds 0
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->arrayindex >= 0) d->arrayindex++;
|
||||
break;
|
||||
}
|
||||
(void) s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype) {
|
||||
struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype};
|
||||
mjson(s, n, next_cb, &d);
|
||||
return d.len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) {
|
||||
struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fndata;
|
||||
int i, left = fb->size - 1 - fb->len;
|
||||
if (left < len) len = left;
|
||||
for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i];
|
||||
fb->len += len;
|
||||
fb->ptr[fb->len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
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) {
|
||||
return 0;
|
||||
} else {
|
||||
memcpy(s + curlen, ptr, len);
|
||||
s[curlen + len] = '\0';
|
||||
*(char **) fndata = s;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
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 *fndata, const char *buf,
|
||||
int len) {
|
||||
return fn(buf, len, fndata);
|
||||
}
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
int mjson_print_dbl(mjson_print_fn_t fn, void *fndata, double d,
|
||||
const char *fmt) {
|
||||
char buf[40];
|
||||
int n = snprintf(buf, sizeof(buf), fmt, d);
|
||||
return fn(buf, n, fndata);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
return n + fn("\"", 1, fndata);
|
||||
}
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_print_b64(mjson_print_fn_t fn, void *fndata, const unsigned char *s,
|
||||
int n) {
|
||||
const char *t =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int i, len = fn("\"", 1, fndata);
|
||||
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);
|
||||
}
|
||||
return len + fn("\"", 1, fndata);
|
||||
}
|
||||
#endif /* MJSON_ENABLE_BASE64 */
|
||||
|
||||
int mjson_vprintf(mjson_print_fn_t fn, void *fndata, const char *fmt,
|
||||
va_list xap) {
|
||||
int i = 0, n = 0;
|
||||
va_list ap;
|
||||
va_copy(ap, xap);
|
||||
while (fmt[i] != '\0') {
|
||||
if (fmt[i] == '%') {
|
||||
char fc = fmt[++i];
|
||||
int is_long = 0;
|
||||
if (fc == 'l') {
|
||||
is_long = 1;
|
||||
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);
|
||||
} 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);
|
||||
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);
|
||||
i++;
|
||||
} else {
|
||||
int val = va_arg(ap, int);
|
||||
n += mjson_print_int(fn, fndata, 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));
|
||||
} else if (fc == 's') {
|
||||
char *buf = va_arg(ap, char *);
|
||||
n += mjson_print_buf(fn, fndata, buf, 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);
|
||||
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");
|
||||
#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);
|
||||
#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);
|
||||
}
|
||||
n += fn("\"", 1, fndata);
|
||||
} else if (fc == 'M') {
|
||||
mjson_vprint_fn_t vfn = va_arg(ap, mjson_vprint_fn_t);
|
||||
n += vfn(fn, fndata, &ap);
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
n += mjson_print_buf(fn, fndata, &fmt[i++], 1);
|
||||
}
|
||||
}
|
||||
va_end(xap);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
int mjson_printf(mjson_print_fn_t fn, void *fndata, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int len;
|
||||
va_start(ap, fmt);
|
||||
len = mjson_vprintf(fn, fndata, fmt, ap);
|
||||
va_end(ap);
|
||||
return len;
|
||||
}
|
||||
#endif /* MJSON_ENABLE_PRINT */
|
||||
|
||||
#if MJSON_IMPLEMENT_STRTOD
|
||||
static inline int is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/* NOTE: strtod() implementation by Yasuhiro Matsumoto. */
|
||||
double strtod(const char *str, char **end) {
|
||||
double d = 0.0;
|
||||
int sign = 1, n = 0;
|
||||
const char *p = str, *a = str;
|
||||
|
||||
/* decimal part */
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
} else if (*p == '+')
|
||||
++p;
|
||||
if (is_digit(*p)) {
|
||||
d = (double) (*p++ - '0');
|
||||
while (*p && is_digit(*p)) {
|
||||
d = d * 10.0 + (double) (*p - '0');
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.')
|
||||
goto done;
|
||||
d *= sign;
|
||||
|
||||
/* fraction part */
|
||||
if (*p == '.') {
|
||||
double f = 0.0;
|
||||
double base = 0.1;
|
||||
++p;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
while (*p && is_digit(*p)) {
|
||||
f += base * (*p - '0');
|
||||
base /= 10.0;
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
d += f * sign;
|
||||
a = p;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if (d == 2.2250738585072011 && e == -308) {
|
||||
d = 0.0;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
if (d == 2.2250738585072012 && e <= -308) {
|
||||
d *= 1.0e-308;
|
||||
a = p;
|
||||
goto done;
|
||||
}
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) d *= 10;
|
||||
}
|
||||
a = p;
|
||||
} else if (p > str && !is_digit(*(p - 1))) {
|
||||
a = str;
|
||||
goto done;
|
||||
}
|
||||
|
||||
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,
|
||||
mjson_print_fn_t fn, void *userdata) {
|
||||
int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0;
|
||||
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
|
||||
const char *val;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s + koff + 1, 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
|
||||
} else {
|
||||
val = s + voff; // Key is not found in the update. Copy the old value.
|
||||
}
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) {
|
||||
len += mjson_merge(s + voff, vlen, val, k, fn, userdata);
|
||||
} else {
|
||||
if (t2 != MJSON_TOK_INVALID) vlen = k;
|
||||
len += fn(val, vlen, userdata);
|
||||
}
|
||||
comma = 1;
|
||||
}
|
||||
// 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
|
||||
const char *val;
|
||||
if (t == MJSON_TOK_NULL) continue;
|
||||
memcpy(path, "$.", 2);
|
||||
memcpy(path + 2, s2 + koff + 1, klen - 2);
|
||||
path[klen] = '\0';
|
||||
if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue;
|
||||
if (comma) len += fn(",", 1, userdata);
|
||||
len += fn(s2 + koff, klen, userdata);
|
||||
len += fn(":", 1, userdata);
|
||||
len += fn(s2 + voff, vlen, userdata);
|
||||
comma = 1;
|
||||
}
|
||||
len += fn("}", 1, userdata);
|
||||
return len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_MERGE
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
struct prettydata {
|
||||
int level;
|
||||
int len;
|
||||
int prev;
|
||||
const char *pad;
|
||||
int padlen;
|
||||
mjson_print_fn_t fn;
|
||||
void *userdata;
|
||||
};
|
||||
|
||||
static int pretty_cb(int ev, const char *s, int off, int len, void *ud) {
|
||||
struct prettydata *d = (struct prettydata *) ud;
|
||||
int i;
|
||||
switch (ev) {
|
||||
case '{':
|
||||
case '[':
|
||||
d->level++;
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case '}':
|
||||
case ']':
|
||||
d->level--;
|
||||
if (d->prev != '[' && d->prev != '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
case ',':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata);
|
||||
break;
|
||||
case MJSON_TOK_KEY:
|
||||
if (d->prev == '{' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
default:
|
||||
if (d->prev == '[' && d->padlen > 0) {
|
||||
d->len += d->fn("\n", 1, d->userdata);
|
||||
for (i = 0; i < d->level; i++)
|
||||
d->len += d->fn(d->pad, d->padlen, d->userdata);
|
||||
}
|
||||
d->len += d->fn(s + off, len, d->userdata);
|
||||
break;
|
||||
}
|
||||
d->prev = ev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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};
|
||||
if (mjson(s, n, pretty_cb, &d) < 0) return -1;
|
||||
return d.len;
|
||||
}
|
||||
#endif // MJSON_ENABLE_PRETTY
|
||||
|
||||
#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;
|
||||
while (i < n1 || j < n2) {
|
||||
if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
|
||||
i++, j++;
|
||||
} else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
|
||||
ni = i, nj = j + 1, i++;
|
||||
} else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
|
||||
i = ni, j = nj;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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_vprintf(r->fn, r->fndata, data_fmt, ap);
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}}\n");
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt,
|
||||
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) {
|
||||
mjson_vprintf(r->fn, r->fndata, result_fmt, ap);
|
||||
} else {
|
||||
mjson_printf(r->fn, r->fndata, "%s", "null");
|
||||
}
|
||||
mjson_printf(r->fn, r->fndata, "}\n");
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
mjson_print_fn_t fn, void *fndata) {
|
||||
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;
|
||||
|
||||
// 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);
|
||||
if (result_sz > 0 || error_sz > 0) {
|
||||
if (ctx->response_cb != NULL) ctx->response_cb(req, req_sz, ctx->userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Method must exist and must be a string
|
||||
if (mjson_find(req, req_sz, "$.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;
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (m == NULL) {
|
||||
jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata,
|
||||
va_list *ap) {
|
||||
struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *);
|
||||
struct jsonrpc_method *m;
|
||||
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));
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void rpclist(struct jsonrpc_request *r) {
|
||||
jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->userdata);
|
||||
}
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb,
|
||||
void *userdata) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) {
|
||||
jsonrpc_ctx_init(&jsonrpc_default_context, response_cb, userdata);
|
||||
}
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright (c) 2018-2020 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef MJSON_H
|
||||
#define MJSON_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef MJSON_ENABLE_PRINT
|
||||
#define MJSON_ENABLE_PRINT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_RPC
|
||||
#define MJSON_ENABLE_RPC 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_BASE64
|
||||
#define MJSON_ENABLE_BASE64 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_MERGE 0
|
||||
#elif MJSON_ENABLE_MERGE
|
||||
#define MJSON_ENABLE_NEXT 1
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_PRETTY 0
|
||||
#elif MJSON_ENABLE_PRETTY
|
||||
#define MJSON_ENABLE_NEXT 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
|
||||
#endif
|
||||
|
||||
#ifndef MJSON_RPC_LIST_NAME
|
||||
#define MJSON_RPC_LIST_NAME "rpc.list"
|
||||
#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_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20)
|
||||
|
||||
typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud);
|
||||
|
||||
#ifndef MJSON_MAX_DEPTH
|
||||
#define MJSON_MAX_DEPTH 20
|
||||
#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_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);
|
||||
int mjson_get_hex(const char *s, int len, const char *path, char *to, int n);
|
||||
|
||||
#if MJSON_ENABLE_NEXT
|
||||
int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,
|
||||
int *vlen, int *vtype);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_BASE64
|
||||
int mjson_get_base64(const char *s, int len, const char *path, char *to, int n);
|
||||
int mjson_base64_dec(const char *src, int n, char *dst, int dlen);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_PRINT
|
||||
typedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata);
|
||||
typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t, void *, va_list *);
|
||||
|
||||
struct mjson_fixedbuf {
|
||||
char *ptr;
|
||||
int size, len;
|
||||
};
|
||||
|
||||
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_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_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);
|
||||
|
||||
#if MJSON_ENABLE_PRETTY
|
||||
int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#if MJSON_ENABLE_MERGE
|
||||
int mjson_merge(const char *, int, const char *, int, mjson_print_fn_t, void *);
|
||||
#endif
|
||||
|
||||
#endif // MJSON_ENABLE_PRINT
|
||||
|
||||
#if MJSON_ENABLE_RPC
|
||||
|
||||
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 {
|
||||
const char *frame; // Points to the whole frame
|
||||
int frame_len; // Frame length
|
||||
const char *params; // Points to the "params" in the request frame
|
||||
int params_len; // Length of the "params"
|
||||
const char *id; // Points to the "id" in the request frame
|
||||
int id_len; // Length of the "id"
|
||||
const char *method; // Points to the "method" in the request frame
|
||||
int method_len; // Length of the "method"
|
||||
mjson_print_fn_t fn; // Printer function
|
||||
void *fndata; // Printer function data
|
||||
void *userdata; // Callback's user data as specified at export time
|
||||
};
|
||||
|
||||
struct jsonrpc_method {
|
||||
const char *method;
|
||||
int method_sz;
|
||||
void (*cb)(struct jsonrpc_request *);
|
||||
void *cbdata;
|
||||
struct jsonrpc_method *next;
|
||||
};
|
||||
|
||||
// Main RPC context, stores current request information and a list of
|
||||
// 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];
|
||||
};
|
||||
|
||||
// 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; \
|
||||
} while (0)
|
||||
|
||||
void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *);
|
||||
void jsonrpc_return_error(struct jsonrpc_request *r, int code,
|
||||
const char *message, const char *data_fmt, ...);
|
||||
void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt,
|
||||
...);
|
||||
void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz,
|
||||
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);
|
||||
|
||||
extern struct jsonrpc_ctx jsonrpc_default_context;
|
||||
|
||||
#define jsonrpc_export(name, fn, ud) \
|
||||
jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn), (ud))
|
||||
|
||||
#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_ERROR_INVALID -32700 /* Invalid JSON was received */
|
||||
#define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */
|
||||
#define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */
|
||||
#define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */
|
||||
|
||||
#endif // MJSON_ENABLE_RPC
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // MJSON_H
|
||||
@@ -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%; } }
|
||||
@@ -5,18 +5,10 @@
|
||||
#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"
|
||||
|
||||
// SPIFFS is flat, so tell Mongoose that the FS root is a directory
|
||||
// This cludge is not required for filesystems with directory support
|
||||
static int my_stat(const char *path, size_t *size, time_t *mtime) {
|
||||
int flags = mg_fs_posix.stat(path, size, mtime);
|
||||
if (strcmp(path, FS_ROOT) == 0) flags |= MG_FS_DIR;
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Event handler for an server (accepted) connection. Implemented endpoints:
|
||||
// /api/stats - return JSON object with ESP32 stats (free RAM)
|
||||
// any other - serve files from the filesystem
|
||||
@@ -26,9 +18,7 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (mg_http_match_uri(hm, "/api/stats")) {
|
||||
mg_http_reply(c, 200, "", "{\"ram\": %lu}\n", xPortGetFreeHeapSize());
|
||||
} else {
|
||||
struct mg_fs fs = mg_fs_posix;
|
||||
fs.stat = my_stat;
|
||||
struct mg_http_serve_opts opts = {.root_dir = FS_ROOT, .fs = &fs};
|
||||
struct mg_http_serve_opts opts = {.root_dir = FS_ROOT};
|
||||
mg_http_serve_dir(c, hm, &opts);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +29,7 @@ void app_main(void) {
|
||||
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_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
|
||||
@@ -49,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
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, &mg_fs_posix, "/tmp");
|
||||
} else {
|
||||
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
|
||||
mg_http_serve_dir(c, hm, &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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -28,8 +28,8 @@ 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 fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_debug_level = "2";
|
||||
static const char *s_root_dir = NULL;
|
||||
static const char *s_listening_address = "http://localhost:8000";
|
||||
static const char *s_root_dir = ".";
|
||||
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 = "#.html";
|
||||
|
||||
@@ -20,7 +20,7 @@ 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_message *hm = ev_data, tmp = {};
|
||||
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;
|
||||
@@ -29,9 +29,9 @@ static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
mg_http_parse((char *) c->send.buf, c->send.len, &tmp);
|
||||
cl = mg_http_get_header(&tmp, "Content-Length");
|
||||
if (cl == NULL) cl = &unknown;
|
||||
LOG(LL_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));
|
||||
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;
|
||||
}
|
||||
@@ -52,7 +52,6 @@ static void usage(const char *prog) {
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char path[MG_PATH_MAX] = ".";
|
||||
const char *root_dir = ".";
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *c;
|
||||
int i;
|
||||
@@ -60,7 +59,7 @@ int main(int argc, char *argv[]) {
|
||||
// Parse command-line flags
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-d") == 0) {
|
||||
root_dir = argv[++i];
|
||||
s_root_dir = argv[++i];
|
||||
} else if (strcmp(argv[i], "-H") == 0) {
|
||||
s_enable_hexdump = argv[++i];
|
||||
} else if (strcmp(argv[i], "-S") == 0) {
|
||||
@@ -75,8 +74,11 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Root directory must not contain double dots. Make it absolute
|
||||
realpath(root_dir, path);
|
||||
s_root_dir = path;
|
||||
// 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);
|
||||
@@ -84,18 +86,18 @@ int main(int argc, char *argv[]) {
|
||||
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, ("Mongoose version : v%s", MG_VERSION));
|
||||
LOG(LL_INFO, ("Listening on : %s", s_listening_address));
|
||||
LOG(LL_INFO, ("Web root : [%s]", 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
|
||||
|
||||
@@ -76,14 +76,12 @@ static void timer_fn(void *arg) {
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer t1;
|
||||
mg_mgr_init(&mgr);
|
||||
srand(time(NULL));
|
||||
mg_timer_init(&t1, 1000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, 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);
|
||||
LOG(LL_INFO, ("Listening on %s", s_listen_on));
|
||||
MG_INFO(("Listening on %s", s_listen_on));
|
||||
for (;;) mg_mgr_poll(&mgr, 1000);
|
||||
mg_timer_free(&t1);
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
NAME ?= $(notdir $(CURDIR))
|
||||
|
||||
build:
|
||||
git clone --depth 1 https://github.com/mongoose-examples/$(NAME)
|
||||
cp ./../../../mongoose.[ch] $(NAME)
|
||||
make -C $(NAME) build
|
||||
|
||||
clean:
|
||||
rm -rf $(NAME)
|
||||
@@ -49,7 +49,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_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);
|
||||
LOG(LL_INFO, ("[%.*s] -> [%s]", (int) req.len, req.ptr, response));
|
||||
MG_INFO(("[%.*s] -> [%s]", (int) req.len, req.ptr, response));
|
||||
free(response);
|
||||
}
|
||||
(void) fn_data;
|
||||
@@ -67,11 +67,10 @@ static void timer_fn(void *arg) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr; // Event manager
|
||||
struct mg_timer t1; // Timer
|
||||
struct mg_mgr mgr; // Event manager
|
||||
|
||||
mg_mgr_init(&mgr); // Init event manager
|
||||
mg_timer_init(&t1, 5000, MG_TIMER_REPEAT, timer_fn, &mgr); // Init timer
|
||||
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
|
||||
@@ -80,7 +79,6 @@ int main(void) {
|
||||
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_timer_free(&t1); // Free timer resources
|
||||
mg_mgr_free(&mgr); // Deallocate event manager
|
||||
return 0;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -43,7 +43,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
// c->is_hexdumping = 1;
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
// On error, log error message
|
||||
LOG(LL_ERROR, ("%p %s", c->fd, (char *) ev_data));
|
||||
MG_ERROR(("%p %s", c->fd, (char *) ev_data));
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
// Set up 2-way TLS that is required by AWS IoT
|
||||
struct mg_tls_opts opts = {
|
||||
@@ -52,28 +52,28 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
} else if (ev == MG_EV_MQTT_OPEN) {
|
||||
// MQTT connect is successful
|
||||
struct mg_str topic = mg_str(s_rx_topic);
|
||||
LOG(LL_INFO, ("Connected to %s", s_url));
|
||||
LOG(LL_INFO, ("Subscribing to %s", s_rx_topic));
|
||||
MG_INFO(("Connected to %s", s_url));
|
||||
MG_INFO(("Subscribing to %s", s_rx_topic));
|
||||
mg_mqtt_sub(c, topic, s_qos);
|
||||
c->label[0] = 'X'; // Set a label that we're logged in
|
||||
} else if (ev == MG_EV_MQTT_MSG) {
|
||||
// When we receive MQTT message, print it
|
||||
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
|
||||
LOG(LL_INFO, ("Received on %.*s : %.*s", (int) mm->topic.len, mm->topic.ptr,
|
||||
(int) mm->data.len, mm->data.ptr));
|
||||
MG_INFO(("Received on %.*s : %.*s", (int) mm->topic.len, mm->topic.ptr,
|
||||
(int) mm->data.len, mm->data.ptr));
|
||||
} else if (ev == MG_EV_POLL && c->label[0] == 'X') {
|
||||
static unsigned long prev_second;
|
||||
unsigned long now_second = (*(unsigned long *) ev_data) / 1000;
|
||||
if (now_second != prev_second) {
|
||||
struct mg_str topic = mg_str(s_tx_topic), data = mg_str("{\"a\":123}");
|
||||
LOG(LL_INFO, ("Publishing to %s", s_tx_topic));
|
||||
MG_INFO(("Publishing to %s", s_tx_topic));
|
||||
mg_mqtt_pub(c, topic, data, s_qos, false);
|
||||
prev_second = now_second;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
|
||||
LOG(LL_INFO, ("Got event %d, stopping...", ev));
|
||||
MG_INFO(("Got event %d, stopping...", ev));
|
||||
*(bool *) fn_data = true; // Signal that we're done
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ int main(void) {
|
||||
struct mg_mqtt_opts opts = {.clean = true};
|
||||
bool done = false;
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
LOG(LL_INFO, ("Connecting to %s", s_url)); // Inform that we're starting
|
||||
MG_INFO(("Connecting to %s", s_url)); // Inform that we're starting
|
||||
mg_mqtt_connect(&mgr, s_url, &opts, fn, &done); // Create client connection
|
||||
while (!done) mg_mgr_poll(&mgr, 1000); // Loop until done
|
||||
mg_mgr_free(&mgr); // Finished, cleanup
|
||||
|
||||
@@ -27,11 +27,11 @@ static void signal_handler(int signo) {
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
LOG(LL_INFO, ("CREATED"));
|
||||
MG_INFO(("CREATED"));
|
||||
// c->is_hexdumping = 1;
|
||||
} else if (ev == MG_EV_ERROR) {
|
||||
// On error, log error message
|
||||
LOG(LL_ERROR, ("%p %s", c->fd, (char *) ev_data));
|
||||
MG_ERROR(("%p %s", c->fd, (char *) ev_data));
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
// If target URL is SSL/TLS, command client connection to use TLS
|
||||
if (mg_url_is_ssl(s_url)) {
|
||||
@@ -42,21 +42,21 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
// MQTT connect is successful
|
||||
struct mg_str subt = mg_str(s_sub_topic);
|
||||
struct mg_str pubt = mg_str(s_pub_topic), data = mg_str("hello");
|
||||
LOG(LL_INFO, ("CONNECTED to %s", s_url));
|
||||
MG_INFO(("CONNECTED to %s", s_url));
|
||||
mg_mqtt_sub(c, subt, s_qos);
|
||||
LOG(LL_INFO, ("SUBSCRIBED to %.*s", (int) subt.len, subt.ptr));
|
||||
MG_INFO(("SUBSCRIBED to %.*s", (int) subt.len, subt.ptr));
|
||||
|
||||
mg_mqtt_pub(c, pubt, data, s_qos, false);
|
||||
LOG(LL_INFO, ("PUBSLISHED %.*s -> %.*s", (int) data.len, data.ptr,
|
||||
(int) pubt.len, pubt.ptr));
|
||||
MG_INFO(("PUBSLISHED %.*s -> %.*s", (int) data.len, data.ptr,
|
||||
(int) pubt.len, pubt.ptr));
|
||||
} else if (ev == MG_EV_MQTT_MSG) {
|
||||
// When we get echo response, print it
|
||||
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
|
||||
LOG(LL_INFO, ("RECEIVED %.*s <- %.*s", (int) mm->data.len, mm->data.ptr,
|
||||
(int) mm->topic.len, mm->topic.ptr));
|
||||
MG_INFO(("RECEIVED %.*s <- %.*s", (int) mm->data.len, mm->data.ptr,
|
||||
(int) mm->topic.len, mm->topic.ptr));
|
||||
c->is_closing = 1;
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
LOG(LL_INFO, ("CLOSED"));
|
||||
MG_INFO(("CLOSED"));
|
||||
s_conn = NULL; // Mark that we're closed
|
||||
}
|
||||
(void) fn_data;
|
||||
@@ -74,17 +74,15 @@ static void timer_fn(void *arg) {
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer timer;
|
||||
int topts = MG_TIMER_REPEAT | MG_TIMER_RUN_NOW;
|
||||
|
||||
signal(SIGINT, signal_handler); // Setup signal handlers - exist event
|
||||
signal(SIGTERM, signal_handler); // manager loop on SIGINT and SIGTERM
|
||||
|
||||
mg_mgr_init(&mgr); // Init event manager
|
||||
mg_timer_init(&timer, 3000, topts, timer_fn, &mgr); // Init timer
|
||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
|
||||
mg_mgr_free(&mgr); // Finished, cleanup
|
||||
mg_timer_free(&timer); // Free timer resources
|
||||
mg_mgr_init(&mgr); // Init event manager
|
||||
mg_timer_add(&mgr, 3000, topts, timer_fn, &mgr); // Init timer
|
||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
|
||||
mg_mgr_free(&mgr); // Finished, cleanup
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ static const char *s_topic = "mg/test";
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_ERROR) {
|
||||
// On error, log error message
|
||||
LOG(LL_ERROR, ("%p %s", c->fd, (char *) ev_data));
|
||||
MG_ERROR(("%p %s", c->fd, (char *) ev_data));
|
||||
} else if (ev == MG_EV_CONNECT) {
|
||||
// If target URL is SSL/TLS, command client connection to use TLS
|
||||
if (mg_url_is_ssl(s_url)) {
|
||||
@@ -27,7 +27,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
}
|
||||
} else if (ev == MG_EV_WS_OPEN) {
|
||||
// WS connection established. Perform MQTT login
|
||||
LOG(LL_INFO, ("Connected to WS. Logging in to MQTT..."));
|
||||
MG_INFO(("Connected to WS. Logging in to MQTT..."));
|
||||
struct mg_mqtt_opts opts = {.will_qos = 1,
|
||||
.will_topic = mg_str(s_topic),
|
||||
.will_message = mg_str("goodbye")};
|
||||
@@ -37,7 +37,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
} else if (ev == MG_EV_WS_MSG) {
|
||||
struct mg_mqtt_message mm;
|
||||
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
|
||||
LOG(LL_INFO, ("GOT %d bytes WS msg", (int) wm->data.len));
|
||||
MG_INFO(("GOT %d bytes WS msg", (int) wm->data.len));
|
||||
while ((mg_mqtt_parse((uint8_t *) wm->data.ptr, wm->data.len, &mm)) == 0) {
|
||||
switch (mm.cmd) {
|
||||
case MQTT_CMD_CONNACK:
|
||||
@@ -45,24 +45,24 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (mm.ack == 0) {
|
||||
struct mg_str topic = mg_str(s_topic), data = mg_str("hello");
|
||||
size_t len = c->send.len;
|
||||
LOG(LL_INFO, ("CONNECTED to %s", s_url));
|
||||
MG_INFO(("CONNECTED to %s", s_url));
|
||||
mg_mqtt_sub(c, topic, 1);
|
||||
mg_ws_wrap(c, len, WEBSOCKET_OP_BINARY);
|
||||
LOG(LL_INFO, ("SUBSCRIBED to %.*s", (int) topic.len, topic.ptr));
|
||||
MG_INFO(("SUBSCRIBED to %.*s", (int) topic.len, topic.ptr));
|
||||
mg_mqtt_pub(c, topic, data, 1, false);
|
||||
LOG(LL_INFO, ("PUBSLISHED %.*s -> %.*s", (int) data.len, data.ptr,
|
||||
(int) topic.len, topic.ptr));
|
||||
MG_INFO(("PUBSLISHED %.*s -> %.*s", (int) data.len, data.ptr,
|
||||
(int) topic.len, topic.ptr));
|
||||
len = mg_ws_wrap(c, c->send.len - len, WEBSOCKET_OP_BINARY);
|
||||
} else {
|
||||
LOG(LL_ERROR, ("%lu MQTT auth failed, code %d", c->id, mm.ack));
|
||||
MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
|
||||
c->is_closing = 1;
|
||||
}
|
||||
break;
|
||||
case MQTT_CMD_PUBLISH: {
|
||||
LOG(LL_DEBUG, ("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
|
||||
mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
|
||||
LOG(LL_INFO, ("RECEIVED %.*s <- %.*s", (int) mm.data.len, mm.data.ptr,
|
||||
(int) mm.topic.len, mm.topic.ptr));
|
||||
MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
|
||||
mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
|
||||
MG_INFO(("RECEIVED %.*s <- %.*s", (int) mm.data.len, mm.data.ptr,
|
||||
(int) mm.topic.len, mm.topic.ptr));
|
||||
c->is_closing = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ static void signal_handler(int signo) {
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_MQTT_CMD) {
|
||||
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
|
||||
LOG(LL_DEBUG, ("cmd %d qos %d", mm->cmd, mm->qos));
|
||||
MG_DEBUG(("cmd %d qos %d", mm->cmd, mm->qos));
|
||||
switch (mm->cmd) {
|
||||
case MQTT_CMD_CONNECT: {
|
||||
// Client connects
|
||||
@@ -57,9 +57,9 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
sub->topic = mg_strdup(topic);
|
||||
sub->qos = qos;
|
||||
LIST_ADD_HEAD(struct sub, &s_subs, sub);
|
||||
LOG(LL_INFO,
|
||||
MG_INFO(
|
||||
("SUB %p [%.*s]", c->fd, (int) sub->topic.len, sub->topic.ptr));
|
||||
// Change '+' to '*' for topic matching using mg_globmatch
|
||||
// Change '+' to '*' for topic matching using mg_match
|
||||
for (size_t i = 0; i < sub->topic.len; i++) {
|
||||
if (sub->topic.ptr[i] == '+') ((char *) sub->topic.ptr)[i] = '*';
|
||||
}
|
||||
@@ -73,12 +73,11 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
}
|
||||
case MQTT_CMD_PUBLISH: {
|
||||
// Client published message. Push to all subscribed channels
|
||||
LOG(LL_INFO, ("PUB %p [%.*s] -> [%.*s]", c->fd, (int) mm->data.len,
|
||||
mm->data.ptr, (int) mm->topic.len, mm->topic.ptr));
|
||||
MG_INFO(("PUB %p [%.*s] -> [%.*s]", c->fd, (int) mm->data.len,
|
||||
mm->data.ptr, (int) mm->topic.len, mm->topic.ptr));
|
||||
for (struct sub *sub = s_subs; sub != NULL; sub = sub->next) {
|
||||
if (mg_globmatch(sub->topic.ptr, sub->topic.len, mm->topic.ptr,
|
||||
mm->topic.len)) {
|
||||
mg_mqtt_pub(sub->c, &mm->topic, &mm->data, 1, false);
|
||||
if (mg_match(mm->topic, sub->topic, NULL)) {
|
||||
mg_mqtt_pub(sub->c, mm->topic, mm->data, 1, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -91,8 +90,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
for (struct sub *next, *sub = s_subs; sub != NULL; sub = next) {
|
||||
next = sub->next;
|
||||
if (c != sub->c) continue;
|
||||
LOG(LL_INFO,
|
||||
("UNSUB %p [%.*s]", c->fd, (int) sub->topic.len, sub->topic.ptr));
|
||||
MG_INFO(("UNSUB %p [%.*s]", c->fd, (int) sub->topic.len, sub->topic.ptr));
|
||||
LIST_DELETE(struct sub, &s_subs, sub);
|
||||
}
|
||||
}
|
||||
@@ -104,9 +102,9 @@ int main(void) {
|
||||
signal(SIGINT, signal_handler); // Setup signal handlers - exist event
|
||||
signal(SIGTERM, signal_handler); // manager loop on SIGINT and SIGTERM
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
LOG(LL_INFO, ("Starting on %s", s_listen_on)); // Inform that we're starting
|
||||
mg_mqtt_listen(&mgr, s_listen_on, fn, NULL); // Create MQTT listener
|
||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
|
||||
mg_mgr_free(&mgr); // Cleanup
|
||||
MG_INFO(("Starting on %s", s_listen_on)); // Inform that we're starting
|
||||
mg_mqtt_listen(&mgr, s_listen_on, fn, NULL); // Create MQTT listener
|
||||
while (s_signo == 0) mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
|
||||
mg_mgr_free(&mgr); // Cleanup
|
||||
return 0;
|
||||
}
|
||||
@@ -35,9 +35,36 @@ static void start_thread(void (*f)(void *), void *p) {
|
||||
}
|
||||
|
||||
static void thread_function(void *param) {
|
||||
struct mg_connection *c = param; // Pipe connection
|
||||
int sock = (int) (size_t) param; // Paired socket. We own it
|
||||
sleep(2); // Simulate long execution
|
||||
mg_mgr_wakeup(c, "hi", 2); // Wakeup event manager
|
||||
send(sock, "hi", 2, 2); // Wakeup event manager
|
||||
close(sock); // Close the connection
|
||||
}
|
||||
|
||||
static void link_conns(struct mg_connection *c1, struct mg_connection *c2) {
|
||||
c1->fn_data = c2;
|
||||
c2->fn_data = c1;
|
||||
}
|
||||
|
||||
static void unlink_conns(struct mg_connection *c1, struct mg_connection *c2) {
|
||||
c1->fn_data = c2->fn_data = NULL;
|
||||
}
|
||||
|
||||
// Pipe event handler
|
||||
static void pcb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
struct mg_connection *parent = (struct mg_connection *) fn_data;
|
||||
MG_INFO(("%lu %p %d %p", c->id, c->fd, ev, parent));
|
||||
if (parent == NULL) { // If parent connection closed, close too
|
||||
c->is_closing = 1;
|
||||
} else if (ev == MG_EV_READ) { // Got data from the worker thread
|
||||
mg_http_reply(parent, 200, "Host: foo.com\r\n", "%.*s\n", c->recv.len,
|
||||
c->recv.buf); // Respond!
|
||||
c->recv.len = 0; // Tell Mongoose we've consumed data
|
||||
} else if (ev == MG_EV_OPEN) {
|
||||
link_conns(c, parent);
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
unlink_conns(c, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request callback
|
||||
@@ -50,32 +77,19 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
mg_http_reply(c, 200, "Host: foo.com\r\n", "hi\n");
|
||||
} else {
|
||||
// Multithreading code path
|
||||
c->label[0] = 'W'; // Mark us as waiting for data
|
||||
start_thread(thread_function, fn_data); // Start handling thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pipe event handler
|
||||
static void pcb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
if (ev == MG_EV_READ) {
|
||||
struct mg_connection *t;
|
||||
for (t = c->mgr->conns; t != NULL; t = t->next) {
|
||||
if (t->label[0] != 'W') continue; // Ignore un-marked connections
|
||||
mg_http_reply(t, 200, "Host: foo.com\r\n", "%.*s\n", c->recv.len,
|
||||
c->recv.buf); // Respond!
|
||||
t->label[0] = 0; // Clear mark
|
||||
int sock = mg_mkpipe(c->mgr, pcb, c); // Create pipe
|
||||
start_thread(thread_function, (void *) (size_t) sock); // Start thread
|
||||
}
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
if (c->fn_data != NULL) unlink_conns(c, c->fn_data);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *pipe; // Used to wake up event manager
|
||||
mg_mgr_init(&mgr);
|
||||
mg_log_set("3");
|
||||
pipe = mg_mkpipe(&mgr, pcb, NULL); // Create pipe
|
||||
mg_http_listen(&mgr, "http://localhost:8000", fn, pipe); // Create listener
|
||||
mg_http_listen(&mgr, "http://localhost:8000", fn, NULL); // Create listener
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
|
||||
mg_mgr_free(&mgr); // Cleanup
|
||||
return 0;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
PROG ?= example
|
||||
|
||||
all: $(PROG)
|
||||
$(DEBUGGER) ./$(PROG)
|
||||
|
||||
$(PROG):
|
||||
$(CC) ../../mongoose.c -I../.. $(CFLAGS) $(EXTRA) -o $(PROG) main.c
|
||||
|
||||
clean:
|
||||
rm -rf $(PROG) *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright (c) 2020 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;
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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, ("[%.*s] auth %s", (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/user/data")) {
|
||||
// Data visible to both users and administrators
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
mg_http_printf_chunk(c, "{\"data\":\"%s\"}\n", "this is user data");
|
||||
mg_http_printf_chunk(c, "");
|
||||
} else if (mg_http_match_uri(hm, "/api/admin/data")) {
|
||||
// Data visible only to administrators
|
||||
if (strcmp(u->name, "admin") == 0) {
|
||||
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
mg_http_printf_chunk(c, "{\"data\":\"%s\"}\n", "this is admin data");
|
||||
mg_http_printf_chunk(c, "");
|
||||
} 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/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 = {.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, NULL);
|
||||
|
||||
for (;;) mg_mgr_poll(&mgr, 1000);
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,178 +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="/settings" title="Settings" 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>
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class AdminDashboard extends Component {
|
||||
state = {udata: '', adata: ''};
|
||||
componentDidMount() {
|
||||
axios.get('/api/user/data').then(r => this.setState({udata: r.data}));
|
||||
axios.get('/api/admin/data').then(r => this.setState({adata: r.data}));
|
||||
}
|
||||
render(props, state) {
|
||||
console.log('-->', state);
|
||||
return html`<div class="container-fluid">
|
||||
this is admin page
|
||||
<div>admin data, returned by server:
|
||||
<code class="mx-2">${JSON.stringify(state.adata)}</code></div>
|
||||
<div>user data, returned by server:
|
||||
<code class="mx-2">${JSON.stringify(state.udata)}</code></div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class UserDashboard extends Component {
|
||||
state = {udata: ''};
|
||||
componentDidMount() {
|
||||
axios.get('/api/user/data').then(r => this.setState({udata: r.data}));
|
||||
axios.get('/api/admin/data').then(r => this.setState({adata: r.data}));
|
||||
}
|
||||
render(props, state) {
|
||||
return html`<div class="container-fluid">
|
||||
<div>admin data, returned by server (must be empty and dev tools should show 403):
|
||||
<code class="mx-2">${JSON.stringify(state.adata)}</code></div>
|
||||
<div>user data, returned by server:
|
||||
<code class="mx-2">${JSON.stringify(state.udata)}</code></div>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class Settings extends Component {
|
||||
state = {};
|
||||
render(props, state) {
|
||||
return html`<div class="container-fluid">settings page</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(Settings, {path: 'settings', 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 |
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,18 +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="axios.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -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,6 +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; }
|
||||
@@ -50,8 +50,6 @@ SOURCES += $(FREERTOS_PLUS_TCP_PATH)/portable/BufferManagement/BufferAllocation_
|
||||
SOURCES += $(FREERTOS_PLUS_TCP_PATH)/portable/NetworkInterface/STM32Fxx/NetworkInterface.c
|
||||
SOURCES += $(FREERTOS_PLUS_TCP_PATH)/portable/NetworkInterface/STM32Fxx/stm32fxx_hal_eth.c
|
||||
|
||||
OBJECTS = obj/boot.o $(SOURCES:%.c=obj/%.o)
|
||||
|
||||
example:
|
||||
true
|
||||
|
||||
@@ -63,18 +61,11 @@ $(PROG).bin: $(PROG).elf
|
||||
$(PROG).hex: $(PROG).bin
|
||||
$(DOCKER) arm-none-eabi-objcopy -I binary -O ihex --change-address 0x8000000 $< $@
|
||||
|
||||
$(PROG).elf: $(OBJECTS) Makefile
|
||||
$(DOCKER) arm-none-eabi-gcc $(OBJECTS) $(LINKFLAGS) -o $@
|
||||
$(PROG).elf: Makefile
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOCKER) arm-none-eabi-gcc $(SOURCES) $(ARCH)/boot.s $(CFLAGS) $(LINKFLAGS) -o $@
|
||||
$(DOCKER) arm-none-eabi-size -A $@
|
||||
|
||||
obj/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOCKER) arm-none-eabi-gcc $(CFLAGS) -c $< -o $@
|
||||
|
||||
obj/boot.o: $(ARCH)/boot.s
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOCKER) arm-none-eabi-as -g3 --warn --fatal-warnings $(MCU_FLAGS) $< -o $@
|
||||
|
||||
flash: $(PROG).bin
|
||||
st-flash --reset write $< 0x8000000
|
||||
|
||||
@@ -95,4 +86,4 @@ gdb: #$(PROG).elf
|
||||
$(ELF)
|
||||
|
||||
clean:
|
||||
@rm -rf *.{bin,elf,map,lst,tgz,zip,hex} obj
|
||||
@rm -rf firmware.*
|
||||
@@ -20,7 +20,7 @@ static void server(void *args) {
|
||||
struct mg_mgr mgr;
|
||||
mg_log_set(s_debug_level);
|
||||
mg_mgr_init(&mgr);
|
||||
LOG(LL_INFO, ("Starting Mongoose v%s", MG_VERSION)); // Tell the world
|
||||
MG_INFO(("Starting Mongoose v%s", MG_VERSION)); // Tell the world
|
||||
mg_http_listen(&mgr, s_listening_address, cb, &mgr); // Web listener
|
||||
while (args == NULL) mg_mgr_poll(&mgr, 1000); // Infinite event loop
|
||||
mg_mgr_free(&mgr); // Unreachable
|
||||
@@ -32,14 +32,14 @@ static void blinker(void *args) {
|
||||
for (;;) {
|
||||
gpio_toggle(pin);
|
||||
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||
LOG(LL_INFO, ("blink %s, RAM: %u", (char *) args, xPortGetFreeHeapSize()));
|
||||
MG_INFO(("blink %s, RAM: %u", (char *) args, xPortGetFreeHeapSize()));
|
||||
}
|
||||
}
|
||||
|
||||
// Start Mongoose server when network is ready
|
||||
void vApplicationIPNetworkEventHook(eIPCallbackEvent_t ev) {
|
||||
static bool mongoose_started = false;
|
||||
LOG(LL_INFO, ("FreeRTOS net event %d, up: %d", ev, eNetworkUp));
|
||||
MG_INFO(("FreeRTOS net event %d, up: %d", ev, eNetworkUp));
|
||||
if (ev == eNetworkUp && mongoose_started == false) {
|
||||
xTaskCreate(server, "server", 8192, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
mongoose_started = true;
|
||||
@@ -49,7 +49,7 @@ void vApplicationIPNetworkEventHook(eIPCallbackEvent_t ev) {
|
||||
static void init_heap(void) {
|
||||
extern uint32_t _end, _estack;
|
||||
uint8_t *ptr = (uint8_t *) ((((uint32_t) &_end) + 7) & ~7U);
|
||||
uint32_t len = (uint32_t)((char *) &_estack - (char *) &_end);
|
||||
uint32_t len = (uint32_t) ((char *) &_estack - (char *) &_end);
|
||||
HeapRegion_t regions[] = {{ptr, len}, {NULL, 0}};
|
||||
vPortDefineHeapRegions(regions);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
NAME = ti-ek-tm4c1294xl-http-server
|
||||
DOCKER ?= docker run -v $(CURDIR):/workspace mdashnet/ccs
|
||||
|
||||
build:
|
||||
git clone --depth 1 https://github.com/mongoose-examples/$(NAME)
|
||||
cp ./../../../mongoose.[ch] $(NAME)/
|
||||
$(DOCKER) /opt/ti/ccs/eclipse/eclipse -noSplash -data /workspace -application com.ti.ccstudio.apps.projectBuild -ccs.autoImport -ccs.projects $(NAME) -ccs.configuration Release
|
||||
|
||||
clean:
|
||||
rm -rf $(NAME)
|
||||
@@ -40,13 +40,11 @@ static void timer_fn(void *arg) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr; // Event manager
|
||||
struct mg_timer t1; // Timer
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_timer_init(&t1, 300, MG_TIMER_REPEAT, timer_fn, &mgr); // Init timer
|
||||
struct mg_mgr mgr; // Event manager
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_timer_add(&mgr, 300, MG_TIMER_REPEAT, timer_fn, &mgr); // Init timer
|
||||
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
|
||||
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
|
||||
mg_timer_free(&t1); // Free timer resources
|
||||
mg_mgr_free(&mgr); // Free manager resources
|
||||
return 0;
|
||||
}
|
||||
@@ -6,25 +6,25 @@
|
||||
static const char *s_ssdp_url = "udp://239.255.255.250:1900";
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
LOG(LL_DEBUG, ("%p got event: %d %p %p", c, ev, ev_data, fn_data));
|
||||
MG_DEBUG(("%p got event: %d %p %p", c, ev, ev_data, fn_data));
|
||||
if (ev == MG_EV_OPEN) {
|
||||
c->is_hexdumping = 1;
|
||||
} else if (ev == MG_EV_RESOLVE) {
|
||||
// c->peer gets populated with multicast address. Store it in c->label
|
||||
memcpy(c->label, &c->peer, sizeof(c->peer));
|
||||
// c->rem gets populated with multicast address. Store it in c->label
|
||||
memcpy(c->label, &c->rem, sizeof(c->rem));
|
||||
} else if (ev == MG_EV_READ) {
|
||||
// Each response to the SSDP socket will change c->peer.
|
||||
// Each response to the SSDP socket will change c->rem.
|
||||
// We can now do mg_printf(c, "haha"); to respond back to the remote side.
|
||||
// But in our case, we should restore the multicast address in order
|
||||
// to have next search to go to the multicast address
|
||||
memcpy(&c->peer, c->label, sizeof(c->peer));
|
||||
memcpy(&c->rem, c->label, sizeof(c->rem));
|
||||
}
|
||||
}
|
||||
|
||||
static void tfn(void *param) {
|
||||
struct mg_connection *c = param;
|
||||
if (c == NULL) return;
|
||||
LOG(LL_INFO, ("Sending M-SEARCH"));
|
||||
MG_INFO(("Sending M-SEARCH"));
|
||||
mg_printf(c, "%s",
|
||||
"M-SEARCH * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
@@ -36,11 +36,10 @@ static void tfn(void *param) {
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_timer timer;
|
||||
static struct mg_connection *c;
|
||||
mg_mgr_init(&mgr);
|
||||
c = mg_connect(&mgr, s_ssdp_url, fn, NULL);
|
||||
mg_timer_init(&timer, 2000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, tfn, c);
|
||||
mg_timer_add(&mgr, 2000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, tfn, c);
|
||||
while (true) mg_mgr_poll(&mgr, 200);
|
||||
mg_mgr_free(&mgr);
|
||||
return 0;
|
||||
|
||||
@@ -54,13 +54,11 @@ static void timer_callback(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, 500, MG_TIMER_REPEAT, timer_callback, &mgr);
|
||||
mg_timer_add(&mgr, 500, MG_TIMER_REPEAT, timer_callback, &mgr);
|
||||
for (;;) mg_mgr_poll(&mgr, 50);
|
||||
mg_timer_free(&t1);
|
||||
mg_mgr_free(&mgr);
|
||||
|
||||
return 0;
|
||||
|
||||