mirror of
https://github.com/aantron/dream.git
synced 2025-06-24 00:02:27 -04:00
Compare commits
482 Commits
1.0.0-alph
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
27ff43177b | ||
|
204a792b57 | ||
|
da94944400 | ||
|
ac92bc072e | ||
|
deb4526387 | ||
|
59ce312808 | ||
|
e4fa99812b | ||
|
e94401a7d8 | ||
|
6c75fb9e22 | ||
|
186ab997d8 | ||
|
b7b477379f | ||
|
a888e44e88 | ||
|
c8ec9d1af9 | ||
|
6a1a2f0db2 | ||
|
91b470dcd8 | ||
|
d088771512 | ||
|
5858e47c92 | ||
|
3f556c89f0 | ||
|
1bf9f992f8 | ||
|
e84720d2ee | ||
|
d3cb68bf18 | ||
|
f8958e71cf | ||
|
2b13c3206a | ||
|
d2656380b5 | ||
|
5a7871f845 | ||
|
05a04f56c2 | ||
|
ea262bce22 | ||
|
b3f89c860f | ||
|
b0b401bf4b | ||
|
6081749621 | ||
|
4ea4d94ca6 | ||
|
17c113380f | ||
|
7e5f18abcb | ||
|
d72f21d18f | ||
|
b059575c57 | ||
|
41eaac9afc | ||
|
d30fa5e322 | ||
|
8eb7cf2c40 | ||
|
b2abbecfe5 | ||
|
e71c7aed2f | ||
|
3033ad6f85 | ||
|
ba5e384b9f | ||
|
78c79e3328 | ||
|
e9154ac51d | ||
|
e513b8a6db | ||
|
f96c81209f | ||
|
bbbf188a01 | ||
|
5f42c87227 | ||
|
1d582af222 | ||
|
1337a4d4d7 | ||
|
337a35fb12 | ||
|
087b5ff1ea | ||
|
e570114299 | ||
|
090b62287b | ||
|
8abe1a162e | ||
|
e27a8123cf | ||
|
108c3cc4a9 | ||
|
680d5d289d | ||
|
c75379625d | ||
|
41d4bb0b38 | ||
|
20f62d209e | ||
|
59abca6af0 | ||
|
a9a9dfc09f | ||
|
3c49624bb2 | ||
|
cb60070633 | ||
|
689eb2cff6 | ||
|
99388503cc | ||
|
4c18ba8450 | ||
|
a2cfbf4975 | ||
|
aaabf4dfe3 | ||
|
8cb51f18ee | ||
|
a3579218c2 | ||
|
d802548914 | ||
|
51902fc63c | ||
|
c86e49225e | ||
|
9c72a39ef8 | ||
|
1c60e60f17 | ||
|
f5c8ba0cff | ||
|
60a0dc5df5 | ||
|
6e58736e28 | ||
|
8140a600e4 | ||
|
4a5310b09b | ||
|
aada26ce54 | ||
|
7a2c5a03ff | ||
|
1bf8e1115c | ||
|
10ae36fa5f | ||
|
8e89f7cd2a | ||
|
7b46896bf9 | ||
|
635c0589e9 | ||
|
b55d1914cf | ||
|
e529b0eb15 | ||
|
6e666d4c70 | ||
|
f5558638f3 | ||
|
d35a6338d1 | ||
|
c7da98b718 | ||
|
e14bd91a44 | ||
|
14093e16cc | ||
|
006196fa0c | ||
|
0826c267b3 | ||
|
881d26da23 | ||
|
765da90414 | ||
|
860e4563bd | ||
|
313fe46c3f | ||
|
76faf6f3b2 | ||
|
561e99d837 | ||
|
c27a83ecd9 | ||
|
7117d90379 | ||
|
d135ba6046 | ||
|
dbf01b83df | ||
|
f784018e1d | ||
|
23b740622b | ||
|
a57e8bc860 | ||
|
3df0d85c0a | ||
|
1b0ac702b0 | ||
|
a2961cd7fa | ||
|
baa222bb8e | ||
|
18cd2a08ac | ||
|
a39c938835 | ||
|
edd0cd9167 | ||
|
da604f1478 | ||
|
599efd55fc | ||
|
f0cde12979 | ||
|
d2f0016613 | ||
|
fcc618d200 | ||
|
27ee901773 | ||
|
cede19ac1b | ||
|
a6b705ca16 | ||
|
c078067e71 | ||
|
1b96d180c9 | ||
|
da15b89c38 | ||
|
70bb4332b1 | ||
|
89f21f4427 | ||
|
368d84cadd | ||
|
ff97ac7583 | ||
|
a1ca19e10e | ||
|
f20a335ed9 | ||
|
72834957cc | ||
|
ac4a4e077b | ||
|
c394615293 | ||
|
c8d2db0f63 | ||
|
1ed0a6fed5 | ||
|
80be1362df | ||
|
30690bc299 | ||
|
206f19d0ed | ||
|
ec533f2306 | ||
|
fedfd2bec7 | ||
|
dd83bf3760 | ||
|
10a6e3459f | ||
|
235ac215cd | ||
|
bc58f05af9 | ||
|
b5187b8ca4 | ||
|
c132456293 | ||
|
0d837d4b37 | ||
|
bb2e409c09 | ||
|
c1025ad8fb | ||
|
b703ad1730 | ||
|
930d15f114 | ||
|
4da674a3c5 | ||
|
104859bc5f | ||
|
9cc4a6e76d | ||
|
6361b0467e | ||
|
831f32ba91 | ||
|
c800706b74 | ||
|
f7b45a357e | ||
|
e75e50e70e | ||
|
abaf7d551b | ||
|
3eb7834bb1 | ||
|
ca3faeaab9 | ||
|
d26fd04cea | ||
|
79201d7af9 | ||
|
b3d1f9495d | ||
|
c8286522b5 | ||
|
7c0182215c | ||
|
180da09056 | ||
|
61fa8d7099 | ||
|
ef29ea9eb7 | ||
|
105fc05b52 | ||
|
3eb6f38e4f | ||
|
f088baecb7 | ||
|
915e318496 | ||
|
4294547f39 | ||
|
645805b5ad | ||
|
e0ff41e70e | ||
|
f31a2ca898 | ||
|
4ced286937 | ||
|
fd784d9379 | ||
|
cdd4a1bd9d | ||
|
9439ca16b5 | ||
|
2386083170 | ||
|
3098d0a194 | ||
|
7f196a2573 | ||
|
b5d6561db3 | ||
|
f93d09befd | ||
|
dcc351386e | ||
|
9ac86d789f | ||
|
929a9042db | ||
|
dc844913d7 | ||
|
cfacc8469a | ||
|
3c44be0cea | ||
|
ec9643b731 | ||
|
6009610e49 | ||
|
3ada178030 | ||
|
67cd3b0168 | ||
|
3d9c7c3346 | ||
|
a88a8cecbf | ||
|
515def2875 | ||
|
2162ec7e37 | ||
|
5f50acf779 | ||
|
2621045100 | ||
|
67e91f1501 | ||
|
55c4d0f836 | ||
|
e76e3855ff | ||
|
9f9caeab0d | ||
|
a929404aef | ||
|
38d89e4f23 | ||
|
24dff54482 | ||
|
77fb36b1e8 | ||
|
e0388203f0 | ||
|
c82308b976 | ||
|
bc3c00f700 | ||
|
847acd2fa2 | ||
|
c0aeaada68 | ||
|
cc10603064 | ||
|
9bec26f837 | ||
|
940b07a78f | ||
|
a365a1762e | ||
|
7320f875d1 | ||
|
30ef880708 | ||
|
80b2f60a92 | ||
|
181175d3a9 | ||
|
2cb98ad0a6 | ||
|
618a21dcdc | ||
|
25c0d1ea39 | ||
|
ff9639dfaf | ||
|
11243faf70 | ||
|
e533e70397 | ||
|
f82a51b0d5 | ||
|
c7fb937a64 | ||
|
acc2a1695e | ||
|
257f0e0d41 | ||
|
264e37d7fc | ||
|
b76ac67cba | ||
|
3d05ac6ed7 | ||
|
7e18c4d2f4 | ||
|
3705621262 | ||
|
9ee3e04481 | ||
|
e20b0b4e18 | ||
|
5896c5a641 | ||
|
acc2efc8fe | ||
|
a8d6e5c06d | ||
|
5cd57e1ea1 | ||
|
4dce6a7a4d | ||
|
d418a79165 | ||
|
03e4d37cb5 | ||
|
024131b0eb | ||
|
84128adee6 | ||
|
0664abd933 | ||
|
fa815c0259 | ||
|
536c4a41db | ||
|
2ecfc044d4 | ||
|
3acb3cc799 | ||
|
56504439b6 | ||
|
95f6a6f895 | ||
|
8e7c711dda | ||
|
fe537f4aa9 | ||
|
a8357030aa | ||
|
4fbb0c56f4 | ||
|
812bcad8c4 | ||
|
6f3b2a235a | ||
|
9df46cb329 | ||
|
553567f2d8 | ||
|
d2b8e0f7a0 | ||
|
85f3aec86e | ||
|
773c346416 | ||
|
71e06b78eb | ||
|
bfa5ab1c5c | ||
|
64d7df3538 | ||
|
a680f4be41 | ||
|
d1ba236f1d | ||
|
1da1452bfc | ||
|
ed4c24f6a8 | ||
|
2335f69fbd | ||
|
9e8bc2fb8e | ||
|
5a54d5c1ee | ||
|
dd41df9e65 | ||
|
73c56ce1c4 | ||
|
110575db83 | ||
|
7aa5e7ba1c | ||
|
d7e81a28fe | ||
|
865217859f | ||
|
ba859abb23 | ||
|
918cb3cb35 | ||
|
5d391a62a2 | ||
|
267e2de87b | ||
|
598367067b | ||
|
0c7b464f90 | ||
|
3dcb88d6ea | ||
|
037645f5ff | ||
|
af8085a5f1 | ||
|
b355e00f8f | ||
|
640a1b218b | ||
|
6c726d5271 | ||
|
ebeac7c735 | ||
|
3da5e2354d | ||
|
2529b9adda | ||
|
d81b1986b6 | ||
|
bb2924de0b | ||
|
e106e6ec37 | ||
|
655be1f754 | ||
|
3f50f75a01 | ||
|
2f40d67224 | ||
|
ed1c949226 | ||
|
ce6c0084a0 | ||
|
ff6f1d2457 | ||
|
1e74850608 | ||
|
f69b95644a | ||
|
4afbe19046 | ||
|
faf81b4c2a | ||
|
1e1e7b3958 | ||
|
38bdb60971 | ||
|
457258e64c | ||
|
b178eda5c7 | ||
|
e95f6be28c | ||
|
c9ad93b7c9 | ||
|
c98d11063c | ||
|
560ea7d999 | ||
|
d94b4a2bf1 | ||
|
9743279d41 | ||
|
54c4600cb0 | ||
|
bf710df1bc | ||
|
f1cec7222f | ||
|
873c048429 | ||
|
d21ca95501 | ||
|
0613640568 | ||
|
639d59dad1 | ||
|
b5c0e77450 | ||
|
c7e37548da | ||
|
08fd6d0f64 | ||
|
587d55b921 | ||
|
a339ef917d | ||
|
ec56e226f9 | ||
|
4e66f78a29 | ||
|
018b459658 | ||
|
1f0ef73845 | ||
|
fd0ba1fa59 | ||
|
706810a95d | ||
|
4813c07914 | ||
|
40bc994680 | ||
|
586f93bfc6 | ||
|
9a426fa5e5 | ||
|
bde5fc8285 | ||
|
abeba7b4b8 | ||
|
2b57a463c6 | ||
|
6ab46c3226 | ||
|
df4f4b6300 | ||
|
6ece622f97 | ||
|
f58b8b5cda | ||
|
4f5f3c4dcf | ||
|
2075347172 | ||
|
5578b901ff | ||
|
b4402449f1 | ||
|
8efb1adaff | ||
|
f056d0b34d | ||
|
3ae5855c0b | ||
|
972d3a9ec0 | ||
|
264febe50c | ||
|
c1ca22d266 | ||
|
0d7b042214 | ||
|
237acbbbe1 | ||
|
440fa71d5b | ||
|
b11f9f83b0 | ||
|
6225e2d4ab | ||
|
f584c1a348 | ||
|
d54b466b4a | ||
|
250d973bfc | ||
|
876933e174 | ||
|
431cf8c7a4 | ||
|
469db38d33 | ||
|
a1985d9ae8 | ||
|
34bd2868b7 | ||
|
923d0f4816 | ||
|
52d4da207f | ||
|
fa3cf53492 | ||
|
ecf98bd4ed | ||
|
31be59c4c5 | ||
|
2714e1ce9b | ||
|
279802f9d0 | ||
|
120fc9f6ec | ||
|
22e936f7c0 | ||
|
ae3b5d24d3 | ||
|
2d2083702c | ||
|
ead188384b | ||
|
47718666e4 | ||
|
3e3ce68893 | ||
|
8a0759b3a2 | ||
|
995c3e08e5 | ||
|
b8befbb8bb | ||
|
1fb90c83c2 | ||
|
62d7cd4832 | ||
|
669d2f6997 | ||
|
b162944fa1 | ||
|
47f4ed5056 | ||
|
b046bca1b8 | ||
|
3dc2ac01c1 | ||
|
8e1fe6a170 | ||
|
9913ad2cf8 | ||
|
afc9cbfc5e | ||
|
5f6b7f5081 | ||
|
e78c8dc8fd | ||
|
4f38c24659 | ||
|
bf0bda92d0 | ||
|
6ec88be579 | ||
|
a44493dc82 | ||
|
a22d7f7a0c | ||
|
dd4160da72 | ||
|
277eeff5c8 | ||
|
41789365da | ||
|
ce2646247d | ||
|
3a33705829 | ||
|
a169c107b0 | ||
|
32bbf56de8 | ||
|
3d566d2ba7 | ||
|
f14c38a46d | ||
|
b0f64df675 | ||
|
ee08358bec | ||
|
0818d2fdd8 | ||
|
48cc4d4484 | ||
|
a8b2e5aa72 | ||
|
1a865b9dd7 | ||
|
027f7b4c64 | ||
|
8612f6a661 | ||
|
a43a591b3f | ||
|
80d6c09756 | ||
|
5d5c6826ba | ||
|
f01aca54ff | ||
|
af2421cec1 | ||
|
1b2384b461 | ||
|
c5ffe90631 | ||
|
c06394c2c3 | ||
|
cac2db79bd | ||
|
581ecaac1b | ||
|
dd2b8dcdf4 | ||
|
8b3300792c | ||
|
cc172c5813 | ||
|
7e4dae59fd | ||
|
cc19ad564f | ||
|
65738dfbf1 | ||
|
5862115ad2 | ||
|
b8a46f4649 | ||
|
809835dcfe | ||
|
cd8464e642 | ||
|
795f824490 | ||
|
65e104d9ec | ||
|
500f4d0d37 | ||
|
de3310a8f8 | ||
|
8233243689 | ||
|
970e964fe5 | ||
|
9f6ae7573c | ||
|
e325d9920c | ||
|
093cd38eb1 | ||
|
d67ed02073 | ||
|
62e4add848 | ||
|
481e293fd7 | ||
|
5a5046496a | ||
|
034a712d58 | ||
|
7715e62248 | ||
|
347d615bd6 | ||
|
2eddc7b3b5 | ||
|
bbf9822f3f | ||
|
0d22bea1cd | ||
|
2dcc6c0f4c | ||
|
06167cd539 | ||
|
328c7575b9 | ||
|
6f472aafc4 | ||
|
cc691bd37a | ||
|
88b37398a1 | ||
|
fa20aebf36 | ||
|
b79b06dd6a | ||
|
9f06ce54a2 | ||
|
21f3ed5d2c | ||
|
f08a2fa37d | ||
|
b3c1fd97b8 |
10
.github/workflows/docker-esy.yml
vendored
10
.github/workflows/docker-esy.yml
vendored
@ -1,9 +1,9 @@
|
||||
name: docker-esy
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'example/z-docker-esy/**'
|
||||
- .github/workflows/docker-esy.yml
|
||||
on: workflow_call
|
||||
# push:
|
||||
# paths:
|
||||
# - 'example/z-docker-esy/**'
|
||||
# - .github/workflows/docker-esy.yml
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
10
.github/workflows/docker-opam.yml
vendored
10
.github/workflows/docker-opam.yml
vendored
@ -1,9 +1,9 @@
|
||||
name: docker-opam
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'example/z-docker-opam/**'
|
||||
- .github/workflows/docker-opam.yml
|
||||
on: workflow_call
|
||||
# push:
|
||||
# paths:
|
||||
# - 'example/z-docker-opam/**'
|
||||
# - .github/workflows/docker-opam.yml
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
10
.github/workflows/systemd.yml
vendored
10
.github/workflows/systemd.yml
vendored
@ -1,9 +1,9 @@
|
||||
name: systemd
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'example/z-systemd/**'
|
||||
- .github/workflows/systemd.yml
|
||||
on: workflow_call
|
||||
# push:
|
||||
# paths:
|
||||
# - 'example/z-systemd/**'
|
||||
# - .github/workflows/systemd.yml
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
116
.github/workflows/test.yml
vendored
116
.github/workflows/test.yml
vendored
@ -7,40 +7,71 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
# - windows-latest
|
||||
# Blocked until we no longer require libev; Dream still works on
|
||||
# Windows, but testing it is awkward at the moment.
|
||||
# Until https://github.com/ocaml/setup-ocaml/issues/872.
|
||||
# When fixing, search for other instances of this string in this file.
|
||||
- ubuntu-22.04
|
||||
ocaml:
|
||||
- 4.12.0
|
||||
- 5.2.x
|
||||
- 4.14.x
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
ocaml: 4.08.1
|
||||
- os: macos-latest
|
||||
ocaml: 4.14.x
|
||||
- os: windows-latest
|
||||
ocaml: 4.14.x
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: avsm/setup-ocaml@v1
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-version: ${{matrix.ocaml}}
|
||||
ocaml-compiler: ${{matrix.ocaml}}
|
||||
dune-cache: true
|
||||
|
||||
- run: opam depext --yes conf-libev
|
||||
- run: opam install --yes --deps-only --with-test .
|
||||
- run: opam exec -- dune runtest
|
||||
- run: |
|
||||
# For Caqti PostgreSQL examples. opam does actually install PostgreSQL for
|
||||
# us. However, Homebrew doesn't link it by default, so we have to install
|
||||
# and link it manually.
|
||||
- run: brew install postgresql@15 && brew link --overwrite postgresql@15
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
# Workaround https://github.com/savonet/ocaml-ssl/issues/155 and/or
|
||||
# https://github.com/ocaml/setup-ocaml/issues/856.
|
||||
- run: opam pin add ssl 0.6.0 --no-action
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
- run: opam exec -- make deps
|
||||
|
||||
- run: opam exec -- make
|
||||
|
||||
# Tests on Windows are disabled because of a difference in ppx_expect
|
||||
# output. See https://github.com/aantron/dream/pull/282. This difference
|
||||
# remains as of ppx_expect 0.16.
|
||||
- run: opam exec -- make test
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- run: opam lint --recursive example
|
||||
|
||||
- name: Build examples
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
set -e
|
||||
set -x
|
||||
|
||||
EXAMPLES=$(find example -maxdepth 1 -type d | grep -v "^example/0" | grep -v "^example$" | sort)
|
||||
EXCLUDED_EXAMPLES='w-mirage*|r-tyxml|w-dream-html'
|
||||
EXAMPLES=$(find example -maxdepth 1 -type d | grep -Ev $EXCLUDED_EXAMPLES | grep -v "^example/0" | grep -v "^example$" | sort)
|
||||
shopt -s nullglob
|
||||
|
||||
for EXAMPLE in $EXAMPLES
|
||||
do
|
||||
FILE=$(ls $EXAMPLE/*.ml $EXAMPLE/*.re $EXAMPLE/server/*.ml $EXAMPLE/server/*.re)
|
||||
FILE=$(find $EXAMPLE -maxdepth 1 -type f -and -path "${EXAMPLE}/*.ml")
|
||||
FILE+=$(find $EXAMPLE -maxdepth 1 -type f -and -path "${EXAMPLE}/*.re")
|
||||
FILE+=$(find $EXAMPLE -maxdepth 2 -type f -and -path "${EXAMPLE}/server/*.ml")
|
||||
FILE+=$(find $EXAMPLE -maxdepth 2 -type f -and -path "${EXAMPLE}/server/*.re")
|
||||
|
||||
if [[ "$FILE" == "" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
EXE=$(echo $FILE | sed 's/\..*$/.exe/g')
|
||||
echo dune build $EXE
|
||||
opam exec -- dune build $EXE
|
||||
@ -51,12 +82,23 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- ubuntu-22.04
|
||||
ocaml:
|
||||
- 5.2.x
|
||||
- 4.14.x
|
||||
include:
|
||||
- os: macos-latest
|
||||
ocaml: 4.14.x
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- name: Quick start
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: ${{matrix.ocaml}}
|
||||
dune-cache: true
|
||||
|
||||
- name: Run quickstart.sh
|
||||
shell: bash
|
||||
run: |
|
||||
set -x
|
||||
touch output
|
||||
@ -70,3 +112,33 @@ jobs:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mirage:
|
||||
if: false
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- run: mkdir ../repo-copy
|
||||
- run: cp -r * ../repo-copy/
|
||||
- uses: ocaml/setup-ocaml@v3
|
||||
with:
|
||||
ocaml-compiler: 4.14.x
|
||||
dune-cache: true
|
||||
# Needed until https://github.com/robur-coop/ocaml-letsencrypt/pull/34.
|
||||
- run: opam pin add letsencrypt git+https://github.com/hannesm/ocaml-letsencrypt.git#no-cstruct --no-action
|
||||
- run: opam install --yes --deps-only ./dream-pure.opam ./dream-httpaf.opam ./dream.opam ./dream-mirage.opam
|
||||
- run: opam install --yes mirage mirage-clock-unix mirage-crypto-rng-mirage
|
||||
- run: cd example/w-mirage && mv config.ml config.ml.backup
|
||||
- run: cd example/w-mirage && sed -e 's/package "dream-mirage"//' < config.ml.backup > config.ml
|
||||
- run: cd example/w-mirage && opam exec -- mirage configure -t unix
|
||||
- run: cd example/w-mirage && opam exec -- make depends
|
||||
- run: cd example/w-mirage && ls duniverse
|
||||
- run: cp -r ../repo-copy example/w-mirage/duniverse/dream
|
||||
- run: cd example/w-mirage/duniverse && rm -rf ocaml-cstruct logs ke fmt lwt bytes seq mirage-flow sexplib0 ptime tls domain-name ocaml-ipaddr mirage-clock ocplib-endian digestif eqaf mirage-crypto mirage-runtime
|
||||
- run: cd example/w-mirage && mv config.ml.backup config.ml
|
||||
- run: cd example/w-mirage && sed -e 's/(libraries/(libraries dream-mirage/' < dune.build > dune.build.2
|
||||
- run: cd example/w-mirage && mv dune.build.2 dune.build
|
||||
- run: cd example/w-mirage && opam exec -- dune build
|
||||
- run: file example/w-mirage/_build/default/main.exe
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -15,10 +15,15 @@ _esy/
|
||||
esy.lock
|
||||
|
||||
# Release script
|
||||
dream-*
|
||||
dream-*.gz
|
||||
dream-*/
|
||||
|
||||
# Bisect_ppx
|
||||
_coverage/
|
||||
|
||||
# Humans
|
||||
scratch/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
*.swp
|
||||
|
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -1,12 +0,0 @@
|
||||
[submodule "src/vendor/httpaf"]
|
||||
path = src/vendor/httpaf
|
||||
url = https://github.com/aantron/httpaf.git
|
||||
[submodule "src/vendor/gluten"]
|
||||
path = src/vendor/gluten
|
||||
url = https://github.com/aantron/gluten.git
|
||||
[submodule "src/vendor/websocketaf"]
|
||||
path = src/vendor/websocketaf
|
||||
url = https://github.com/aantron/websocketaf.git
|
||||
[submodule "src/vendor/h2"]
|
||||
path = src/vendor/h2
|
||||
url = https://github.com/aantron/ocaml-h2.git
|
16
.ocamlformat
Normal file
16
.ocamlformat
Normal file
@ -0,0 +1,16 @@
|
||||
version = 0.25.1
|
||||
profile = conventional
|
||||
|
||||
leading-nested-match-parens = false
|
||||
space-around-variants = false
|
||||
space-around-arrays = false
|
||||
space-around-lists = false
|
||||
space-around-records = false
|
||||
break-infix = fit-or-vertical
|
||||
break-separators = after
|
||||
break-cases = fit-or-vertical
|
||||
cases-exp-indent = 2
|
||||
exp-grouping = preserve
|
||||
if-then-else = fit-or-vertical
|
||||
let-and = sparse
|
||||
type-decl = sparse
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021, Anton Bachin
|
||||
Copyright (c) 2021-2024, Anton Bachin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
67
Makefile
67
Makefile
@ -1,31 +1,41 @@
|
||||
PACKAGES := dream-pure,dream-httpaf,dream
|
||||
|
||||
.PHONY : build
|
||||
build :
|
||||
@dune build --no-print-directory @install
|
||||
@dune build --only-packages $(PACKAGES) --no-print-directory @install
|
||||
|
||||
.PHONY : watch
|
||||
watch :
|
||||
@dune build --no-print-directory -w
|
||||
@dune build --only-packages $(PACKAGES) --no-print-directory @install -w
|
||||
|
||||
.PHONY : deps
|
||||
deps :
|
||||
opam install --deps-only --with-test --with-doc \
|
||||
./dream-pure.opam ./dream-httpaf.opam ./dream.opam
|
||||
|
||||
TEST ?= test
|
||||
ROOT := $(shell [ -f ../dune-workspace ] && echo .. || echo .)
|
||||
|
||||
.PHONY : test
|
||||
test :
|
||||
@find . -name '*.coverage' | xargs rm -f
|
||||
@find $(ROOT) -name '*.coverage' | xargs rm -f
|
||||
@dune build --no-print-directory \
|
||||
--instrument-with bisect_ppx --force @test/runtest
|
||||
--instrument-with bisect_ppx --force @$(TEST)/runtest
|
||||
@bisect-ppx-report html
|
||||
@bisect-ppx-report summary
|
||||
@echo See _coverage/index.html
|
||||
|
||||
.PHONY : test-watch
|
||||
test-watch :
|
||||
@dune build --no-print-directory -w --root . @test/runtest
|
||||
@dune build --no-print-directory -w @$(TEST)/runtest
|
||||
|
||||
.PHONY : coverage-serve
|
||||
coverage-serve :
|
||||
cd _coverage && dune exec -- serve -p 8082
|
||||
cd _coverage && dune exec -- dream-serve -p 8082
|
||||
|
||||
.PHONY : promote
|
||||
promote :
|
||||
dune promote --root .
|
||||
dune promote
|
||||
@make --no-print-directory test
|
||||
|
||||
.PHONY : docs
|
||||
@ -66,7 +76,17 @@ clean : clean-coverage
|
||||
dune clean
|
||||
dune clean --root .
|
||||
make --no-print-directory -C docs/web clean
|
||||
rm -rf src/graphiql/node_modules dream-* _release
|
||||
rm -rf src/graphiql/node_modules
|
||||
|
||||
.PHONY : test-ocamlformat
|
||||
test-ocamlformat :
|
||||
touch test/ocamlformat/test.expect.ml
|
||||
ocamlformat test/ocamlformat/test.ml > test/ocamlformat/test.actual.ml
|
||||
diff -u3 test/ocamlformat/test.expect.ml test/ocamlformat/test.actual.ml
|
||||
|
||||
.PHONY : test-ocamlformat-promote
|
||||
test-ocamlformat-promote :
|
||||
ocamlformat test/ocamlformat/test.ml > test/ocamlformat/test.expect.ml
|
||||
|
||||
.PHONY : utop
|
||||
utop :
|
||||
@ -82,21 +102,25 @@ todo-all :
|
||||
|
||||
VERSION := $(shell git describe --abbrev=0)
|
||||
RELEASE := dream-$(VERSION)
|
||||
FILES := src dream.opam dune-project LICENSE.md README.md
|
||||
FILES := \
|
||||
src dream.opam dream-httpaf.opam dream-pure.opam dream-mirage.opam \
|
||||
dune-project LICENSE.md README.md
|
||||
|
||||
.PHONY : release
|
||||
release : clean
|
||||
rm -rf $(RELEASE) $(RELEASE).tar $(RELEASE).tar.gz _release
|
||||
mkdir $(RELEASE)
|
||||
mkdir -p $(RELEASE)
|
||||
cp -r $(FILES) $(RELEASE)
|
||||
rm -rf $(RELEASE)/src/vendor/gluten/.github
|
||||
rm -rf $(RELEASE)/src/vendor/gluten/async
|
||||
rm -rf $(RELEASE)/src/vendor/gluten/eio
|
||||
rm -rf $(RELEASE)/src/vendor/gluten/mirage
|
||||
rm -rf $(RELEASE)/src/vendor/gluten/nix
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/.github
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/async
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/benchmarks
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/certificates
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/eio
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/examples
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/images
|
||||
rm -rf $(RELEASE)/src/vendor/httpaf/lib_test
|
||||
@ -105,6 +129,7 @@ release : clean
|
||||
rm -rf $(RELEASE)/src/vendor/h2/.github
|
||||
rm -rf $(RELEASE)/src/vendor/h2/async
|
||||
rm -rf $(RELEASE)/src/vendor/h2/certificates
|
||||
rm -rf $(RELEASE)/src/vendor/h2/eio
|
||||
rm -rf $(RELEASE)/src/vendor/h2/examples
|
||||
rm -rf $(RELEASE)/src/vendor/h2/lib_test
|
||||
rm -rf $(RELEASE)/src/vendor/h2/mirage
|
||||
@ -113,20 +138,34 @@ release : clean
|
||||
rm -rf $(RELEASE)/src/vendor/h2/vegeta-plot.png
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/.github
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/async
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/eio
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/examples
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/lib_test
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/mirage
|
||||
rm -rf $(RELEASE)/src/vendor/websocketaf/nix
|
||||
rm -rf $(RELEASE)/src/vendor/paf
|
||||
tar cf $(RELEASE).tar $(RELEASE)
|
||||
ls -l $(RELEASE).tar
|
||||
gzip -9 $(RELEASE).tar
|
||||
mkdir -p _release
|
||||
cp $(RELEASE).tar.gz _release
|
||||
(cd _release && tar xf $(RELEASE).tar.gz)
|
||||
opam pin add -y --no-action dream _release/$(RELEASE) --kind=path
|
||||
opam remove -y dream-pure dream-httpaf dream gluten httpaf h2 websocketaf paf
|
||||
opam pin remove -y dream-pure dream-httpaf dream
|
||||
opam pin add -y --no-action dream-pure.dev _release/$(RELEASE) --kind=path
|
||||
opam pin add -y --no-action dream-httpaf.dev _release/$(RELEASE) --kind=path
|
||||
opam pin add -y --no-action dream.dev _release/$(RELEASE) --kind=path
|
||||
opam reinstall -y --verbose dream
|
||||
@echo Run make release-finish to complete after killing the server
|
||||
cd example/1-hello && dune exec --root . ./hello.exe || true
|
||||
opam remove -y dream
|
||||
opam pin remove -y dream
|
||||
md5sum $(RELEASE).tar.gz
|
||||
|
||||
.PHONY : release-finish
|
||||
release-finish :
|
||||
opam remove -y dream-pure dream-httpaf dream
|
||||
opam pin remove -y dream-pure dream-httpaf dream
|
||||
sha256sum $(RELEASE).tar.gz
|
||||
ls -l $(RELEASE).tar.gz
|
||||
|
||||
.PHONY : release-clean
|
||||
release-clean :
|
||||
rm -rf $(RELEASE) $(RELEASE).tar.gz _release
|
||||
|
147
README.md
147
README.md
@ -12,7 +12,6 @@ Easy-to-use, feature-complete Web framework without boilerplate.
|
||||
|
||||
<p align="center">
|
||||
<a href="#quick-start">Quick Start</a> |
|
||||
<a href="http://dream.as">Playground</a> |
|
||||
<a href="https://github.com/aantron/dream/tree/master/example#readme">
|
||||
Tutorial</a> |
|
||||
<a href="https://aantron.github.io/dream/">Reference</a>
|
||||
@ -47,8 +46,8 @@ Dream is **one flat module** in **one package**, documented on
|
||||
- [**Cryptography**][crypto] helpers, key rotation, and a chosen cipher.
|
||||
- A neat [**logger**][logging], and attention to configuring the OCaml runtime
|
||||
nicely.
|
||||
- [**Deployment**][deploy] instructions for **Digital Ocean** and **Heroku**,
|
||||
with sample CI scripts.
|
||||
- [**Deployment**][deploy] instructions for **Digital Ocean**, **Heroku**, and
|
||||
**Fly.io**, with sample CI scripts.
|
||||
|
||||
<br>
|
||||
|
||||
@ -78,24 +77,24 @@ Dream binary][one-binary], or use Dream in a subcommand. Dream tries to be as
|
||||
functional as possible, touching global runtime state only lazily, when called
|
||||
into.
|
||||
|
||||
[https]: https://github.com/aantron/dream/tree/master/example/l-https#files
|
||||
[websocket]: https://github.com/aantron/dream/tree/master/example/k-websocket#files
|
||||
[graphql]: https://github.com/aantron/dream/tree/master/example/w-graphql-subscription#files
|
||||
[templates]: https://github.com/aantron/dream/tree/master/example/7-template#files
|
||||
[reason-templates]: https://github.com/aantron/dream/tree/master/example/r-template#files
|
||||
[middleware]: https://github.com/aantron/dream/tree/master/example/2-middleware#files
|
||||
[https]: https://github.com/aantron/dream/tree/master/example/l-https#folders-and-files
|
||||
[websocket]: https://github.com/aantron/dream/tree/master/example/k-websocket#folders-and-files
|
||||
[graphql]: https://github.com/aantron/dream/tree/master/example/w-graphql-subscription#folders-and-files
|
||||
[templates]: https://github.com/aantron/dream/tree/master/example/7-template#folders-and-files
|
||||
[reason-templates]: https://github.com/aantron/dream/tree/master/example/r-template#folders-and-files
|
||||
[middleware]: https://github.com/aantron/dream/tree/master/example/2-middleware#folders-and-files
|
||||
[handler]: https://aantron.github.io/dream/#type-handler
|
||||
[routing]: https://github.com/aantron/dream/tree/master/example/3-router#files
|
||||
[routing]: https://github.com/aantron/dream/tree/master/example/3-router#folders-and-files
|
||||
[cookies]: https://aantron.github.io/dream/#cookies
|
||||
[forms]: https://aantron.github.io/dream/#forms
|
||||
[sessions]: https://github.com/aantron/dream/tree/master/example/b-session#files
|
||||
[sessions]: https://github.com/aantron/dream/tree/master/example/b-session#folders-and-files
|
||||
[back-ends]: https://aantron.github.io/dream/#back-ends
|
||||
[errors]: https://github.com/aantron/dream/tree/master/example/9-error#files
|
||||
[errors]: https://github.com/aantron/dream/tree/master/example/9-error#folders-and-files
|
||||
[crypto]: https://aantron.github.io/dream/#cryptography
|
||||
[logging]: https://github.com/aantron/dream/tree/master/example/2-middleware#files
|
||||
[melange]: https://github.com/aantron/dream/tree/master/example/r-fullstack-melange#files
|
||||
[rescript]: https://github.com/aantron/dream/tree/master/example/w-fullstack-rescript#files
|
||||
[jsoo]: https://github.com/aantron/dream/tree/master/example/w-fullstack-jsoo#files
|
||||
[logging]: https://github.com/aantron/dream/tree/master/example/2-middleware#folders-and-files
|
||||
[melange]: https://github.com/aantron/dream/tree/master/example/r-fullstack-melange#folders-and-files
|
||||
[rescript]: https://github.com/aantron/dream/tree/master/example/w-fullstack-rescript#folders-and-files
|
||||
[jsoo]: https://github.com/aantron/dream/tree/master/example/w-fullstack-jsoo#folders-and-files
|
||||
[types]: https://aantron.github.io/dream/#types
|
||||
[basic-read]: https://aantron.github.io/dream/#val-body
|
||||
[streaming]: https://aantron.github.io/dream/#streaming
|
||||
@ -103,62 +102,55 @@ into.
|
||||
[alpn]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
|
||||
[libs]: https://github.com/aantron/dream/tree/master/src
|
||||
[deploy]: https://github.com/aantron/dream/tree/master/example#deploying
|
||||
[jsx]: https://github.com/aantron/dream/tree/master/example/r-tyxml#files
|
||||
[one-binary]: https://github.com/aantron/dream/tree/master/example/w-one-binary#files
|
||||
[jsx]: https://github.com/aantron/dream/tree/master/example/r-tyxml#folders-and-files
|
||||
[one-binary]: https://github.com/aantron/dream/tree/master/example/w-one-binary#folders-and-files
|
||||
|
||||
<br>
|
||||
|
||||
## Quick start
|
||||
|
||||
<br>
|
||||
You can get
|
||||
[one](https://github.com/aantron/dream/tree/master/example/2-middleware#folders-and-files)
|
||||
of the first [tutorials][tutorial] and build it locally with:
|
||||
|
||||
<pre><b>bash -c "$(curl -fsSL https://raw.githubusercontent.com/aantron/dream/master/example/quickstart.sh)"</b></pre>
|
||||
|
||||
<br>
|
||||
|
||||
This downloads and runs [`quickstart.sh`][quickstart.sh], which does a
|
||||
sandboxed build of one of the first [tutorials][tutorial],
|
||||
[**`2-middleware`**][2-middleware]. It's mostly the same as:
|
||||
|
||||
```
|
||||
git clone https://github.com/aantron/dream.git --recursive
|
||||
cd dream/example/2-middleware
|
||||
npm install esy && npx esy
|
||||
npx esy start
|
||||
```
|
||||
|
||||
Knowing that, you can start from any other [example][tutorial]. All of them
|
||||
include their own build commands. They don't have to be subdirectories of
|
||||
`dream` — you can copy them out to start your own project directory.
|
||||
Especially consider starting with the [full-stack examples][fullstack], which
|
||||
build both a Dream server and a JavaScript client.
|
||||
|
||||
### opam
|
||||
|
||||
Create a project directory with an optional local switch:
|
||||
|
||||
```
|
||||
mkdir project
|
||||
cd project
|
||||
opam switch create . 5.1.0
|
||||
eval $(opam env)
|
||||
```
|
||||
|
||||
Install Dream:
|
||||
|
||||
```
|
||||
opam install dream
|
||||
```
|
||||
|
||||
After that, go to one of the examples, such as [**`1-hello`**][1-hello], and
|
||||
build it:
|
||||
After that, go to any of the [examples][tutorial], such as
|
||||
[**`2-middleware`**][2-middleware], re-create the files locally, and run it:
|
||||
|
||||
```
|
||||
cd example/1-hello
|
||||
dune exec --root . ./hello.exe
|
||||
dune exec ./middleware.exe
|
||||
```
|
||||
|
||||
### Playground
|
||||
|
||||
Most of the examples are loaded into the [playground][playground]. For instance,
|
||||
[**`2-middleware`**][2-middleware] is at
|
||||
[http://dream.as/2-middleware][2-middleware-playground].
|
||||
|
||||
[esy-example]: https://github.com/aantron/dream/tree/master/example/w-esy#files
|
||||
[esy-example]: https://github.com/aantron/dream/tree/master/example/w-esy#folders-and-files
|
||||
[quickstart.sh]: https://github.com/aantron/dream/blob/master/example/quickstart.sh
|
||||
[esy]: https://esy.sh/
|
||||
[2-middleware]: https://github.com/aantron/dream/tree/master/example/2-middleware#files
|
||||
[playground]: http://dream.as
|
||||
[2-middleware-playground]: http://dream.as/2-middleware
|
||||
[2-middleware]: https://github.com/aantron/dream/tree/master/example/2-middleware#folders-and-files
|
||||
|
||||
## esy
|
||||
|
||||
Visit any of the [examples][tutorial], such as
|
||||
[**`2-middleware`**][2-middleware], and re-create the files locally. The file
|
||||
[`esy.json`](https://github.com/aantron/dream/blob/master/example/2-middleware/esy.json)
|
||||
shows how to depend on Dream. All of the examples are installed by running `npx
|
||||
esy`, and started with `npx esy start`.
|
||||
|
||||
<br>
|
||||
|
||||
@ -177,18 +169,59 @@ Most of the examples are loaded into the [playground][playground]. For instance,
|
||||
small-to-medium deployments.
|
||||
- [**Examples**][examples] — These cover various HTTP scenarios.
|
||||
- [**API reference**][api-main]
|
||||
- [Watching][fswatch] and [live reloading][reload].
|
||||
- [Watching][watch] and [live reloading][reload].
|
||||
|
||||
[tutorial]: https://github.com/aantron/dream/tree/master/example#readme
|
||||
[examples]: https://github.com/aantron/dream/tree/master/example#examples
|
||||
[1-hello]: https://github.com/aantron/dream/tree/master/example/1-hello#files
|
||||
[r-hello]: https://github.com/aantron/dream/tree/master/example/r-hello#files
|
||||
[1-hello]: https://github.com/aantron/dream/tree/master/example/1-hello#folders-and-files
|
||||
[r-hello]: https://github.com/aantron/dream/tree/master/example/r-hello#folders-and-files
|
||||
[reason-examples]: https://github.com/aantron/dream/tree/master/example#reason
|
||||
[deploying]: https://github.com/aantron/dream/tree/master/example#deploying
|
||||
[api-main]: https://aantron.github.io/dream/#types
|
||||
[fullstack]: https://github.com/aantron/dream/tree/master/example#full-stack
|
||||
[fswatch]: https://github.com/aantron/dream/tree/master/example/w-fswatch#files
|
||||
[reload]: https://github.com/aantron/dream/tree/master/example/w-live-reload#files
|
||||
[watch]: https://github.com/aantron/dream/tree/master/example/w-watch#folders-and-files
|
||||
[reload]: https://github.com/aantron/dream/tree/master/example/w-live-reload#folders-and-files
|
||||
|
||||
<br>
|
||||
|
||||
## Recommended projects
|
||||
|
||||
- [`dream-cli`](https://github.com/tmattio/dream-cli) —
|
||||
command-line interface for Dream applications.
|
||||
- [`dream-encoding`](https://github.com/tmattio/dream-encoding) —
|
||||
compression middleware.
|
||||
- [`dream-livereload`](https://github.com/tmattio/dream-livereload)
|
||||
— live reloading.
|
||||
- [`emile`](https://github.com/dinosaure/emile) — email
|
||||
address syntax validation.
|
||||
- [`letters`](https://github.com/oxidizing/letters) — SMTP
|
||||
client.
|
||||
|
||||
<br>
|
||||
|
||||
## Example repositories
|
||||
|
||||
- [`dream-mail-example`](https://github.com/jsthomas/dream-email-example)
|
||||
— sends email using RabbitMQ and Mailgun
|
||||
[[blog post](https://jsthomas.github.io/ocaml-email.html),
|
||||
[discuss](https://discuss.ocaml.org/t/how-to-send-email-from-dream/8201)].
|
||||
- [`dream-melange-tea-tailwind`](https://github.com/tcoopman/dream-melange-tea-tailwind)
|
||||
— The Elm Architecture with a Dream server, client compiled
|
||||
by Melange.
|
||||
|
||||
<br>
|
||||
|
||||
## Contact
|
||||
|
||||
Apart from the [issues](https://github.com/aantron/dream/issues), good places
|
||||
to discuss Dream are...
|
||||
|
||||
- #dream on the [Reason Discord](https://discord.gg/2JTYRq2rYh).
|
||||
- #webdev on the [OCaml Discord](https://discord.gg/sx45hPkkWV).
|
||||
- The [OCaml Discuss forum](https://discuss.ocaml.org/).
|
||||
- The development stream on [Twitch](https://www.twitch.tv/antron_ML).
|
||||
|
||||
Highlight `@antron` to poke @aantron specifically.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -5,9 +5,11 @@ Contributions are very welcome. This includes not only code PRs, but also:
|
||||
- [Examples](https://github.com/aantron/dream/tree/master/example#readme)
|
||||
- [Docs fixes](https://aantron.github.io/dream/)
|
||||
- [Bug reports](https://github.com/aantron/dream/issues)
|
||||
- Links to blogs — different people benefit from different presentations!
|
||||
- Links to projects that use Dream — to serve as large examples.
|
||||
- Links to libraries to use with Dream.
|
||||
- Links to [blogs](https://github.com/aantron/dream#example-repositories)
|
||||
— different people benefit from different presentations!
|
||||
- Links to projects that use Dream — to serve as large examples
|
||||
- Links to [libraries](https://github.com/aantron/dream#recommended-projects)
|
||||
to use with Dream
|
||||
- And more!
|
||||
|
||||
<br>
|
||||
@ -17,13 +19,13 @@ Contributions are very welcome. This includes not only code PRs, but also:
|
||||
To get the version of Dream installed in a project that uses it, run
|
||||
|
||||
```
|
||||
npx esy ls-builds
|
||||
opam list dream
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
opam list dream
|
||||
npx esy ls-builds
|
||||
```
|
||||
|
||||
<br>
|
||||
@ -38,35 +40,42 @@ cd dream
|
||||
```
|
||||
|
||||
Note: the clone *must* be `--recursive`, because Dream several dependencies
|
||||
vendored as [submodules](https://github.com/aantron/dream/tree/master/src/vendor)!
|
||||
vendored as
|
||||
[submodules](https://github.com/aantron/dream/tree/master/src/vendor).
|
||||
|
||||
Later, you'll need to fork the repository on GitHub, and add your fork as a
|
||||
remote:
|
||||
|
||||
```
|
||||
git remote add fork git@github.com/my-github-name/dream.git
|
||||
git remote add fork git@github.com:my-github-name/dream.git
|
||||
```
|
||||
|
||||
Install Dream's dependencies:
|
||||
|
||||
```
|
||||
opam install --deps-only . --with-test
|
||||
make deps
|
||||
```
|
||||
|
||||
If you don't have an opam switch ready, first create one with
|
||||
|
||||
```
|
||||
opam switch create . 4.12.0
|
||||
opam switch create . 4.14.1 --no-install
|
||||
```
|
||||
|
||||
You can now add some code that will exercise your change, so you can test it as
|
||||
you work. There are two main places for this:
|
||||
|
||||
1. The tests in `test/`. They can be run with `make test`. View the generated
|
||||
coverage report in `_coverage/index.html` to see how much the tests exercies
|
||||
coverage report in `_coverage/index.html` to see how much the tests exercise
|
||||
your changes.
|
||||
|
||||
2. The examples in `example/`. I often test changes by modifying an example that
|
||||
To run tests from a single directory, for example `test/expect/pure`, run
|
||||
`make test TEST=test/expect/pure`.
|
||||
|
||||
2. The tests can also be run in watch mode using `make test-watch`. This is not
|
||||
compatible with coverage reports at the moment.
|
||||
|
||||
3. The examples in `example/`. I often test changes by modifying an example that
|
||||
is almost on topic for the code I'm changing, and then not committing the
|
||||
example. In some cases, though, it's easiest to fork or write a new example
|
||||
for some new code, and commit it. New examples greatly appreciated! To build
|
||||
@ -99,6 +108,37 @@ If you want to work again later, be sure to use `--recurse-submodules` during
|
||||
git pull --recurse-submodules
|
||||
```
|
||||
|
||||
**Note:** Please don't force-push into a PR — it makes incremental review
|
||||
very difficult, and we will squash-merge most PRs anyway!
|
||||
|
||||
**Note:** Please don't resolve conversations in PRs. Reviewers use resolving
|
||||
conversations to keep track of what has been addressed.
|
||||
|
||||
<br>
|
||||
|
||||
If you need to link to the local version of Dream from a project that lives in
|
||||
a different directory, and you are using esy, add this line to your `esy.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"resolutions": {
|
||||
...
|
||||
"@opam/dream": "link:path/to/dream.opam"
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Then, run
|
||||
```
|
||||
npx esy install
|
||||
npx esy build
|
||||
npx esy start
|
||||
```
|
||||
|
||||
Don't forget the `esy build` step, which is necessary to build the local
|
||||
dependency.
|
||||
|
||||
<br>
|
||||
|
||||
## Docs
|
||||
@ -110,8 +150,8 @@ To build the docs, go to
|
||||
make deps
|
||||
```
|
||||
|
||||
This will install npm packages and opam packages (some of which are pinned to
|
||||
git commits).
|
||||
This will install npm and opam packages. In particular, the site currently
|
||||
requires odoc 2.0.2, Soupault, and a specific version of Highlight.js.
|
||||
|
||||
After that, back in the project root,
|
||||
|
||||
@ -126,3 +166,11 @@ make docs
|
||||
```
|
||||
|
||||
to build the docs locally. They are output to `docs/web/build/index.html`.
|
||||
|
||||
You can also use
|
||||
|
||||
```
|
||||
make docs-watch
|
||||
```
|
||||
|
||||
to rebuild the docs automatically as you write them.
|
||||
|
@ -39,8 +39,8 @@ b {
|
||||
<body>
|
||||
|
||||
<pre><code><b>$ cd example/2-middleware</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./middleware.exe</b>
|
||||
<span class="dim">08.03.21 22:19:21.126</span> Running at http://localhost:8080
|
||||
<span class="dim">08.03.21 22:19:21.126</span> Type Ctrl+C to stop
|
||||
<span class="dim">08.03.21 22:19:24.927</span> dream.log <span class="info">INFO</span> <span class="odd">REQ 1</span> GET / 127.0.0.1:58549 Mozilla/5.0 ...
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 80 KiB |
@ -69,7 +69,6 @@ pre {
|
||||
<span class="apply">@@</span> <span class="module">Dream</span>.router <span class="punct">[</span>
|
||||
<span class="module">Dream</span>.get <span class="string">"/"</span> (<span class="keyword fun">fun</span> _ <span class="punct">-></span> <span class="module">Dream</span>.html (hello <span class="string">"world"</span>))<span class="punct">;</span>
|
||||
<span class="punct">]</span>
|
||||
<span class="apply">@@</span> <span class="module">Dream</span>.not_found
|
||||
</pre>
|
||||
|
||||
</body>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
@ -3,7 +3,8 @@ ODOC := odoc/default/_doc/_html
|
||||
|
||||
.PHONY : build
|
||||
build :
|
||||
dune build @doc --root $(ROOT) --no-print-directory --build-dir `pwd`/odoc
|
||||
dune build @doc --root $(ROOT) --no-print-directory --build-dir `pwd`/odoc \
|
||||
--only-packages dream-pure,dream-httpaf,dream
|
||||
rm -f site/index.html
|
||||
dune exec -- postprocess/index.exe \
|
||||
$(ODOC)/dream/Dream/index.html site/index.html
|
||||
|
@ -26,6 +26,6 @@ lockfile.
|
||||
|
||||
Useful links:
|
||||
|
||||
- OCaml [*Syntax of documentation comments*](http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html#ss:ocamldoc-syntax)
|
||||
- OCaml [*Syntax of documentation comments*](https://v2.ocaml.org/manual/ocamldoc.html#ss:ocamldoc-syntax)
|
||||
- Lambda Soup [*Module Soup*](https://aantron.github.io/lambdasoup/)
|
||||
- Soupault [*Tips and tricks*](https://soupault.app/tips-and-tricks/)
|
||||
|
@ -13,8 +13,35 @@ let if_expected expected test f =
|
||||
f ()
|
||||
else begin
|
||||
Soup.write_file "actual" actual;
|
||||
Printf.ksprintf failwith "Mismatch; wrote %s"
|
||||
(Filename.concat (Sys.getcwd ()) "actual")
|
||||
prerr_newline ();
|
||||
prerr_newline ();
|
||||
prerr_endline "Mismatch with expected initial HTML content.";
|
||||
prerr_newline ();
|
||||
prerr_endline
|
||||
"The Dream docs build rewrites HTML emitted by odoc to make it neater.";
|
||||
prerr_endline
|
||||
"Each rewritten tag has an expected initial content for sanity checking.";
|
||||
|
||||
prerr_endline "The actual found content has been written to";
|
||||
prerr_newline ();
|
||||
prerr_endline (" " ^ (Filename.concat (Sys.getcwd ()) "actual"));
|
||||
prerr_newline ();
|
||||
|
||||
begin match String.split_on_char '\n' actual with
|
||||
| [] -> ()
|
||||
| first_line::_ ->
|
||||
prerr_endline "Hint:";
|
||||
prerr_newline ();
|
||||
prerr_endline (" " ^ first_line);
|
||||
prerr_newline ()
|
||||
end;
|
||||
|
||||
prerr_endline "Hint: make sure odoc 2.0.2 is installed.";
|
||||
prerr_endline
|
||||
"Other versions of odoc generate markup that doesn't match the expected.";
|
||||
prerr_newline ();
|
||||
|
||||
Printf.ksprintf failwith "Mismatch"
|
||||
end
|
||||
|
||||
let add_backing_lines soup =
|
||||
|
@ -1,7 +1,7 @@
|
||||
(executable
|
||||
(name index)
|
||||
(modules index)
|
||||
(libraries common lambdasoup))
|
||||
(libraries common lambdasoup str))
|
||||
|
||||
(library
|
||||
(name common)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -60,6 +60,49 @@
|
||||
src: url('tenor-sans-v12-latin-regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Theme */
|
||||
|
||||
/* Dark theme (default) */
|
||||
:root, body:not([data-theme="light"]) {
|
||||
--bg-color: #131618;
|
||||
--text-color: #c9d1d9;
|
||||
--code-bg-color: #2c333b;
|
||||
--border-color: #282828;
|
||||
--link-color: #8dc5ff;
|
||||
--external-link-color: #5d7fcd;
|
||||
--anchor-color: #bfcdea;
|
||||
|
||||
--of-color: #bec5cd;
|
||||
--target-backing-color: #390022;
|
||||
|
||||
--hljs-keyword-color: #ff6c9b;
|
||||
--hljs-identifier-color: #70df5c;
|
||||
--hljs-tag-color: #c28eff;
|
||||
--hljs-string-color: #e3db7a;
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
:root, body[data-theme="light"] {
|
||||
--bg-color: #f5f7fa;
|
||||
--text-color: #1f2937;
|
||||
--code-bg-color: #eef1f6;
|
||||
--header-bg-color: #f5f7fa;
|
||||
--border-color: #e0e0e0;
|
||||
--link-color: #1c7ed6;
|
||||
--external-link-color: #1d4ed8;
|
||||
--anchor-color: #888;
|
||||
|
||||
--of-color: #6b7280;
|
||||
--target-backing-color: #f7f6f3;
|
||||
|
||||
--hljs-keyword-color: #d94879;
|
||||
--hljs-identifier-color: #22863a;
|
||||
--hljs-tag-color: #6f42c1;
|
||||
--hljs-string-color: #b94e48;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Lato, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
@ -101,15 +144,15 @@ h6 {
|
||||
/* Colors and presentation styles. */
|
||||
|
||||
body {
|
||||
background-color: #131618;
|
||||
color: #c9d1d9;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.odoc-content pre {
|
||||
background-color: #1a1f26;
|
||||
background-color: var(--code-bg-color);
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
border: 1px solid #111;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.odoc-content .spec > pre {
|
||||
background: none;
|
||||
@ -119,7 +162,7 @@ body {
|
||||
|
||||
.odoc-content code {
|
||||
/* color: #ddd; */
|
||||
background-color: #2c333b;
|
||||
background-color: var(--code-bg-color);
|
||||
padding: 0 5px;
|
||||
margin: 0 1px;
|
||||
white-space: nowrap;
|
||||
@ -150,17 +193,40 @@ body {
|
||||
} */
|
||||
|
||||
header {
|
||||
background-color: #131618;
|
||||
border-bottom: 1px solid #282828;
|
||||
background-color: var(--bg-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
header .topmost {
|
||||
/* background-color: #0f131a; */
|
||||
border-bottom: 1px solid #282828;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-shadow: -2px 2px black;
|
||||
.topmost .toolbar {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.topmost .toggle-theme-btn {
|
||||
all: unset;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.topmost .toggle-theme-btn::before {
|
||||
content: "\F185"; /* sun */
|
||||
position: absolute;
|
||||
left: calc(0% - 16px + -8px);
|
||||
top: calc(0% + 4px);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: FontAwesome, FontAwesomeBrands;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body:not([data-theme="light"]) .topmost .toggle-theme-btn::before {
|
||||
content: "\F186"; /* moon */
|
||||
}
|
||||
|
||||
header pre {
|
||||
@ -191,7 +257,7 @@ footer {
|
||||
}
|
||||
|
||||
:target .backing {
|
||||
background-color: #390022;
|
||||
background-color: var(--target-backing-color);
|
||||
}
|
||||
|
||||
nav ~ * a[href="#builtin"],
|
||||
@ -205,18 +271,18 @@ nav ~ * a[href="#templates"] {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
a[href*=example] {
|
||||
header a[href*=example], .spec-doc a[href*=example] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a[href^=http]::after {
|
||||
header a[href^=http]::after, .spec-doc a[href^=http]::after {
|
||||
content: "\f35d";
|
||||
font-family: FontAwesome;
|
||||
font-size: 10px;
|
||||
line-height: 18px;
|
||||
color: #5d7fcd;
|
||||
color: var(--external-link-color);
|
||||
position: relative;
|
||||
top: -0.5px;
|
||||
top: -1px;
|
||||
margin-left: 2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
@ -225,7 +291,7 @@ a[href^=http]::after {
|
||||
}
|
||||
|
||||
a, a:visited, a:active {
|
||||
color: #8dc5ff;
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@ -234,27 +300,27 @@ a:hover {
|
||||
}
|
||||
|
||||
.odoc-content a > code {
|
||||
color: #8dc5ff;
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.hljs-module-access, .hljs-keyword, .keyword {
|
||||
color: #ff6c9b;
|
||||
color: var(--hljs-keyword-color);
|
||||
}
|
||||
|
||||
.hljs-identifier, .hljs-literal, .hljs-type {
|
||||
color: #70df5c;
|
||||
color: var(--hljs-identifier-color);
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #c28eff;
|
||||
color: var(--hljs-tag-color);
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: #e3db7a;
|
||||
color: var(--hljs-string-color);
|
||||
}
|
||||
|
||||
.of {
|
||||
color: #bec5cd;
|
||||
color: var(--of-color);
|
||||
}
|
||||
|
||||
.topmost ul {
|
||||
@ -365,10 +431,9 @@ p + .odoc-spec {
|
||||
}
|
||||
#type-bigstring + .spec-doc li + li,
|
||||
#val-next + .spec-doc li + li,
|
||||
#val-origin_referer_check + .spec-doc li + li,
|
||||
#val-origin_referrer_check + .spec-doc li + li,
|
||||
#val-form + .spec-doc li + li,
|
||||
#type-part + .spec-doc li + li,
|
||||
#type-upload_event + .spec-doc li + li,
|
||||
#val-upload + .spec-doc li + li,
|
||||
#val-static + .spec-doc li + li,
|
||||
#val-from_path + .spec-doc li + li {
|
||||
@ -407,7 +472,7 @@ ul ul li {
|
||||
height: 100%;
|
||||
width: 43rem;
|
||||
/* background-color: #262626; */
|
||||
border-right: 1px solid #282828;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
h2, h2 ~ :not(.odoc-spec):not(nav), footer {
|
||||
@ -462,8 +527,8 @@ ul ul li {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
#type-outgoing + .spec-doc {
|
||||
margin-top: -1.5rem;
|
||||
#type-server + .spec-doc {
|
||||
margin-top: -26px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
#val-patch + .spec-doc {
|
||||
@ -582,8 +647,8 @@ h2:first-of-type {
|
||||
overflow-y: scroll;
|
||||
scrollbar-width: none;
|
||||
line-height: 30px;
|
||||
border-right: 1px solid #262626;
|
||||
background-color: #131618;
|
||||
border-right: 1px solid var(--border-color);
|
||||
background-color: var(--bg-color);
|
||||
/* color: #ddd; */
|
||||
}
|
||||
.odoc-toc::-webkit-scrollbar {
|
||||
@ -674,7 +739,7 @@ h2 > .anchor, h3 > .anchor {
|
||||
font-family: FontAwesome;
|
||||
font-size: 10px;
|
||||
font-style: oblique;
|
||||
color: #bfcdea;
|
||||
color: var(--anchor-color);
|
||||
position: relative;
|
||||
top: -1.75px;
|
||||
left: -4px;
|
||||
|
@ -4,8 +4,7 @@
|
||||
// Copyright 2021 Anton Bachin *)
|
||||
|
||||
|
||||
|
||||
console.log("foo");
|
||||
/* Scrolling */
|
||||
|
||||
function current_section() {
|
||||
var threshold = window.innerHeight / 2;
|
||||
@ -49,3 +48,38 @@ function scroll() {
|
||||
};
|
||||
|
||||
window.onscroll = scroll;
|
||||
|
||||
|
||||
/* Theme mode */
|
||||
|
||||
var THEME_MODE_KEY = "dream-theme"
|
||||
|
||||
function apply_theme(theme) {
|
||||
if (theme === "light") {
|
||||
document.body.setAttribute("data-theme", "light");
|
||||
} else {
|
||||
document.body.removeAttribute("data-theme");
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_theme() {
|
||||
var current_theme = localStorage.getItem(THEME_MODE_KEY);
|
||||
var new_theme = current_theme === "dark" ? "light" : "dark";
|
||||
localStorage.setItem(THEME_MODE_KEY, new_theme);
|
||||
apply_theme(new_theme);
|
||||
}
|
||||
|
||||
function init_theme() {
|
||||
var default_theme = "dark";
|
||||
var stored_theme = localStorage.getItem(THEME_MODE_KEY) || default_theme;
|
||||
apply_theme(stored_theme);
|
||||
}
|
||||
|
||||
function prepare_button() {
|
||||
var theme_toggle_button = document.querySelector(".toggle-theme-btn");
|
||||
if (theme_toggle_button) {
|
||||
theme_toggle_button.addEventListener("click", toggle_theme);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", prepare_button);
|
||||
|
@ -24,6 +24,10 @@
|
||||
</head>
|
||||
<body class="index">
|
||||
|
||||
<script>
|
||||
init_theme();
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="topmost">
|
||||
<div class="titles">
|
||||
@ -32,11 +36,15 @@
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li><code>1.0.0~alpha2</code></li>
|
||||
<li><code>1.0.0~alpha7</code></li>
|
||||
<li><code>opam install dream</code></li>
|
||||
<li><a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream">GitHub</a></li>
|
||||
<li><a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/blob/master/src/dream.mli">Edit these docs</a></li>
|
||||
<li><a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/edit/master/src/dream.mli">Edit these docs</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="toolbar">
|
||||
<button class="toggle-theme-btn"> </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre><span class="keyword">let</span> hello who =
|
||||
@ -52,8 +60,7 @@
|
||||
@@ <span class="hljs-type">Dream</span>.router <span class="hljs-string">[</span>
|
||||
<span class="hljs-type">Dream</span>.get <span class="hljs-string">"/"</span> (<span class="keyword">fun</span> _ ->
|
||||
<span class="hljs-type">Dream</span>.html (hello <span class="hljs-string">"world"</span>));
|
||||
<span class="hljs-string">]</span>
|
||||
@@ <span class="hljs-type">Dream</span>.not_found</pre>
|
||||
<span class="hljs-string">]</span></pre>
|
||||
|
||||
<!-- Send TLS link to HTTPS example. -->
|
||||
|
||||
@ -116,18 +123,13 @@
|
||||
<li>
|
||||
A <a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example#readme">
|
||||
Tutorial</a> — get started at
|
||||
<a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example/1-hello#files">
|
||||
<a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example/1-hello#folders-and-files">
|
||||
<code>1-hello</code></a>!
|
||||
</li>
|
||||
<li>
|
||||
Many <a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example#reason">
|
||||
Examples</a>, covering all kinds of scenarios.
|
||||
</li>
|
||||
<li>
|
||||
An online <a target="_blank" rel="noreferrer noopener" href="http://dream.as/">
|
||||
<b>Playground</b></a>, where you can try Dream without installing
|
||||
anything!
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
@ -136,7 +138,7 @@
|
||||
<div id="api"></div>
|
||||
|
||||
<footer>
|
||||
Copyright © 2021 Anton Bachin.
|
||||
Copyright © 2021-2024 Anton Bachin.
|
||||
<br>
|
||||
Released under the MIT license. See
|
||||
<a href="https://github.com/aantron/dream/blob/master/LICENSE.md">
|
||||
|
@ -1,11 +1,9 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
synopsis: "Dream docs"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
depends: [
|
||||
"lambdasoup"
|
||||
"odoc"
|
||||
"odoc" {= "2.0.2"}
|
||||
"soupault" {>= "2.5.0"}
|
||||
]
|
||||
|
||||
pin-depends: [
|
||||
["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"]
|
||||
]
|
||||
|
@ -1,71 +1,78 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ISO8601" {= "0.2.6"}
|
||||
"astring" {= "0.8.5"}
|
||||
"base-bigarray" {= "base"}
|
||||
"base-bytes" {= "base"}
|
||||
"base-threads" {= "base"}
|
||||
"base-unix" {= "base"}
|
||||
"base64" {= "3.5.0"}
|
||||
"bigarray-compat" {= "1.0.0"}
|
||||
"cmdliner" {= "1.0.4"}
|
||||
"conf-libev" {= "4-11"}
|
||||
"containers" {= "3.3"}
|
||||
"cppo" {= "1.6.7"}
|
||||
"base64" {= "3.5.1"}
|
||||
"bigarray-compat" {= "1.1.0"}
|
||||
"bos" {= "0.2.1"}
|
||||
"camlp-streams" {= "5.0.1"}
|
||||
"camomile" {= "1.0.2"}
|
||||
"cmdliner" {= "1.1.1"}
|
||||
"conf-libev" {= "4-12"}
|
||||
"conf-pkg-config" {= "2"}
|
||||
"containers" {= "3.11"}
|
||||
"cppo" {= "1.6.9"}
|
||||
"csexp" {= "1.5.1"}
|
||||
"cstruct" {= "6.0.0"}
|
||||
"dune" {= "2.8.5"}
|
||||
"dune-configurator" {= "2.8.5"}
|
||||
"ezjsonm" {= "1.2.0"}
|
||||
"fileutils" {= "0.6.3"}
|
||||
"fmt" {= "0.8.9"}
|
||||
"cstruct" {= "6.1.1"}
|
||||
"ctypes" {= "0.20.1"}
|
||||
"digestif" {= "1.1.3"}
|
||||
"dune" {= "3.7.0"}
|
||||
"dune-configurator" {= "3.7.0"}
|
||||
"either" {= "1.0.0"}
|
||||
"eqaf" {= "0.9"}
|
||||
"ezjsonm" {= "1.3.0"}
|
||||
"fileutils" {= "0.6.4"}
|
||||
"fmt" {= "0.9.0"}
|
||||
"fpath" {= "0.7.3"}
|
||||
"hex" {= "1.4.0"}
|
||||
"jingoo" {= "1.4.3"}
|
||||
"jsonm" {= "1.0.1"}
|
||||
"lambdasoup" {= "0.7.2"}
|
||||
"hex" {= "1.5.0"}
|
||||
"integers" {= "0.7.0"}
|
||||
"jingoo" {= "1.4.4"}
|
||||
"jsonm" {= "1.0.2"}
|
||||
"lambdasoup" {= "1.0.0"}
|
||||
"logs" {= "0.7.0"}
|
||||
"lua-ml" {= "0.9.2"}
|
||||
"lwt" {= "5.4.0"}
|
||||
"markup" {= "1.0.0-1"}
|
||||
"menhir" {= "20210310"}
|
||||
"menhirLib" {= "20210310"}
|
||||
"menhirSdk" {= "20210310"}
|
||||
"mmap" {= "1.1.0"}
|
||||
"ocaml" {= "4.12.0"}
|
||||
"ocaml-base-compiler" {= "4.12.0"}
|
||||
"ocaml-compiler-libs" {= "v0.12.3"}
|
||||
"lua-ml" {= "0.9.4"}
|
||||
"lwt" {= "5.6.1"}
|
||||
"markup" {= "1.0.3"}
|
||||
"menhir" {= "20211128"}
|
||||
"menhirLib" {= "20211128"}
|
||||
"menhirSdk" {= "20211128"}
|
||||
"ocaml" {= "4.14.1"}
|
||||
"ocaml-base-compiler" {= "4.14.1"}
|
||||
"ocaml-compiler-libs" {= "v0.12.4"}
|
||||
"ocaml-config" {= "2"}
|
||||
"ocaml-migrate-parsetree" {= "2.1.0"}
|
||||
"ocaml-migrate-parsetree" {= "2.4.0"}
|
||||
"ocaml-options-vanilla" {= "1"}
|
||||
"ocaml-syntax-shims" {= "1.0.0"}
|
||||
"ocamlbuild" {= "0.14.0"}
|
||||
"ocamlfind" {= "1.9.1"}
|
||||
"ocplib-endian" {= "1.1"}
|
||||
"ocamlbuild" {= "0.14.2"}
|
||||
"ocamlfind" {= "1.9.6"}
|
||||
"ocplib-endian" {= "1.2"}
|
||||
"odate" {= "0.6"}
|
||||
"odoc" {= "2.0.0~master"}
|
||||
"odoc" {= "2.0.2"}
|
||||
"odoc-parser" {= "1.0.1"}
|
||||
"otoml" {= "1.0.4"}
|
||||
"ppx_derivers" {= "1.2.1"}
|
||||
"ppx_deriving" {= "5.2.1"}
|
||||
"ppxlib" {= "0.22.0"}
|
||||
"re" {= "1.9.0"}
|
||||
"ppxlib" {= "0.25.1"}
|
||||
"re" {= "1.10.4"}
|
||||
"result" {= "1.5"}
|
||||
"rresult" {= "0.7.0"}
|
||||
"seq" {= "base"}
|
||||
"sexplib0" {= "v0.14.0"}
|
||||
"soupault" {= "2.5.0"}
|
||||
"spelll" {= "0.3"}
|
||||
"sexplib0" {= "v0.15.1"}
|
||||
"soupault" {= "4.4.0"}
|
||||
"spelll" {= "0.4"}
|
||||
"stdlib-shims" {= "0.3.0"}
|
||||
"stringext" {= "1.6.0"}
|
||||
"toml" {= "7.0.0"}
|
||||
"topkg" {= "1.0.3"}
|
||||
"tsort" {= "2.0.0"}
|
||||
"tyxml" {= "4.4.0"}
|
||||
"topkg" {= "1.0.7"}
|
||||
"tsort" {= "2.1.0"}
|
||||
"tyxml" {= "4.5.0"}
|
||||
"uchar" {= "0.0.2"}
|
||||
"uucp" {= "13.0.0"}
|
||||
"uutf" {= "1.0.2"}
|
||||
]
|
||||
pin-depends: [
|
||||
["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"]
|
||||
"uucp" {= "15.0.0"}
|
||||
"uutf" {= "1.0.3"}
|
||||
"yaml" {= "3.1.0"}
|
||||
]
|
||||
name: "web"
|
||||
version: "dev"
|
||||
version: "~dev"
|
||||
synopsis: "Dream docs"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
|
34
dream-httpaf.opam
Normal file
34
dream-httpaf.opam
Normal file
@ -0,0 +1,34 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
synopsis: "Internal: shared http/af stack for Dream (server) and Hyper (client)"
|
||||
description: "This package does not have a stable API."
|
||||
|
||||
license: "MIT"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
doc: "https://aantron.github.io/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
dev-repo: "git+https://github.com/aantron/dream.git"
|
||||
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
|
||||
depends: [
|
||||
"dream-pure"
|
||||
"dune" {>= "2.7.0"} # --instrument-with.
|
||||
"gluten"
|
||||
"gluten-lwt-unix"
|
||||
"h2" {< "0.13.0"}
|
||||
"h2-lwt-unix"
|
||||
"httpun" {< "0.2.0"}
|
||||
"httpun-lwt-unix"
|
||||
"httpun-ws"
|
||||
"lwt"
|
||||
"lwt_ppx" {>= "1.2.2"}
|
||||
"lwt_ssl"
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"ssl" {>= "0.5.8"} # Ssl.get_negotiated_alpn_protocol.
|
||||
]
|
||||
|
||||
build: [
|
||||
["dune" "build" "-p" name "-j" jobs]
|
||||
]
|
72
dream-mirage.opam
Normal file
72
dream-mirage.opam
Normal file
@ -0,0 +1,72 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
synopsis: "Tidy, feature-complete Web framework"
|
||||
tags: ["http" "web" "framework" "websocket" "graphql" "server" "http2" "tls"]
|
||||
|
||||
description: """
|
||||
Dream is a feature-complete Web framework with a simple programming
|
||||
model and no boilerplate. It provides only two data types, request and
|
||||
response.
|
||||
|
||||
Almost everything else is either a built-in OCaml type, or an
|
||||
abbreviation for a bare function. For example, a Web app, known in
|
||||
Dream as a handler, is just an ordinary function from requests to
|
||||
responses. And a middleware is then just a function from handlers to
|
||||
handlers.
|
||||
|
||||
Within this model, Dream adds:
|
||||
|
||||
- Session management with pluggable back ends.
|
||||
- A fully composable router.
|
||||
- Support for HTTP/1.1, HTTP/2, and HTTPS.
|
||||
- WebSockets.
|
||||
- GraphQL, including subscriptions and a built-in GraphiQL editor.
|
||||
- SQL connection pool helpers.
|
||||
- Server-side HTML templates.
|
||||
- Automatic secure handling of cookies and forms.
|
||||
- Unified, internationalization-friendly error handling.
|
||||
- A neat log, and OCaml runtime configuration.
|
||||
- Helpers for Web formats, such as Base64url, and a modern cipher.
|
||||
|
||||
Because of the simple programming model, everything is optional and
|
||||
composable. It is trivially possible to strip Dream down to just a
|
||||
bare driver of the various HTTP protocols.
|
||||
|
||||
Dream is presented as a single module, whose API is documented on one
|
||||
page. In addition, Dream comes with a large number of examples.
|
||||
Security topics are introduced throughout, wherever they are
|
||||
applicable."""
|
||||
|
||||
license: "MIT"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
doc: "https://aantron.github.io/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
dev-repo: "git+https://github.com/aantron/dream.git"
|
||||
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
|
||||
depends: [
|
||||
"bigarray-compat"
|
||||
"bigstringaf"
|
||||
"digestif" {>= "1.0.0"}
|
||||
"dream"
|
||||
"dream-httpaf"
|
||||
"dream-pure"
|
||||
"dune" {>= "2.7.0"}
|
||||
"duration"
|
||||
"emile" {>= "1.1"}
|
||||
"ke" {>= "0.4"} # paf.
|
||||
"letsencrypt" {>= "0.3.0"}
|
||||
"lwt"
|
||||
"lwt_ppx" {>= "1.2.2"}
|
||||
"mimic" {>= "0.0.5"}
|
||||
"mirage-time"
|
||||
"rresult"
|
||||
"tcpip"
|
||||
"tls-mirage"
|
||||
]
|
||||
|
||||
build: [
|
||||
["dune" "build" "-p" name "-j" jobs]
|
||||
]
|
35
dream-pure.opam
Normal file
35
dream-pure.opam
Normal file
@ -0,0 +1,35 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
synopsis: "Internal: shared HTTP types for Dream (server) and Hyper (client)"
|
||||
description: "This package does not have a stable API."
|
||||
|
||||
license: "MIT"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
doc: "https://aantron.github.io/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
dev-repo: "git+https://github.com/aantron/dream.git"
|
||||
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
|
||||
depends: [
|
||||
"base64" {>= "3.1.0"} # Base64.encode_string.
|
||||
"bigstringaf" {>= "0.5.0"} # Bigstringaf.to_string.
|
||||
"dune" {>= "2.7.0"} # --instrument-with.
|
||||
"hmap"
|
||||
"lwt"
|
||||
"lwt_ppx" {>= "1.2.2"}
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"ptime" {>= "0.8.1"} # Ptime.weekday.
|
||||
"uri" {>= "4.2.0"}
|
||||
|
||||
# Testing, development.
|
||||
"alcotest" {with-test}
|
||||
"bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with.
|
||||
"ppx_expect" {with-test}
|
||||
"ppx_yojson_conv" {with-test}
|
||||
]
|
||||
|
||||
build: [
|
||||
["dune" "build" "-p" name "-j" jobs]
|
||||
]
|
65
dream.opam
65
dream.opam
@ -29,7 +29,7 @@ Within this model, Dream adds:
|
||||
- Helpers for Web formats, such as Base64url, and a modern cipher.
|
||||
|
||||
Because of the simple programming model, everything is optional and
|
||||
composable. It is trivailly possible to strip Dream down to just a
|
||||
composable. It is trivially possible to strip Dream down to just a
|
||||
bare driver of the various HTTP protocols.
|
||||
|
||||
Dream is presented as a single module, whose API is documented on one
|
||||
@ -48,65 +48,52 @@ maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
|
||||
depends: [
|
||||
"base-unix"
|
||||
"base64" {>= "3.1.0"} # Base64.encode_string.
|
||||
"bigarray-compat"
|
||||
"caqti" {>= "1.4.0"} # ~post_connect.
|
||||
"caqti-lwt"
|
||||
"conf-libev" {os != "win32"}
|
||||
"camlp-streams"
|
||||
"caqti" {>= "2.0.0"}
|
||||
"caqti-lwt" {>= "2.0.0"}
|
||||
("conf-libev" {os != "win32"} | "ocaml" {os = "win32"})
|
||||
"cstruct" {>= "6.0.0"}
|
||||
"digestif" {>= "0.7"} # to_raw_string.
|
||||
"dream-httpaf" {>= "1.0.0~alpha4"}
|
||||
"dream-pure" {>= "1.0.0~alpha2"}
|
||||
"dune" {>= "2.7.0"} # --instrument-with.
|
||||
"fmt" {>= "0.8.7"} # `Italic.
|
||||
"graphql_parser"
|
||||
"graphql-lwt"
|
||||
"hmap"
|
||||
"lambdasoup" {>= "0.6.1"}
|
||||
"lwt"
|
||||
"lwt_ppx"
|
||||
"lwt_ppx" {>= "1.2.2"}
|
||||
"lwt_ssl"
|
||||
"logs" {>= "0.5.0"}
|
||||
"magic-mime"
|
||||
"mirage-crypto" {>= "0.8.1"} # AES-256-GCM.
|
||||
"mirage-crypto-rng" {>= "0.8.0"} # Signature of initialize.
|
||||
"multipart_form" {>= "0.3.0"}
|
||||
"markup" {>= "1.0.2"}
|
||||
"mirage-clock" {>= "3.0.0"} # now_d_ps : unit -> int * int64.
|
||||
"mirage-crypto" {>= "1.0.0"}
|
||||
"mirage-crypto-rng" {>= "1.0.0"}
|
||||
"mirage-crypto-rng-lwt"
|
||||
"multipart_form" {>= "0.4.0"}
|
||||
"multipart_form-lwt"
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"ptime" {>= "0.8.1"} # Ptime.v.
|
||||
"ssl" {>= "0.5.8"} # Ssl.get_negotiated_alpn_protocol.
|
||||
"uri" {>= "4.2.0"}
|
||||
"yojson" # ...
|
||||
|
||||
# Currently vendored.
|
||||
# "gluten"
|
||||
# "gluten-lwt-unix"
|
||||
# "httpaf"
|
||||
# "httpaf-lwt-unix"
|
||||
# "h2"
|
||||
# "h2-lwt-unix"
|
||||
# "hpack"
|
||||
# "websocketaf"
|
||||
|
||||
# Dependencies of vendored packages.
|
||||
"angstrom" {>= "0.14.0"}
|
||||
"bigstringaf" {>= "0.4.0"}
|
||||
"digestif" {>= "0.7"} # websocket/af, sha1.
|
||||
"faraday" {>= "0.6.1"}
|
||||
"faraday-lwt-unix"
|
||||
"psq" # h2.
|
||||
"result" # http/af, websocket/af.
|
||||
|
||||
# https://github.com/ocaml-ppx/ppxlib/issues/221.
|
||||
# esy appears to ignore conflicts.
|
||||
"ppxlib" {< "0.21.0" | > "0.22.0"}
|
||||
|
||||
# Testing, development.
|
||||
"alcotest" {with-test}
|
||||
# Commented out because of https://github.com/ocaml-ppx/ppxlib/issues/221.
|
||||
# "bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with.
|
||||
"bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with.
|
||||
"caqti-driver-postgresql" {with-test}
|
||||
"caqti-driver-sqlite3" {with-test}
|
||||
"crunch" {with-test}
|
||||
"lambdasoup" {with-test}
|
||||
"ppx_expect" {with-test}
|
||||
"html_of_jsx" {with-test}
|
||||
"js_of_ocaml" {with-test}
|
||||
"js_of_ocaml-ppx" {with-test}
|
||||
"ppx_expect" {with-test & >= "v0.15.0" & < "v0.17.0"} # Breaking changes.
|
||||
"ppx_yojson_conv" {with-test}
|
||||
"reason" {with-test}
|
||||
"tyxml" {with-test & >= "4.5.0"}
|
||||
"tyxml-jsx" {with-test & >= "4.5.0"}
|
||||
"tyxml-ppx" {with-test & >= "4.5.0"}
|
||||
"tyxml-jsx" {with-test}
|
||||
]
|
||||
|
||||
build: [
|
||||
|
14
example/1-hello/1-hello.opam
Normal file
14
example/1-hello/1-hello.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -12,26 +12,25 @@ let () =
|
||||
|
||||
<br>
|
||||
|
||||
It's the absolute minimum Dream server. It responds to all requests with the
|
||||
same text. At startup, Dream prints a message to the log, telling you where to
|
||||
point your browser. Your terminal probably makes the link clickable.
|
||||
It's the minimal Dream server. It responds to all requests with the same text.
|
||||
At startup, Dream prints a message to the log, telling you where to point your
|
||||
browser. Your terminal probably makes the link clickable.
|
||||
|
||||
<pre><code><b>$ cd example/1-hello</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./hello.exe</b>
|
||||
08.03.21 21:17:21.471 Running at http://localhost:8080
|
||||
08.03.21 21:17:21.471 Type Ctrl+C to stop
|
||||
</code></pre>
|
||||
|
||||
If you go to [http://localhost:8080](http://localhost:8080), you will, of
|
||||
course, see `Good morning, world!`. You can also try it in the [Dream
|
||||
Playground](http://dream.as/1-hello).
|
||||
course, see `Good morning, world!`.
|
||||
|
||||
<br>
|
||||
|
||||
If you'd like to copy out the server binary, you can do it like this:
|
||||
|
||||
<pre><code><b>$ npx esy cp '#{self.target_dir}/default/hello.exe' .
|
||||
<pre><code><b>$ cp _build/default/hello.exe .
|
||||
</b></code></pre>
|
||||
|
||||
The name will change as you go through the tutorial examples. It's always the
|
||||
@ -41,19 +40,17 @@ name of the `.ml` file, but with `.ml` changed to `.exe`.
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- The next example, [**`2-middleware`**](../2-middleware#files), adds a logger
|
||||
- The next example, [**`2-middleware`**](../2-middleware#folders-and-files), adds a logger
|
||||
to the app.
|
||||
- [**`3-router`**](../3-router#files) sends requests to different handlers,
|
||||
- [**`3-router`**](../3-router#folders-and-files) sends requests to different handlers,
|
||||
depending on their path.
|
||||
|
||||
<br>
|
||||
|
||||
**See also:**
|
||||
|
||||
- [**`r-hello`**](../r-hello#files) is a Reason syntax version of this example.
|
||||
- [**`w-esy`**](../w-esy#files) gives more detail on the [esy](https://esy.sh/)
|
||||
packaging.
|
||||
- [**`w-fswatch`**](../w-fswatch#files) sets up a primitive development watcher.
|
||||
- [**`r-hello`**](../r-hello#folders-and-files) is a Reason syntax version of this example.
|
||||
- [**`w-watch`**](../w-watch#folders-and-files) sets up a development watcher.
|
||||
|
||||
|
||||
<br>
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name hello)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./hello.exe"
|
||||
}
|
||||
}
|
14
example/2-middleware/2-middleware.opam
Normal file
14
example/2-middleware/2-middleware.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -4,7 +4,7 @@
|
||||
|
||||
*Middleware* is just functions that take handlers and wrap them, producing
|
||||
bigger handlers that do a little bit more. This example takes the handler from
|
||||
[**`1-hello`**](../1-hello#files) and wraps it in one of the most useful
|
||||
[**`1-hello`**](../1-hello#folders-and-files) and wraps it in one of the most useful
|
||||
middlewares, the [*logger*](https://aantron.github.io/dream/#val-logger):
|
||||
|
||||
```ocaml
|
||||
@ -19,7 +19,7 @@ let () =
|
||||
However, as you can see, the more middlewares we stack on top of each other
|
||||
like this, the more parentheses and indentation we will end up with! To keep
|
||||
the code tidy, we use `@@`, the
|
||||
[standard OCaml operator](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Stdlib.html#VAL(@@)) for calling functions without parentheses. So, the [actual
|
||||
[standard OCaml operator](https://v2.ocaml.org/api/Stdlib.html#VAL(@@)) for calling functions without parentheses. So, the [actual
|
||||
code](https://github.com/aantron/dream/blob/master/example/2-middleware/middleware.ml)
|
||||
in this example looks like this:
|
||||
|
||||
@ -33,15 +33,14 @@ let () =
|
||||
<br>
|
||||
|
||||
When you run this server and visit
|
||||
[http://localhost:8080](http://localhost:8080)
|
||||
[[playground](http://dream.as/2-middleware)], you get much more interesting
|
||||
[http://localhost:8080](http://localhost:8080), you get much more interesting
|
||||
(and colorful!) output:
|
||||
|
||||

|
||||
|
||||
You can write your own messages to the log using
|
||||
[`Dream.log`](https://aantron.github.io/dream/#val-log). See example
|
||||
[**`a-log`**](../a-log#files) for more logging options. Now that we have the
|
||||
[**`a-log`**](../a-log#folders-and-files) for more logging options. Now that we have the
|
||||
logger, we will use it in all other examples, even though it's not really
|
||||
necessary — it just makes it much easier to see what is going on.
|
||||
|
||||
@ -49,14 +48,14 @@ necessary — it just makes it much easier to see what is going on.
|
||||
|
||||
There's not much else to middlewares — they are really just functions
|
||||
from handlers to handlers, so you can create them anywhere. Example
|
||||
[**`4-counter`**](../4-counter#files) already shows a simple custom middleware.
|
||||
[**`4-counter`**](../4-counter#folders-and-files) already shows a simple custom middleware.
|
||||
|
||||
<!--
|
||||
There are also more complicated middlewares defined in
|
||||
|
||||
- [**`m-locals`**](../m-locals/#files),
|
||||
- [**`w-auto-reload`**](../w-auto-reload/#files), and
|
||||
- [**`w-index-html`**](../w-index-html/#files).
|
||||
- [**`m-locals`**](../m-locals#folders-and-files),
|
||||
- [**`w-auto-reload`**](../w-auto-reload#folders-and-files), and
|
||||
- [**`w-index-html`**](../w-index-html#folders-and-files).
|
||||
-->
|
||||
|
||||
<!-- TODO Fill out this list; probably a-promise belongs here. -->
|
||||
@ -65,10 +64,10 @@ There are also more complicated middlewares defined in
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- The next example, [**`3-router`**](../3-router#files), shows
|
||||
- The next example, [**`3-router`**](../3-router#folders-and-files), shows
|
||||
[*routes*](https://aantron.github.io/dream/#routing), the other way to build
|
||||
up handlers in Dream.
|
||||
- [**`4-counter`**](../4-counter#files) builds the first custom middleware.
|
||||
- [**`4-counter`**](../4-counter#folders-and-files) builds the first custom middleware.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name middleware)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./middleware.exe"
|
||||
}
|
||||
}
|
14
example/3-router/3-router.opam
Normal file
14
example/3-router/3-router.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -20,50 +20,42 @@ let () =
|
||||
|
||||
Dream.get "/echo/:word"
|
||||
(fun request ->
|
||||
Dream.html (Dream.param "word" request));
|
||||
Dream.html (Dream.param request "word"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/3-router</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./router.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
This is also our first dynamic site! A request to `/echo/foo` gets the response
|
||||
`foo`, and a request to `/echo/bar` gets `bar`! Try it in the
|
||||
[playground](http://dream.as/3-router/echo/foo) — once the server loads,
|
||||
edit the URL in the right pane to visit `/echo/bar`.
|
||||
`foo`, and a request to `/echo/bar` gets `bar`!
|
||||
|
||||
The syntax `:word` in a route creates a path parameter, which can be read with
|
||||
[`Dream.param`](https://aantron.github.io/dream/#val-param).
|
||||
|
||||
<!-- TODO hyperlink Dream.param to docsc, also Dream.logger. -->
|
||||
|
||||
[The whole router is a middleware](https://aantron.github.io/dream/#val-router),
|
||||
just like [`Dream.logger`](https://aantron.github.io/dream/#val-logger). When
|
||||
none of the routes match, the router passes the request to the next handler,
|
||||
which is right beneath it. In this example, we just respond with `404 Not
|
||||
Found` when that happens.
|
||||
|
||||
When none of the routes match, the router returns a `404 Not Found` response.
|
||||
Except for the status code, the `404 Not Found` response is *completely* empty,
|
||||
so it might not display well in your browser. In example
|
||||
[**`9-error`**](../9-error#files), we will decorate all error responses with
|
||||
[**`9-error`**](../9-error#folders-and-files), we will decorate all error responses with
|
||||
an error template in one central location.
|
||||
|
||||
<br>
|
||||
|
||||
The router can do more than match simple routes:
|
||||
|
||||
- [**`f-static`**](../f-static#files) forwards all requests with a certain
|
||||
- [**`f-static`**](../f-static#folders-and-files) forwards all requests with a certain
|
||||
prefix to a static file handler.
|
||||
|
||||
|
||||
<!-- - [**`w-scope`**](../w-scope/#files) applies middlewares to groups of routes
|
||||
<!-- - [**`w-scope`**](../w-scope#folders-and-files) applies middlewares to groups of routes
|
||||
— but only when they match.
|
||||
- [**`w-subsite`**](../w-subsite/#files) attaches a handler as a complete,
|
||||
- [**`w-subsite`**](../w-subsite#folders-and-files) attaches a handler as a complete,
|
||||
nested sub-site, which might have its own router. -->
|
||||
<!-- TODO -->
|
||||
|
||||
@ -71,9 +63,9 @@ The router can do more than match simple routes:
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`4-counter`**](../4-counter#files) counts requests, and exposes a route for
|
||||
- [**`4-counter`**](../4-counter#folders-and-files) counts requests, and exposes a route for
|
||||
getting the count.
|
||||
- [**`5-promise`**](../5-promise#files) introduces
|
||||
- [**`5-promise`**](../5-promise#folders-and-files) introduces
|
||||
[Lwt](https://github.com/ocsigen/lwt), the promise library used by Dream.
|
||||
|
||||
<br>
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name router)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./router.exe"
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ let () =
|
||||
|
||||
Dream.get "/echo/:word"
|
||||
(fun request ->
|
||||
Dream.html (Dream.param "word" request));
|
||||
Dream.html (Dream.param request "word"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
14
example/4-counter/4-counter.opam
Normal file
14
example/4-counter/4-counter.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -4,8 +4,7 @@
|
||||
|
||||
This example shows how easy it is to define a custom middleware,
|
||||
`count_requests`. It exposes the request count at
|
||||
[http://localhost:8080/](http://localhost:8080/)
|
||||
[[playground](http://dream.as/4-counter)], in a sort of dashboard:
|
||||
[http://localhost:8080/](http://localhost:8080/), in a sort of dashboard:
|
||||
|
||||
```ocaml
|
||||
let count = ref 0
|
||||
@ -22,11 +21,10 @@ let () =
|
||||
Dream.get "/" (fun _ ->
|
||||
Dream.html (Printf.sprintf "Saw %i request(s)!" !count));
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
<pre><code><b>$ cd example/4-counter</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./counter.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
@ -39,24 +37,16 @@ which means they usually also
|
||||
This example's middleware only does something *before* calling the
|
||||
`inner_handler`. To do something *after*, we will need to await the response
|
||||
promise with [Lwt](https://github.com/ocsigen/lwt#readme), the promise library
|
||||
used by Dream. The next example, [**`5-promise`**](../5-promise#files), does
|
||||
used by Dream. The next example, [**`5-promise`**](../5-promise#folders-and-files), does
|
||||
exactly that!
|
||||
|
||||
<!-- TODO
|
||||
<br>
|
||||
|
||||
Advanced example [**`w-globals`**](../w-globals/#files) shows how to replace
|
||||
global state like `count` by state scoped to the application. This is useful if
|
||||
you are writing middleware to publish in a library. It's fine to use a global
|
||||
`ref` in private code!
|
||||
-->
|
||||
<br>
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`5-promise`**](../5-promise#files) shows a middleware that awaits
|
||||
- [**`5-promise`**](../5-promise#folders-and-files) shows a middleware that awaits
|
||||
responses using [Lwt](https://github.com/ocsigen/lwt).
|
||||
- [**`6-echo`**](../6-echo#files) responds to `POST` requests and reads their
|
||||
- [**`6-echo`**](../6-echo#folders-and-files) responds to `POST` requests and reads their
|
||||
bodies.
|
||||
|
||||
<br>
|
||||
|
@ -12,4 +12,3 @@ let () =
|
||||
Dream.get "/" (fun _ ->
|
||||
Dream.html (Printf.sprintf "Saw %i request(s)!" !count));
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name counter)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./counter.exe"
|
||||
}
|
||||
}
|
14
example/5-promise/5-promise.opam
Normal file
14
example/5-promise/5-promise.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -2,7 +2,7 @@
|
||||
|
||||
<br>
|
||||
|
||||
[**`4-counter`**](../4-counter#files) was limited to counting requests *before*
|
||||
[**`4-counter`**](../4-counter#folders-and-files) was limited to counting requests *before*
|
||||
passing them on to the rest of the app. With the promise library
|
||||
[Lwt](https://github.com/ocsigen/lwt), we can await responses, and do something
|
||||
*after*. In this example, we separately count requests that were handled
|
||||
@ -38,14 +38,11 @@ let () =
|
||||
!successful !failed));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/5-promise</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
|
||||
Try it in the [playground](http://dream.as/5-promise).
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./promise.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
@ -94,8 +91,8 @@ We will stick to `let%lwt` in the examples and keep things tidy.
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`6-echo`**](../6-echo#files) uses Dream and Lwt to read a request body.
|
||||
- [**`7-template`**](../7-template#files) shows how to interleave HTML and
|
||||
- [**`6-echo`**](../6-echo#folders-and-files) uses Dream and Lwt to read a request body.
|
||||
- [**`7-template`**](../7-template#folders-and-files) shows how to interleave HTML and
|
||||
OCaml.
|
||||
|
||||
<br>
|
||||
|
@ -2,5 +2,3 @@
|
||||
(name promise)
|
||||
(libraries dream)
|
||||
(preprocess (pps lwt_ppx)))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./promise.exe"
|
||||
}
|
||||
}
|
@ -27,4 +27,3 @@ let () =
|
||||
!successful !failed));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
14
example/6-echo/6-echo.opam
Normal file
14
example/6-echo/6-echo.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -18,14 +18,11 @@ let () =
|
||||
body);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/6-echo</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
|
||||
...or run it in the [playground](http://dream.as/6-echo).
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./echo.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
@ -49,11 +46,11 @@ foo
|
||||
We usually want to do something more interesting with the request body than just
|
||||
echo it, and there are several examples for that!
|
||||
|
||||
- [**`d-form`**](../d-form#files) parses request bodies as forms.
|
||||
- [**`e-json`**](../e-json#files) parses bodies as JSON.
|
||||
- [**`g-upload`**](../g-upload#files) receives file upload forms.
|
||||
- [**`i-graphql`**](../i-graphql#files) receives GraphQL queries.
|
||||
- [**`j-stream`**](../j-stream#files) streams huge bodies.
|
||||
- [**`d-form`**](../d-form#folders-and-files) parses request bodies as forms.
|
||||
- [**`e-json`**](../e-json#folders-and-files) parses bodies as JSON.
|
||||
- [**`g-upload`**](../g-upload#folders-and-files) receives file upload forms.
|
||||
- [**`i-graphql`**](../i-graphql#folders-and-files) receives GraphQL queries.
|
||||
- [**`j-stream`**](../j-stream#folders-and-files) streams huge bodies.
|
||||
|
||||
We delay these examples a bit, so we can squeeze in a couple security topics
|
||||
first. These examples do take client input, after all! So, it's better to
|
||||
@ -65,9 +62,9 @@ present them the right way.
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`7-template`**](../7-template#files) builds responses from templates and
|
||||
- [**`7-template`**](../7-template#folders-and-files) builds responses from templates and
|
||||
guards against injection attacks (XSS).
|
||||
- [**`8-debug`**](../8-debug#files) renders error information in responses.
|
||||
- [**`8-debug`**](../8-debug#folders-and-files) renders error information in responses.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -2,5 +2,3 @@
|
||||
(name echo)
|
||||
(libraries dream)
|
||||
(preprocess (pps lwt_ppx)))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -10,4 +10,3 @@ let () =
|
||||
body);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./echo.exe"
|
||||
}
|
||||
}
|
14
example/7-template/7-template.opam
Normal file
14
example/7-template/7-template.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -5,8 +5,8 @@
|
||||
Dream [*templates*](https://aantron.github.io/dream/#templates) allow
|
||||
interleaving OCaml and HTML in a straightforward way, and help with
|
||||
[XSS protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).
|
||||
After looking at the correct example, we will
|
||||
[weaken and then exploit it](#security).
|
||||
After looking at a secure example, we will [weaken and then exploit
|
||||
it](#security).
|
||||
|
||||
```ocaml
|
||||
let render param =
|
||||
@ -23,17 +23,16 @@ let () =
|
||||
|
||||
Dream.get "/:word"
|
||||
(fun request ->
|
||||
Dream.param "word" request
|
||||
Dream.param request "word"
|
||||
|> render
|
||||
|> Dream.html);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/7-template</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./template.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
@ -55,7 +54,7 @@ file to run the template preprocessor:
|
||||
<br>
|
||||
|
||||
The substitution, `<%s param %>`, uses
|
||||
[`Printf` conversion specifications](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html)
|
||||
[`Printf` conversion specifications](https://v2.ocaml.org/api/Printf.html)
|
||||
from the standard library. So, you can do things like this:
|
||||
|
||||
- `<%i my_int %>` to print an OCaml `int`.
|
||||
@ -85,15 +84,11 @@ already escaped, or if it is safe for some other reason. But be careful!
|
||||
To show the danger, let's launch a **script injection (XSS) attack** against
|
||||
this tiny Web app! First, go to
|
||||
[`template.eml.ml`](https://github.com/aantron/dream/blob/master/example/7-template/template.eml.ml#L4),
|
||||
change the substitution to `<%s! param %>`, and restart the app. You can also
|
||||
make the edit in the [playground](http://dream.as/7-template/foo). Then,
|
||||
visit
|
||||
change the substitution to `<%s! param %>`, and restart the app. Then, visit
|
||||
this highly questionable URL:
|
||||
|
||||
[http://localhost:8080/%3Cscript%3Ealert(%22Impossible!%22)%3C%2Fscript%3E](http://localhost:8080/%3Cscript%3Ealert(%22Impossible!%22)%3C%2Fscript%3E)
|
||||
|
||||
If you are using the playground, change the host and port accordingly.
|
||||
|
||||
This URL will cause our Web app to display an alert box, which we, as the
|
||||
developers, did not intend!
|
||||
|
||||
@ -130,23 +125,31 @@ and not supported by Dream.
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`8-debug`**](../8-debug#files) shows how to turn on debug responses, and
|
||||
- [**`8-debug`**](../8-debug#folders-and-files) shows how to turn on debug responses, and
|
||||
get more info about errors.
|
||||
- [**`9-error`**](../9-error#files) sets up a central error template for all
|
||||
- [**`9-error`**](../9-error#folders-and-files) sets up a central error template for all
|
||||
errors.
|
||||
- [**`r-template`**](../r-template#files) is a Reason syntax version of this
|
||||
- [**`r-template`**](../r-template#folders-and-files) is a Reason syntax version of this
|
||||
example.
|
||||
|
||||
<br>
|
||||
|
||||
**See also:**
|
||||
|
||||
- [**`w-tyxml`**](../w-tyxml#files) shows how to use
|
||||
- [**`w-template-files`**](../w-template-files#folders-and-files) moves the template into a
|
||||
separate `.eml.html` to avoid problems with editor support.
|
||||
- [**`w-template-logic`**](../w-template-logic#folders-and-files) shows how to put control
|
||||
flow into templates.
|
||||
- [**`w-tyxml`**](../w-tyxml#folders-and-files) shows how to use
|
||||
[TyXML](https://github.com/ocsigen/tyxml), a different templater that uses
|
||||
OCaml's type system to prevent emitting many kinds of invalid HTML.
|
||||
- [**`r-tyxml`**](../r-tyxml#files) if you are using Reason. You can use TyXML
|
||||
- [**`r-tyxml`**](../r-tyxml#folders-and-files) if you are using Reason. You can use TyXML
|
||||
with JSX syntax server-side!
|
||||
- [**`w-template-stream`**](../w-template-stream#files) streams templates to
|
||||
- [**`w-dream-html`**](../w-dream-html#folders-and-files) shows how to use
|
||||
[dream-html](https://github.com/yawaramin/dream-html), another alternative
|
||||
library for generating HTML from OCaml, which is more closely integrated with
|
||||
Dream.
|
||||
- [**`w-template-stream`**](../w-template-stream#folders-and-files) streams templates to
|
||||
responses, instead of building up complete response strings.
|
||||
|
||||
<br>
|
||||
|
@ -6,5 +6,3 @@
|
||||
(targets template.ml)
|
||||
(deps template.eml.ml)
|
||||
(action (run dream_eml %{deps} --workspace %{workspace_root})))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./template.exe"
|
||||
}
|
||||
}
|
@ -12,9 +12,8 @@ let () =
|
||||
|
||||
Dream.get "/:word"
|
||||
(fun request ->
|
||||
Dream.param "word" request
|
||||
Dream.param request "word"
|
||||
|> render
|
||||
|> Dream.html);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
14
example/8-debug/8-debug.opam
Normal file
14
example/8-debug/8-debug.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -2,12 +2,12 @@
|
||||
|
||||
<br>
|
||||
|
||||
Getting Dream to respond with more debug information is as easy as adding
|
||||
`~debug:true` to [`Dream.run`](https://aantron.github.io/dream/#val-run):
|
||||
Dream has a built-in error handler for showing debug information. You can enable
|
||||
it by passing it to `Dream.run`:
|
||||
|
||||
```ocaml
|
||||
let () =
|
||||
Dream.run ~debug:true
|
||||
Dream.run ~error_handler:Dream.debug_error_handler
|
||||
@@ Dream.logger
|
||||
@@ Dream.router [
|
||||
|
||||
@ -20,49 +20,50 @@ let () =
|
||||
raise (Failure "The Web app failed!"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/8-debug</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./debug.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
The rest of the app just adds two routes for triggering two kinds of
|
||||
failures that the debugger will detail. Visit
|
||||
[http://localhost:8080/bad](http://localhost:8080/bad)
|
||||
[[playground](http://dream.as/8-debug/bad)] to trigger a `400 Bad Request`
|
||||
response, and [http://localhost:8080/fail](http://localhost:8080/fail)
|
||||
[[playground](http://dream.as/8-debug/fail)] to trigger an exception. The
|
||||
debugger will show reports like this:
|
||||
[http://localhost:8080/bad](http://localhost:8080/bad) to trigger a
|
||||
`400 Bad Request` response, and
|
||||
[http://localhost:8080/fail](http://localhost:8080/fail) to trigger an
|
||||
exception. The debugger will show reports like this:
|
||||
|
||||
```
|
||||
(Failure "The Web app failed!")
|
||||
Raised at Stdlib__string.index_rec in file "string.ml", line 115, characters 19-34
|
||||
Called from Sexplib0__Sexp.Printing.index_of_newline in file "src/sexp.ml", line 113, characters 13-47
|
||||
Failure("The Web app failed!")
|
||||
Raised at Stdlib__map.Make.find in file "map.ml", line 137, characters 10-25
|
||||
Called from Logs.Tag.find in file "src/logs.ml", line 154, characters 14-32
|
||||
|
||||
From: Application
|
||||
Blame: Server
|
||||
Severity: Error
|
||||
|
||||
Client: 127.0.0.1:61988
|
||||
Client: 127.0.0.1:64687
|
||||
|
||||
GET /fail HTTP/1.1
|
||||
Host: localhost:8080
|
||||
Connection: keep-alive
|
||||
Upgrade-Insecure-Requests: 1
|
||||
User-Agent: Mozilla/5.0 [...snip...]
|
||||
Accept: text/html,application/xhtml+xml, [...snip...]
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
|
||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
|
||||
Sec-GPC: 1
|
||||
Sec-Fetch-Site: none
|
||||
Sec-Fetch-Mode: navigate
|
||||
Sec-Fetch-User: ?1
|
||||
Sec-Fetch-Dest: document
|
||||
Accept-Encoding: gzip, deflate, br
|
||||
Accept-Language: en-US;q=0.9,en;q=0.8
|
||||
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
|
||||
|
||||
dream.request_id.last_id: 2
|
||||
dream.client: 127.0.0.1:64687
|
||||
dream.tls: false
|
||||
dream.request_id: 3
|
||||
dream.params:
|
||||
```
|
||||
<!-- Get the request id in the list. -->
|
||||
|
||||
@ -76,7 +77,7 @@ As you can see, the report includes:
|
||||
- `Severity:` a suggested log level for the error,
|
||||
- `Client:` the client address,
|
||||
- request headers,
|
||||
- any request-scoped and application-scoped variables set in the request.
|
||||
- any other request variables.
|
||||
|
||||
<!-- TODO Link to the tutorial example on variables and also mention that they
|
||||
are advanced and usually internal. -->
|
||||
@ -91,8 +92,9 @@ work with in development.
|
||||
|
||||
<br>
|
||||
|
||||
Both the debugger's output and the non-debug error page are fully customizable
|
||||
— we will do this in the [very next example](../9-error#files)!
|
||||
You can have Dream show a custom error page with any information or graphics
|
||||
that you like — we will do this in the [very next
|
||||
example](../9-error#folders-and-files)!
|
||||
|
||||
<!-- TODO Fix after stack trace is fixed. -->
|
||||
<!-- TODO Show the log -->
|
||||
@ -102,9 +104,9 @@ Both the debugger's output and the non-debug error page are fully customizable
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`9-error`**](../9-error#files) handles all errors in one place, including
|
||||
- [**`9-error`**](../9-error#folders-and-files) handles all errors in one place, including
|
||||
displaying the debugger output.
|
||||
- [**`a-log`**](../a-log#files) shows [log
|
||||
- [**`a-log`**](../a-log#folders-and-files) shows [log
|
||||
levels](https://aantron.github.io/dream/#type-log_level) and
|
||||
[sub-logs](https://aantron.github.io/dream/#type-sub_log).
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
let () =
|
||||
Dream.run ~debug:true
|
||||
Dream.run ~error_handler:Dream.debug_error_handler
|
||||
@@ Dream.logger
|
||||
@@ Dream.router [
|
||||
|
||||
@ -12,4 +12,3 @@ let () =
|
||||
raise (Failure "The Web app failed!"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name debug)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./debug.exe"
|
||||
}
|
||||
}
|
14
example/9-error/9-error.opam
Normal file
14
example/9-error/9-error.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -10,28 +10,21 @@ an `error_handler` is to call
|
||||
[`Dream.error_template`](https://aantron.github.io/dream/#val-error_template):
|
||||
|
||||
```ocaml
|
||||
let my_error_template debug_info suggested_response =
|
||||
let my_error_template _error debug_info suggested_response =
|
||||
let status = Dream.status suggested_response in
|
||||
let code = Dream.status_to_int status
|
||||
and reason = Dream.status_to_string status in
|
||||
|
||||
suggested_response
|
||||
|> Dream.with_header "Content-Type" Dream.text_html
|
||||
|> Dream.with_body begin
|
||||
Dream.set_header suggested_response "Content-Type" Dream.text_html;
|
||||
Dream.set_body suggested_response begin
|
||||
<html>
|
||||
<body>
|
||||
<h1><%i code %> <%s reason %></h1>
|
||||
|
||||
% begin match debug_info with
|
||||
% | None -> ()
|
||||
% | Some debug_info ->
|
||||
<pre><%s debug_info %></pre>
|
||||
% end;
|
||||
|
||||
</body>
|
||||
</html>
|
||||
end
|
||||
|> Lwt.return
|
||||
end;
|
||||
Lwt.return suggested_response
|
||||
|
||||
let () =
|
||||
Dream.run ~error_handler:(Dream.error_template my_error_template)
|
||||
@ -40,22 +33,14 @@ let () =
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/9-error</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
|
||||
Try it in the [playground](http://dream.as/9-error).
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./error.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
We kept the error template simple for the sake of the example, but this is
|
||||
where you'd put in neat graphics to make a beautiful error page!
|
||||
|
||||
This app doesn't show debug information by default. However, try adding
|
||||
`~debug:true` to [`Dream.run`](https://aantron.github.io/dream/#val-run),
|
||||
rebuilding the app, and accessing it again. You will see the same kind of output
|
||||
as in example [**`8-debug`**](../8-debug#files), but now you control its
|
||||
placement and styling.
|
||||
|
||||
<br>
|
||||
|
||||
Dream will call the error template for every single error response it generates:
|
||||
@ -77,10 +62,8 @@ including return a completely new response.
|
||||
|
||||
<br>
|
||||
|
||||
`debug_info` is `None` by default. If you passed `~debug:true` to
|
||||
[`Dream.run`](https://aantron.github.io/dream/#val-run), it is `Some` of a
|
||||
string that contains the debug info that we saw in the previous example,
|
||||
[**`8-debug`**](../8-debug#files).
|
||||
`debug_info` is a multiline string containing the same information as in the
|
||||
previous example, [**`8-debug`**](../8-debug#folders-and-files).
|
||||
|
||||
<!-- TODO Images of the generated pages. -->
|
||||
|
||||
@ -88,17 +71,18 @@ string that contains the debug info that we saw in the previous example,
|
||||
|
||||
If you don't customize the error handler, Dream defaults to sending only empty
|
||||
responses, so that your application can be fully localization-friendly —
|
||||
even at the lowest levels. This is also in accordance with the [OWASP Error
|
||||
Handling Cheat
|
||||
even at the lowest levels. Rather than leaking hardcoded English strings, Dream
|
||||
relies on the user's browser to display its own built-in, localized error pages.
|
||||
This choice is also in accordance with the [OWASP Error Handling Cheat
|
||||
Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html).
|
||||
|
||||
<br>
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`a-log`**](../a-log#files) shows how to write messages to Dream's
|
||||
- [**`a-log`**](../a-log#folders-and-files) shows how to write messages to Dream's
|
||||
[log](https://aantron.github.io/dream/#logging).
|
||||
- [**`b-session`**](../b-session#files) adds [session
|
||||
- [**`b-session`**](../b-session#folders-and-files) adds [session
|
||||
management](https://aantron.github.io/dream/#sessions) for associating state
|
||||
with clients.
|
||||
|
||||
|
@ -6,5 +6,3 @@
|
||||
(targets error.ml)
|
||||
(deps error.eml.ml)
|
||||
(action (run dream_eml %{deps} --workspace %{workspace_root})))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,27 +1,20 @@
|
||||
let my_error_template debug_info suggested_response =
|
||||
let my_error_template _error debug_info suggested_response =
|
||||
let status = Dream.status suggested_response in
|
||||
let code = Dream.status_to_int status
|
||||
and reason = Dream.status_to_string status in
|
||||
|
||||
suggested_response
|
||||
|> Dream.with_header "Content-Type" Dream.text_html
|
||||
|> Dream.with_body begin
|
||||
Dream.set_header suggested_response "Content-Type" Dream.text_html;
|
||||
Dream.set_body suggested_response begin
|
||||
<html>
|
||||
<body>
|
||||
<h1><%i code %> <%s reason %></h1>
|
||||
|
||||
% begin match debug_info with
|
||||
% | None -> ()
|
||||
% | Some debug_info ->
|
||||
<pre><%s debug_info %></pre>
|
||||
% end;
|
||||
|
||||
</body>
|
||||
</html>
|
||||
end
|
||||
|> Lwt.return
|
||||
end;
|
||||
Lwt.return suggested_response
|
||||
|
||||
let () =
|
||||
Dream.run ~error_handler:(Dream.error_template my_error_template)
|
||||
@@ Dream.logger
|
||||
@@ Dream.not_found
|
||||
@@ fun _ -> Dream.empty `Not_Found
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./error.exe"
|
||||
}
|
||||
}
|
@ -2,46 +2,46 @@
|
||||
|
||||
Dream's first several examples make up a **tutorial**. Each example is a
|
||||
complete project with a helpful README, and plenty of links to next steps and
|
||||
documentation. You can begin at [**`1-hello`**](1-hello#files), or look in the
|
||||
documentation. You can begin at [**`1-hello`**](1-hello#folders-and-files), or look in the
|
||||
list below and jump to whatever interests you!
|
||||
|
||||
- [**`1-hello`**](1-hello/#files) — the simplest Dream server
|
||||
- [**`1-hello`**](1-hello#folders-and-files) — the simplest Dream server
|
||||
responds to every request with the same friendly message.
|
||||
- [**`2-middleware`**](2-middleware/#files) — adds the first
|
||||
- [**`2-middleware`**](2-middleware#folders-and-files) — adds the first
|
||||
Dream middleware: the *logger*.
|
||||
- [**`3-router`**](3-router/#files) — different handlers for
|
||||
- [**`3-router`**](3-router#folders-and-files) — different handlers for
|
||||
different paths.
|
||||
- [**`4-counter`**](4-counter/#files) — the first *custom*
|
||||
- [**`4-counter`**](4-counter#folders-and-files) — the first *custom*
|
||||
middleware!
|
||||
- [**`5-promise`**](5-promise/#files) — introduces Lwt, the
|
||||
- [**`5-promise`**](5-promise#folders-and-files) — introduces Lwt, the
|
||||
promise library used by Dream.
|
||||
- [**`6-echo`**](6-echo/#files) — reads request bodies.
|
||||
- [**`7-template`**](7-template/#files) — renders responses
|
||||
- [**`6-echo`**](6-echo#folders-and-files) — reads request bodies.
|
||||
- [**`7-template`**](7-template#folders-and-files) — renders responses
|
||||
from inline HTML templates and guards against XSS.
|
||||
- [**`8-debug`**](8-debug/#files) — includes detailed
|
||||
- [**`8-debug`**](8-debug#folders-and-files) — includes detailed
|
||||
information about errors in responses.
|
||||
- [**`9-error`**](9-error/#files) — customize all error
|
||||
- [**`9-error`**](9-error#folders-and-files) — customize all error
|
||||
responses in one place.
|
||||
- [**`a-log`**](a-log/#files) — writing messages to Dream's
|
||||
- [**`a-log`**](a-log#folders-and-files) — writing messages to Dream's
|
||||
log.
|
||||
- [**`b-session`**](b-session/#files) — associates state with
|
||||
- [**`b-session`**](b-session#folders-and-files) — associates state with
|
||||
client sessions.
|
||||
- [**`c-cookie`**](c-cookie/#files) — sets custom cookies.
|
||||
- [**`d-form`**](d-form#files) — reads forms with CSRF
|
||||
- [**`c-cookie`**](c-cookie#folders-and-files) — sets custom cookies.
|
||||
- [**`d-form`**](d-form#folders-and-files) — reads forms with CSRF
|
||||
prevention.
|
||||
- [**`e-json`**](e-json#files) — sends and receives JSON
|
||||
- [**`e-json`**](e-json#folders-and-files) — sends and receives JSON
|
||||
securely.
|
||||
- [**`f-static`**](f-static#files) — serves static files from
|
||||
- [**`f-static`**](f-static#folders-and-files) — serves static files from
|
||||
a local directory.
|
||||
- [**`g-upload`**](g-upload#files) — receives file uploads.
|
||||
- [**`h-sql`**](h-sql#files) — queries an SQL database.
|
||||
- [**`i-graphql`**](i-graphql#files) — serves a GraphQL
|
||||
- [**`g-upload`**](g-upload#folders-and-files) — receives file uploads.
|
||||
- [**`h-sql`**](h-sql#folders-and-files) — queries an SQL database.
|
||||
- [**`i-graphql`**](i-graphql#folders-and-files) — serves a GraphQL
|
||||
schema and GraphiQL.
|
||||
- [**`j-stream`**](j-stream#files) — streams request and
|
||||
- [**`j-stream`**](j-stream#folders-and-files) — streams request and
|
||||
response bodies.
|
||||
- [**`k-websocket`**](k-websocket#files) — opens a WebSocket
|
||||
- [**`k-websocket`**](k-websocket#folders-and-files) — opens a WebSocket
|
||||
between client and server.
|
||||
- [**`l-https`**](l-https#files) — enables HTTPS and HTTP/2
|
||||
- [**`l-https`**](l-https#folders-and-files) — enables HTTPS and HTTP/2
|
||||
upgrades.
|
||||
|
||||
That's it for the tutorial!
|
||||
@ -52,38 +52,43 @@ That's it for the tutorial!
|
||||
|
||||
There are several examples showing Dream with Reason syntax.
|
||||
|
||||
- [**`r-hello`**](r-hello#files) — the simplest Dream server.
|
||||
- [**`r-template`**](r-template#files) — renders HTML
|
||||
- [**`r-hello`**](r-hello#folders-and-files) — the simplest Dream server.
|
||||
- [**`r-template`**](r-template#folders-and-files) — renders HTML
|
||||
templates and protects against XSS.
|
||||
- [**`r-template-stream`**](r-template-stream#files) — streams
|
||||
- [**`r-template-files`**](r-template-files#folders-and-files) — templates
|
||||
in separate `.html` files for better editor support.
|
||||
- [**`r-template-logic`**](r-template-logic#folders-and-files) — control
|
||||
flow inside templates.
|
||||
- [**`r-template-stream`**](r-template-stream#folders-and-files) — streams
|
||||
templates as response bodies.
|
||||
- [**`r-tyxml`**](r-tyxml#files) — type-checked server-side
|
||||
- [**`r-tyxml`**](r-tyxml#folders-and-files) — type-checked server-side
|
||||
JSX templates.
|
||||
- [**`r-graphql`**](r-graphql#files) — serves a GraphQL
|
||||
- [**`r-graphql`**](r-graphql#folders-and-files) — serves a GraphQL
|
||||
schema.
|
||||
|
||||
<br>
|
||||
|
||||
# Full-stack
|
||||
|
||||
- [**`r-fullstack-melange`**](r-fullstack-melange#files) —
|
||||
- [**`r-fullstack-melange`**](r-fullstack-melange#folders-and-files) —
|
||||
server *and* client written in Reason!
|
||||
- [**`w-fullstack-rescript`**](w-fullstack-rescript#files) —
|
||||
- [**`w-fullstack-rescript`**](w-fullstack-rescript#folders-and-files) —
|
||||
shares OCaml code between server and client using ReScript.
|
||||
- [**`w-fullstack-jsoo`**](w-fullstack-jsoo#files) — shares
|
||||
- [**`w-fullstack-jsoo`**](w-fullstack-jsoo#folders-and-files) — shares
|
||||
OCaml code between server and client using js_of_ocaml.
|
||||
|
||||
<br>
|
||||
|
||||
# Deploying
|
||||
|
||||
- [**`z-heroku`**](z-heroku#files) — to
|
||||
- [**`z-heroku`**](z-heroku#folders-and-files) — to
|
||||
[Heroku](https://www.heroku.com).
|
||||
- [**`z-docker-esy`**](z-docker-esy#files) — on a server,
|
||||
- [**`z-fly`**](z-fly#folders-and-files) — to [Fly.io](https://fly.io/).
|
||||
- [**`z-docker-esy`**](z-docker-esy#folders-and-files) — on a server,
|
||||
using Docker, with package manager esy.
|
||||
- [**`z-docker-opam`**](z-docker-opam#files) — on a server,
|
||||
- [**`z-docker-opam`**](z-docker-opam#folders-and-files) — on a server,
|
||||
using Docker, with package manager opam.
|
||||
- [**`z-systemd`**](z-systemd#files) — on a server, as a
|
||||
- [**`z-systemd`**](z-systemd#folders-and-files) — on a server, as a
|
||||
systemd daemon.
|
||||
|
||||
<br>
|
||||
@ -97,35 +102,51 @@ if something is missing!
|
||||
|
||||
<br>
|
||||
|
||||
- [**`w-graphql-subscription`**](w-graphql-subscription#files)
|
||||
- [**`w-template-files`**](w-template-files#folders-and-files) — templates
|
||||
in separate `.html` files for better editor support.
|
||||
- [**`w-template-logic`**](w-template-logic#folders-and-files) — control
|
||||
flow inside templates.
|
||||
- [**`w-graphql-subscription`**](w-graphql-subscription#folders-and-files)
|
||||
— GraphQL subscriptions.
|
||||
- [**`w-esy`**](w-esy#files) — gives detail on packaging with
|
||||
- [**`w-postgres`**](w-postgres#folders-and-files) — connects to a
|
||||
PostgreSQL database.
|
||||
- [**`w-flash`**](w-flash#folders-and-files) — using flash messages, which
|
||||
are displayed on the next request.
|
||||
- [**`w-chat`**](w-chat#folders-and-files) — a chat room based on
|
||||
WebSockets.
|
||||
- [**`w-content-security-policy`**](w-content-security-policy#folders-and-files)
|
||||
— sandboxes Web pages using `Content-Security-Policy`.
|
||||
- [**`w-esy`**](w-esy#folders-and-files) — gives detail on packaging with
|
||||
[esy](https://esy.sh/), an npm-like package manager.
|
||||
- [**`w-one-binary`**](w-one-binary#files) — bakes static
|
||||
- [**`w-one-binary`**](w-one-binary#folders-and-files) — bakes static
|
||||
assets into a self-contained server binary.
|
||||
- [**`w-fswatch`**](w-fswatch#files) — sets up a development
|
||||
watcher using fswatch.
|
||||
- [**`w-live-reload`**](w-live-reload#files) — a simple
|
||||
- [**`w-watch`**](w-watch#folders-and-files) — sets up a development
|
||||
watcher.
|
||||
- [**`w-live-reload`**](w-live-reload#folders-and-files) — a simple
|
||||
live-reloading setup.
|
||||
- [**`w-tyxml`**](w-tyxml#files) — uses TyXML for type-checked
|
||||
- [**`w-nginx`**](w-nginx#folders-and-files) — uses nginx as a
|
||||
reverse proxy.
|
||||
- [**`w-tyxml`**](w-tyxml#folders-and-files) — uses TyXML for type-checked
|
||||
HTML templating.
|
||||
- [**`w-long-polling`**](w-long-polling#files) — old form of
|
||||
- [**`w-dream-html`**](../w-dream-html#folders-and-files) — uses
|
||||
dream-html for convenient HTML generation from OCaml.
|
||||
- [**`w-long-polling`**](w-long-polling#folders-and-files) — old form of
|
||||
asynchronous communication without WebSockets.
|
||||
- [**`w-query`**](w-query#files) — reads URL query parameters.
|
||||
- [**`w-server-sent-events`**](w-server-sent-events#files) —
|
||||
- [**`w-query`**](w-query#folders-and-files) — reads URL query parameters.
|
||||
- [**`w-server-sent-events`**](w-server-sent-events#folders-and-files) —
|
||||
[`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource),
|
||||
an older alternative to WebSockets.
|
||||
- [**`w-template-stream`**](w-template-stream#files) — sends
|
||||
- [**`w-template-stream`**](w-template-stream#folders-and-files) — sends
|
||||
templates asynchronously, one chunk at a time.
|
||||
- [**`w-upload-stream`**](w-upload-stream#files) — streams
|
||||
- [**`w-upload-stream`**](w-upload-stream#folders-and-files) — streams
|
||||
uploaded files.
|
||||
- [**`w-stress-response`**](w-stress-response#files) —
|
||||
- [**`w-stress-response`**](w-stress-response#folders-and-files) —
|
||||
benchmarks streaming very large responses.
|
||||
- [**`w-stress-websocket-send`**](w-stress-websocket-send#files)
|
||||
- [**`w-stress-websocket-send`**](w-stress-websocket-send#folders-and-files)
|
||||
— benchmarks sending WebSocket messages quickly.
|
||||
- [**`w-multipart-dump`**](w-multipart-dump#files) — echoes
|
||||
- [**`w-multipart-dump`**](w-multipart-dump#folders-and-files) — echoes
|
||||
`multipart/form-data` bodies for debugging.
|
||||
- [**`z-playground`**](z-playground#files) — source code of
|
||||
- [**`z-playground`**](z-playground#folders-and-files) — source code of
|
||||
the Dream playground.
|
||||
|
||||
<br>
|
||||
@ -148,7 +169,7 @@ Ideas:
|
||||
Basics:
|
||||
|
||||
- `w-content-negotiation`
|
||||
- [**`w-query`**](w-query#files) — done.
|
||||
- [**`w-query`**](w-query#folders-and-files) — done.
|
||||
- `w-scope` — for
|
||||
[`Dream.scope`](https://aantron.github.io/dream/#val-scope).
|
||||
- `w-subsite` — for
|
||||
@ -181,7 +202,6 @@ Techniques:
|
||||
- `w-graphql-sql`
|
||||
- `w-graphql-mutation`
|
||||
- `w-https-redirect`
|
||||
- `w-postgres-docker`
|
||||
- `w-sql-stream`
|
||||
- `w-websocket-stream`
|
||||
|
||||
|
@ -22,17 +22,15 @@ let () =
|
||||
raise (Failure "The Web app failed!"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/a-log</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./log.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
If you visit [http://localhost:8080](http://localhost:8080)
|
||||
[[playground](http://dream.as/a-log)] and then
|
||||
If you visit [http://localhost:8080](http://localhost:8080) and then
|
||||
[http://localhost:8080/fail](http://localhost:8080/fail), you will find these
|
||||
messages in the log, between the others:
|
||||
|
||||
@ -42,7 +40,7 @@ messages in the log, between the others:
|
||||
```
|
||||
|
||||
Note that this is on `stderr`. As you can see, the functions take
|
||||
[`Printf`-style format strings](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html),
|
||||
[`Printf`-style format strings](https://v2.ocaml.org/api/Printf.html),
|
||||
so you can quickly print values of various types to the log.
|
||||
|
||||
<br>
|
||||
@ -78,9 +76,9 @@ let () =
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`b-session`**](../b-session#files) returns Web development proper with
|
||||
- [**`b-session`**](../b-session#folders-and-files) returns Web development proper with
|
||||
session management.
|
||||
- [**`c-cookie`**](../c-cookie#files) shows cookie handling in Dream.
|
||||
- [**`c-cookie`**](../c-cookie#folders-and-files) shows cookie handling in Dream.
|
||||
|
||||
<br>
|
||||
|
||||
|
14
example/a-log/a-log.opam
Normal file
14
example/a-log/a-log.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name log)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./log.exe"
|
||||
}
|
||||
}
|
@ -14,4 +14,3 @@ let () =
|
||||
raise (Failure "The Web app failed!"));
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -11,10 +11,10 @@ let () =
|
||||
@@ Dream.memory_sessions
|
||||
@@ fun request ->
|
||||
|
||||
match Dream.session "user" request with
|
||||
match Dream.session_field request "user" with
|
||||
| None ->
|
||||
let%lwt () = Dream.invalidate_session request in
|
||||
let%lwt () = Dream.put_session "user" "alice" request in
|
||||
let%lwt () = Dream.set_session_field request "user" "alice" in
|
||||
Dream.html "You weren't logged in; but now you are!"
|
||||
|
||||
| Some username ->
|
||||
@ -23,13 +23,13 @@ let () =
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/b-session</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./session.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
The first time you access the app [[playground](http://dream.as/b-session)], it
|
||||
“logs you in” by saving you user name in a session. The session manager,
|
||||
The first time you access the app, it “logs you in” by saving you user name in
|
||||
a session. The session manager,
|
||||
[`Dream.memory_sessions`](https://aantron.github.io/dream/#val-memory_sessions),
|
||||
a middleware, adds a `dream.session` cookie to the response, containing the
|
||||
session key. The next time you access the app, the session is looked up again
|
||||
@ -62,13 +62,13 @@ There are two other session back ends, which are persistent:
|
||||
stores session data in encrypted cookies. That is, session data is stored on
|
||||
clients, rather than on the server. You can replace `Dream.memory_sessions`
|
||||
with `Dream.cookie_sessions` and it will work right away. However, if you
|
||||
want to be able to decrypt sessions set by previous runs of the server, pass
|
||||
`~secret:"my-secret"` to
|
||||
[`Dream.run`](https://aantron.github.io/dream/#val-run) so that it doesn't
|
||||
generate a random key each time.
|
||||
want to be able to decrypt sessions set by previous runs of the server, use
|
||||
the [`Dream.set_secret`](https://aantron.github.io/dream/#val-set_secret)
|
||||
middleware before `Dream.cookie_sessions`. If you don't, the server will be
|
||||
using a different random encryption key each time it starts.
|
||||
- [`Dream.sql_sessions`](https://aantron.github.io/dream/#val-sql_sessions)
|
||||
stores sessions in a database. It's used in example
|
||||
[**`h-sql`**](../h-sql#files).
|
||||
stores sessions in a database. It is shown in example
|
||||
[**`h-sql`**](../h-sql#folders-and-files).
|
||||
|
||||
<br>
|
||||
|
||||
@ -90,7 +90,7 @@ new session will, again, be an empty pre-session.
|
||||
It is best to use HTTPS when using sessions, to prevent session cookies from
|
||||
being easily observed by third parties. See
|
||||
[`Dream.run`](https://aantron.github.io/dream/#val-run) argument `~https`, and
|
||||
example [**`l-https`**](../l-https#files). If you redirect from HTTP to HTTPS,
|
||||
example [**`l-https`**](../l-https#folders-and-files). If you redirect from HTTP to HTTPS,
|
||||
do not issue sessions for HTTP requests. If you do, don't accept them later
|
||||
from HTTPS requests.
|
||||
|
||||
@ -100,8 +100,8 @@ from HTTPS requests.
|
||||
**Next steps:**
|
||||
|
||||
- Sessions already use cookies internally, but in
|
||||
[**`c-cookie`**](../c-cookie#files) we set cookies for our own purposes!
|
||||
- [**`d-form`**](../d-form#files) builds secure forms on top of sessions.
|
||||
[**`c-cookie`**](../c-cookie#folders-and-files) we set cookies for our own purposes!
|
||||
- [**`d-form`**](../d-form#folders-and-files) builds secure forms on top of sessions.
|
||||
|
||||
<br>
|
||||
|
||||
|
14
example/b-session/b-session.opam
Normal file
14
example/b-session/b-session.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -2,5 +2,3 @@
|
||||
(name session)
|
||||
(libraries dream)
|
||||
(preprocess (pps lwt_ppx)))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./session.exe"
|
||||
}
|
||||
}
|
@ -4,10 +4,10 @@ let () =
|
||||
@@ Dream.memory_sessions
|
||||
@@ fun request ->
|
||||
|
||||
match Dream.session "user" request with
|
||||
match Dream.session_field request "user" with
|
||||
| None ->
|
||||
let%lwt () = Dream.invalidate_session request in
|
||||
let%lwt () = Dream.put_session "user" "alice" request in
|
||||
let%lwt () = Dream.set_session_field request "user" "alice" in
|
||||
Dream.html "You weren't logged in; but now you are!"
|
||||
|
||||
| Some username ->
|
||||
|
@ -6,32 +6,32 @@ Let's [set our own cookie](https://aantron.github.io/dream/#cookies):
|
||||
|
||||
```ocaml
|
||||
let () =
|
||||
Dream.run ~secret:"foo"
|
||||
Dream.run
|
||||
@@ Dream.set_secret "foo"
|
||||
@@ Dream.logger
|
||||
@@ fun request ->
|
||||
|
||||
match Dream.cookie "ui.language" request with
|
||||
match Dream.cookie request "ui.language" with
|
||||
| Some value ->
|
||||
Printf.ksprintf
|
||||
Dream.html "Your preferred language is %s!" (Dream.html_escape value)
|
||||
|
||||
| None ->
|
||||
Dream.response "Set language preference; come again!"
|
||||
|> Dream.add_header "Content-Type" Dream.text_html
|
||||
|> Dream.set_cookie "ui.language" "ut-OP" request
|
||||
|> Lwt.return
|
||||
let response = Dream.response "Set language preference; come again!" in
|
||||
Dream.add_header response "Content-Type" Dream.text_html;
|
||||
Dream.set_cookie response request "ui.language" "ut-OP";
|
||||
Lwt.return response
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/c-cookie</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./cookie.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
The first time you access this app [[playground](http://dream.as/c-cookie)], it
|
||||
sets up a language preference, `ut-OP`. This string is sent to the client in a
|
||||
`ui.language` cookie. On the next request, the client sends it back. The app
|
||||
retrieves and displays it.
|
||||
The first time you access this app, it sets up a language preference, `ut-OP`.
|
||||
This string is sent to the client in a `ui.language` cookie. On the next
|
||||
request, the client sends it back. The app retrieves and displays it.
|
||||
|
||||
<br>
|
||||
|
||||
@ -42,7 +42,9 @@ That's because it access certain fields of the request to set some fairly
|
||||
aggressive security defaults:
|
||||
|
||||
- Cookie encryption, for which it accesses the encryption key. This is why we
|
||||
passed `~secret` to [`Dream.run`](https://aantron.github.io/dream/#val-run).
|
||||
used the
|
||||
[`Dream.set_secret`](https://aantron.github.io/dream/#val-set_secret)
|
||||
middleware.
|
||||
- Whether the request likely came through an HTTPS connection, to set the
|
||||
[`Secure`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
|
||||
attribute.
|
||||
@ -94,9 +96,9 @@ The easiest way to do that for general data is to use
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`d-form`**](../d-form#files) builds secure forms on top of sessions, and
|
||||
- [**`d-form`**](../d-form#folders-and-files) builds secure forms on top of sessions, and
|
||||
introduces automatic handling of CSRF tokens.
|
||||
- [**`e-json`**](../e-json#files) sends and receives JSON instead!
|
||||
- [**`e-json`**](../e-json#folders-and-files) sends and receives JSON instead!
|
||||
|
||||
<br>
|
||||
|
||||
|
14
example/c-cookie/c-cookie.opam
Normal file
14
example/c-cookie/c-cookie.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -1,15 +1,16 @@
|
||||
let () =
|
||||
Dream.run ~secret:"foo"
|
||||
Dream.run
|
||||
@@ Dream.set_secret "foo"
|
||||
@@ Dream.logger
|
||||
@@ fun request ->
|
||||
|
||||
match Dream.cookie "ui.language" request with
|
||||
match Dream.cookie request "ui.language" with
|
||||
| Some value ->
|
||||
Printf.ksprintf
|
||||
Dream.html "Your preferred language is %s!" (Dream.html_escape value)
|
||||
|
||||
| None ->
|
||||
Dream.response "Set language preference; come again!"
|
||||
|> Dream.add_header "Content-Type" Dream.text_html
|
||||
|> Dream.set_cookie "ui.language" "ut-OP" request
|
||||
|> Lwt.return
|
||||
let response = Dream.response "Set language preference; come again!" in
|
||||
Dream.add_header response "Content-Type" Dream.text_html;
|
||||
Dream.set_cookie response request "ui.language" "ut-OP";
|
||||
Lwt.return response
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name cookie)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./cookie.exe"
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
<br>
|
||||
|
||||
With the session middleware from example [**`b-session`**](../b-session#files),
|
||||
With the session middleware from example [**`b-session`**](../b-session#folders-and-files),
|
||||
we can build a [secure form](https://aantron.github.io/dream/#forms):
|
||||
|
||||
```ocaml
|
||||
@ -16,7 +16,8 @@ let show_form ?message request =
|
||||
<p>You entered: <b><%s message %>!</b></p>
|
||||
% end;
|
||||
|
||||
<%s! Dream.form_tag ~action:"/" request %>
|
||||
<form method="POST" action="/">
|
||||
<%s! Dream.csrf_tag request %>
|
||||
<input name="message" autofocus>
|
||||
</form>
|
||||
|
||||
@ -42,49 +43,47 @@ let () =
|
||||
Dream.empty `Bad_Request);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<pre><code><b>$ cd example/d-form</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
|
||||
Try it in the [playground](http://dream.as/d-form).
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./form.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
We didn't write a literal `<form>` tag in the template. Instead, we used
|
||||
[`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) to generate
|
||||
the tag. [`Dream.form_tag`](https://aantron.github.io/dream/#val-form_tag) also
|
||||
snuck in a hidden `<input>` field containing a CSRF token:
|
||||
The template adds a CSRF token to the form using
|
||||
[`Dream.csrf_tag`](https://aantron.github.io/dream/#val-csrf_tag). Its output
|
||||
looks something like this:
|
||||
|
||||
```html
|
||||
<form method="POST" action="/">
|
||||
<input name="dream.csrf" type="hidden" value="j8vjZ6...">
|
||||
|
||||
<!-- The rest we actually wrote ourselves in the template! -->
|
||||
<input name="message" autofocus>
|
||||
</form>
|
||||
```
|
||||
|
||||
This hidden `dream.csrf` field helps to
|
||||
[prevent CSRF attacks](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
|
||||
attacks against the form.
|
||||
That generated, hidden `dream.csrf` field helps to [prevent CSRF
|
||||
attacks](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html).
|
||||
It should be the [first
|
||||
field](https://portswigger.net/web-security/csrf/tokens#how-should-csrf-tokens-be-transmitted)
|
||||
in your form.
|
||||
|
||||
[`Dream.form`](https://aantron.github.io/dream/#val-form) expects `dream.csrf`
|
||||
and checks it. If there is anything wrong with the token,
|
||||
[`Dream.form`](https://aantron.github.io/dream/#val-form) will return a [value
|
||||
other than `` `Ok _``](https://aantron.github.io/dream/#type-form_result).
|
||||
When the form is submitted and parsed using
|
||||
[`Dream.form`](https://aantron.github.io/dream/#val-form), `Dream.form` expects
|
||||
to find the `dream.csrf` field, and checks it. If there is anything wrong with
|
||||
the CSRF token, [`Dream.form`](https://aantron.github.io/dream/#val-form) will
|
||||
return a [value other than
|
||||
`` `Ok _``](https://aantron.github.io/dream/#type-form_result).
|
||||
|
||||
<br>
|
||||
|
||||
The form fields carried inside `` `Ok _`` are returned in sorted order, so you
|
||||
can reliably pattern-match on them.
|
||||
|
||||
The bad token results, like `` `Expired _``, also carry the form fields. You can
|
||||
add handling for them to recover. For example, if you receive an expired form,
|
||||
you may want to resend it with some of the fields pre-filled to received
|
||||
values, so that the user can try again quickly.
|
||||
The bad token results, like `` `Expired (_, _)``, also carry the form fields.
|
||||
You can add handling for them to recover. For example, if you receive a form
|
||||
with an expired token, you may want to resend it with some of the fields pre-
|
||||
filled to received values, so that the user can try again quickly.
|
||||
|
||||
However, do not send back any sensitive data, because *any* result other than
|
||||
`` `Ok _`` *might* indicate an attack in progress. That said, `` `Expired _``
|
||||
@ -101,14 +100,14 @@ important on login forms and other sensitive pages.
|
||||
|
||||
However, this server is so simple that it doesn't store the data anywhere, and
|
||||
the data is not sensitive, so we took a shortcut. See
|
||||
[**`h-sql`**](../h-sql#files) for an example with a proper redirection.
|
||||
[**`h-sql`**](../h-sql#folders-and-files) for an example with a proper redirection.
|
||||
|
||||
<br>
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`e-json`**](../e-json#files) receives and sends JSON.
|
||||
- [**`f-static`**](../f-static#files) serves static files from a local
|
||||
- [**`e-json`**](../e-json#folders-and-files) receives and sends JSON.
|
||||
- [**`f-static`**](../f-static#folders-and-files) serves static files from a local
|
||||
directory.
|
||||
|
||||
<br>
|
||||
|
14
example/d-form/d-form.opam
Normal file
14
example/d-form/d-form.opam
Normal file
@ -0,0 +1,14 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -7,5 +7,3 @@
|
||||
(targets form.ml)
|
||||
(deps form.eml.ml)
|
||||
(action (run dream_eml %{deps} --workspace %{workspace_root})))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./form.exe"
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@ let show_form ?message request =
|
||||
<p>You entered: <b><%s message %>!</b></p>
|
||||
% end;
|
||||
|
||||
<%s! Dream.form_tag ~action:"/" request %>
|
||||
<form method="POST" action="/">
|
||||
<%s! Dream.csrf_tag request %>
|
||||
<input name="message" autofocus>
|
||||
</form>
|
||||
|
||||
@ -34,4 +35,3 @@ let () =
|
||||
Dream.empty `Bad_Request);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -10,6 +10,8 @@ converter between JSON and an OCaml data type. We then create a little server
|
||||
that listens for JSON of the right shape, and echoes back its `message` field:
|
||||
|
||||
```ocaml
|
||||
open Ppx_yojson_conv_lib.Yojson_conv.Primitives
|
||||
|
||||
type message_object = {
|
||||
message : string;
|
||||
} [@@deriving yojson]
|
||||
@ -17,7 +19,7 @@ type message_object = {
|
||||
let () =
|
||||
Dream.run
|
||||
@@ Dream.logger
|
||||
@@ Dream.origin_referer_check
|
||||
@@ Dream.origin_referrer_check
|
||||
@@ Dream.router [
|
||||
|
||||
Dream.post "/"
|
||||
@ -35,7 +37,6 @@ let () =
|
||||
|> Dream.json);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
To get this working, we have to add `ppx_yojson_conv` to our
|
||||
@ -48,24 +49,21 @@ To get this working, we have to add `ppx_yojson_conv` to our
|
||||
</code></pre>
|
||||
|
||||
and to
|
||||
[`esy.json`](https://github.com/aantron/dream/blob/master/example/e-json/esy.json):
|
||||
[`json.opam`](https://github.com/aantron/dream/blob/master/example/e-json/e-json.opam):
|
||||
|
||||
<pre><code>{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
<b>"@opam/ppx_yojson_conv": "*",</b>
|
||||
"ocaml": "4.12.x"
|
||||
}
|
||||
<pre><code>depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
<b>"ppx_yojson_conv"</b>
|
||||
]
|
||||
</code></pre>
|
||||
|
||||
The build commands, as always, are:
|
||||
|
||||
<pre><code><b>$ cd example/e-json</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
|
||||
You can try this example in the [playground](http://dream.as/e-json).
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./json.exe</b></code></pre>
|
||||
|
||||
<br>
|
||||
|
||||
@ -94,7 +92,7 @@ Content-Type: application/json
|
||||
|
||||
## Security
|
||||
|
||||
[`Dream.origin_referer_check`](https://aantron.github.io/dream/#val-origin_referer_check)
|
||||
[`Dream.origin_referrer_check`](https://aantron.github.io/dream/#val-origin_referrer_check)
|
||||
implements the
|
||||
[OWASP Verifying Origin With Standard Headers](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#verifying-origin-with-standard-headers)
|
||||
CSRF protection technique. It doesn't protect `GET` requests, so they shouldn't
|
||||
@ -114,9 +112,9 @@ requests!
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`f-static`**](../f-static#files) serves static files from the local
|
||||
- [**`f-static`**](../f-static#folders-and-files) serves static files from the local
|
||||
file system.
|
||||
- [**`g-upload`**](../g-upload#files) receives files from an upload form.
|
||||
- [**`g-upload`**](../g-upload#folders-and-files) receives files from an upload form.
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -2,5 +2,3 @@
|
||||
(name json)
|
||||
(libraries dream)
|
||||
(preprocess (pps lwt_ppx ppx_yojson_conv)))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
15
example/e-json/e-json.opam
Normal file
15
example/e-json/e-json.opam
Normal file
@ -0,0 +1,15 @@
|
||||
opam-version: "2.0"
|
||||
|
||||
depends: [
|
||||
"ocaml" {>= "4.08.0"}
|
||||
"dream"
|
||||
"dune" {>= "2.0.0"}
|
||||
"ppx_yojson_conv"
|
||||
]
|
||||
|
||||
synopsis: "One of the Dream examples"
|
||||
homepage: "https://github.com/aantron/dream"
|
||||
bug-reports: "https://github.com/aantron/dream/issues"
|
||||
author: "Anton Bachin <antonbachin@yahoo.com>"
|
||||
license: "MIT"
|
||||
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@opam/dream": "aantron/dream:dream.opam",
|
||||
"@opam/dune": "^2.0",
|
||||
"@opam/ppx_yojson_conv": "*",
|
||||
"ocaml": "4.12.x"
|
||||
},
|
||||
"resolutions": {
|
||||
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dune exec --root . ./json.exe"
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
open Ppx_yojson_conv_lib.Yojson_conv.Primitives
|
||||
|
||||
type message_object = {
|
||||
message : string;
|
||||
} [@@deriving yojson]
|
||||
@ -5,7 +7,7 @@ type message_object = {
|
||||
let () =
|
||||
Dream.run
|
||||
@@ Dream.logger
|
||||
@@ Dream.origin_referer_check
|
||||
@@ Dream.origin_referrer_check
|
||||
@@ Dream.router [
|
||||
|
||||
Dream.post "/"
|
||||
@ -23,4 +25,3 @@ let () =
|
||||
|> Dream.json);
|
||||
|
||||
]
|
||||
@@ Dream.not_found
|
||||
|
@ -5,8 +5,8 @@
|
||||
Run this example:
|
||||
|
||||
<pre><code><b>$ cd example/f-static</b>
|
||||
<b>$ npm install esy && npx esy</b>
|
||||
<b>$ npx esy start</b></code></pre>
|
||||
<b>$ opam install --deps-only --yes .</b>
|
||||
<b>$ dune exec --root . ./static.exe</b></code></pre>
|
||||
|
||||
...and visit
|
||||
[http://localhost:8080/static/static.ml](http://localhost:8080/static/static.ml).
|
||||
@ -20,7 +20,6 @@ let () =
|
||||
@@ Dream.router [
|
||||
Dream.get "/static/**" (Dream.static ".")
|
||||
]
|
||||
@@ Dream.not_found
|
||||
```
|
||||
|
||||
<br>
|
||||
@ -57,7 +56,7 @@ You can replace the file loading behavior of
|
||||
[crunch](https://github.com/mirage/ocaml-crunch) to compile a directory right
|
||||
into your Web app binary, and then serve that directory from memory with
|
||||
[`Dream.static`](https://aantron.github.io/dream/#val-static)! See example
|
||||
[**`w-one-binary`**](../w-one-binary#files).
|
||||
[**`w-one-binary`**](../w-one-binary#folders-and-files).
|
||||
|
||||
You can also use `~loader` to set arbitrary headers on the response.
|
||||
|
||||
@ -65,9 +64,9 @@ You can also use `~loader` to set arbitrary headers on the response.
|
||||
|
||||
**Next steps:**
|
||||
|
||||
- [**`g-upload`**](../g-upload#files) receives files instead of serving them.
|
||||
- [**`h-sql`**](../h-sql#files) runs SQL queries against a database.
|
||||
- [**`w-one-binary`**](../w-one-binary#files) bundles assets into a
|
||||
- [**`g-upload`**](../g-upload#folders-and-files) receives files instead of serving them.
|
||||
- [**`h-sql`**](../h-sql#folders-and-files) runs SQL queries against a database.
|
||||
- [**`w-one-binary`**](../w-one-binary#folders-and-files) bundles assets into a
|
||||
self-contained binary.
|
||||
|
||||
<br>
|
||||
|
@ -1,5 +1,3 @@
|
||||
(executable
|
||||
(name static)
|
||||
(libraries dream))
|
||||
|
||||
(data_only_dirs _esy esy.lock)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user