Compare commits

..

No commits in common. "master" and "1.0.0-alpha2" have entirely different histories.

483 changed files with 6841 additions and 19203 deletions

View File

@ -1,9 +1,9 @@
name: docker-esy
on: workflow_call
# push:
# paths:
# - 'example/z-docker-esy/**'
# - .github/workflows/docker-esy.yml
on:
push:
paths:
- 'example/z-docker-esy/**'
- .github/workflows/docker-esy.yml
jobs:
deploy:

View File

@ -1,9 +1,9 @@
name: docker-opam
on: workflow_call
# push:
# paths:
# - 'example/z-docker-opam/**'
# - .github/workflows/docker-opam.yml
on:
push:
paths:
- 'example/z-docker-opam/**'
- .github/workflows/docker-opam.yml
jobs:
deploy:

View File

@ -1,9 +1,9 @@
name: systemd
on: workflow_call
# push:
# paths:
# - 'example/z-systemd/**'
# - .github/workflows/systemd.yml
on:
push:
paths:
- 'example/z-systemd/**'
- .github/workflows/systemd.yml
jobs:
deploy:

View File

@ -7,71 +7,40 @@ jobs:
fail-fast: false
matrix:
os:
# Until https://github.com/ocaml/setup-ocaml/issues/872.
# When fixing, search for other instances of this string in this file.
- ubuntu-22.04
- 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.
ocaml:
- 5.2.x
- 4.14.x
- 4.12.0
include:
- os: macos-latest
ocaml: 4.14.x
- os: windows-latest
ocaml: 4.14.x
- os: ubuntu-latest
ocaml: 4.08.1
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
submodules: recursive
- uses: ocaml/setup-ocaml@v3
- uses: avsm/setup-ocaml@v1
with:
ocaml-compiler: ${{matrix.ocaml}}
dune-cache: true
ocaml-version: ${{matrix.ocaml}}
# 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: |
- run: opam depext --yes conf-libev
- run: opam install --yes --deps-only --with-test .
- run: opam exec -- dune runtest
- run: |
set -e
set -x
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)
EXAMPLES=$(find example -maxdepth 1 -type d | grep -v "^example/0" | grep -v "^example$" | sort)
shopt -s nullglob
for EXAMPLE in $EXAMPLES
do
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
FILE=$(ls $EXAMPLE/*.ml $EXAMPLE/*.re $EXAMPLE/server/*.ml $EXAMPLE/server/*.re)
EXE=$(echo $FILE | sed 's/\..*$/.exe/g')
echo dune build $EXE
opam exec -- dune build $EXE
@ -82,23 +51,12 @@ jobs:
fail-fast: false
matrix:
os:
- ubuntu-22.04
ocaml:
- 5.2.x
- 4.14.x
include:
- os: macos-latest
ocaml: 4.14.x
- ubuntu-latest
- macos-latest
runs-on: ${{matrix.os}}
steps:
- uses: ocaml/setup-ocaml@v3
with:
ocaml-compiler: ${{matrix.ocaml}}
dune-cache: true
- name: Run quickstart.sh
shell: bash
- name: Quick start
run: |
set -x
touch output
@ -112,33 +70,3 @@ 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
View File

@ -15,15 +15,10 @@ _esy/
esy.lock
# Release script
dream-*.gz
dream-*/
dream-*
# Bisect_ppx
_coverage/
# Humans
scratch/
# Editors
.vscode/
*.swp

12
.gitmodules vendored Normal file
View File

@ -0,0 +1,12 @@
[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

View File

@ -1,16 +0,0 @@
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

View File

@ -1,4 +1,4 @@
Copyright (c) 2021-2024, Anton Bachin
Copyright (c) 2021, 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

View File

@ -1,41 +1,31 @@
PACKAGES := dream-pure,dream-httpaf,dream
.PHONY : build
build :
@dune build --only-packages $(PACKAGES) --no-print-directory @install
@dune build --no-print-directory @install
.PHONY : watch
watch :
@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 .)
@dune build --no-print-directory -w
.PHONY : test
test :
@find $(ROOT) -name '*.coverage' | xargs rm -f
@find . -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 @$(TEST)/runtest
@dune build --no-print-directory -w --root . @test/runtest
.PHONY : coverage-serve
coverage-serve :
cd _coverage && dune exec -- dream-serve -p 8082
cd _coverage && dune exec -- serve -p 8082
.PHONY : promote
promote :
dune promote
dune promote --root .
@make --no-print-directory test
.PHONY : docs
@ -76,17 +66,7 @@ clean : clean-coverage
dune clean
dune clean --root .
make --no-print-directory -C docs/web clean
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
rm -rf src/graphiql/node_modules dream-* _release
.PHONY : utop
utop :
@ -102,25 +82,21 @@ todo-all :
VERSION := $(shell git describe --abbrev=0)
RELEASE := dream-$(VERSION)
FILES := \
src dream.opam dream-httpaf.opam dream-pure.opam dream-mirage.opam \
dune-project LICENSE.md README.md
FILES := src dream.opam dune-project LICENSE.md README.md
.PHONY : release
release : clean
rm -rf $(RELEASE) $(RELEASE).tar $(RELEASE).tar.gz _release
mkdir -p $(RELEASE)
mkdir $(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
@ -129,7 +105,6 @@ 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
@ -138,34 +113,20 @@ 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 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 pin add -y --no-action dream _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
.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
opam remove -y dream
opam pin remove -y dream
md5sum $(RELEASE).tar.gz
ls -l $(RELEASE).tar.gz
.PHONY : release-clean
release-clean :
rm -rf $(RELEASE) $(RELEASE).tar.gz _release

147
README.md
View File

@ -12,6 +12,7 @@ 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>
@ -46,8 +47,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**, **Heroku**, and
**Fly.io**, with sample CI scripts.
- [**Deployment**][deploy] instructions for **Digital Ocean** and **Heroku**,
with sample CI scripts.
<br>
@ -77,24 +78,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#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
[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
[handler]: https://aantron.github.io/dream/#type-handler
[routing]: https://github.com/aantron/dream/tree/master/example/3-router#folders-and-files
[routing]: https://github.com/aantron/dream/tree/master/example/3-router#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#folders-and-files
[sessions]: https://github.com/aantron/dream/tree/master/example/b-session#files
[back-ends]: https://aantron.github.io/dream/#back-ends
[errors]: https://github.com/aantron/dream/tree/master/example/9-error#folders-and-files
[errors]: https://github.com/aantron/dream/tree/master/example/9-error#files
[crypto]: https://aantron.github.io/dream/#cryptography
[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
[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
[types]: https://aantron.github.io/dream/#types
[basic-read]: https://aantron.github.io/dream/#val-body
[streaming]: https://aantron.github.io/dream/#streaming
@ -102,55 +103,62 @@ 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#folders-and-files
[one-binary]: https://github.com/aantron/dream/tree/master/example/w-one-binary#folders-and-files
[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
<br>
## Quick start
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:
<br>
<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` &mdash; 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 any of the [examples][tutorial], such as
[**`2-middleware`**][2-middleware], re-create the files locally, and run it:
After that, go to one of the examples, such as [**`1-hello`**][1-hello], and
build it:
```
dune exec ./middleware.exe
cd example/1-hello
dune exec --root . ./hello.exe
```
[esy-example]: https://github.com/aantron/dream/tree/master/example/w-esy#folders-and-files
### 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
[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#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`.
[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
<br>
@ -169,59 +177,18 @@ esy`, and started with `npx esy start`.
small-to-medium deployments.
- [**Examples**][examples] &mdash; These cover various HTTP scenarios.
- [**API reference**][api-main]
- [Watching][watch] and [live reloading][reload].
- [Watching][fswatch] 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#folders-and-files
[r-hello]: https://github.com/aantron/dream/tree/master/example/r-hello#folders-and-files
[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
[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
[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) &nbsp;&mdash;&nbsp;
command-line interface for Dream applications.
- [`dream-encoding`](https://github.com/tmattio/dream-encoding) &nbsp;&mdash;
&nbsp; compression middleware.
- [`dream-livereload`](https://github.com/tmattio/dream-livereload)
&nbsp;&mdash;&nbsp; live reloading.
- [`emile`](https://github.com/dinosaure/emile) &nbsp;&mdash;&nbsp; email
address syntax validation.
- [`letters`](https://github.com/oxidizing/letters) &nbsp;&mdash;&nbsp; SMTP
client.
<br>
## Example repositories
- [`dream-mail-example`](https://github.com/jsthomas/dream-email-example)
&nbsp;&mdash;&nbsp; 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)
&nbsp;&mdash;&nbsp; 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.
[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
<br>

View File

@ -5,11 +5,9 @@ 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](https://github.com/aantron/dream#example-repositories)
&mdash; different people benefit from different presentations!
- Links to projects that use Dream &mdash; to serve as large examples
- Links to [libraries](https://github.com/aantron/dream#recommended-projects)
to use with Dream
- Links to blogs &mdash; different people benefit from different presentations!
- Links to projects that use Dream &mdash; to serve as large examples.
- Links to libraries to use with Dream.
- And more!
<br>
@ -19,13 +17,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
```
opam list dream
npx esy ls-builds
```
or
```
npx esy ls-builds
opam list dream
```
<br>
@ -40,42 +38,35 @@ 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:
```
make deps
opam install --deps-only . --with-test
```
If you don't have an opam switch ready, first create one with
```
opam switch create . 4.14.1 --no-install
opam switch create . 4.12.0
```
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 exercise
coverage report in `_coverage/index.html` to see how much the tests exercies
your changes.
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
2. 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
@ -108,37 +99,6 @@ 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 &mdash; 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
@ -150,8 +110,8 @@ To build the docs, go to
make deps
```
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.
This will install npm packages and opam packages (some of which are pinned to
git commits).
After that, back in the project root,
@ -166,11 +126,3 @@ 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.

View File

@ -39,8 +39,8 @@ b {
<body>
<pre><code><b>$ cd example/2-middleware</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./middleware.exe</b>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</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: 80 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -69,6 +69,7 @@ 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: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,10 +1,9 @@
ROOT := ../..
ROOT := ../..
ODOC := odoc/default/_doc/_html
.PHONY : build
build :
dune build @doc --root $(ROOT) --no-print-directory --build-dir `pwd`/odoc \
--only-packages dream-pure,dream-httpaf,dream
dune build @doc --root $(ROOT) --no-print-directory --build-dir `pwd`/odoc
rm -f site/index.html
dune exec -- postprocess/index.exe \
$(ODOC)/dream/Dream/index.html site/index.html

View File

@ -26,6 +26,6 @@ lockfile.
Useful links:
- OCaml [*Syntax of documentation comments*](https://v2.ocaml.org/manual/ocamldoc.html#ss:ocamldoc-syntax)
- OCaml [*Syntax of documentation comments*](http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html#ss:ocamldoc-syntax)
- Lambda Soup [*Module Soup*](https://aantron.github.io/lambdasoup/)
- Soupault [*Tips and tricks*](https://soupault.app/tips-and-tricks/)

View File

@ -13,35 +13,8 @@ let if_expected expected test f =
f ()
else begin
Soup.write_file "actual" 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"
Printf.ksprintf failwith "Mismatch; wrote %s"
(Filename.concat (Sys.getcwd ()) "actual")
end
let add_backing_lines soup =

View File

@ -1,7 +1,7 @@
(executable
(executable
(name index)
(modules index)
(libraries common lambdasoup str))
(libraries common lambdasoup))
(library
(name common)

File diff suppressed because it is too large Load Diff

View File

@ -60,49 +60,6 @@
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;
@ -144,15 +101,15 @@ h6 {
/* Colors and presentation styles. */
body {
background-color: var(--bg-color);
color: var(--text-color);
background-color: #131618;
color: #c9d1d9;
}
.odoc-content pre {
background-color: var(--code-bg-color);
background-color: #1a1f26;
margin-left: 1em;
margin-right: 1em;
border: 1px solid var(--border-color);
border: 1px solid #111;
}
.odoc-content .spec > pre {
background: none;
@ -162,7 +119,7 @@ body {
.odoc-content code {
/* color: #ddd; */
background-color: var(--code-bg-color);
background-color: #2c333b;
padding: 0 5px;
margin: 0 1px;
white-space: nowrap;
@ -193,40 +150,17 @@ body {
} */
header {
background-color: var(--bg-color);
border-bottom: 1px solid var(--border-color);
background-color: #131618;
border-bottom: 1px solid #282828;
}
header .topmost {
/* background-color: #0f131a; */
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid #282828;
}
.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 */
h1 {
text-shadow: -2px 2px black;
}
header pre {
@ -257,7 +191,7 @@ footer {
}
:target .backing {
background-color: var(--target-backing-color);
background-color: #390022;
}
nav ~ * a[href="#builtin"],
@ -271,18 +205,18 @@ nav ~ * a[href="#templates"] {
font-style: italic;
}
header a[href*=example], .spec-doc a[href*=example] {
a[href*=example] {
font-weight: bold;
}
header a[href^=http]::after, .spec-doc a[href^=http]::after {
a[href^=http]::after {
content: "\f35d";
font-family: FontAwesome;
font-size: 10px;
line-height: 18px;
color: var(--external-link-color);
color: #5d7fcd;
position: relative;
top: -1px;
top: -0.5px;
margin-left: 2px;
margin-right: 3px;
}
@ -291,7 +225,7 @@ header a[href^=http]::after, .spec-doc a[href^=http]::after {
}
a, a:visited, a:active {
color: var(--link-color);
color: #8dc5ff;
text-decoration: none;
}
@ -300,27 +234,27 @@ a:hover {
}
.odoc-content a > code {
color: var(--link-color);
color: #8dc5ff;
}
.hljs-module-access, .hljs-keyword, .keyword {
color: var(--hljs-keyword-color);
color: #ff6c9b;
}
.hljs-identifier, .hljs-literal, .hljs-type {
color: var(--hljs-identifier-color);
color: #70df5c;
}
.hljs-tag {
color: var(--hljs-tag-color);
color: #c28eff;
}
.hljs-string {
color: var(--hljs-string-color);
color: #e3db7a;
}
.of {
color: var(--of-color);
color: #bec5cd;
}
.topmost ul {
@ -431,9 +365,10 @@ p + .odoc-spec {
}
#type-bigstring + .spec-doc li + li,
#val-next + .spec-doc li + li,
#val-origin_referrer_check + .spec-doc li + li,
#val-origin_referer_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 {
@ -472,7 +407,7 @@ ul ul li {
height: 100%;
width: 43rem;
/* background-color: #262626; */
border-right: 1px solid var(--border-color);
border-right: 1px solid #282828;
}
h2, h2 ~ :not(.odoc-spec):not(nav), footer {
@ -527,8 +462,8 @@ ul ul li {
margin-bottom: 24px;
}
#type-server + .spec-doc {
margin-top: -26px;
#type-outgoing + .spec-doc {
margin-top: -1.5rem;
margin-bottom: 48px;
}
#val-patch + .spec-doc {
@ -647,8 +582,8 @@ h2:first-of-type {
overflow-y: scroll;
scrollbar-width: none;
line-height: 30px;
border-right: 1px solid var(--border-color);
background-color: var(--bg-color);
border-right: 1px solid #262626;
background-color: #131618;
/* color: #ddd; */
}
.odoc-toc::-webkit-scrollbar {
@ -739,7 +674,7 @@ h2 > .anchor, h3 > .anchor {
font-family: FontAwesome;
font-size: 10px;
font-style: oblique;
color: var(--anchor-color);
color: #bfcdea;
position: relative;
top: -1.75px;
left: -4px;

View File

@ -4,7 +4,8 @@
// Copyright 2021 Anton Bachin *)
/* Scrolling */
console.log("foo");
function current_section() {
var threshold = window.innerHeight / 2;
@ -48,38 +49,3 @@ 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);

View File

@ -24,10 +24,6 @@
</head>
<body class="index">
<script>
init_theme();
</script>
<header>
<div class="topmost">
<div class="titles">
@ -36,15 +32,11 @@ init_theme();
</div>
<ul>
<li><code>1.0.0~alpha7</code></li>
<li><code>1.0.0~alpha2</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/edit/master/src/dream.mli">Edit these docs</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>
</ul>
<div class="toolbar">
<button class="toggle-theme-btn">&nbsp;</button>
</div>
</div>
<pre><span class="keyword">let</span> hello who =
@ -60,7 +52,8 @@ init_theme();
@@ <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></pre>
<span class="hljs-string">]</span>
@@ <span class="hljs-type">Dream</span>.not_found</pre>
<!-- Send TLS link to HTTPS example. -->
@ -123,13 +116,18 @@ init_theme();
<li>
A <a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example#readme">
Tutorial</a> &mdash; get started at
<a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example/1-hello#folders-and-files">
<a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/tree/master/example/1-hello#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>
@ -138,7 +136,7 @@ init_theme();
<div id="api"></div>
<footer>
Copyright © 2021-2024 Anton Bachin.
Copyright © 2021 Anton Bachin.
<br>
Released under the MIT license. See
<a href="https://github.com/aantron/dream/blob/master/LICENSE.md">

View File

@ -1,9 +1,11 @@
opam-version: "2.0"
synopsis: "Dream docs"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
depends: [
"lambdasoup"
"odoc" {= "2.0.2"}
"odoc"
"soupault" {>= "2.5.0"}
]
pin-depends: [
["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"]
]

View File

@ -1,78 +1,71 @@
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.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"}
"base64" {= "3.5.0"}
"bigarray-compat" {= "1.0.0"}
"cmdliner" {= "1.0.4"}
"conf-libev" {= "4-11"}
"containers" {= "3.3"}
"cppo" {= "1.6.7"}
"csexp" {= "1.5.1"}
"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"}
"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"}
"fpath" {= "0.7.3"}
"hex" {= "1.5.0"}
"integers" {= "0.7.0"}
"jingoo" {= "1.4.4"}
"jsonm" {= "1.0.2"}
"lambdasoup" {= "1.0.0"}
"hex" {= "1.4.0"}
"jingoo" {= "1.4.3"}
"jsonm" {= "1.0.1"}
"lambdasoup" {= "0.7.2"}
"logs" {= "0.7.0"}
"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"}
"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"}
"ocaml-config" {= "2"}
"ocaml-migrate-parsetree" {= "2.4.0"}
"ocaml-migrate-parsetree" {= "2.1.0"}
"ocaml-options-vanilla" {= "1"}
"ocamlbuild" {= "0.14.2"}
"ocamlfind" {= "1.9.6"}
"ocplib-endian" {= "1.2"}
"ocaml-syntax-shims" {= "1.0.0"}
"ocamlbuild" {= "0.14.0"}
"ocamlfind" {= "1.9.1"}
"ocplib-endian" {= "1.1"}
"odate" {= "0.6"}
"odoc" {= "2.0.2"}
"odoc-parser" {= "1.0.1"}
"otoml" {= "1.0.4"}
"odoc" {= "2.0.0~master"}
"ppx_derivers" {= "1.2.1"}
"ppx_deriving" {= "5.2.1"}
"ppxlib" {= "0.25.1"}
"re" {= "1.10.4"}
"ppxlib" {= "0.22.0"}
"re" {= "1.9.0"}
"result" {= "1.5"}
"rresult" {= "0.7.0"}
"seq" {= "base"}
"sexplib0" {= "v0.15.1"}
"soupault" {= "4.4.0"}
"spelll" {= "0.4"}
"sexplib0" {= "v0.14.0"}
"soupault" {= "2.5.0"}
"spelll" {= "0.3"}
"stdlib-shims" {= "0.3.0"}
"topkg" {= "1.0.7"}
"tsort" {= "2.1.0"}
"tyxml" {= "4.5.0"}
"stringext" {= "1.6.0"}
"toml" {= "7.0.0"}
"topkg" {= "1.0.3"}
"tsort" {= "2.0.0"}
"tyxml" {= "4.4.0"}
"uchar" {= "0.0.2"}
"uucp" {= "15.0.0"}
"uutf" {= "1.0.3"}
"yaml" {= "3.1.0"}
"uucp" {= "13.0.0"}
"uutf" {= "1.0.2"}
]
pin-depends: [
["odoc.2.0.0~master" "git+https://github.com/aantron/odoc.git#dbb37e20717985edfd8f734e00b9ab6f705d81a4"]
]
name: "web"
version: "~dev"
synopsis: "Dream docs"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
version: "dev"

View File

@ -1,34 +0,0 @@
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]
]

View File

@ -1,72 +0,0 @@
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]
]

View File

@ -1,35 +0,0 @@
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]
]

View File

@ -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 trivially possible to strip Dream down to just a
composable. It is trivailly 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,52 +48,65 @@ maintainer: "Anton Bachin <antonbachin@yahoo.com>"
depends: [
"base-unix"
"base64" {>= "3.1.0"} # Base64.encode_string.
"bigarray-compat"
"camlp-streams"
"caqti" {>= "2.0.0"}
"caqti-lwt" {>= "2.0.0"}
("conf-libev" {os != "win32"} | "ocaml" {os = "win32"})
"caqti" {>= "1.4.0"} # ~post_connect.
"caqti-lwt"
"conf-libev" {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"
"lambdasoup" {>= "0.6.1"}
"hmap"
"lwt"
"lwt_ppx" {>= "1.2.2"}
"lwt_ppx"
"lwt_ssl"
"logs" {>= "0.5.0"}
"magic-mime"
"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"
"mirage-crypto" {>= "0.8.1"} # AES-256-GCM.
"mirage-crypto-rng" {>= "0.8.0"} # Signature of initialize.
"multipart_form" {>= "0.3.0"}
"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}
"bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with.
"caqti-driver-postgresql" {with-test}
# Commented out because of https://github.com/ocaml-ppx/ppxlib/issues/221.
# "bisect_ppx" {with-test & >= "2.5.0"} # --instrument-with.
"caqti-driver-sqlite3" {with-test}
"crunch" {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.
"lambdasoup" {with-test}
"ppx_expect" {with-test}
"ppx_yojson_conv" {with-test}
"reason" {with-test}
"tyxml" {with-test & >= "4.5.0"}
"tyxml-jsx" {with-test}
"tyxml-jsx" {with-test & >= "4.5.0"}
"tyxml-ppx" {with-test & >= "4.5.0"}
]
build: [

View File

@ -1,14 +0,0 @@
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>"

View File

@ -12,25 +12,26 @@ let () =
<br>
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.
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.
<pre><code><b>$ cd example/1-hello</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./hello.exe</b>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</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!`.
course, see `Good morning, world!`. You can also try it in the [Dream
Playground](http://dream.as/1-hello).
<br>
If you'd like to copy out the server binary, you can do it like this:
<pre><code><b>$ cp _build/default/hello.exe .
<pre><code><b>$ npx esy cp '#{self.target_dir}/default/hello.exe' .
</b></code></pre>
The name will change as you go through the tutorial examples. It's always the
@ -40,17 +41,19 @@ name of the `.ml` file, but with `.ml` changed to `.exe`.
**Next steps:**
- The next example, [**`2-middleware`**](../2-middleware#folders-and-files), adds a logger
- The next example, [**`2-middleware`**](../2-middleware#files), adds a logger
to the app.
- [**`3-router`**](../3-router#folders-and-files) sends requests to different handlers,
- [**`3-router`**](../3-router#files) sends requests to different handlers,
depending on their path.
<br>
**See also:**
- [**`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.
- [**`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.
<br>

View File

@ -1,3 +1,5 @@
(executable
(name hello)
(libraries dream))
(data_only_dirs _esy esy.lock)

13
example/1-hello/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -1,14 +0,0 @@
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>"

View File

@ -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#folders-and-files) and wraps it in one of the most useful
[**`1-hello`**](../1-hello#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://v2.ocaml.org/api/Stdlib.html#VAL(@@)) for calling functions without parentheses. So, the [actual
[standard OCaml operator](https://caml.inria.fr/pub/docs/manual-ocaml/libref/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,14 +33,15 @@ let () =
<br>
When you run this server and visit
[http://localhost:8080](http://localhost:8080), you get much more interesting
[http://localhost:8080](http://localhost:8080)
[[playground](http://dream.as/2-middleware)], you get much more interesting
(and colorful!) output:
![Dream log example](https://raw.githubusercontent.com/aantron/dream/master/docs/asset/log-sanitized.png)
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#folders-and-files) for more logging options. Now that we have the
[**`a-log`**](../a-log#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 &mdash; it just makes it much easier to see what is going on.
@ -48,14 +49,14 @@ necessary &mdash; it just makes it much easier to see what is going on.
There's not much else to middlewares &mdash; they are really just functions
from handlers to handlers, so you can create them anywhere. Example
[**`4-counter`**](../4-counter#folders-and-files) already shows a simple custom middleware.
[**`4-counter`**](../4-counter#files) already shows a simple custom middleware.
<!--
There are also more complicated middlewares defined in
- [**`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).
- [**`m-locals`**](../m-locals/#files),
- [**`w-auto-reload`**](../w-auto-reload/#files), and
- [**`w-index-html`**](../w-index-html/#files).
-->
<!-- TODO Fill out this list; probably a-promise belongs here. -->
@ -64,10 +65,10 @@ There are also more complicated middlewares defined in
**Next steps:**
- The next example, [**`3-router`**](../3-router#folders-and-files), shows
- The next example, [**`3-router`**](../3-router#files), shows
[*routes*](https://aantron.github.io/dream/#routing), the other way to build
up handlers in Dream.
- [**`4-counter`**](../4-counter#folders-and-files) builds the first custom middleware.
- [**`4-counter`**](../4-counter#files) builds the first custom middleware.
<br>

View File

@ -1,3 +1,5 @@
(executable
(name middleware)
(libraries dream))
(data_only_dirs _esy esy.lock)

View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -1,14 +0,0 @@
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>"

View File

@ -20,42 +20,50 @@ let () =
Dream.get "/echo/:word"
(fun request ->
Dream.html (Dream.param request "word"));
Dream.html (Dream.param "word" request));
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/3-router</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./router.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</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`!
`foo`, and a request to `/echo/bar` gets `bar`! Try it in the
[playground](http://dream.as/3-router/echo/foo) &mdash; once the server loads,
edit the URL in the right pane to visit `/echo/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. -->
When none of the routes match, the router returns a `404 Not Found` response.
[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.
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#folders-and-files), we will decorate all error responses with
[**`9-error`**](../9-error#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#folders-and-files) forwards all requests with a certain
- [**`f-static`**](../f-static#files) forwards all requests with a certain
prefix to a static file handler.
<!-- - [**`w-scope`**](../w-scope#folders-and-files) applies middlewares to groups of routes
<!-- - [**`w-scope`**](../w-scope/#files) applies middlewares to groups of routes
&mdash; but only when they match.
- [**`w-subsite`**](../w-subsite#folders-and-files) attaches a handler as a complete,
- [**`w-subsite`**](../w-subsite/#files) attaches a handler as a complete,
nested sub-site, which might have its own router. -->
<!-- TODO -->
@ -63,9 +71,9 @@ The router can do more than match simple routes:
**Next steps:**
- [**`4-counter`**](../4-counter#folders-and-files) counts requests, and exposes a route for
- [**`4-counter`**](../4-counter#files) counts requests, and exposes a route for
getting the count.
- [**`5-promise`**](../5-promise#folders-and-files) introduces
- [**`5-promise`**](../5-promise#files) introduces
[Lwt](https://github.com/ocsigen/lwt), the promise library used by Dream.
<br>

View File

@ -1,3 +1,5 @@
(executable
(name router)
(libraries dream))
(data_only_dirs _esy esy.lock)

13
example/3-router/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -9,6 +9,7 @@ let () =
Dream.get "/echo/:word"
(fun request ->
Dream.html (Dream.param request "word"));
Dream.html (Dream.param "word" request));
]
@@ Dream.not_found

View File

@ -1,14 +0,0 @@
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>"

View File

@ -4,7 +4,8 @@
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/), in a sort of dashboard:
[http://localhost:8080/](http://localhost:8080/)
[[playground](http://dream.as/4-counter)], in a sort of dashboard:
```ocaml
let count = ref 0
@ -21,10 +22,11 @@ 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>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./counter.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
<br>
@ -37,16 +39,24 @@ 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#folders-and-files), does
used by Dream. The next example, [**`5-promise`**](../5-promise#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#folders-and-files) shows a middleware that awaits
- [**`5-promise`**](../5-promise#files) shows a middleware that awaits
responses using [Lwt](https://github.com/ocsigen/lwt).
- [**`6-echo`**](../6-echo#folders-and-files) responds to `POST` requests and reads their
- [**`6-echo`**](../6-echo#files) responds to `POST` requests and reads their
bodies.
<br>

View File

@ -12,3 +12,4 @@ let () =
Dream.get "/" (fun _ ->
Dream.html (Printf.sprintf "Saw %i request(s)!" !count));
]
@@ Dream.not_found

View File

@ -1,3 +1,5 @@
(executable
(name counter)
(libraries dream))
(data_only_dirs _esy esy.lock)

View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -1,14 +0,0 @@
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>"

View File

@ -2,7 +2,7 @@
<br>
[**`4-counter`**](../4-counter#folders-and-files) was limited to counting requests *before*
[**`4-counter`**](../4-counter#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,11 +38,14 @@ let () =
!successful !failed));
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/5-promise</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./promise.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
Try it in the [playground](http://dream.as/5-promise).
<br>
@ -91,8 +94,8 @@ We will stick to `let%lwt` in the examples and keep things tidy.
**Next steps:**
- [**`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
- [**`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
OCaml.
<br>

View File

@ -2,3 +2,5 @@
(name promise)
(libraries dream)
(preprocess (pps lwt_ppx)))
(data_only_dirs _esy esy.lock)

View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -27,3 +27,4 @@ let () =
!successful !failed));
]
@@ Dream.not_found

View File

@ -1,14 +0,0 @@
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>"

View File

@ -18,11 +18,14 @@ let () =
body);
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/6-echo</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./echo.exe</b></code></pre>
<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).
<br>
@ -46,11 +49,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#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.
- [**`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.
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
@ -62,9 +65,9 @@ present them the right way.
**Next steps:**
- [**`7-template`**](../7-template#folders-and-files) builds responses from templates and
- [**`7-template`**](../7-template#files) builds responses from templates and
guards against injection attacks (XSS).
- [**`8-debug`**](../8-debug#folders-and-files) renders error information in responses.
- [**`8-debug`**](../8-debug#files) renders error information in responses.
<br>

View File

@ -2,3 +2,5 @@
(name echo)
(libraries dream)
(preprocess (pps lwt_ppx)))
(data_only_dirs _esy esy.lock)

View File

@ -10,3 +10,4 @@ let () =
body);
]
@@ Dream.not_found

13
example/6-echo/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -1,14 +0,0 @@
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>"

View File

@ -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 a secure example, we will [weaken and then exploit
it](#security).
After looking at the correct example, we will
[weaken and then exploit it](#security).
```ocaml
let render param =
@ -23,16 +23,17 @@ let () =
Dream.get "/:word"
(fun request ->
Dream.param request "word"
Dream.param "word" request
|> render
|> Dream.html);
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/7-template</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./template.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
<br>
@ -54,7 +55,7 @@ file to run the template preprocessor:
<br>
The substitution, `<%s param %>`, uses
[`Printf` conversion specifications](https://v2.ocaml.org/api/Printf.html)
[`Printf` conversion specifications](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html)
from the standard library. So, you can do things like this:
- `<%i my_int %>` to print an OCaml `int`.
@ -84,11 +85,15 @@ 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. Then, visit
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
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!
@ -125,31 +130,23 @@ and not supported by Dream.
**Next steps:**
- [**`8-debug`**](../8-debug#folders-and-files) shows how to turn on debug responses, and
- [**`8-debug`**](../8-debug#files) shows how to turn on debug responses, and
get more info about errors.
- [**`9-error`**](../9-error#folders-and-files) sets up a central error template for all
- [**`9-error`**](../9-error#files) sets up a central error template for all
errors.
- [**`r-template`**](../r-template#folders-and-files) is a Reason syntax version of this
- [**`r-template`**](../r-template#files) is a Reason syntax version of this
example.
<br>
**See also:**
- [**`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
- [**`w-tyxml`**](../w-tyxml#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#folders-and-files) if you are using Reason. You can use TyXML
- [**`r-tyxml`**](../r-tyxml#files) if you are using Reason. You can use TyXML
with JSX syntax server-side!
- [**`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
- [**`w-template-stream`**](../w-template-stream#files) streams templates to
responses, instead of building up complete response strings.
<br>

View File

@ -6,3 +6,5 @@
(targets template.ml)
(deps template.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))
(data_only_dirs _esy esy.lock)

View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -12,8 +12,9 @@ let () =
Dream.get "/:word"
(fun request ->
Dream.param request "word"
Dream.param "word" request
|> render
|> Dream.html);
]
@@ Dream.not_found

View File

@ -1,14 +0,0 @@
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>"

View File

@ -2,12 +2,12 @@
<br>
Dream has a built-in error handler for showing debug information. You can enable
it by passing it to `Dream.run`:
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):
```ocaml
let () =
Dream.run ~error_handler:Dream.debug_error_handler
Dream.run ~debug:true
@@ Dream.logger
@@ Dream.router [
@ -20,50 +20,49 @@ let () =
raise (Failure "The Web app failed!"));
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/8-debug</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./debug.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</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) 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:
[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:
```
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
(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
From: Application
Blame: Server
Severity: Error
Client: 127.0.0.1:64687
Client: 127.0.0.1:61988
GET /fail HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
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
User-Agent: Mozilla/5.0 [...snip...]
Accept: text/html,application/xhtml+xml, [...snip...]
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,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
Accept-Language: en-US;q=0.9,en;q=0.8
dream.client: 127.0.0.1:64687
dream.tls: false
dream.request_id: 3
dream.params:
dream.request_id.last_id: 2
```
<!-- Get the request id in the list. -->
@ -77,7 +76,7 @@ As you can see, the report includes:
- `Severity:` a suggested log level for the error,
- `Client:` the client address,
- request headers,
- any other request variables.
- any request-scoped and application-scoped variables set in the request.
<!-- TODO Link to the tutorial example on variables and also mention that they
are advanced and usually internal. -->
@ -92,9 +91,8 @@ work with in development.
<br>
You can have Dream show a custom error page with any information or graphics
that you like &mdash; we will do this in the [very next
example](../9-error#folders-and-files)!
Both the debugger's output and the non-debug error page are fully customizable
&mdash; we will do this in the [very next example](../9-error#files)!
<!-- TODO Fix after stack trace is fixed. -->
<!-- TODO Show the log -->
@ -104,9 +102,9 @@ example](../9-error#folders-and-files)!
**Next steps:**
- [**`9-error`**](../9-error#folders-and-files) handles all errors in one place, including
- [**`9-error`**](../9-error#files) handles all errors in one place, including
displaying the debugger output.
- [**`a-log`**](../a-log#folders-and-files) shows [log
- [**`a-log`**](../a-log#files) shows [log
levels](https://aantron.github.io/dream/#type-log_level) and
[sub-logs](https://aantron.github.io/dream/#type-sub_log).

View File

@ -1,5 +1,5 @@
let () =
Dream.run ~error_handler:Dream.debug_error_handler
Dream.run ~debug:true
@@ Dream.logger
@@ Dream.router [
@ -12,3 +12,4 @@ let () =
raise (Failure "The Web app failed!"));
]
@@ Dream.not_found

View File

@ -1,3 +1,5 @@
(executable
(name debug)
(libraries dream))
(data_only_dirs _esy esy.lock)

13
example/8-debug/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -1,14 +0,0 @@
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>"

View File

@ -10,21 +10,28 @@ an `error_handler` is to call
[`Dream.error_template`](https://aantron.github.io/dream/#val-error_template):
```ocaml
let my_error_template _error debug_info suggested_response =
let my_error_template 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
Dream.set_header suggested_response "Content-Type" Dream.text_html;
Dream.set_body suggested_response begin
suggested_response
|> Dream.with_header "Content-Type" Dream.text_html
|> Dream.with_body 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 suggested_response
end
|> Lwt.return
let () =
Dream.run ~error_handler:(Dream.error_template my_error_template)
@ -33,14 +40,22 @@ let () =
```
<pre><code><b>$ cd example/9-error</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./error.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
Try it in the [playground](http://dream.as/9-error).
<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:
@ -62,8 +77,10 @@ including return a completely new response.
<br>
`debug_info` is a multiline string containing the same information as in the
previous example, [**`8-debug`**](../8-debug#folders-and-files).
`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).
<!-- TODO Images of the generated pages. -->
@ -71,18 +88,17 @@ previous example, [**`8-debug`**](../8-debug#folders-and-files).
If you don't customize the error handler, Dream defaults to sending only empty
responses, so that your application can be fully localization-friendly &mdash;
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
even at the lowest levels. This 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#folders-and-files) shows how to write messages to Dream's
- [**`a-log`**](../a-log#files) shows how to write messages to Dream's
[log](https://aantron.github.io/dream/#logging).
- [**`b-session`**](../b-session#folders-and-files) adds [session
- [**`b-session`**](../b-session#files) adds [session
management](https://aantron.github.io/dream/#sessions) for associating state
with clients.

View File

@ -6,3 +6,5 @@
(targets error.ml)
(deps error.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))
(data_only_dirs _esy esy.lock)

View File

@ -1,20 +1,27 @@
let my_error_template _error debug_info suggested_response =
let my_error_template 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
Dream.set_header suggested_response "Content-Type" Dream.text_html;
Dream.set_body suggested_response begin
suggested_response
|> Dream.with_header "Content-Type" Dream.text_html
|> Dream.with_body 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 suggested_response
end
|> Lwt.return
let () =
Dream.run ~error_handler:(Dream.error_template my_error_template)
@@ Dream.logger
@@ fun _ -> Dream.empty `Not_Found
@@ Dream.not_found

13
example/9-error/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -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#folders-and-files), or look in the
documentation. You can begin at [**`1-hello`**](1-hello#files), or look in the
list below and jump to whatever interests you!
- [**`1-hello`**](1-hello#folders-and-files) &nbsp;&mdash;&nbsp; the simplest Dream server
- [**`1-hello`**](1-hello/#files) &nbsp;&mdash;&nbsp; the simplest Dream server
responds to every request with the same friendly message.
- [**`2-middleware`**](2-middleware#folders-and-files) &nbsp;&mdash;&nbsp; adds the first
- [**`2-middleware`**](2-middleware/#files) &nbsp;&mdash;&nbsp; adds the first
Dream middleware: the *logger*.
- [**`3-router`**](3-router#folders-and-files) &nbsp;&mdash;&nbsp; different handlers for
- [**`3-router`**](3-router/#files) &nbsp;&mdash;&nbsp; different handlers for
different paths.
- [**`4-counter`**](4-counter#folders-and-files) &nbsp;&mdash;&nbsp; the first *custom*
- [**`4-counter`**](4-counter/#files) &nbsp;&mdash;&nbsp; the first *custom*
middleware!
- [**`5-promise`**](5-promise#folders-and-files) &nbsp;&mdash;&nbsp; introduces Lwt, the
- [**`5-promise`**](5-promise/#files) &nbsp;&mdash;&nbsp; introduces Lwt, the
promise library used by Dream.
- [**`6-echo`**](6-echo#folders-and-files) &nbsp;&mdash;&nbsp; reads request bodies.
- [**`7-template`**](7-template#folders-and-files) &nbsp;&mdash;&nbsp; renders responses
- [**`6-echo`**](6-echo/#files) &nbsp;&mdash;&nbsp; reads request bodies.
- [**`7-template`**](7-template/#files) &nbsp;&mdash;&nbsp; renders responses
from inline HTML templates and guards against XSS.
- [**`8-debug`**](8-debug#folders-and-files) &nbsp;&mdash;&nbsp; includes detailed
- [**`8-debug`**](8-debug/#files) &nbsp;&mdash;&nbsp; includes detailed
information about errors in responses.
- [**`9-error`**](9-error#folders-and-files) &nbsp;&mdash;&nbsp; customize all error
- [**`9-error`**](9-error/#files) &nbsp;&mdash;&nbsp; customize all error
responses in one place.
- [**`a-log`**](a-log#folders-and-files) &nbsp;&mdash;&nbsp; writing messages to Dream's
- [**`a-log`**](a-log/#files) &nbsp;&mdash;&nbsp; writing messages to Dream's
log.
- [**`b-session`**](b-session#folders-and-files) &nbsp;&mdash;&nbsp; associates state with
- [**`b-session`**](b-session/#files) &nbsp;&mdash;&nbsp; associates state with
client sessions.
- [**`c-cookie`**](c-cookie#folders-and-files) &nbsp;&mdash;&nbsp; sets custom cookies.
- [**`d-form`**](d-form#folders-and-files) &nbsp;&mdash;&nbsp; reads forms with CSRF
- [**`c-cookie`**](c-cookie/#files) &nbsp;&mdash;&nbsp; sets custom cookies.
- [**`d-form`**](d-form#files) &nbsp;&mdash;&nbsp; reads forms with CSRF
prevention.
- [**`e-json`**](e-json#folders-and-files) &nbsp;&mdash;&nbsp; sends and receives JSON
- [**`e-json`**](e-json#files) &nbsp;&mdash;&nbsp; sends and receives JSON
securely.
- [**`f-static`**](f-static#folders-and-files) &nbsp;&mdash;&nbsp; serves static files from
- [**`f-static`**](f-static#files) &nbsp;&mdash;&nbsp; serves static files from
a local directory.
- [**`g-upload`**](g-upload#folders-and-files) &nbsp;&mdash;&nbsp; receives file uploads.
- [**`h-sql`**](h-sql#folders-and-files) &nbsp;&mdash;&nbsp; queries an SQL database.
- [**`i-graphql`**](i-graphql#folders-and-files) &nbsp;&mdash;&nbsp; serves a GraphQL
- [**`g-upload`**](g-upload#files) &nbsp;&mdash;&nbsp; receives file uploads.
- [**`h-sql`**](h-sql#files) &nbsp;&mdash;&nbsp; queries an SQL database.
- [**`i-graphql`**](i-graphql#files) &nbsp;&mdash;&nbsp; serves a GraphQL
schema and GraphiQL.
- [**`j-stream`**](j-stream#folders-and-files) &nbsp;&mdash;&nbsp; streams request and
- [**`j-stream`**](j-stream#files) &nbsp;&mdash;&nbsp; streams request and
response bodies.
- [**`k-websocket`**](k-websocket#folders-and-files) &nbsp;&mdash;&nbsp; opens a WebSocket
- [**`k-websocket`**](k-websocket#files) &nbsp;&mdash;&nbsp; opens a WebSocket
between client and server.
- [**`l-https`**](l-https#folders-and-files) &nbsp;&mdash;&nbsp; enables HTTPS and HTTP/2
- [**`l-https`**](l-https#files) &nbsp;&mdash;&nbsp; enables HTTPS and HTTP/2
upgrades.
That's it for the tutorial!
@ -52,43 +52,38 @@ That's it for the tutorial!
There are several examples showing Dream with Reason syntax.
- [**`r-hello`**](r-hello#folders-and-files) &nbsp;&mdash;&nbsp; the simplest Dream server.
- [**`r-template`**](r-template#folders-and-files) &nbsp;&mdash;&nbsp; renders HTML
- [**`r-hello`**](r-hello#files) &nbsp;&mdash;&nbsp; the simplest Dream server.
- [**`r-template`**](r-template#files) &nbsp;&mdash;&nbsp; renders HTML
templates and protects against XSS.
- [**`r-template-files`**](r-template-files#folders-and-files) &nbsp;&mdash;&nbsp; templates
in separate `.html` files for better editor support.
- [**`r-template-logic`**](r-template-logic#folders-and-files) &nbsp;&mdash;&nbsp; control
flow inside templates.
- [**`r-template-stream`**](r-template-stream#folders-and-files) &nbsp;&mdash;&nbsp; streams
- [**`r-template-stream`**](r-template-stream#files) &nbsp;&mdash;&nbsp; streams
templates as response bodies.
- [**`r-tyxml`**](r-tyxml#folders-and-files) &nbsp;&mdash;&nbsp; type-checked server-side
- [**`r-tyxml`**](r-tyxml#files) &nbsp;&mdash;&nbsp; type-checked server-side
JSX templates.
- [**`r-graphql`**](r-graphql#folders-and-files) &nbsp;&mdash;&nbsp; serves a GraphQL
- [**`r-graphql`**](r-graphql#files) &nbsp;&mdash;&nbsp; serves a GraphQL
schema.
<br>
# Full-stack
- [**`r-fullstack-melange`**](r-fullstack-melange#folders-and-files) &nbsp;&mdash;&nbsp;
- [**`r-fullstack-melange`**](r-fullstack-melange#files) &nbsp;&mdash;&nbsp;
server *and* client written in Reason!
- [**`w-fullstack-rescript`**](w-fullstack-rescript#folders-and-files) &nbsp;&mdash;&nbsp;
- [**`w-fullstack-rescript`**](w-fullstack-rescript#files) &nbsp;&mdash;&nbsp;
shares OCaml code between server and client using ReScript.
- [**`w-fullstack-jsoo`**](w-fullstack-jsoo#folders-and-files) &nbsp;&mdash;&nbsp; shares
- [**`w-fullstack-jsoo`**](w-fullstack-jsoo#files) &nbsp;&mdash;&nbsp; shares
OCaml code between server and client using js_of_ocaml.
<br>
# Deploying
- [**`z-heroku`**](z-heroku#folders-and-files) &nbsp;&mdash;&nbsp; to
- [**`z-heroku`**](z-heroku#files) &nbsp;&mdash;&nbsp; to
[Heroku](https://www.heroku.com).
- [**`z-fly`**](z-fly#folders-and-files) &nbsp;&mdash;&nbsp; to [Fly.io](https://fly.io/).
- [**`z-docker-esy`**](z-docker-esy#folders-and-files) &nbsp;&mdash;&nbsp; on a server,
- [**`z-docker-esy`**](z-docker-esy#files) &nbsp;&mdash;&nbsp; on a server,
using Docker, with package manager esy.
- [**`z-docker-opam`**](z-docker-opam#folders-and-files) &nbsp;&mdash;&nbsp; on a server,
- [**`z-docker-opam`**](z-docker-opam#files) &nbsp;&mdash;&nbsp; on a server,
using Docker, with package manager opam.
- [**`z-systemd`**](z-systemd#folders-and-files) &nbsp;&mdash;&nbsp; on a server, as a
- [**`z-systemd`**](z-systemd#files) &nbsp;&mdash;&nbsp; on a server, as a
systemd daemon.
<br>
@ -102,51 +97,35 @@ if something is missing!
<br>
- [**`w-template-files`**](w-template-files#folders-and-files) &nbsp;&mdash;&nbsp; templates
in separate `.html` files for better editor support.
- [**`w-template-logic`**](w-template-logic#folders-and-files) &nbsp;&mdash;&nbsp; control
flow inside templates.
- [**`w-graphql-subscription`**](w-graphql-subscription#folders-and-files)
- [**`w-graphql-subscription`**](w-graphql-subscription#files)
&nbsp;&mdash;&nbsp; GraphQL subscriptions.
- [**`w-postgres`**](w-postgres#folders-and-files) &nbsp;&mdash;&nbsp; connects to a
PostgreSQL database.
- [**`w-flash`**](w-flash#folders-and-files) &nbsp;&mdash;&nbsp; using flash messages, which
are displayed on the next request.
- [**`w-chat`**](w-chat#folders-and-files) &nbsp;&mdash;&nbsp; a chat room based on
WebSockets.
- [**`w-content-security-policy`**](w-content-security-policy#folders-and-files)
&nbsp;&mdash;&nbsp; sandboxes Web pages using `Content-Security-Policy`.
- [**`w-esy`**](w-esy#folders-and-files) &nbsp;&mdash;&nbsp; gives detail on packaging with
- [**`w-esy`**](w-esy#files) &nbsp;&mdash;&nbsp; gives detail on packaging with
[esy](https://esy.sh/), an npm-like package manager.
- [**`w-one-binary`**](w-one-binary#folders-and-files) &nbsp;&mdash;&nbsp; bakes static
- [**`w-one-binary`**](w-one-binary#files) &nbsp;&mdash;&nbsp; bakes static
assets into a self-contained server binary.
- [**`w-watch`**](w-watch#folders-and-files) &nbsp;&mdash;&nbsp; sets up a development
watcher.
- [**`w-live-reload`**](w-live-reload#folders-and-files) &nbsp;&mdash;&nbsp; a simple
- [**`w-fswatch`**](w-fswatch#files) &nbsp;&mdash;&nbsp; sets up a development
watcher using fswatch.
- [**`w-live-reload`**](w-live-reload#files) &nbsp;&mdash;&nbsp; a simple
live-reloading setup.
- [**`w-nginx`**](w-nginx#folders-and-files) &nbsp;&mdash;&nbsp; uses nginx as a
reverse proxy.
- [**`w-tyxml`**](w-tyxml#folders-and-files) &nbsp;&mdash;&nbsp; uses TyXML for type-checked
- [**`w-tyxml`**](w-tyxml#files) &nbsp;&mdash;&nbsp; uses TyXML for type-checked
HTML templating.
- [**`w-dream-html`**](../w-dream-html#folders-and-files) &nbsp;&mdash;&nbsp; uses
dream-html for convenient HTML generation from OCaml.
- [**`w-long-polling`**](w-long-polling#folders-and-files) &nbsp;&mdash;&nbsp; old form of
- [**`w-long-polling`**](w-long-polling#files) &nbsp;&mdash;&nbsp; old form of
asynchronous communication without WebSockets.
- [**`w-query`**](w-query#folders-and-files) &nbsp;&mdash;&nbsp; reads URL query parameters.
- [**`w-server-sent-events`**](w-server-sent-events#folders-and-files) &nbsp;&mdash;&nbsp;
- [**`w-query`**](w-query#files) &nbsp;&mdash;&nbsp; reads URL query parameters.
- [**`w-server-sent-events`**](w-server-sent-events#files) &nbsp;&mdash;&nbsp;
[`EventSource`](https://developer.mozilla.org/en-US/docs/Web/API/EventSource),
an older alternative to WebSockets.
- [**`w-template-stream`**](w-template-stream#folders-and-files) &nbsp;&mdash;&nbsp; sends
- [**`w-template-stream`**](w-template-stream#files) &nbsp;&mdash;&nbsp; sends
templates asynchronously, one chunk at a time.
- [**`w-upload-stream`**](w-upload-stream#folders-and-files) &nbsp;&mdash;&nbsp; streams
- [**`w-upload-stream`**](w-upload-stream#files) &nbsp;&mdash;&nbsp; streams
uploaded files.
- [**`w-stress-response`**](w-stress-response#folders-and-files) &nbsp;&mdash;&nbsp;
- [**`w-stress-response`**](w-stress-response#files) &nbsp;&mdash;&nbsp;
benchmarks streaming very large responses.
- [**`w-stress-websocket-send`**](w-stress-websocket-send#folders-and-files)
- [**`w-stress-websocket-send`**](w-stress-websocket-send#files)
&nbsp;&mdash;&nbsp; benchmarks sending WebSocket messages quickly.
- [**`w-multipart-dump`**](w-multipart-dump#folders-and-files) &nbsp;&mdash;&nbsp; echoes
- [**`w-multipart-dump`**](w-multipart-dump#files) &nbsp;&mdash;&nbsp; echoes
`multipart/form-data` bodies for debugging.
- [**`z-playground`**](z-playground#folders-and-files) &nbsp;&mdash;&nbsp; source code of
- [**`z-playground`**](z-playground#files) &nbsp;&mdash;&nbsp; source code of
the Dream playground.
<br>
@ -169,7 +148,7 @@ Ideas:
Basics:
- `w-content-negotiation`
- [**`w-query`**](w-query#folders-and-files) &nbsp;&mdash;&nbsp; done.
- [**`w-query`**](w-query#files) &nbsp;&mdash;&nbsp; done.
- `w-scope` &nbsp;&mdash;&nbsp; for
[`Dream.scope`](https://aantron.github.io/dream/#val-scope).
- `w-subsite` &nbsp;&mdash;&nbsp; for
@ -202,6 +181,7 @@ Techniques:
- `w-graphql-sql`
- `w-graphql-mutation`
- `w-https-redirect`
- `w-postgres-docker`
- `w-sql-stream`
- `w-websocket-stream`

View File

@ -22,15 +22,17 @@ let () =
raise (Failure "The Web app failed!"));
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/a-log</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./log.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
<br>
If you visit [http://localhost:8080](http://localhost:8080) and then
If you visit [http://localhost:8080](http://localhost:8080)
[[playground](http://dream.as/a-log)] and then
[http://localhost:8080/fail](http://localhost:8080/fail), you will find these
messages in the log, between the others:
@ -40,7 +42,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://v2.ocaml.org/api/Printf.html),
[`Printf`-style format strings](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html),
so you can quickly print values of various types to the log.
<br>
@ -76,9 +78,9 @@ let () =
**Next steps:**
- [**`b-session`**](../b-session#folders-and-files) returns Web development proper with
- [**`b-session`**](../b-session#files) returns Web development proper with
session management.
- [**`c-cookie`**](../c-cookie#folders-and-files) shows cookie handling in Dream.
- [**`c-cookie`**](../c-cookie#files) shows cookie handling in Dream.
<br>

View File

@ -1,14 +0,0 @@
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>"

View File

@ -1,3 +1,5 @@
(executable
(name log)
(libraries dream))
(data_only_dirs _esy esy.lock)

13
example/a-log/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -14,3 +14,4 @@ let () =
raise (Failure "The Web app failed!"));
]
@@ Dream.not_found

View File

@ -11,10 +11,10 @@ let () =
@@ Dream.memory_sessions
@@ fun request ->
match Dream.session_field request "user" with
match Dream.session "user" request with
| None ->
let%lwt () = Dream.invalidate_session request in
let%lwt () = Dream.set_session_field request "user" "alice" in
let%lwt () = Dream.put_session "user" "alice" request 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>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./session.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
<br>
The first time you access the app, it “logs you in” by saving you user name in
a session. The session manager,
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,
[`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, 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.
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.
- [`Dream.sql_sessions`](https://aantron.github.io/dream/#val-sql_sessions)
stores sessions in a database. It is shown in example
[**`h-sql`**](../h-sql#folders-and-files).
stores sessions in a database. It's used in example
[**`h-sql`**](../h-sql#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#folders-and-files). If you redirect from HTTP to HTTPS,
example [**`l-https`**](../l-https#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#folders-and-files) we set cookies for our own purposes!
- [**`d-form`**](../d-form#folders-and-files) builds secure forms on top of sessions.
[**`c-cookie`**](../c-cookie#files) we set cookies for our own purposes!
- [**`d-form`**](../d-form#files) builds secure forms on top of sessions.
<br>

View File

@ -1,14 +0,0 @@
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>"

View File

@ -2,3 +2,5 @@
(name session)
(libraries dream)
(preprocess (pps lwt_ppx)))
(data_only_dirs _esy esy.lock)

View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -4,10 +4,10 @@ let () =
@@ Dream.memory_sessions
@@ fun request ->
match Dream.session_field request "user" with
match Dream.session "user" request with
| None ->
let%lwt () = Dream.invalidate_session request in
let%lwt () = Dream.set_session_field request "user" "alice" in
let%lwt () = Dream.put_session "user" "alice" request in
Dream.html "You weren't logged in; but now you are!"
| Some username ->

View File

@ -6,32 +6,32 @@ Let's [set our own cookie](https://aantron.github.io/dream/#cookies):
```ocaml
let () =
Dream.run
@@ Dream.set_secret "foo"
Dream.run ~secret:"foo"
@@ Dream.logger
@@ fun request ->
match Dream.cookie request "ui.language" with
match Dream.cookie "ui.language" request with
| Some value ->
Printf.ksprintf
Dream.html "Your preferred language is %s!" (Dream.html_escape value)
| None ->
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
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
```
<pre><code><b>$ cd example/c-cookie</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./cookie.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
<br>
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.
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.
<br>
@ -42,9 +42,7 @@ 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
used the
[`Dream.set_secret`](https://aantron.github.io/dream/#val-set_secret)
middleware.
passed `~secret` to [`Dream.run`](https://aantron.github.io/dream/#val-run).
- 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.
@ -96,9 +94,9 @@ The easiest way to do that for general data is to use
**Next steps:**
- [**`d-form`**](../d-form#folders-and-files) builds secure forms on top of sessions, and
- [**`d-form`**](../d-form#files) builds secure forms on top of sessions, and
introduces automatic handling of CSRF tokens.
- [**`e-json`**](../e-json#folders-and-files) sends and receives JSON instead!
- [**`e-json`**](../e-json#files) sends and receives JSON instead!
<br>

View File

@ -1,14 +0,0 @@
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>"

View File

@ -1,16 +1,15 @@
let () =
Dream.run
@@ Dream.set_secret "foo"
Dream.run ~secret:"foo"
@@ Dream.logger
@@ fun request ->
match Dream.cookie request "ui.language" with
match Dream.cookie "ui.language" request with
| Some value ->
Printf.ksprintf
Dream.html "Your preferred language is %s!" (Dream.html_escape value)
| None ->
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
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

View File

@ -1,3 +1,5 @@
(executable
(name cookie)
(libraries dream))
(data_only_dirs _esy esy.lock)

13
example/c-cookie/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -2,7 +2,7 @@
<br>
With the session middleware from example [**`b-session`**](../b-session#folders-and-files),
With the session middleware from example [**`b-session`**](../b-session#files),
we can build a [secure form](https://aantron.github.io/dream/#forms):
```ocaml
@ -16,8 +16,7 @@ let show_form ?message request =
<p>You entered: <b><%s message %>!</b></p>
% end;
<form method="POST" action="/">
<%s! Dream.csrf_tag request %>
<%s! Dream.form_tag ~action:"/" request %>
<input name="message" autofocus>
</form>
@ -43,47 +42,49 @@ let () =
Dream.empty `Bad_Request);
]
@@ Dream.not_found
```
<pre><code><b>$ cd example/d-form</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./form.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
Try it in the [playground](http://dream.as/d-form).
<br>
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:
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:
```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>
```
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.
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.
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).
[`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).
<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 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.
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.
However, do not send back any sensitive data, because *any* result other than
`` `Ok _`` *might* indicate an attack in progress. That said, `` `Expired _``
@ -100,14 +101,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#folders-and-files) for an example with a proper redirection.
[**`h-sql`**](../h-sql#files) for an example with a proper redirection.
<br>
**Next steps:**
- [**`e-json`**](../e-json#folders-and-files) receives and sends JSON.
- [**`f-static`**](../f-static#folders-and-files) serves static files from a local
- [**`e-json`**](../e-json#files) receives and sends JSON.
- [**`f-static`**](../f-static#files) serves static files from a local
directory.
<br>

View File

@ -1,14 +0,0 @@
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>"

View File

@ -7,3 +7,5 @@
(targets form.ml)
(deps form.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))
(data_only_dirs _esy esy.lock)

13
example/d-form/esy.json Normal file
View File

@ -0,0 +1,13 @@
{
"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"
}
}

View File

@ -8,8 +8,7 @@ let show_form ?message request =
<p>You entered: <b><%s message %>!</b></p>
% end;
<form method="POST" action="/">
<%s! Dream.csrf_tag request %>
<%s! Dream.form_tag ~action:"/" request %>
<input name="message" autofocus>
</form>
@ -35,3 +34,4 @@ let () =
Dream.empty `Bad_Request);
]
@@ Dream.not_found

View File

@ -10,8 +10,6 @@ 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]
@ -19,7 +17,7 @@ type message_object = {
let () =
Dream.run
@@ Dream.logger
@@ Dream.origin_referrer_check
@@ Dream.origin_referer_check
@@ Dream.router [
Dream.post "/"
@ -37,6 +35,7 @@ let () =
|> Dream.json);
]
@@ Dream.not_found
```
To get this working, we have to add `ppx_yojson_conv` to our
@ -49,21 +48,24 @@ To get this working, we have to add `ppx_yojson_conv` to our
</code></pre>
and to
[`json.opam`](https://github.com/aantron/dream/blob/master/example/e-json/e-json.opam):
[`esy.json`](https://github.com/aantron/dream/blob/master/example/e-json/esy.json):
<pre><code>depends: [
"ocaml" {>= "4.08.0"}
"dream"
"dune" {>= "2.0.0"}
<b>"ppx_yojson_conv"</b>
]
<pre><code>{
"dependencies": {
"@opam/dream": "aantron/dream:dream.opam",
"@opam/dune": "^2.0",
<b>"@opam/ppx_yojson_conv": "*",</b>
"ocaml": "4.12.x"
}
</code></pre>
The build commands, as always, are:
<pre><code><b>$ cd example/e-json</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./json.exe</b></code></pre>
<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).
<br>
@ -92,7 +94,7 @@ Content-Type: application/json
## Security
[`Dream.origin_referrer_check`](https://aantron.github.io/dream/#val-origin_referrer_check)
[`Dream.origin_referer_check`](https://aantron.github.io/dream/#val-origin_referer_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
@ -112,9 +114,9 @@ requests!
**Next steps:**
- [**`f-static`**](../f-static#folders-and-files) serves static files from the local
- [**`f-static`**](../f-static#files) serves static files from the local
file system.
- [**`g-upload`**](../g-upload#folders-and-files) receives files from an upload form.
- [**`g-upload`**](../g-upload#files) receives files from an upload form.
<br>

View File

@ -2,3 +2,5 @@
(name json)
(libraries dream)
(preprocess (pps lwt_ppx ppx_yojson_conv)))
(data_only_dirs _esy esy.lock)

View File

@ -1,15 +0,0 @@
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>"

14
example/e-json/esy.json Normal file
View File

@ -0,0 +1,14 @@
{
"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"
}
}

View File

@ -1,5 +1,3 @@
open Ppx_yojson_conv_lib.Yojson_conv.Primitives
type message_object = {
message : string;
} [@@deriving yojson]
@ -7,7 +5,7 @@ type message_object = {
let () =
Dream.run
@@ Dream.logger
@@ Dream.origin_referrer_check
@@ Dream.origin_referer_check
@@ Dream.router [
Dream.post "/"
@ -25,3 +23,4 @@ let () =
|> Dream.json);
]
@@ Dream.not_found

View File

@ -5,8 +5,8 @@
Run this example:
<pre><code><b>$ cd example/f-static</b>
<b>$ opam install --deps-only --yes .</b>
<b>$ dune exec --root . ./static.exe</b></code></pre>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>
...and visit
[http://localhost:8080/static/static.ml](http://localhost:8080/static/static.ml).
@ -20,6 +20,7 @@ let () =
@@ Dream.router [
Dream.get "/static/**" (Dream.static ".")
]
@@ Dream.not_found
```
<br>
@ -56,7 +57,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#folders-and-files).
[**`w-one-binary`**](../w-one-binary#files).
You can also use `~loader` to set arbitrary headers on the response.
@ -64,9 +65,9 @@ You can also use `~loader` to set arbitrary headers on the response.
**Next steps:**
- [**`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
- [**`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
self-contained binary.
<br>

View File

@ -1,3 +1,5 @@
(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