diff --git a/Makefile b/Makefile index fe0f7ad4..496d9e5f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ .PHONY : build + +default: + @dune build + build : @dune build -p dream-pure,dream-httpaf,dream --no-print-directory @install diff --git a/dream-httpaf.opam b/dream-httpaf.opam index 443479ee..12bb4683 100644 --- a/dream-httpaf.opam +++ b/dream-httpaf.opam @@ -16,9 +16,7 @@ depends: [ "dune" {>= "2.7.0"} # --instrument-with. "lwt" "lwt_ppx" {>= "1.2.2"} - "lwt_ssl" "ocaml" {>= "4.08.0"} - "ssl" {>= "0.5.8"} # Ssl.get_negotiated_alpn_protocol. # Currently vendored. # "gluten" diff --git a/dream-pure.opam b/dream-pure.opam index 6f6b7f26..b3a44e28 100644 --- a/dream-pure.opam +++ b/dream-pure.opam @@ -22,6 +22,7 @@ depends: [ "ocaml" {>= "4.08.0"} "ptime" {>= "0.8.1"} # Ptime.weekday. "uri" {>= "4.2.0"} + "eio" {>= "0.2"} # Testing, development. "alcotest" {with-test} diff --git a/dream.opam b/dream.opam index 198b9bcd..c9f79833 100644 --- a/dream.opam +++ b/dream.opam @@ -61,7 +61,6 @@ depends: [ "graphql-lwt" "lwt" "lwt_ppx" {>= "1.2.2"} - "lwt_ssl" "logs" {>= "0.5.0"} "magic-mime" "mirage-clock" @@ -70,9 +69,10 @@ depends: [ "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" # ... + "eio_main" {>= "0.2"} + "lwt_eio" {>= "0.1"} # Testing, development. "alcotest" {with-test} diff --git a/example/1-hello/README.md b/example/1-hello/README.md index 64ef42f6..542bba40 100644 --- a/example/1-hello/README.md +++ b/example/1-hello/README.md @@ -6,8 +6,10 @@ This project is so simple that it doesn't even log requests! ```ocaml let () = - Dream.run (fun _ -> - Dream.html "Good morning, world!") + Eio_main.run (fun env -> + Dream.run env (fun _ -> + Dream.html "Good morning, world!") + ) ```
@@ -39,6 +41,11 @@ name of the `.ml` file, but with `.ml` changed to `.exe`.
+A Dream server runs in an [Eio](https://github.com/ocaml-multicore/eio) event loop, +which is created by `Eio_main.run`. + +
+ **Next steps:** - The next example, [**`2-middleware`**](../2-middleware#files), adds a logger diff --git a/example/1-hello/hello.ml b/example/1-hello/hello.ml index 5411c9ee..83de636c 100644 --- a/example/1-hello/hello.ml +++ b/example/1-hello/hello.ml @@ -1,3 +1,5 @@ let () = - Dream.run (fun _ -> - Dream.html "Good morning, world!") + Eio_main.run (fun env -> + Dream.run env (fun _ -> + Dream.html "Good morning, world!") + ) diff --git a/example/2-middleware/README.md b/example/2-middleware/README.md index 5ec6b67e..3949861e 100644 --- a/example/2-middleware/README.md +++ b/example/2-middleware/README.md @@ -9,9 +9,10 @@ middlewares, the [*logger*](https://aantron.github.io/dream/#val-logger): ```ocaml let () = - Dream.run - (Dream.logger (fun _ -> - Dream.html "Good morning, world!")) + Eio_main.run (fun env -> + Dream.run env + (Dream.logger (fun _ -> + Dream.html "Good morning, world!"))) ```
@@ -25,7 +26,8 @@ in this example looks like this: ```ocaml let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" ``` diff --git a/example/2-middleware/middleware.ml b/example/2-middleware/middleware.ml index a35eb21d..c04d64f5 100644 --- a/example/2-middleware/middleware.ml +++ b/example/2-middleware/middleware.ml @@ -1,4 +1,5 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" diff --git a/example/3-router/router.ml b/example/3-router/router.ml index 0c0c410f..faff8819 100644 --- a/example/3-router/router.ml +++ b/example/3-router/router.ml @@ -1,5 +1,6 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ diff --git a/example/4-counter/counter.ml b/example/4-counter/counter.ml index 0f4c9d50..3d1a5756 100644 --- a/example/4-counter/counter.ml +++ b/example/4-counter/counter.ml @@ -5,7 +5,8 @@ let count_requests inner_handler request = inner_handler request let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ count_requests @@ Dream.router [ diff --git a/example/5-promise/README.md b/example/5-promise/README.md index 60110392..821daa08 100644 --- a/example/5-promise/README.md +++ b/example/5-promise/README.md @@ -1,29 +1,30 @@ # `5-promise` +(note this example is now badly named, as it doesn't use any promises) +
[**`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 -successfully, and those that caused an exception: +passing them on to the rest of the app. We can also await responses, and do +something *after*. In this example, we separately count requests that were +handled successfully, and those that caused an exception: ```ocaml let successful = ref 0 let failed = ref 0 let count_requests inner_handler request = - try%lwt - let%lwt response = inner_handler request in + try + let response = inner_handler request in successful := !successful + 1; - Lwt.return response - + response with exn -> failed := !failed + 1; raise exn let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ count_requests @@ Dream.router [ @@ -49,49 +50,13 @@ Try it in the [playground](http://dream.as/5-promise).
-As you can see, the -[core constructs](https://ocsigen.org/lwt/latest/api/Ppx_lwt) of Lwt are: - -- `let%lwt` to await the result of a promise. -- `try%lwt` to catch both exceptions and rejections. Lwt promises can only be - rejected with exceptions, of OCaml type `exn`. -- `Lwt.return` to resolve a promise. - -Besides these, Lwt has a lot of [convenience -functions](https://ocsigen.org/lwt/latest/api/Lwt), and an [asychronous -I/O library](https://ocsigen.org/lwt/latest/api/Lwt_unix). +As you can see, we use `try` to catch both exceptions and rejections.
-To use `let%lwt`, we need to modify our -[`dune`](https://github.com/aantron/dream/blob/master/example/5-promise/dune) -file a bit to include `lwt_ppx`: - -
(executable
- (name promise)
- (libraries dream)
- (preprocess (pps lwt_ppx)))
-
- -There are other ways to write *await* and *catch* in Lwt that don't require -`lwt_ppx`, but `lwt_ppx` is presently the best for preserving nice stack traces. -For example, `let%lwt` is equivalent to... - -- [`Lwt.bind`](https://github.com/ocsigen/lwt/blob/c5f895e35a38df2d06f19fd23bf553129b9e95b3/src/core/lwt.mli#L475), - which is almost never used directly. -- [`>>=`](https://github.com/ocsigen/lwt/blob/c5f895e35a38df2d06f19fd23bf553129b9e95b3/src/core/lwt.mli#L1395) - from module `Lwt.Infix`. -- [`let*`](https://github.com/ocsigen/lwt/blob/c5f895e35a38df2d06f19fd23bf553129b9e95b3/src/core/lwt.mli#L1511) - from module `Lwt.Syntax`, which is showcased in Lwt's - [README](https://github.com/ocsigen/lwt#readme). - -We will stick to `let%lwt` in the examples and keep things tidy. - -
- **Next steps:** - [**`6-echo`**](../6-echo#files) uses Dream and Lwt to read a request body. diff --git a/example/5-promise/dune b/example/5-promise/dune index 438ffc03..b5b1aa57 100644 --- a/example/5-promise/dune +++ b/example/5-promise/dune @@ -1,6 +1,5 @@ (executable (name promise) - (libraries dream) - (preprocess (pps lwt_ppx))) + (libraries dream)) (data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/5-promise/promise.ml b/example/5-promise/promise.ml index 34bc200c..91769d02 100644 --- a/example/5-promise/promise.ml +++ b/example/5-promise/promise.ml @@ -2,17 +2,17 @@ let successful = ref 0 let failed = ref 0 let count_requests inner_handler request = - try%lwt - let%lwt response = inner_handler request in + try + let response = inner_handler request in successful := !successful + 1; - Lwt.return response - + response with exn -> failed := !failed + 1; raise exn let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ count_requests @@ Dream.router [ diff --git a/example/6-echo/dune b/example/6-echo/dune index aeebe713..8784a629 100644 --- a/example/6-echo/dune +++ b/example/6-echo/dune @@ -1,6 +1,5 @@ (executable (name echo) - (libraries dream) - (preprocess (pps lwt_ppx))) + (libraries dream)) (data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/6-echo/echo.ml b/example/6-echo/echo.ml index fbdca6fe..27907306 100644 --- a/example/6-echo/echo.ml +++ b/example/6-echo/echo.ml @@ -1,10 +1,11 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.post "/echo" (fun request -> - let%lwt body = Dream.body request in + let body = Dream.body request in Dream.respond ~headers:["Content-Type", "application/octet-stream"] body); diff --git a/example/7-template/README.md b/example/7-template/README.md index 4162f6c0..4fcc5ea5 100644 --- a/example/7-template/README.md +++ b/example/7-template/README.md @@ -43,8 +43,7 @@ file to run the template preprocessor:
(executable
  (name template)
- (libraries dream)
- (preprocess (pps lwt_ppx)))
+ (libraries dream))
 
 (rule
  (targets template.ml)
diff --git a/example/7-template/template.eml.ml b/example/7-template/template.eml.ml
index 27e7e351..3fedf055 100644
--- a/example/7-template/template.eml.ml
+++ b/example/7-template/template.eml.ml
@@ -6,7 +6,8 @@ let render param =
   
 
 let () =
-  Dream.run
+  Eio_main.run @@ fun env ->
+  Dream.run env
   @@ Dream.logger
   @@ Dream.router [
 
diff --git a/example/8-debug/debug.ml b/example/8-debug/debug.ml
index b1aed12e..6fed9a35 100644
--- a/example/8-debug/debug.ml
+++ b/example/8-debug/debug.ml
@@ -1,5 +1,6 @@
 let () =
-  Dream.run ~error_handler:Dream.debug_error_handler
+  Eio_main.run @@ fun env ->
+  Dream.run ~error_handler:Dream.debug_error_handler env
   @@ Dream.logger
   @@ Dream.router [
 
diff --git a/example/9-error/error.eml.ml b/example/9-error/error.eml.ml
index 515091cf..67937346 100644
--- a/example/9-error/error.eml.ml
+++ b/example/9-error/error.eml.ml
@@ -12,9 +12,10 @@ let my_error_template _error debug_info suggested_response =
     
     
   end;
-  Lwt.return suggested_response
+  suggested_response
 
 let () =
-  Dream.run ~error_handler:(Dream.error_template my_error_template)
+  Eio_main.run @@ fun env ->
+  Dream.run ~error_handler:(Dream.error_template my_error_template) env
   @@ Dream.logger
   @@ Dream.not_found
diff --git a/example/a-log/log.ml b/example/a-log/log.ml
index 7afc7e21..a66a5c47 100644
--- a/example/a-log/log.ml
+++ b/example/a-log/log.ml
@@ -1,5 +1,6 @@
 let () =
-  Dream.run
+  Eio_main.run @@ fun env ->
+  Dream.run env
   @@ Dream.logger
   @@ Dream.router [
 
diff --git a/example/b-session/dune b/example/b-session/dune
index 0087f76d..557b34a4 100644
--- a/example/b-session/dune
+++ b/example/b-session/dune
@@ -1,6 +1,5 @@
 (executable
  (name session)
- (libraries dream)
- (preprocess (pps lwt_ppx)))
+ (libraries dream))
 
 (data_only_dirs _esy esy.lock lib node_modules)
diff --git a/example/b-session/session.ml b/example/b-session/session.ml
index b1d981b5..57cb238b 100644
--- a/example/b-session/session.ml
+++ b/example/b-session/session.ml
@@ -1,13 +1,14 @@
 let () =
-  Dream.run
+  Eio_main.run @@ fun env ->
+  Dream.run env
   @@ Dream.logger
   @@ Dream.memory_sessions
   @@ fun request ->
 
     match Dream.session "user" request with
     | None ->
-      let%lwt () = Dream.invalidate_session request in
-      let%lwt () = Dream.put_session "user" "alice" request in
+      Dream.invalidate_session request;
+      Dream.put_session "user" "alice" request;
       Dream.html "You weren't logged in; but now you are!"
 
     | Some username ->
diff --git a/example/c-cookie/cookie.ml b/example/c-cookie/cookie.ml
index 73435ada..fcc56b60 100644
--- a/example/c-cookie/cookie.ml
+++ b/example/c-cookie/cookie.ml
@@ -1,5 +1,6 @@
 let () =
-  Dream.run
+  Eio_main.run @@ fun env ->
+  Dream.run env
   @@ Dream.set_secret "foo"
   @@ Dream.logger
   @@ fun request ->
@@ -13,4 +14,4 @@ let () =
       let response = Dream.response "Set language preference; come again!" in
       Dream.add_header response "Content-Type" Dream.text_html;
       Dream.set_cookie response "ui.language" "ut-OP" request;
-      Lwt.return response
+      response
diff --git a/example/d-form/dune b/example/d-form/dune
index 6918056c..d8ba1e88 100644
--- a/example/d-form/dune
+++ b/example/d-form/dune
@@ -1,7 +1,6 @@
 (executable
  (name form)
- (libraries dream)
- (preprocess (pps lwt_ppx)))
+ (libraries dream))
 
 (rule
  (targets form.ml)
diff --git a/example/d-form/form.eml.ml b/example/d-form/form.eml.ml
index 7ee950b5..4b9650e4 100644
--- a/example/d-form/form.eml.ml
+++ b/example/d-form/form.eml.ml
@@ -16,7 +16,8 @@ let show_form ?message request =
   
 
 let () =
-  Dream.run
+  Eio_main.run @@ fun env ->
+  Dream.run env
   @@ Dream.logger
   @@ Dream.memory_sessions
   @@ Dream.router [
@@ -27,7 +28,7 @@ let () =
 
     Dream.post "/"
       (fun request ->
-        match%lwt Dream.form request with
+        match Dream.form request with
         | `Ok ["message", message] ->
           Dream.html (show_form ~message request)
         | _ ->
diff --git a/example/e-json/README.md b/example/e-json/README.md
index fc26a1cf..08002072 100644
--- a/example/e-json/README.md
+++ b/example/e-json/README.md
@@ -44,7 +44,7 @@ To get this working, we have to add `ppx_yojson_conv` to our
 
(executable
  (name json)
  (libraries dream)
- (preprocess (pps lwt_ppx ppx_yojson_conv)))
+ (preprocess (pps ppx_yojson_conv)))
 
and to diff --git a/example/e-json/dune b/example/e-json/dune index 15568cec..dc82cfd9 100644 --- a/example/e-json/dune +++ b/example/e-json/dune @@ -1,6 +1,6 @@ (executable (name json) (libraries dream) - (preprocess (pps lwt_ppx ppx_yojson_conv))) + (preprocess (pps ppx_yojson_conv))) (data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/e-json/json.ml b/example/e-json/json.ml index fa8fad05..2fa3a2d6 100644 --- a/example/e-json/json.ml +++ b/example/e-json/json.ml @@ -3,17 +3,16 @@ type message_object = { } [@@deriving yojson] let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.origin_referrer_check @@ Dream.router [ Dream.post "/" (fun request -> - let%lwt body = Dream.body request in - let message_object = - body + Dream.body request |> Yojson.Safe.from_string |> message_object_of_yojson in diff --git a/example/f-static/static.ml b/example/f-static/static.ml index 2ede0c2c..7e365e2e 100644 --- a/example/f-static/static.ml +++ b/example/f-static/static.ml @@ -1,5 +1,6 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.get "/static/**" (Dream.static ".") diff --git a/example/g-upload/dune b/example/g-upload/dune index 72f71e70..b5700e4e 100644 --- a/example/g-upload/dune +++ b/example/g-upload/dune @@ -1,7 +1,6 @@ (executable (name upload) - (libraries dream) - (preprocess (pps lwt_ppx))) + (libraries dream)) (rule (targets upload.ml) diff --git a/example/g-upload/upload.eml.ml b/example/g-upload/upload.eml.ml index 5c2a1752..cb22653b 100644 --- a/example/g-upload/upload.eml.ml +++ b/example/g-upload/upload.eml.ml @@ -23,7 +23,8 @@ let report files = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.memory_sessions @@ Dream.router [ @@ -32,7 +33,7 @@ let () = Dream.html (home request)); Dream.post "/" (fun request -> - match%lwt Dream.multipart request with + match Dream.multipart request with | `Ok ["files", files] -> Dream.html (report files) | _ -> Dream.empty `Bad_Request); diff --git a/example/h-sql/sql.eml.ml b/example/h-sql/sql.eml.ml index fe406b97..f33389f7 100644 --- a/example/h-sql/sql.eml.ml +++ b/example/h-sql/sql.eml.ml @@ -33,20 +33,21 @@ let render comments request = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.sql_pool "sqlite3:db.sqlite" @@ Dream.sql_sessions @@ Dream.router [ Dream.get "/" (fun request -> - let%lwt comments = Dream.sql request list_comments in + let comments = Dream.sql request list_comments in Dream.html (render comments request)); Dream.post "/" (fun request -> - match%lwt Dream.form request with + match Dream.form request with | `Ok ["text", text] -> - let%lwt () = Dream.sql request (add_comment text) in + Dream.sql request (add_comment text); Dream.redirect request "/" | _ -> Dream.empty `Bad_Request); diff --git a/example/i-graphql/graphql.ml b/example/i-graphql/graphql.ml index 27a68c7c..de46fec9 100644 --- a/example/i-graphql/graphql.ml +++ b/example/i-graphql/graphql.ml @@ -36,7 +36,8 @@ let default_query = "{\\n users {\\n name\\n id\\n }\\n}\\n" let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.origin_referrer_check @@ Dream.router [ diff --git a/example/j-stream/dune b/example/j-stream/dune index 9cf43884..dedf8f7b 100644 --- a/example/j-stream/dune +++ b/example/j-stream/dune @@ -1,6 +1,5 @@ (executable (name stream) - (libraries dream) - (preprocess (pps lwt_ppx))) + (libraries dream)) (data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/j-stream/stream.ml b/example/j-stream/stream.ml index 3f9c2477..089871c9 100644 --- a/example/j-stream/stream.ml +++ b/example/j-stream/stream.ml @@ -1,22 +1,23 @@ let echo request response = let rec loop () = - match%lwt Dream.read request with + match Dream.read request with | None -> Dream.close response | Some chunk -> - let%lwt () = Dream.write response chunk in - let%lwt () = Dream.flush response in + Dream.write response chunk; + Dream.flush response; loop () in loop () let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.post "/echo" (fun request -> - Dream.stream + Dream.stream request ~headers:["Content-Type", "application/octet-stream"] (echo request)); diff --git a/example/k-websocket/dune b/example/k-websocket/dune index 2e18f039..2ad9a331 100644 --- a/example/k-websocket/dune +++ b/example/k-websocket/dune @@ -1,7 +1,6 @@ (executable (name websocket) - (libraries dream) - (preprocess (pps lwt_ppx))) + (libraries dream)) (rule (targets websocket.ml) diff --git a/example/k-websocket/websocket.eml.ml b/example/k-websocket/websocket.eml.ml index 1166c93e..b05877a5 100644 --- a/example/k-websocket/websocket.eml.ml +++ b/example/k-websocket/websocket.eml.ml @@ -18,7 +18,8 @@ let home = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ @@ -27,11 +28,11 @@ let () = Dream.html home); Dream.get "/websocket" - (fun _ -> - Dream.websocket (fun response -> - match%lwt Dream.read response with + (fun request -> + Dream.websocket request (fun response -> + match Dream.read response with | Some "Hello?" -> - let%lwt () = Dream.write response "Good-bye!" in + Dream.write response "Good-bye!"; Dream.close response | _ -> Dream.close response)); diff --git a/example/l-https/https.ml b/example/l-https/https.ml index d9f4a077..7e76651f 100644 --- a/example/l-https/https.ml +++ b/example/l-https/https.ml @@ -1,4 +1,5 @@ let () = - Dream.run ~https:true + Eio_main.run @@ fun env -> + Dream.run ~https:true env @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" diff --git a/example/r-advanced-template/template.eml.re b/example/r-advanced-template/template.eml.re index 94ad8e49..da357d2a 100644 --- a/example/r-advanced-template/template.eml.re +++ b/example/r-advanced-template/template.eml.re @@ -38,7 +38,8 @@ let tasks = [ ]; let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.router([ Dream.get("/", _ => render_home(tasks) |> Dream.html), diff --git a/example/r-fullstack-melange/server/server.eml.re b/example/r-fullstack-melange/server/server.eml.re index c55429b6..9cbe627c 100644 --- a/example/r-fullstack-melange/server/server.eml.re +++ b/example/r-fullstack-melange/server/server.eml.re @@ -8,7 +8,8 @@ let home = { }; let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.router([ diff --git a/example/r-graphql/graphql.re b/example/r-graphql/graphql.re index cd129b1b..768c95f4 100644 --- a/example/r-graphql/graphql.re +++ b/example/r-graphql/graphql.re @@ -47,7 +47,8 @@ let default_query = "{\\n users {\\n name\\n id\\n }\\n}\\n"; let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.origin_referrer_check @@ Dream.router([ diff --git a/example/r-hello/hello.re b/example/r-hello/hello.re index d6fdbaf3..7ac71b66 100644 --- a/example/r-hello/hello.re +++ b/example/r-hello/hello.re @@ -1,3 +1,4 @@ let () = - Dream.run(_ => + Eio_main.run @@ env => + Dream.run(env, _ => Dream.html("Good morning, reasonable world!")); diff --git a/example/r-template-files/server.re b/example/r-template-files/server.re index 656b6c13..63b229c7 100644 --- a/example/r-template-files/server.re +++ b/example/r-template-files/server.re @@ -1,5 +1,6 @@ let () = - Dream.run @@ + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.router([ Dream.get("/:word", request => diff --git a/example/r-template-stream/template_stream.eml.re b/example/r-template-stream/template_stream.eml.re index e51238b3..7a4f4ac8 100644 --- a/example/r-template-stream/template_stream.eml.re +++ b/example/r-template-stream/template_stream.eml.re @@ -1,16 +1,16 @@ let render = response => { - let%lwt () = { + let () = { %% response % let rec paragraphs = index => {

<%i index %>

-% let%lwt () = Dream.flush(response); -% let%lwt () = Lwt_unix.sleep(1.); -% paragraphs(index + 1); +% Dream.flush(response); +% Eio_unix.sleep(1.); +% if (index < 10) paragraphs(index + 1); % }; -% let%lwt () = paragraphs(0); +% paragraphs(0); @@ -19,6 +19,7 @@ let render = response => { }; let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger - @@ _ => Dream.stream(~headers=[("Content-Type", Dream.text_html)], render); + @@ request => Dream.stream(~headers=[("Content-Type", Dream.text_html)], request, render); diff --git a/example/r-template/template.eml.re b/example/r-template/template.eml.re index 5fe9eaaa..b7fbd522 100644 --- a/example/r-template/template.eml.re +++ b/example/r-template/template.eml.re @@ -7,7 +7,8 @@ let greet = who => { }; let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.router([ diff --git a/example/r-tyxml/tyxml.re b/example/r-tyxml/tyxml.re index e89a44a9..c0d06328 100644 --- a/example/r-tyxml/tyxml.re +++ b/example/r-tyxml/tyxml.re @@ -12,7 +12,8 @@ let html_to_string = html => Format.asprintf("%a", Tyxml.Html.pp(), html); let () = - Dream.run + Eio_main.run @@ env => + Dream.run(env) @@ Dream.logger @@ Dream.router([ diff --git a/example/w-advanced-template/template.eml.ml b/example/w-advanced-template/template.eml.ml index 6d17bd81..7e26831e 100644 --- a/example/w-advanced-template/template.eml.ml +++ b/example/w-advanced-template/template.eml.ml @@ -37,7 +37,8 @@ let tasks = [ ] let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.get "/" diff --git a/example/w-chat/chat.eml.ml b/example/w-chat/chat.eml.ml index 28d15b52..8d71e7cc 100644 --- a/example/w-chat/chat.eml.ml +++ b/example/w-chat/chat.eml.ml @@ -1,3 +1,5 @@ +open Eio.Std + let home = @@ -44,16 +46,19 @@ let forget client_id = Hashtbl.remove clients client_id let send message = + Switch.run @@ fun sw -> Hashtbl.to_seq_values clients |> List.of_seq - |> Lwt_list.iter_p (fun client -> Dream.write client message) + |> List.iter (fun client -> + Fiber.fork ~sw (fun () -> Dream.write client message) + ) let handle_client client = let client_id = track client in let rec loop () = - match%lwt Dream.read client with + match Dream.read client with | Some message -> - let%lwt () = send message in + send message; loop () | None -> forget client_id; @@ -62,7 +67,8 @@ let handle_client client = loop () let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ @@ -70,7 +76,7 @@ let () = (fun _ -> Dream.html home); Dream.get "/websocket" - (fun _ -> Dream.websocket handle_client); + (fun request -> Dream.websocket request handle_client); ] @@ Dream.not_found diff --git a/example/w-content-security-policy/content_security_policy.eml.ml b/example/w-content-security-policy/content_security_policy.eml.ml index 257442b8..78460b98 100644 --- a/example/w-content-security-policy/content_security_policy.eml.ml +++ b/example/w-content-security-policy/content_security_policy.eml.ml @@ -6,7 +6,8 @@ let home = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ @@ -21,7 +22,7 @@ let () = "You should not be able to see this inside a frame!"); Dream.post "/violation" (fun request -> - let%lwt report = Dream.body request in + let report = Dream.body request in Dream.error (fun log -> log "%s" report); Dream.empty `OK); diff --git a/example/w-esy/hello.ml b/example/w-esy/hello.ml index a35eb21d..c04d64f5 100644 --- a/example/w-esy/hello.ml +++ b/example/w-esy/hello.ml @@ -1,4 +1,5 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" diff --git a/example/w-flash/flash.eml.ml b/example/w-flash/flash.eml.ml index 1b9cd121..b0edf034 100644 --- a/example/w-flash/flash.eml.ml +++ b/example/w-flash/flash.eml.ml @@ -19,7 +19,8 @@ let result request = let () = Dream.set_log_level "dream.flash" `Debug; - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.memory_sessions @@ Dream.flash_messages @@ -31,9 +32,9 @@ let () = Dream.post "/" (fun request -> - match%lwt Dream.form request with + match Dream.form request with | `Ok ["text", text] -> - let () = Dream.put_flash request "Info" text in + Dream.put_flash request "Info" text; Dream.redirect request "/result" | _ -> Dream.redirect request "/"); diff --git a/example/w-fswatch/hello.ml b/example/w-fswatch/hello.ml index a35eb21d..c04d64f5 100644 --- a/example/w-fswatch/hello.ml +++ b/example/w-fswatch/hello.ml @@ -1,4 +1,5 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ fun _ -> Dream.html "Good morning, world!" diff --git a/example/w-fullstack-jsoo/.gitignore b/example/w-fullstack-jsoo/.gitignore deleted file mode 100644 index 980c8512..00000000 --- a/example/w-fullstack-jsoo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -static/ diff --git a/example/w-fullstack-jsoo/README.md b/example/w-fullstack-jsoo/README.md deleted file mode 100644 index d5bf7b8a..00000000 --- a/example/w-fullstack-jsoo/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# `w-fullstack-jsoo` - -
- -This example shares a toy function between client and server using -[js_of_ocaml](https://ocsigen.org/js_of_ocaml/latest/manual/overview). The -function is in -[common/common.ml](https://github.com/aantron/dream/blob/master/example/w-fullstack-jsoo/common/common.ml). - -```ocaml -let greet = function - | `Server -> "Hello..." - | `Client -> "...world!" -``` - -The first part of the message is printed by the server, in -[server/server.eml.ml](https://github.com/aantron/dream/blob/master/example/w-fullstack-jsoo/server/server.eml.ml): - -```ocaml -let home = - - -

<%s Common.greet `Server %>

- - - - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router [ - - Dream.get "/" - (fun _ -> Dream.html home); - - Dream.get "/static/**" - (Dream.static "./static"); - - ] - @@ Dream.not_found -``` - -The rest is printed by the client, in -[client/client.ml](https://github.com/aantron/dream/blob/master/example/w-fullstack-jsoo/client/client.ml): - -```ocaml -open Js_of_ocaml - -let () = - let body = Dom_html.getElementById_exn "body" in - let p = Dom_html.(createP document) in - p##.innerHTML := Js.string (Common.greet `Client); - Dom.appendChild body p -``` - -To run the example, do - -
cd example/w-fullstack-jsoo
-dune build --root . client/client.bc.js
-mkdir -p static
-cp _build/default/client/client.bc.js static/client.js
-dune exec --root . server/server.exe
-
- -You can also trigger it all with esy with - -
$ cd example/w-fullstack-jsoo
-$ npm install esy && npx esy
-$ npx esy start
- -Then visit [http://localhost:8080](http://localhost:8080), and you will see... - -![Full-stack greeting](https://raw.githubusercontent.com/aantron/dream/master/docs/asset/fullstack.png) - -
- -**See also:** - -- [**`w-one-binary`**](../w-one-binary#files) for bundling assets into a - self-contained binary. - -
- -[Up to the example index](../#full-stack) diff --git a/example/w-fullstack-jsoo/client/client.ml b/example/w-fullstack-jsoo/client/client.ml deleted file mode 100644 index 50b65562..00000000 --- a/example/w-fullstack-jsoo/client/client.ml +++ /dev/null @@ -1,7 +0,0 @@ -open Js_of_ocaml - -let () = - let body = Dom_html.getElementById_exn "body" in - let p = Dom_html.(createP document) in - p##.innerHTML := Js.string (Common.greet `Client); - Dom.appendChild body p diff --git a/example/w-fullstack-jsoo/client/dune b/example/w-fullstack-jsoo/client/dune deleted file mode 100644 index 45f19c4d..00000000 --- a/example/w-fullstack-jsoo/client/dune +++ /dev/null @@ -1,5 +0,0 @@ -(executable - (name client) - (modes js) - (libraries common js_of_ocaml) - (preprocess (pps js_of_ocaml-ppx))) diff --git a/example/w-fullstack-jsoo/common/common.ml b/example/w-fullstack-jsoo/common/common.ml deleted file mode 100644 index 6a1a1208..00000000 --- a/example/w-fullstack-jsoo/common/common.ml +++ /dev/null @@ -1,3 +0,0 @@ -let greet = function - | `Server -> "Hello..." - | `Client -> "...world!" diff --git a/example/w-fullstack-jsoo/common/dune b/example/w-fullstack-jsoo/common/dune deleted file mode 100644 index 35b99062..00000000 --- a/example/w-fullstack-jsoo/common/dune +++ /dev/null @@ -1,2 +0,0 @@ -(library - (name common)) diff --git a/example/w-fullstack-jsoo/dune b/example/w-fullstack-jsoo/dune deleted file mode 100644 index 8ab777a7..00000000 --- a/example/w-fullstack-jsoo/dune +++ /dev/null @@ -1 +0,0 @@ -(data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/w-fullstack-jsoo/dune-project b/example/w-fullstack-jsoo/dune-project deleted file mode 100644 index 929c696e..00000000 --- a/example/w-fullstack-jsoo/dune-project +++ /dev/null @@ -1 +0,0 @@ -(lang dune 2.0) diff --git a/example/w-fullstack-jsoo/esy.json b/example/w-fullstack-jsoo/esy.json deleted file mode 100644 index 37d63a63..00000000 --- a/example/w-fullstack-jsoo/esy.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "dependencies": { - "@opam/dream": "1.0.0~alpha2", - "@opam/dune": "^2.0", - "@opam/js_of_ocaml": "*", - "@opam/js_of_ocaml-ppx": "*", - "ocaml": "4.12.x" - }, - "devDependencies": { - "@opam/ocaml-lsp-server": "*" - }, - "resolutions": { - "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829", - "esy-openssl": "esy-packages/esy-openssl#619ae2d46ca981ec26ab3287487ad98b157a01d1" - }, - "esy": { - "buildsInSource": "unsafe", - "build": [ - "dune build --root . client/client.bc.js", - "mkdir -p static", - "cp _build/default/client/client.bc.js static/client.js", - "dune build --root . server/server.exe" - ] - }, - "scripts": { - "start": "dune exec --root . server/server.exe" - } -} diff --git a/example/w-fullstack-jsoo/server/dune b/example/w-fullstack-jsoo/server/dune deleted file mode 100644 index e167254b..00000000 --- a/example/w-fullstack-jsoo/server/dune +++ /dev/null @@ -1,8 +0,0 @@ -(executable - (name server) - (libraries common dream)) - -(rule - (targets server.ml) - (deps server.eml.ml) - (action (run dream_eml %{deps} --workspace %{workspace_root}))) diff --git a/example/w-fullstack-jsoo/server/server.eml.ml b/example/w-fullstack-jsoo/server/server.eml.ml deleted file mode 100644 index a9be889e..00000000 --- a/example/w-fullstack-jsoo/server/server.eml.ml +++ /dev/null @@ -1,21 +0,0 @@ -let home = - - -

<%s Common.greet `Server %>

- - - - -let () = - Dream.run - @@ Dream.logger - @@ Dream.router [ - - Dream.get "/" - (fun _ -> Dream.html home); - - Dream.get "/static/**" - (Dream.static "./static"); - - ] - @@ Dream.not_found diff --git a/example/w-fullstack-rescript/server/server.eml.ml b/example/w-fullstack-rescript/server/server.eml.ml index 0827f76b..b1f69f6b 100644 --- a/example/w-fullstack-rescript/server/server.eml.ml +++ b/example/w-fullstack-rescript/server/server.eml.ml @@ -7,7 +7,8 @@ let home = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ diff --git a/example/w-graphql-subscription/graphql_subscription.ml b/example/w-graphql-subscription/graphql_subscription.ml index 7f7d304f..f00d94e9 100644 --- a/example/w-graphql-subscription/graphql_subscription.ml +++ b/example/w-graphql-subscription/graphql_subscription.ml @@ -29,7 +29,8 @@ let default_query = "subscription {\\n count(until: 3)\\n}\\n" let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.origin_referrer_check @@ Dream.router [ diff --git a/example/w-live-reload/live_reload.ml b/example/w-live-reload/live_reload.ml index 9750d90f..70c748a0 100644 --- a/example/w-live-reload/live_reload.ml +++ b/example/w-live-reload/live_reload.ml @@ -33,11 +33,11 @@ socket.onclose = function(event) { |js} let inject_live_reload_script inner_handler request = - let%lwt response = inner_handler request in + let response = inner_handler request in match Dream.header response "Content-Type" with | Some "text/html; charset=utf-8" -> - let%lwt body = Dream.body response in + let body = Dream.body response in let soup = Markup.string body |> Markup.parse_html ~context:`Document @@ -47,19 +47,20 @@ let inject_live_reload_script inner_handler request = begin match Soup.Infix.(soup $? "head") with | None -> - Lwt.return response + response | Some head -> Soup.create_element "script" ~inner_text:live_reload_script |> Soup.append_child head; Dream.set_body response (Soup.to_string soup); - Lwt.return response + response end | _ -> - Lwt.return response + response let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ inject_live_reload_script @@ Dream.router [ @@ -70,9 +71,9 @@ let () = |> Printf.sprintf "Good morning, world! Random tag: %s" |> Dream.html); - Dream.get "/_live-reload" (fun _ -> - Dream.websocket (fun socket -> - let%lwt _ = Dream.read socket in + Dream.get "/_live-reload" (fun request -> + Dream.websocket request (fun socket -> + let _ = Dream.read socket in Dream.close socket)); ] diff --git a/example/w-long-polling/long_polling.eml.ml b/example/w-long-polling/long_polling.eml.ml index 6562c838..cc9f07c4 100644 --- a/example/w-long-polling/long_polling.eml.ml +++ b/example/w-long-polling/long_polling.eml.ml @@ -1,3 +1,5 @@ +open Eio.Std + let home = @@ -34,44 +36,47 @@ let server_state = let last_message = ref 0 -let rec message_loop () = - let%lwt () = Lwt_unix.sleep (Random.float 2.) in - incr last_message; - - let message = string_of_int !last_message in - Dream.log "Generated message %s" message; - - begin match !server_state with - | Client_waiting f -> - server_state := Messages_accumulating []; - f message - | Messages_accumulating list -> - server_state := Messages_accumulating (message::list) - end; +let message_loop () = + while true do + Eio_unix.sleep (Random.float 2.); + incr last_message; - message_loop () + let message = string_of_int !last_message in + Dream.log "Generated message %s" message; -let () = - Lwt.async message_loop; - - Dream.run - @@ Dream.logger - @@ Dream.router [ - - Dream.get "/" (fun _ -> Dream.html home); - - Dream.get "/poll" (fun _ -> - match !server_state with - | Client_waiting _ -> - Dream.empty `Unauthorized - | Messages_accumulating [] -> - let response_promise, respond = Lwt.wait () in - server_state := Client_waiting (fun message -> - Lwt.wakeup_later respond (Dream.response message)); - response_promise - | Messages_accumulating messages -> + begin match !server_state with + | Client_waiting f -> server_state := Messages_accumulating []; - Dream.html (String.concat "\n" (List.rev messages))); + f message + | Messages_accumulating list -> + server_state := Messages_accumulating (message::list) + end + done - ] - @@ Dream.not_found +let () = + Eio_main.run @@ fun env -> + Fiber.both + message_loop + (fun () -> + Dream.run env + @@ Dream.logger + @@ Dream.router [ + + Dream.get "/" (fun _ -> Dream.html home); + + Dream.get "/poll" (fun _ -> + match !server_state with + | Client_waiting _ -> + Dream.empty `Unauthorized + | Messages_accumulating [] -> + let response_promise, respond = Promise.create () in + server_state := Client_waiting (fun message -> + Promise.resolve respond (Dream.response message)); + Promise.await response_promise + | Messages_accumulating messages -> + server_state := Messages_accumulating []; + Dream.html (String.concat "\n" (List.rev messages))); + + ] + @@ Dream.not_found + ) diff --git a/example/w-multipart-dump/multipart_dump.eml.ml b/example/w-multipart-dump/multipart_dump.eml.ml index 6521436c..b4754cfe 100644 --- a/example/w-multipart-dump/multipart_dump.eml.ml +++ b/example/w-multipart-dump/multipart_dump.eml.ml @@ -10,7 +10,8 @@ let home request = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.memory_sessions @@ Dream.router [ @@ -19,7 +20,7 @@ let () = Dream.html (home request)); Dream.post "/" (fun request -> - let%lwt body = Dream.body request in + let body = Dream.body request in Dream.respond ~headers:["Content-Type", "text/plain"] body); diff --git a/example/w-nginx/server.eml.ml b/example/w-nginx/server.eml.ml index 0a9905bd..ba5f4b46 100644 --- a/example/w-nginx/server.eml.ml +++ b/example/w-nginx/server.eml.ml @@ -9,7 +9,8 @@ let home = let () = - Dream.run ~interface:"0.0.0.0" ~port:8081 + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" ~port:8081 env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _request -> Dream.html home) diff --git a/example/w-one-binary/one_binary.ml b/example/w-one-binary/one_binary.ml index ac200fb1..7d287b61 100644 --- a/example/w-one-binary/one_binary.ml +++ b/example/w-one-binary/one_binary.ml @@ -4,7 +4,8 @@ let loader _root path _request = | Some asset -> Dream.respond asset let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.get "/assets/**" (Dream.static ~loader "") diff --git a/example/w-postgres/postgres.eml.ml b/example/w-postgres/postgres.eml.ml index 52ecd53c..54acddf8 100644 --- a/example/w-postgres/postgres.eml.ml +++ b/example/w-postgres/postgres.eml.ml @@ -33,20 +33,21 @@ let render comments request = let () = - Dream.run ~interface:"0.0.0.0" + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" env @@ Dream.logger @@ Dream.sql_pool "postgresql://dream:password@postgres/dream" @@ Dream.sql_sessions @@ Dream.router [ Dream.get "/" (fun request -> - let%lwt comments = Dream.sql request list_comments in + let comments = Dream.sql request list_comments in Dream.html (render comments request)); Dream.post "/" (fun request -> - match%lwt Dream.form request with + match Dream.form request with | `Ok ["text", text] -> - let%lwt () = Dream.sql request (add_comment text) in + Dream.sql request (add_comment text); Dream.redirect request "/" | _ -> Dream.empty `Bad_Request); diff --git a/example/w-query/query.ml b/example/w-query/query.ml index 0dbff411..b5d06342 100644 --- a/example/w-query/query.ml +++ b/example/w-query/query.ml @@ -1,5 +1,6 @@ let () = - Dream.run (fun request -> + Eio_main.run @@ fun env -> + Dream.run env (fun request -> match Dream.query "echo" request with | None -> Dream.html "Use ?echo=foo to give a message to echo!" diff --git a/example/w-server-sent-events/server_sent_events.eml.ml b/example/w-server-sent-events/server_sent_events.eml.ml index 12a6dad2..7621bd45 100644 --- a/example/w-server-sent-events/server_sent_events.eml.ml +++ b/example/w-server-sent-events/server_sent_events.eml.ml @@ -1,3 +1,5 @@ +open Eio.Std + let home = @@ -26,17 +28,17 @@ let notify = let last_message = ref 0 -let rec message_loop () = - let%lwt () = Lwt_unix.sleep (Random.float 2.) in - - incr last_message; - let message = string_of_int !last_message in - Dream.log "Generated message %s" message; +let message_loop () = + while true do + Eio_unix.sleep (Random.float 2.); - server_state := message::!server_state; - !notify (); + incr last_message; + let message = string_of_int !last_message in + Dream.log "Generated message %s" message; - message_loop () + server_state := message::!server_state; + !notify () + done let rec forward_messages response = let%lwt messages = @@ -58,23 +60,28 @@ let rec forward_messages response = |> List.map (Printf.sprintf "data: %s\n\n") |> String.concat "" |> fun text -> - let%lwt () = Dream.write response text in - let%lwt () = Dream.flush response in + Dream.write response text; + Dream.flush response; forward_messages response -let () = - Lwt.async message_loop; - - Dream.run - @@ Dream.logger - @@ Dream.router [ +let forward_messages response = Lwt_eio.Promise.await_lwt (forward_messages response) - Dream.get "/" (fun _ -> Dream.html home); - - Dream.get "/push" (fun _ -> - Dream.stream - ~headers:["Content-Type", "text/event-stream"] - forward_messages); - - ] - @@ Dream.not_found +let () = + Eio_main.run @@ fun env -> + Fiber.both + message_loop + (fun () -> + Dream.run env + @@ Dream.logger + @@ Dream.router [ + + Dream.get "/" (fun _ -> Dream.html home); + + Dream.get "/push" (fun request -> + Dream.stream request + ~headers:["Content-Type", "text/event-stream"] + forward_messages); + + ] + @@ Dream.not_found + ) diff --git a/example/w-stress-response/stress_response.ml b/example/w-stress-response/stress_response.ml index 29854159..d4013b4a 100644 --- a/example/w-stress-response/stress_response.ml +++ b/example/w-stress-response/stress_response.ml @@ -1,3 +1,5 @@ +open Eio.Std + let show_heap_size () = Gc.((quick_stat ()).heap_words) * 8 |> float_of_int @@ -14,23 +16,22 @@ let stress ?(megabytes = 1024) ?(chunk = 64) response = let start = Unix.gettimeofday () in let rec loop sent = - if sent >= limit then - let%lwt () = Dream.flush response in - let%lwt () = Dream.close response in - Lwt.return (Unix.gettimeofday () -. start) - else - let%lwt () = Dream.write response chunk_a in - let%lwt () = Dream.write response chunk_b in - let%lwt () = Lwt.pause () in + if sent >= limit then ( + Dream.flush response; + Dream.close response; + (Unix.gettimeofday () -. start) + ) else ( + Dream.write response chunk_a; + Dream.write response chunk_b; + Fiber.yield (); loop (sent + chunk + chunk) + ) in - let%lwt elapsed = loop 0 in + let elapsed = loop 0 in Dream.log "%.0f MB/s over %.1f s" ((float_of_int megabytes) /. elapsed) elapsed; - show_heap_size (); - - Lwt.return_unit + show_heap_size () let query_int name request = Dream.query name request |> Option.map int_of_string @@ -38,12 +39,13 @@ let query_int name request = let () = show_heap_size (); - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun request -> - Dream.stream + Dream.stream request ~headers:["Content-Type", "application/octet-stream"] (stress ?megabytes:(query_int "mb" request) diff --git a/example/w-stress-websocket-send/stress_websocket_send.eml.ml b/example/w-stress-websocket-send/stress_websocket_send.eml.ml index 9e17de31..ee401093 100644 --- a/example/w-stress-websocket-send/stress_websocket_send.eml.ml +++ b/example/w-stress-websocket-send/stress_websocket_send.eml.ml @@ -1,5 +1,7 @@ (* TODO Definitely needs flow control. *) +open Eio.Std + let home = @@ -41,27 +43,27 @@ let stress websocket = let limit = 1024 * 1024 * 1024 in let start = Unix.gettimeofday () in let rec loop sent = - if sent >= limit then - let%lwt () = Dream.close websocket in - Lwt.return (Unix.gettimeofday () -. start) - else - let%lwt () = Dream.write websocket frame_a ~kind:`Binary in - let%lwt () = Dream.write websocket frame_b ~kind:`Binary in - let%lwt () = Lwt.pause () in + if sent >= limit then ( + Dream.close websocket; + (Unix.gettimeofday () -. start) + ) else ( + Dream.write websocket frame_a ~kind:`Binary; + Dream.write websocket frame_b ~kind:`Binary; + Fiber.yield (); loop (sent + frame + frame) + ) in - let%lwt elapsed = loop 0 in + let elapsed = loop 0 in Dream.log "%.0f MB/s over %.1f s" ((float_of_int limit) /. elapsed /. 1024. /. 1024.) elapsed; - show_heap_size (); - - Lwt.return_unit + show_heap_size () let () = show_heap_size (); - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ @@ -69,7 +71,7 @@ let () = (fun _ -> Dream.html home); Dream.get "/websocket" - (fun _ -> Dream.websocket stress); + (fun request -> Dream.websocket request stress); ] @@ Dream.not_found diff --git a/example/w-template-files/server.ml b/example/w-template-files/server.ml index 0f56c2f8..6cab8853 100644 --- a/example/w-template-files/server.ml +++ b/example/w-template-files/server.ml @@ -1,5 +1,6 @@ let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ diff --git a/example/w-template-stream/template_stream.eml.ml b/example/w-template-stream/template_stream.eml.ml index c8033744..db390873 100644 --- a/example/w-template-stream/template_stream.eml.ml +++ b/example/w-template-stream/template_stream.eml.ml @@ -1,16 +1,16 @@ let render response = - let%lwt () = + let () = %% response % let rec paragraphs index =

<%i index %>

-% let%lwt () = Dream.flush response in -% let%lwt () = Lwt_unix.sleep 1. in -% paragraphs (index + 1) +% Dream.flush response; +% Eio_unix.sleep 1.; +% if index < 10 then paragraphs (index + 1) % in -% let%lwt () = paragraphs 0 in +% paragraphs 0; @@ -18,6 +18,7 @@ let render response = Dream.close response let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger - @@ fun _ -> Dream.stream ~headers:["Content-Type", Dream.text_html] render + @@ fun request -> Dream.stream ~headers:["Content-Type", Dream.text_html] request render diff --git a/example/w-tyxml/tyxml.ml b/example/w-tyxml/tyxml.ml index ce23849d..fa17b22c 100644 --- a/example/w-tyxml/tyxml.ml +++ b/example/w-tyxml/tyxml.ml @@ -12,7 +12,8 @@ let html_to_string html = Format.asprintf "%a" (Tyxml.Html.pp ()) html let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.router [ diff --git a/example/w-upload-stream/upload_stream.eml.ml b/example/w-upload-stream/upload_stream.eml.ml index 743f5960..4a3e3bab 100644 --- a/example/w-upload-stream/upload_stream.eml.ml +++ b/example/w-upload-stream/upload_stream.eml.ml @@ -23,7 +23,8 @@ let report files = let () = - Dream.run + Eio_main.run @@ fun env -> + Dream.run env @@ Dream.logger @@ Dream.memory_sessions @@ Dream.router [ @@ -33,11 +34,11 @@ let () = Dream.post "/" (fun request -> let rec receive file_sizes = - match%lwt Dream.upload request with + match Dream.upload request with | None -> Dream.html (report (List.rev file_sizes)) | Some (_, filename, _) -> let rec count_size size = - match%lwt Dream.upload_part request with + match Dream.upload_part request with | None -> receive ((filename, size)::file_sizes) | Some chunk -> count_size (size + String.length chunk) in diff --git a/example/z-docker-esy/app.ml b/example/z-docker-esy/app.ml index e2c65265..57bdc8b8 100644 --- a/example/z-docker-esy/app.ml +++ b/example/z-docker-esy/app.ml @@ -1,5 +1,6 @@ let () = - Dream.run ~interface:"0.0.0.0" + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _ -> diff --git a/example/z-docker-opam/app.ml b/example/z-docker-opam/app.ml index e7af6315..269d1c68 100644 --- a/example/z-docker-opam/app.ml +++ b/example/z-docker-opam/app.ml @@ -1,5 +1,6 @@ let () = - Dream.run ~interface:"0.0.0.0" + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _ -> diff --git a/example/z-fly/app.ml b/example/z-fly/app.ml index 2184ee89..77188580 100644 --- a/example/z-fly/app.ml +++ b/example/z-fly/app.ml @@ -1,5 +1,6 @@ let () = - Dream.run ~interface:"0.0.0.0" + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream deployed on Fly!"); diff --git a/example/z-heroku/app.ml b/example/z-heroku/app.ml index c2c86bdb..2cd99b3b 100644 --- a/example/z-heroku/app.ml +++ b/example/z-heroku/app.ml @@ -1,5 +1,6 @@ let () = - Dream.run ~interface:"0.0.0.0" ~port:(int_of_string (Sys.getenv "PORT")) + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" ~port:(int_of_string (Sys.getenv "PORT")) env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream running in Heroku!"); diff --git a/example/z-playground/.gitignore b/example/z-playground/.gitignore deleted file mode 100644 index 3189f954..00000000 --- a/example/z-playground/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!package-lock.json diff --git a/example/z-playground/README.md b/example/z-playground/README.md deleted file mode 100644 index 112aa46c..00000000 --- a/example/z-playground/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# `z-playground` - -
- -This “example” is, in fact, the Dream online playground, running at -[http://dream.as](http://dream.as). - -It's a simple, one-page app that uses a WebSocket to communicates with its -server. The server starts and stops Docker containers that run visitors' code. -An ` - - - - - - diff --git a/example/z-playground/client/dune b/example/z-playground/client/dune deleted file mode 100644 index 6c6dcf4f..00000000 --- a/example/z-playground/client/dune +++ /dev/null @@ -1,8 +0,0 @@ -(library - (name client) - (libraries dream)) - -(rule - (targets client.ml) - (deps client.eml.html) - (action (run dream_eml %{deps} --workspace %{workspace_root}))) diff --git a/example/z-playground/client/playground.css b/example/z-playground/client/playground.css deleted file mode 100644 index 0e0f462a..00000000 --- a/example/z-playground/client/playground.css +++ /dev/null @@ -1,312 +0,0 @@ -/* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin */ - -/* - -Playground layout: 2 panels with normal element and fluid -┌──────────────────────────────────────────────────┐ -│ │ -│ body (desktop) │ -│ │ -│ ┌───────────────────────┐ ┌────────────────────┐ │ -│ │ │ │ │ │ -│ │ ┌───────────────────┐ │ │ ┌────────────────┐ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ .panel-element │ │ │ │ .panel-element │ │ │ -│ │ ├───────────────────┤ │ │ ├────────────────┤ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ .panel-fluid │ │ │ │ .panel-fluid │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ └───────────────────┘ │ │ └────────────────┘ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ └───────────────────────┘ └────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────┘ - -This is on mobile or when pressing Change -┌──────────────────────────────────────────────────┐ -│ │ -│ body (mobile/Change view actived) │ -│ ┌──────────────────────────────────────────────┐ │ -│ │ │ │ -│ │ ┌──────────────────────────────────────────┐ │ │ -│ │ │ .panel-element │ │ │ -│ │ │ │ │ │ -│ │ ├──────────────────────────────────────────┤ │ │ -│ │ │ .panel-fluid │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ └──────────────────────────────────────────┘ │ │ -│ │ │ │ -│ ├──────────────────────────────────────────────┤ │ -│ │ │ │ -│ │ ┌──────────────────────────────────────────┐ │ │ -│ │ │ .panel-element │ │ │ -│ │ │ │ │ │ -│ │ ├──────────────────────────────────────────┤ │ │ -│ │ │ .panel-fluid │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ └──────────────────────────────────────────┘ │ │ -│ │ │ │ -│ └──────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────┘ - */ -body { - margin: 0; - font-size: 14px; - line-height: 21px; - color: #ddd; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Helvetica, Arial, sans-serif; - display: flex; - height: 100vh; - overflow-y: hidden; -} - -@supports (-webkit-touch-callout: none) { - body { - /* The hack for Safari Mobile hack */ - height: -webkit-fill-available; - } -} - -.panel { - flex: 0 0 50%; - width: 50%; - display: flex; - flex-direction: column; -} -.panel-fluid { - flex: 1 0 auto; - min-height: 300px; -} - -/* - * Change view activation and mobile mode is the same - * Please ensure that they are in perfect sync - */ -body.full-editor { - flex-direction: column; - overflow-y: auto; - height: auto; -} - -.full-editor .panel { - width: 100%; -} - -@media (max-width: 1100px) { - body { - flex-direction: column; - overflow-y: auto; - height: auto; - } - .panel { - width: 100%; - } -} - -#textarea { - position: relative; -} -#textarea .CodeMirror { - height: 100%; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; -} - -header { - height: 64px; - display: flex; - align-items: center -} -/* Editor */ - -h1 { - margin: 0; - display: inline-block; - margin-left: 24px; - font-weight: normal; -} - -#log { - height: 100px; - margin: 0; - overflow-x: hidden; - padding-left: 34px; - padding-top: 14px; - overflow: auto; -} - -.CodeMirror, #log { - font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; -} - -#editor button { - font: inherit; - color: inherit; - margin-left: 2em; - background-color: #4338CA; - font-weight: bold; - border: none; - padding: 4px 8px; - border-radius: 4px; -} - -#editor button:hover { - cursor: pointer; - background-color: #3730A3; -} - -#editor header > a { - color: inherit; - text-decoration: none; - flex: 1; - text-align: right; - margin-right: 24px; -} - -@media (max-width: 550px) { - #editor header > a { - display: none; - } -} - -/* width */ -::-webkit-scrollbar { - width: 10px; - height: 10px; - opacity: 0.2; -} - -/* Track */ -::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.2); -} - -/* Handle */ -::-webkit-scrollbar-thumb { - background: #888; -} - -/* Handle on hover */ -::-webkit-scrollbar-thumb:hover { - background: #555; -} - -/* Client */ - -#client header { - background-color: #eee; - box-sizing: border-box; - border-bottom: 1px solid #ccc; - padding: 16px; -} - -#client input { - width: 100%; - height: 100%; - background: none; - border: none; - border: 1px solid #aaa; - padding: 8px; -} - -#client input:focus { - outline: none; -} - -#client iframe { - border: 0; - width: 100%; - background-color: white; -} - - -/* Syntax */ - -.cm-s-dream.CodeMirror, body { - background-color: #181b1e; -} - -.cm-s-dream.CodeMirror, #editor > header { - border-bottom: 1px solid #263838; - box-sizing: border-box; -} - -.cm-s-dream.CodeMirror { - color: #ddd; - border-bottom: 1px solid #2a2a26; -} - -#log { - color: #ddd; -} - -.cm-s-dream .CodeMirror-gutters { - background: none; - border-right: 1px solid #262626; -} - -.cm-s-dream .CodeMirror-linenumber { - color: #999; -} - -.cm-s-dream .cm-keyword, .t-magenta { - color: #ff6c9b; -} - -.cm-s-dream .cm-operator, .t-cyan { - color: #8dc5ff; -} - -.cm-s-dream .cm-string, .t-yellow { - color: #e3db7a; -} - -.cm-s-dream .cm-variable { - color: #ddd; -} - -.cm-s-dream .cm-variable-2, .t-green { - color: #70df5c; -} - -.t-dim { - color: #999; - display: none; -} - -.t-white { - color: #ddd; -} - -.t-red { - color: #ff2300; -} - -.t-blue { - color: #81a2ff; -} diff --git a/example/z-playground/client/playground.js b/example/z-playground/client/playground.js deleted file mode 100644 index aded91d8..00000000 --- a/example/z-playground/client/playground.js +++ /dev/null @@ -1,113 +0,0 @@ -// This file is part of Dream, released under the MIT license. See LICENSE.md -// for details, or visit https://github.com/aantron/dream. -// -// Copyright 2021 Anton Bachin *) - - - -var editor = document.querySelector("#textarea"); -var run = document.querySelector("#run"); -var refresh = document.querySelector("#refresh"); -var address = document.querySelector("input"); -var iframe = document.querySelector("iframe"); -var pre = document.querySelector("pre"); -var chview = document.querySelector("#chview"); - -var codemirror = CodeMirror(editor, { - theme: "material dream", - lineNumbers: true, - tabSize: 2, - extraKeys: { - "Tab": function (editor) { - if (editor.somethingSelected()) - editor.execCommand("indentMore"); - else - editor.execCommand("insertSoftTab"); - } - } -}); - -function colorizeLog(string) { - return string - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") - .replace(/\033\[\?7l/g, "") - .replace(/\033\[2m/g, "") - .replace(/\033\[35m\033\[3m/g, "") - .replace(/\033\[36m\033\[3m/g, "") - .replace(/\033\[37m\033\[3m/g, "") - .replace(/\033\[0;35m\033\[0m/g, "") - .replace(/\033\[0;36m\033\[0m/g, "") - .replace(/\033\[0;37m\033\[0m/g, "") - .replace(/\033\[31m/g, "") - .replace(/\033\[32m/g, "") - .replace(/\033\[33m/g, "") - .replace(/\033\[34m/g, "") - .replace(/\033\[35m/g, "") - .replace(/\033\[36m/g, "") - .replace(/\033\[37m/g, "") - .replace(/\033\[0m/g, "") - ; -}; - -var components = window.location.pathname.split("/"); -var sandbox = components[1]; -sandbox = sandbox || "ocaml"; -var socket = - new WebSocket("ws://" + window.location.host + "/socket?sandbox=" + sandbox); - -var path = components.slice(2).join("/"); -if (path !== "") - path = "/" + path; - -var firstStart = true; - -socket.onmessage = function (e) { - var message = JSON.parse(e.data); - switch (message.kind) { - case "content": - codemirror.setValue(message.payload); - pre.innerHTML += "Building image...\n"; - socket.send(codemirror.getValue()); - break; - - case "log": - pre.innerHTML += colorizeLog(message.payload); - pre.scrollTop = pre.scrollHeight; - break; - - case "started": { - var frame_location = - window.location.protocol + "//" + - window.location.hostname + ":" + message.port + path + location.search; - iframe.src = frame_location; - address.value = frame_location; - history.replaceState( - null, "", "/" + message.sandbox + path + location.search); - if (firstStart) - firstStart = false; - else - pre.scrollIntoView(); - break; - } - } -}; - -run.onclick = function () { - pre.innerHTML += "Building image...\n"; - pre.scrollTop = pre.scrollHeight; - socket.send(codemirror.getValue()); -}; - -chview.onclick = function(){ - var body = document.body; - body.classList.toggle("full-editor") -} - -address.onkeyup = function (event) { - if (event.keyCode === 13) - iframe.src = this.value; -}; diff --git a/example/z-playground/dune b/example/z-playground/dune deleted file mode 100644 index 8ab777a7..00000000 --- a/example/z-playground/dune +++ /dev/null @@ -1 +0,0 @@ -(data_only_dirs _esy esy.lock lib node_modules) diff --git a/example/z-playground/dune-project b/example/z-playground/dune-project deleted file mode 100644 index 929c696e..00000000 --- a/example/z-playground/dune-project +++ /dev/null @@ -1 +0,0 @@ -(lang dune 2.0) diff --git a/example/z-playground/opam-switch b/example/z-playground/opam-switch deleted file mode 100644 index 9d51863d..00000000 --- a/example/z-playground/opam-switch +++ /dev/null @@ -1,161 +0,0 @@ -opam-version: "2.0" -compiler: [ - "base-bigarray.base" - "base-threads.base" - "base-unix.base" - "ocaml.4.12.0" - "ocaml-base-compiler.4.12.0" - "ocaml-config.2" - "ocaml-options-vanilla.1" -] -roots: ["multipart_form.git" "ocaml-base-compiler.4.12.0"] -installed: [ - "angstrom.0.15.0" - "base.v0.14.1" - "base-bigarray.base" - "base-bytes.base" - "base-threads.base" - "base-unix.base" - "base64.3.5.0" - "bigarray-compat.1.0.0" - "bigarray-overlap.0.2.0" - "bigstringaf.0.7.0" - "biniou.1.2.1" - "bisect_ppx.2.6.0" - "caqti.1.5.0" - "caqti-driver-sqlite3.1.5.0" - "caqti-lwt.1.3.0" - "cmdliner.1.0.4" - "conf-libev.4-11" - "conf-libssl.3" - "conf-pkg-config.2" - "conf-sqlite3.1" - "cppo.1.6.7" - "csexp.1.5.1" - "cstruct.6.0.0" - "cudf.0.9-1" - "digestif.1.0.0" - "dune.2.8.5" - "dune-configurator.2.8.5" - "duration.0.1.3" - "easy-format.1.3.2" - "eqaf.0.7" - "extlib.1.7.7-1" - "faraday.0.7.2" - "faraday-lwt.0.7.2" - "faraday-lwt-unix.0.7.2" - "fix.20201120" - "fmt.0.8.9" - "graphql.0.13.0" - "graphql-lwt.0.13.0" - "graphql_parser.0.13.0" - "hmap.0.8.1" - "ke.0.4" - "logs.0.7.0" - "lwt.5.4.0" - "lwt_ppx.2.0.2" - "lwt_ssl.1.1.3" - "magic-mime.1.1.3" - "markup.1.0.0-1" - "menhir.20210419" - "menhirLib.20210419" - "menhirSdk.20210419" - "merlin-extend.0.6" - "mirage-crypto.0.10.1" - "mirage-crypto-rng.0.10.1" - "mmap.1.1.0" - "mtime.1.2.0" - "multipart-form-data.0.3.0" - "multipart_form.git" - "ocaml.4.12.0" - "ocaml-base-compiler.4.12.0" - "ocaml-compiler-libs.v0.12.3" - "ocaml-config.2" - "ocaml-migrate-parsetree.2.1.0" - "ocaml-options-vanilla.1" - "ocaml-syntax-shims.1.0.0" - "ocamlbuild.0.14.0" - "ocamlfind.1.9.1" - "ocamlgraph.2.0.0" - "ocplib-endian.1.1" - "octavius.1.2.2" - "opam-core.2.1.0~beta4" - "opam-file-format.2.1.2" - "opam-format.2.1.0~beta4" - "opam-installer.2.1.0~beta4" - "pecu.0.5" - "ppx_derivers.1.2.1" - "ppx_js_style.v0.14.0" - "ppx_yojson_conv.v0.14.0" - "ppx_yojson_conv_lib.v0.14.0" - "ppxlib.0.22.0" - "prettym.0.0.1" - "psq.0.2.0" - "ptime.0.8.5" - "re.1.9.0" - "reason.3.7.0" - "result.1.5" - "rresult.0.6.0" - "seq.base" - "sexplib0.v0.14.0" - "sqlite3.5.0.3" - "ssl.0.5.10" - "stdlib-shims.0.3.0" - "stringext.1.6.0" - "topkg.1.0.3" - "tyxml.4.5.0" - "tyxml-jsx.4.5.0" - "tyxml-ppx.4.5.0" - "tyxml-syntax.4.5.0" - "uchar.0.0.2" - "unstrctrd.0.2" - "uri.4.2.0" - "uutf.1.0.2" - "yojson.1.7.0" -] -pinned: "multipart_form.git" -package "multipart_form" { - opam-version: "2.0" - version: "git" - synopsis: "Multipart-form: RFC2183, RFC2388 & RFC7578" - description: """ -Implementation of RFC7578 in OCaml - -Returning values from forms: multipart/form-data""" - maintainer: "Romain Calascibetta " - authors: "Romain Calascibetta " - license: "MIT" - homepage: "https://github.com/dinosaure/multipart_form" - doc: "https://dinosaure.github.io/multipart_form/" - bug-reports: "https://github.com/dinosaure/multipart_form/issues" - depends: [ - "ocaml" {>= "4.08.0"} - "dune" {>= "2.0.0"} - "angstrom" {>= "0.14.0"} - "base64" - "unstrctrd" {>= "0.2"} - "rresult" - "uutf" - "stdlib-shims" - "pecu" {>= "0.4"} - "lwt" - "prettym" - "fmt" - "logs" - "ke" {>= "0.4"} - "alcotest" {with-test} - "rosetta" {with-test} - "bigarray-compat" {>= "1.0.0"} - "bigstringaf" {>= "0.7.0"} - "result" {>= "1.5"} - ] - build: [ - ["dune" "build" "-p" name "-j" jobs] - ["dune" "runtest" "-p" name "-j" jobs] {with-test} - ] - dev-repo: "git+https://github.com/dinosaure/multipart_form.git" - url { - src: - "git+https://github.com/dinosaure/multipart_form.git#8f5b6cb77af1a385155b0aeac40437dba6e56577" - } -} diff --git a/example/z-playground/package-lock.json b/example/z-playground/package-lock.json deleted file mode 100644 index f34206be..00000000 --- a/example/z-playground/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dream-playground", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "codemirror": { - "version": "5.61.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.0.tgz", - "integrity": "sha512-D3wYH90tYY1BsKlUe0oNj2JAhQ9TepkD51auk3N7q+4uz7A/cgJ5JsWHreT0PqieW1QhOuqxQ2reCXV1YXzecg==" - } - } -} diff --git a/example/z-playground/package.json b/example/z-playground/package.json deleted file mode 100644 index 224b9483..00000000 --- a/example/z-playground/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "dream-playground", - "dependencies": { - "codemirror": "*" - }, - "scripts": { - "start": "npm run bundle && opam exec -- dune exec server/playground.exe" - } -} diff --git a/example/z-playground/playground.opam b/example/z-playground/playground.opam deleted file mode 100644 index d3ea9a3e..00000000 --- a/example/z-playground/playground.opam +++ /dev/null @@ -1,9 +0,0 @@ -opam-version: "2.0" - -depends: [ - "caqti-driver-sqlite3" - "dune" {>= "2.0.0"} - "ocaml" - "ppx_yojson_conv" - "reason" -] diff --git a/example/z-playground/runtime/dune b/example/z-playground/runtime/dune deleted file mode 100644 index 0a30838c..00000000 --- a/example/z-playground/runtime/dune +++ /dev/null @@ -1,9 +0,0 @@ -(library - (name runtime) - (wrapped false) - (libraries dream)) - -(rule - (targets playground.ml) - (deps playground.eml.ml) - (action (run dream_eml %{deps} --workspace %{workspace_root}))) diff --git a/example/z-playground/runtime/playground.eml.ml b/example/z-playground/runtime/playground.eml.ml deleted file mode 100644 index c959f54f..00000000 --- a/example/z-playground/runtime/playground.eml.ml +++ /dev/null @@ -1,62 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -let welcome = - - - - - - - - -

Welcome to the Dream Playground!

-

- Edit the code to the left, and press Run to recompile! Use - the navigation bar above to visit different paths on your server. Many of - the - examples are loaded into the playground. For example, try - dream.as/2-middleware. -

-

Links:

- -

Loaded examples:

- - - diff --git a/example/z-playground/runtime/runtime.opam b/example/z-playground/runtime/runtime.opam deleted file mode 100644 index 0d2ac090..00000000 --- a/example/z-playground/runtime/runtime.opam +++ /dev/null @@ -1,11 +0,0 @@ -opam-version: "2.0" - -depends: [ - "dream" - "dune" {>= "2.0.0"} - "ocaml" -] - -build: [ - ["dune" "build" "-p" name "-j" jobs] -] diff --git a/example/z-playground/sandbox/ocaml/keep b/example/z-playground/sandbox/ocaml/keep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/z-playground/sandbox/ocaml/server.eml.ml b/example/z-playground/sandbox/ocaml/server.eml.ml deleted file mode 100644 index 4876aa13..00000000 --- a/example/z-playground/sandbox/ocaml/server.eml.ml +++ /dev/null @@ -1,7 +0,0 @@ -let () = - Dream.run ~interface:"0.0.0.0" - @@ Dream.logger - @@ Dream.router [ - Dream.get "/" (fun _ -> Dream.html Playground.welcome); - ] - @@ Dream.not_found diff --git a/example/z-playground/sandbox/reason/keep b/example/z-playground/sandbox/reason/keep deleted file mode 100644 index e69de29b..00000000 diff --git a/example/z-playground/sandbox/reason/server.eml.re b/example/z-playground/sandbox/reason/server.eml.re deleted file mode 100644 index 03f3fbca..00000000 --- a/example/z-playground/sandbox/reason/server.eml.re +++ /dev/null @@ -1,7 +0,0 @@ -let () = - Dream.run(~interface="0.0.0.0") - @@ Dream.logger - @@ Dream.router([ - Dream.get("/", _ => Dream.html(Playground.welcome)), - ]) - @@ Dream.not_found diff --git a/example/z-playground/server/build.sh b/example/z-playground/server/build.sh deleted file mode 100644 index 9e5e4101..00000000 --- a/example/z-playground/server/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e -set -x - -mkdir -p static -cp node_modules/codemirror/lib/codemirror.js static/ -cp node_modules/codemirror/lib/codemirror.css static/ -cp node_modules/codemirror/theme/material.css static/ -cp node_modules/codemirror/mode/mllike/mllike.js static/ -cp client/playground.css static/ -cp client/playground.js static/ -opam exec -- dune build server/playground.exe diff --git a/example/z-playground/server/deploy.sh b/example/z-playground/server/deploy.sh deleted file mode 100644 index ba2ba326..00000000 --- a/example/z-playground/server/deploy.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -e -set -x - -sudo cp \ - /home/playground/playground/example/z-playground/server/playground.service \ - /etc/systemd/system -sudo chmod a-x /etc/systemd/system/playground.service -sudo systemctl daemon-reload -sudo systemctl stop playground -(cd /home/playground/playground/example/z-playground \ - && sudo -H -u playground bash server/build.sh) -sudo cp \ - /home/playground/playground/_build/default/example/z-playground/server/playground.exe \ - /usr/local/bin/playground -sudo chown root:root /usr/local/bin/playground -sudo systemctl start playground diff --git a/example/z-playground/server/dune b/example/z-playground/server/dune deleted file mode 100644 index ce57b96e..00000000 --- a/example/z-playground/server/dune +++ /dev/null @@ -1,4 +0,0 @@ -(executable - (name playground) - (libraries client dream) - (preprocess (pps lwt_ppx))) diff --git a/example/z-playground/server/playground.ml b/example/z-playground/server/playground.ml deleted file mode 100644 index 39f3c111..00000000 --- a/example/z-playground/server/playground.ml +++ /dev/null @@ -1,563 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -(* Sandboxes. *) - -type syntax = [ `OCaml | `Reason ] - -let (//) = Filename.concat - -let sandbox_root = "sandbox" - -let sandbox_dune = {|(executable - (name server) - (libraries caqti caqti-driver-sqlite3 dream runtime tyxml) - (preprocess (pps lwt_ppx ppx_yojson_conv))) - -(rule - (targets server.ml) - (deps server.eml.ml) - (action (run dream_eml %{deps} --workspace %{workspace_root}))) -|} - -let sandbox_dune_re = {|(executable - (name server) - (libraries caqti caqti-driver-sqlite3 dream runtime tyxml) - (preprocess (pps lwt_ppx ppx_yojson_conv))) - -(rule - (targets server.re) - (deps server.eml.re) - (action (run dream_eml %{deps} --workspace %{workspace_root}))) -|} - -let sandbox_dune_no_eml = {|(executable - (name server) - (libraries caqti caqti-driver-sqlite3 dream runtime tyxml) - (preprocess (pps lwt_ppx ppx_yojson_conv tyxml-jsx tyxml-ppx))) -|} - -let base_dockerfile = {|FROM ubuntu:focal-20210416 -RUN apt update && apt install -y openssl libev4 libsqlite3-0 -WORKDIR /www -COPY db.sqlite db.sqlite -RUN chmod -R 777 . -USER 112:3000 -ENTRYPOINT /www/server.exe -|} - -let base_dockerignore = {|* -!db.sqlite|} - -let sandbox_dockerfile = {|FROM base:base -COPY server.exe server.exe -|} - -let exec format = - Printf.ksprintf (fun command -> Lwt_process.(exec (shell command))) format - -let create_sandboxes_directory () = - match%lwt Lwt_unix.mkdir sandbox_root 0o755 with - | () -> Lwt.return_unit - | exception Unix.(Unix_error (EEXIST, _, _)) -> Lwt.return_unit - -let exists sandbox = - Lwt_unix.file_exists (sandbox_root // sandbox) - -let write_file sandbox file content = - Lwt_io.(with_file - ~mode:Output (sandbox_root // sandbox // file) (fun channel -> - write channel content)) - -let create_named sandbox syntax eml code = - Dream.info (fun log -> log "Sandbox %s: creating" sandbox); - begin match%lwt Lwt_unix.mkdir (sandbox_root // sandbox) 0o755 with - | () -> Lwt.return_unit - | exception Unix.(Unix_error (EEXIST, _, _)) -> Lwt.return_unit - end;%lwt - let filename = - match syntax, eml with - | `OCaml, false -> "server.ml" - | `Reason, false -> "server.re" - | `OCaml, true -> "server.eml.ml" - | `Reason, true -> "server.eml.re" - in - write_file sandbox filename code;%lwt - Lwt.return sandbox - -let rec create ?(attempts = 3) syntax eml code = - match attempts with - | 0 -> failwith "Unable to create sandbox directory" - | attempts -> - let sandbox = Dream.random 9 |> Dream.to_base64url in - match sandbox.[0] with - | '_' | '-' -> create ~attempts syntax eml code - | _ -> - match%lwt exists sandbox with - | true -> create ~attempts:(attempts - 1) syntax eml code - | false -> create_named sandbox syntax eml code - -let read sandbox = - let%lwt no_eml_exists = - Lwt_unix.file_exists (sandbox_root // sandbox // "no-eml") in - let eml = not no_eml_exists in - let base = if eml then "server.eml" else "server" in - let ocaml_promise = - Lwt_io.(with_file - ~mode:Input (sandbox_root // sandbox // base ^ ".ml") read) - in - match%lwt ocaml_promise with - | content -> Lwt.return (content, `OCaml, eml) - | exception _ -> - let%lwt content = - Lwt_io.(with_file - ~mode:Input (sandbox_root // sandbox // base ^ ".re") read) - in - Lwt.return (content, `Reason, eml) - -let init_client socket content = - `Assoc [ - "kind", `String "content"; - "payload", `String content; - ] - |> Yojson.Basic.to_string - |> Dream.write socket - -let validate_id sandbox = - String.length sandbox > 0 && Dream.from_base64url sandbox <> None - - - -(* Session state transitions. *) - -type container = { - container_id : string; - port : int; -} - -type session = { - mutable container : container option; - mutable sandbox : string; - syntax : syntax; - eml : bool; - socket : Dream.response; -} - -let allocated_ports = - Hashtbl.create 1024 - -let kill_container session = - match session.container with - | None -> Lwt.return_unit - | Some {container_id; port} -> - session.container <- None; - Dream.info (fun log -> - log "Sandbox %s: killing container %s" session.sandbox container_id); - let%lwt _status = - exec "docker kill %s > /dev/null 2> /dev/null" container_id in - Hashtbl.remove allocated_ports port; - Lwt.return_unit - -let min_port = 9000 -let max_port = 9999 - -let next_port = - ref min_port - -(* This can fail if there is a huge number of sandboxes, or very large spikes in - sandbox creation. However, the failure is not catastrophic. *) -let rec allocate_port () = - let port = !next_port in - incr next_port; - let%lwt () = - if !next_port > max_port then begin - next_port := min_port; - Lwt.pause () - end - else - Lwt.return_unit - in - if Hashtbl.mem allocated_ports port then - allocate_port () - else begin - Hashtbl.replace allocated_ports port (); - Lwt.return port - end - -let client_log ?(add_newline = false) session message = - let message = - if add_newline then message ^ "\n" - else message - in - `Assoc [ - "kind", `String "log"; - "payload", `String message; - ] - |> Yojson.Basic.to_string - |> Dream.write session.socket - -let build_sandbox sandbox syntax eml = - let dune = - match syntax, eml with - | _, false -> sandbox_dune_no_eml - | `OCaml, true -> sandbox_dune - | `Reason, true -> sandbox_dune_re - in - write_file sandbox "dune" dune;%lwt - begin - if eml then - Lwt.return_unit - else - write_file sandbox "no-eml" "" - end;%lwt - let%lwt _status = exec "rm -f %s/server.exe" (sandbox_root // sandbox) in - let process = - Printf.sprintf - "cd %s && opam exec %s -- dune build %s ./server.exe 2>&1" - (sandbox_root // sandbox) "--color=always" "--no-print-directory" - |> Lwt_process.shell - |> new Lwt_process.process_in in - let%lwt output = Lwt_io.read process#stdout in - match%lwt process#close with - | Unix.WEXITED 0 -> - let%lwt _status = - exec - "cp ../../_build/default/example/z-playground/%s/server.exe %s" - (sandbox_root // sandbox) (sandbox_root // sandbox) - in - Lwt.return None - | _ -> - Lwt.return (Some output) - -let build session = - match%lwt build_sandbox session.sandbox session.syntax session.eml with - | None -> - Dream.info (fun log -> log "Sandbox %s: build succeeded" session.sandbox); - Lwt.return_true - | Some output -> - Dream.info (fun log -> - log "Sandbox %s: sending build output" session.sandbox); - client_log session output;%lwt - Lwt.return_false - -let image_exists sandbox = - match%lwt exec "docker image inspect sandbox:%s 2>&1 > /dev/null" sandbox with - | Unix.WEXITED 0 -> Lwt.return_true - | _ -> Lwt.return_false - -let image_sandbox sandbox = - write_file sandbox "Dockerfile" sandbox_dockerfile;%lwt - let%lwt _status = - exec "cd %s && docker build -t sandbox:%s . 2>&1" - (sandbox_root // sandbox) sandbox in - Lwt.return_unit - -let image session = - image_sandbox session.sandbox;%lwt - Dream.info (fun log -> log "Sandbox %s: built image" session.sandbox); - Lwt.return_unit - -let started session port = - `Assoc [ - "kind", `String "started"; - "sandbox", `String session.sandbox; - "port", `Int port; - ] - |> Yojson.Basic.to_string - |> Dream.write session.socket - -let rec make_container_id () = - let candidate = Dream.random 9 |> Dream.to_base64url in - match candidate.[0] with - | '_' | '-' -> make_container_id () - | _ -> candidate - -let run session = - let alive, signal_alive = Lwt.wait () in - let signalled = ref false in - let signal_alive () = - if !signalled then - () - else begin - signalled := true; - Lwt.wakeup_later signal_alive () - end - in - let%lwt port = allocate_port () in - let container_id = make_container_id () in - session.container <- Some {container_id; port}; - Lwt.async begin fun () -> - Printf.sprintf - "docker run -p %i:8080 --name %s --rm -t sandbox:%s 2>&1" - port container_id session.sandbox - |> Lwt_process.shell - |> Lwt_process.pread_lines - |> Lwt_stream.iter_s (fun line -> - signal_alive (); - client_log ~add_newline:true session line) - end; - alive;%lwt - started session port;%lwt - Dream.info (fun log -> - log "Sandbox %s: started %s on port %i" session.sandbox container_id port); - Lwt.return_unit - -let kill session = - let%lwt () = kill_container session in - Dream.close session.socket - - - -(* Main loop for each connected client WebSocket. *) - -let gc_running = - ref None - -let notify_gc = - ref ignore - -let sandbox_users = - ref 0 - -let sandbox_locks = - Hashtbl.create 256 - -let lock_sandbox sandbox f = - begin match !gc_running with - | None -> Lwt.return_unit - | Some finished -> finished - end;%lwt - - incr sandbox_users; - let mutex = - match Hashtbl.find_opt sandbox_locks sandbox with - | Some mutex -> mutex - | None -> - let mutex = Lwt_mutex.create () in - Hashtbl.add sandbox_locks sandbox mutex; - mutex - in - Lwt.finalize - (fun () -> Lwt_mutex.with_lock mutex f) - (fun () -> - decr sandbox_users; - if !sandbox_users = 0 then - !notify_gc (); - Lwt.return_unit) - -let rec listen session = - match%lwt Dream.read session.socket with - | None -> - Dream.info (fun log -> log "WebSocket closed by client"); - kill session - | Some code -> - - Dream.info (fun log -> log "Sandbox %s: code update" session.sandbox); - ignore (kill_container session); - - lock_sandbox session.sandbox begin fun () -> - - let%lwt current_code, _, _ = read session.sandbox in - if code = current_code then - Lwt.return_unit - else begin - let%lwt sandbox = create session.syntax session.eml code in - session.sandbox <- sandbox; - Lwt.return_unit - end;%lwt - - match%lwt image_exists session.sandbox with - | true -> run session - | false -> - match%lwt build session with - | false -> Lwt.return_unit - | true -> - image session;%lwt - run session - end;%lwt - - listen session - -let listen session = - try%lwt - listen session - with exn -> - kill session;%lwt - raise exn - - - -let rec gc ?(initial = true) () = - let next = Lwt_unix.sleep 3600. in - - let%lwt keep = - Lwt_process.shell "ls sandbox/*/keep | awk -F / '{print $2}'" - |> Lwt_process.pread_lines - |> Lwt_stream.to_list in - - let can_start, signal_can_start = Lwt.wait () in - let finished, signal_finished = Lwt.wait () in - - gc_running := Some finished; - - if !sandbox_users = 0 then - Lwt.return_unit - else begin - notify_gc := - (fun () -> Lwt.wakeup_later signal_can_start (); notify_gc := ignore); - can_start - end;%lwt - - Lwt.finalize begin fun () -> - Dream.log "Running playground GC"; - - let%lwt images = - Lwt_process.shell "docker images | awk '{print $1, $2, $3}'" - |> Lwt_process.pread_lines - |> Lwt_stream.to_list - in - - let images = - images - |> List.tl - |> List.map (String.split_on_char ' ') - |> List.filter_map (function - | ["base"; _; _] -> None - | ["ubuntu"; _; ] -> None - | ["sandbox"; tag; _] when List.mem tag keep -> None - | [_; _; id] -> Some id - | _ -> None) - in - - let%lwt _status = exec "docker rmi %s" (String.concat " " images) in - - Lwt_unix.files_of_directory "sandbox" - |> Lwt_stream.iter_n ~max_concurrency:16 begin fun sandbox -> - if List.mem sandbox keep then - Lwt.return_unit - else - let%lwt _status = exec "rm -rf sandbox/%s/_build" sandbox in - Lwt.return_unit - end;%lwt - - Hashtbl.reset sandbox_locks; - - Lwt.return_unit - end - (fun () -> - gc_running := None; - Lwt.wakeup_later signal_finished (); - Lwt.return_unit);%lwt - - Dream.log "Warming caches"; - - keep |> Lwt_list.iteri_s begin fun index sandbox -> - Lwt_unix.sleep 1.;%lwt - if initial then - Dream.log "Warming %s (%i/%i)" sandbox (index + 1) (List.length keep); - lock_sandbox sandbox (fun () -> - if%lwt image_exists sandbox then - Lwt.return_unit - else begin - let%lwt _, syntax, eml = read sandbox in - let%lwt _ = build_sandbox sandbox syntax eml in - image_sandbox sandbox - end) - end;%lwt - - next;%lwt - gc ~initial:false () - - - -(* Entry point. *) - -let () = - Dream.log "Starting playground"; - - (* Stop when systemd sends SIGTERM. *) - let stop, signal_stop = Lwt.wait () in - Lwt_unix.on_signal Sys.sigterm (fun _signal -> - Lwt.wakeup_later signal_stop ()) - |> ignore; - - (* Build the base image. *) - Lwt_main.run begin - Lwt_io.(with_file ~mode:Output "Dockerfile" (fun channel -> - write channel base_dockerfile));%lwt - Lwt_io.(with_file ~mode:Output ".dockerignore" (fun channel -> - write channel base_dockerignore));%lwt - let%lwt _status = exec "docker build -t base:base . 2>&1" in - Lwt.return_unit - end; - - (* Start the sandbox gc. *) - Lwt.async gc; - - (* Start the Web server. *) - let playground_handler request = - let sandbox = Dream.param request "id" in - match validate_id sandbox with - | false -> Dream.empty `Not_Found - | true -> - match%lwt exists sandbox with - | false -> Dream.empty `Not_Found - | true -> - let%lwt example = - match sandbox.[1] with - | '-' -> - if%lwt Lwt_unix.file_exists (sandbox_root // sandbox // "keep") then - Lwt.return (Some sandbox) - else - Lwt.return_none - | _ -> Lwt.return_none - | exception _ -> Lwt.return_none - in - Dream.html (Client.html example) - in - - Dream.run ~interface:"0.0.0.0" ~port:80 ~stop ~adjust_terminal:false - @@ Dream.logger - @@ Dream.router [ - - (* The client will send a default sandbox id in this case. *) - Dream.get "/" (fun _ -> - Dream.html (Client.html None)); - - (* Upon request for /socket?sandbox=id, send the code in the sandbox to the - client, and then enter the "REPL." Not bothering with nice replies or - nice error handling here, because a valid client won't trigger them. If - they occur, they are harmless to the server. *) - Dream.get "/socket" (fun request -> - match Dream.query "sandbox" request with - | None -> Dream.empty `Bad_Request - | Some sandbox -> - match validate_id sandbox with - | false -> Dream.empty `Bad_Request - | true -> - (* Read the sandbox. If the requested sandbox doesn't exist, this will - raise an exception, causing a 500 reply to the JavaScript client. *) - let%lwt content, syntax, eml = read sandbox in - Dream.websocket (fun socket -> - init_client socket content;%lwt - Dream.info (fun log -> - log "Sandbox %s: content sent to client" sandbox); - listen {container = None; sandbox; syntax; eml; socket})); - - (* Serve scripts and CSS. *) - Dream.get "/static/**" (Dream.static "./static"); - - (* For sandbox ids, respond with the sandbox page. *) - Dream.get "/:id" playground_handler; - Dream.get "/:id/**" playground_handler; - - ] - @@ Dream.not_found; - - Dream.log "Killing all containers"; - Sys.command "docker kill $(docker ps -q)" |> ignore; - Dream.log "Exiting" diff --git a/example/z-playground/server/playground.service b/example/z-playground/server/playground.service deleted file mode 100644 index 732c14d6..00000000 --- a/example/z-playground/server/playground.service +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=Dream Playground -After=network.target -Requires=docker.service - -[Service] -Type=simple -User=playground -Restart=on-failure -RestartSec=1 -StandardOutput=journal -WorkingDirectory=/home/playground/playground/example/z-playground -ExecStart=/usr/local/bin/playground -AmbientCapabilities=CAP_NET_BIND_SERVICE - -[Install] -WantedBy=multi-user.target diff --git a/example/z-playground/server/setup.sh b/example/z-playground/server/setup.sh deleted file mode 100644 index 2657f90b..00000000 --- a/example/z-playground/server/setup.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -# Upon getting a fresh Droplet (virtual machine), the system packages inside the -# image it was made from are likely somewhat out of date. Upgrade them -# immediately. -sudo apt update -sudo apt -y upgrade - -# A restart is likely needed, as there is often a kernel upgrade. -sudo init 6 - -# Install the latest Docker. We use an APT repository for the absolute latest -# release, including all the latest security features. The commands are based on -# https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04 -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - -sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" -sudo apt update -sudo apt install -y docker-ce - -# Install packages required for building OCaml projects and opam, including a C -# compiler as part of build-essential. -sudo apt install -y build-essential m4 unzip bubblewrap pkg-config - -# Install opam itself. -wget -O opam https://github.com/ocaml/opam/releases/download/2.0.8/opam-2.0.8-x86_64-linux -sudo mv opam /usr/local/bin/ -sudo chmod a+x /usr/local/bin/opam - -# Install npm, which we use to build the client. -sudo apt install -y npm - -# Install system libraries that will be needed by Dream. -sudo apt install -y libev-dev libsqlite3-dev libssl-dev pkg-config - -# Create users. User playground is used for building and running the playground. -# The reason there isn't a separate user for buulding it is that the playground -# itself will use the build setup to build the sandboxes. User sandbox is for -# the containers. -sudo adduser --disabled-password playground -sudo usermod -a -G docker playground -sudo -H -u playground mkdir /home/playground/.ssh -m 700 -sudo cp .ssh/authorized_keys /home/playground/.ssh/ -sudo chown playground:playground /home/playground/.ssh/authorized_keys -sudo adduser --system sandbox - -# Initialize opam and install a compiler. -sudo -H -u playground opam init --no-setup --bare -sudo -H -u playground opam switch create 4.12.0 - -# Set up UFW. -sudo ufw allow ssh -sudo ufw allow http -sudo ufw enable diff --git a/example/z-playground/server/sync.sh b/example/z-playground/server/sync.sh deleted file mode 100644 index 43b1db92..00000000 --- a/example/z-playground/server/sync.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash - -set -e -set -x - -HOST=$1 -DIR=playground/example/z-playground - -rsync -v $HOST:$DIR/package-lock.json $HOST:$DIR/opam-switch . || true -rsync -rlv --exclude node_modules \ - ../../dream.opam ../../dune-project ../../src $HOST:playground -ssh $HOST "mkdir -p $DIR" -rsync -rlv . $HOST:$DIR - -set +x - -mkdir -p ./sync-temp/runtime -echo "let list = [" > ./sync-temp/runtime/examples.ml - -function index_example { - EXAMPLE=$1 - echo " \"$EXAMPLE\";" >> ./sync-temp/runtime/examples.ml -} - -function example { - EXAMPLE=$1 - mkdir -p ./sync-temp/sandbox/$1 - cat ../$1/*.ml | sed 's/Dream\.run/Dream\.run ~interface:"0.0.0.0"/g' \ - > ./sync-temp/sandbox/$1/server.eml.ml - touch ./sync-temp/sandbox/$1/keep - index_example $EXAMPLE -} -example 1-hello -example 2-middleware -example 3-router -example 4-counter -example 5-promise -example 6-echo -example 7-template -example 8-debug -example 9-error -example a-log -example b-session -example c-cookie -example d-form -example e-json -example g-upload -example h-sql -example i-graphql -example j-stream -example k-websocket -example w-graphql-subscription -example w-flash -example w-content-security-policy -example w-long-polling -example w-multipart-dump -example w-query -example w-server-sent-events -example w-template-stream -example w-tyxml -example w-upload-stream -example w-chat -touch ./sync-temp/sandbox/w-tyxml/no-eml -mv ./sync-temp/sandbox/w-tyxml/server.eml.ml \ - ./sync-temp/sandbox/w-tyxml/server.ml - -function example_re { - EXAMPLE=$1 - mkdir -p ./sync-temp/sandbox/$1 - cat ../$1/*.re \ - | sed 's/Dream\.run(/Dream\.run(~interface="0.0.0.0", /g' \ - | sed 's/Dream\.run$/Dream\.run(~interface="0.0.0.0")/g' \ - > ./sync-temp/sandbox/$1/server.eml.re - touch ./sync-temp/sandbox/$1/keep - index_example $EXAMPLE -} -example_re r-hello -example_re r-template -example_re r-template-stream -example_re r-graphql -example_re r-tyxml -touch ./sync-temp/sandbox/r-tyxml/no-eml -mv ./sync-temp/sandbox/r-tyxml/server.eml.re \ - ./sync-temp/sandbox/r-tyxml/server.re - -echo "]" >> ./sync-temp/runtime/examples.ml - -cp ../h-sql/db.sqlite ./sync-temp/ - -set -x - -rsync -rlv ./sync-temp/* $HOST:$DIR -rm -rf sync-temp -ssh $HOST "touch playground/dune-workspace" - -set +x - -echo -echo "If this is the first sync, run as playground@$HOST in ~/playground:" -echo " opam install --deps-only ." -echo " opam switch export opam-switch" -echo " npm install" -echo "Then, as root@$HOST:" -echo " systemctl enable playground" diff --git a/example/z-systemd/app.ml b/example/z-systemd/app.ml index 98261d50..1feda01c 100644 --- a/example/z-systemd/app.ml +++ b/example/z-systemd/app.ml @@ -1,5 +1,6 @@ let () = - Dream.run ~interface:"0.0.0.0" ~port:80 + Eio_main.run @@ fun env -> + Dream.run ~interface:"0.0.0.0" ~port:80 env @@ Dream.logger @@ Dream.router [ Dream.get "/" (fun _ -> Dream.html "Dream started by systemd!"); diff --git a/src/dream.ml b/src/dream.ml index 72d291cb..b0adff18 100644 --- a/src/dream.ml +++ b/src/dream.ml @@ -147,12 +147,12 @@ let all_cookies = Cookie.all_cookies (* Bodies *) -let body = Message.body +let body x = Lwt_eio.Promise.await_lwt (Message.body x) let set_body = Message.set_body -let read = Message.read -let write = Message.write -let flush = Message.flush -let close = Message.close +let read body = Lwt_eio.Promise.await_lwt (Message.read body) +let write ?kind response data = Lwt_eio.Promise.await_lwt (Message.write ?kind response data) +let flush response = Lwt_eio.Promise.await_lwt (Message.flush response) +let close ?code msg = Lwt_eio.Promise.await_lwt (Message.close ?code msg) type buffer = Stream.buffer type stream = Stream.stream let client_stream = Message.client_stream @@ -178,12 +178,12 @@ let origin_referrer_check = Origin_referrer_check.origin_referrer_check (* Forms *) type 'a form_result = 'a Form.form_result -let form = Form.form ~now +let form ?csrf x = Lwt_eio.Promise.await_lwt (Form.form ~now ?csrf x) type multipart_form = Upload.multipart_form -let multipart = Upload.multipart ~now +let multipart ?csrf x = Lwt_eio.Promise.await_lwt (Upload.multipart ~now ?csrf x) type part = Upload.part -let upload = Upload.upload -let upload_part = Upload.upload_part +let upload request = Lwt_eio.Promise.await_lwt (Upload.upload request) +let upload_part request = Lwt_eio.Promise.await_lwt (Upload.upload_part request) type csrf_result = Csrf.csrf_result let csrf_token = Csrf.csrf_token ~now let verify_csrf_token = Csrf.verify_csrf_token ~now @@ -265,7 +265,7 @@ let graphiql = Graphql.graphiql (* SQL *) let sql_pool = Sql.sql_pool -let sql = Sql.sql +let sql req fn = Lwt_eio.Promise.await_lwt (Sql.sql req fn) @@ -370,7 +370,11 @@ let test ?(prefix = "") handler request = @@ handler in - Lwt_main.run (app request) + let result = ref None in + Eio_main.run (fun _env -> + result := Some (app request) + ); + Option.get !result let sort_headers = Message.sort_headers let echo = Echo.echo diff --git a/src/dream.mli b/src/dream.mli index 24ca0863..786f8d04 100644 --- a/src/dream.mli +++ b/src/dream.mli @@ -4,7 +4,6 @@ Copyright 2021 Anton Bachin *) - (** {1 Types} Dream is built on just five types. The first two are the data types of @@ -19,7 +18,7 @@ and response = server message (** The remaining three types are for building up Web apps. *) -and handler = request -> response promise +and handler = request -> response (** Handlers are asynchronous functions from requests to responses. Example {{:https://github.com/aantron/dream/tree/master/example/1-hello#files} [1-hello]} \[{{:http://dream.as/1-hello} playground}\] shows the simplest @@ -471,7 +470,7 @@ val respond : ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> - string -> response promise + string -> response (** Same as {!Dream.val-response}, but the new {!type-response} is wrapped in a {!type-promise}. *) @@ -479,7 +478,7 @@ val html : ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> - string -> response promise + string -> response (** Same as {!Dream.respond}, but adds [Content-Type: text/html; charset=utf-8]. See {!Dream.text_html}. @@ -493,7 +492,7 @@ val json : ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> - string -> response promise + string -> response (** Same as {!Dream.respond}, but adds [Content-Type: application/json]. See {!Dream.application_json}. *) @@ -501,7 +500,7 @@ val redirect : ?status:[< redirection ] -> ?code:int -> ?headers:(string * string) list -> - request -> string -> response promise + request -> string -> response (** Creates a new {!type-response}. Adds a [Location:] header with the given string. The default status code is [303 See Other], for a temporary redirection. Use [~status:`Moved_Permanently] or [~code:301] for a permanent @@ -515,14 +514,14 @@ val redirect : val empty : ?headers:(string * string) list -> - status -> response promise + status -> response (** Same as {!Dream.val-response} with the empty string for a body. *) val stream : ?status:[< status ] -> ?code:int -> ?headers:(string * string) list -> - (response -> unit promise) -> response promise + request -> (response -> unit) -> response (** Same as {!Dream.val-respond}, but calls {!Dream.set_stream} internally to prepare the response for stream writing, and then runs the callback asynchronously to do it. See example @@ -531,14 +530,14 @@ val stream : {[ fun request -> - Dream.stream (fun response -> - let%lwt () = Dream.write response "foo" in + Dream.stream request (fun response -> + Dream.write response "foo"; Dream.close_stream response) ]} *) val websocket : ?headers:(string * string) list -> - (response -> unit promise) -> response promise + request -> (response -> unit) -> response (** Creates a fresh [101 Switching Protocols] response. Once this response is returned to Dream's HTTP layer, the callback is passed a new {!type-websocket}, and the application can begin using it. See example @@ -746,7 +745,7 @@ val all_cookies : request -> (string * string) list (** {1 Bodies} *) -val body : 'a message -> string promise +val body : 'a message -> string (** Retrieves the entire body. See example {{:https://github.com/aantron/dream/tree/master/example/6-echo#files} [6-echo]}. *) @@ -764,7 +763,7 @@ https://aantron.github.io/dream/#val-set_body (** {2 Streaming} *) -val read : 'a message -> string option promise +val read : 'a message -> string option (** Retrieves a body chunk. The chunk is not buffered, thus it can only be read once. See example {{:https://github.com/aantron/dream/tree/master/example/j-stream#files} @@ -780,15 +779,15 @@ https://aantron.github.io/dream/#val-set_stream "] (**/**) -val write : ?kind:[< `Text | `Binary ] -> response -> string -> unit promise +val write : ?kind:[< `Text | `Binary ] -> response -> string -> unit (** Streams out the string. The promise is fulfilled when the response can accept more writes. *) (* TODO Document clearly which of the writing functions can raise exceptions. *) -val flush : response -> unit promise +val flush : response -> unit (** Flushes write buffers. Data is sent to the client. *) -val close : ?code:int -> 'a message -> unit promise +val close : ?code:int -> 'a message -> unit (** Finishes the response stream. *) (* TODO Fix comment. *) @@ -857,7 +856,7 @@ val close_stream : (**/**) val write_buffer : - ?offset:int -> ?length:int -> response -> buffer -> unit promise + ?offset:int -> ?length:int -> response -> buffer -> unit [@@ocaml.deprecated "Use Dream.write_stream. See https://aantron.github.io/dream/#val-write_stream @@ -952,7 +951,7 @@ type 'a form_result = [ activity, or tokens so old that decryption keys have since been rotated on the server. *) -val form : ?csrf:bool -> request -> (string * string) list form_result promise +val form : ?csrf:bool -> request -> (string * string) list form_result (** Parses the request body as a form. Performs CSRF checks. Use {!Dream.form_tag} in a template to transparently generate forms that will pass these checks. See {!section-templates} and example @@ -1043,7 +1042,7 @@ type multipart_form = OWASP {i File Upload Cheat Sheet}} for security precautions for upload forms. *) -val multipart : ?csrf:bool -> request -> multipart_form form_result promise +val multipart : ?csrf:bool -> request -> multipart_form form_result (** Like {!Dream.form}, but also reads files, and [Content-Type:] must be [multipart/form-data]. The [
] tag and CSRF token can be generated in a template with @@ -1076,7 +1075,7 @@ type part = string option * string option * ((string * string) list) Note that, in the general case, [filename] and [headers] are not reliable. [name] is the form field name. *) -val upload : request -> part option promise +val upload : request -> part option (** Retrieves the next upload part. Upon getting [Some (name, filename, headers)] from this function, the user @@ -1095,7 +1094,7 @@ val upload : request -> part option promise [FormData]} in the client to submit [multipart/form-data] by AJAX, and include a custom header. *) -val upload_part : request -> string option promise +val upload_part : request -> string option (** Retrieves a part chunk. *) (** {2 CSRF tokens} @@ -1501,14 +1500,14 @@ val mime_lookup : string -> (string * string) list val session : string -> request -> string option (** Value from the request's session. *) -val put_session : string -> string -> request -> unit promise +val put_session : string -> string -> request -> unit (** Mutates a value in the request's session. The back end may commit the value to storage immediately, so this function returns a promise. *) val all_session_values : request -> (string * string) list (** Full session dictionary. *) -val invalidate_session : request -> unit promise +val invalidate_session : request -> unit (** Invalidates the request's session, replacing it with a fresh, empty pre-session. *) @@ -1579,7 +1578,7 @@ https://aantron.github.io/dream/#type-stream (**/**) (**/**) -val send : ?kind:[< `Text | `Binary ] -> response -> string -> unit promise +val send : ?kind:[< `Text | `Binary ] -> response -> string -> unit [@@ocaml.deprecated "Use Dream.write. See https://aantron.github.io/dream/#val-write @@ -1596,7 +1595,7 @@ https://aantron.github.io/dream/#val-write {{:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType} MDN, [WebSocket.binaryType]}. *) -val receive : response -> string option promise +val receive : response -> string option [@@ocaml.deprecated "Use Dream.read. See https://aantron.github.io/dream/#val-read @@ -1606,7 +1605,7 @@ https://aantron.github.io/dream/#val-read (**/**) (**/**) -val close_websocket : ?code:int -> response -> unit promise +val close_websocket : ?code:int -> response -> unit [@@ocaml.deprecated "Use Dream.close. See https://aantron.github.io/dream/#val-close @@ -1731,7 +1730,7 @@ val graphiql : ?default_query:string -> string -> handler val sql_pool : ?size:int -> string -> middleware (** Makes an SQL connection pool available to its inner handler. *) -val sql : request -> (Caqti_lwt.connection -> 'a promise) -> 'a promise +val sql : request -> (Caqti_lwt.connection -> 'a promise) -> 'a (** Runs the callback with a connection from the SQL pool. See example {{:https://github.com/aantron/dream/tree/master/example/h-sql#files} [h-sql]}. @@ -2006,7 +2005,7 @@ type error = { [true]. }} *) -type error_handler = error -> response option promise +type error_handler = error -> response option (** Error handlers log errors and convert them into responses. Ignore if using {!Dream.error_template}. @@ -2022,7 +2021,7 @@ type error_handler = error -> response option promise (* TODO Get rid of the option? *) val error_template : - (error -> string -> response -> response promise) -> error_handler + (error -> string -> response -> response) -> error_handler (** Builds an {!error_handler} from a template. See example {{:https://github.com/aantron/dream/tree/master/example/9-error#files} [9-error]} \[{{:http://dream.as/9-error} playground}\]. @@ -2067,7 +2066,7 @@ val debug_error_handler : error_handler (** An {!error_handler} for showing extra information about requests and exceptions, for use during development. *) -val catch : (error -> response promise) -> middleware +val catch : (error -> response) -> middleware (** Forwards exceptions, rejections, and [4xx], [5xx] responses from the application to the error handler. See {!section-errors}. *) (* TODO Error handler should not return an option, and then the type can be @@ -2080,7 +2079,6 @@ val catch : (error -> response promise) -> middleware val run : ?interface:string -> ?port:int -> - ?stop:unit promise -> ?error_handler:error_handler -> ?https:bool -> ?certificate_file:string -> @@ -2088,6 +2086,7 @@ val run : ?builtins:bool -> ?greeting:bool -> ?adjust_terminal:bool -> + < clock:#Eio.Time.clock; net:#Eio.Net.t; ..> -> handler -> unit (** Runs the Web application represented by the {!handler}, by default at {{:http://localhost:8080} http://localhost:8080}. @@ -2099,10 +2098,6 @@ val run : - [~interface] is the network interface to listen on. Defaults to ["localhost"]. Use ["0.0.0.0"] to listen on all interfaces. - [~port] is the port to listen on. Defaults to [8080]. - - [~stop] is a promise that causes the server to stop accepting new - requests, and {!Dream.run} to return. Requests that have already entered - the Web application continue to be processed. The default value is a - promise that never resolves. However, see also [~stop_on_input]. - [~debug:true] enables debug information in error templates. See {!Dream.error_template}. The default is [false], to prevent accidental deployment with debug output turned on. See example @@ -2139,13 +2134,13 @@ val run : val serve : ?interface:string -> ?port:int -> - ?stop:unit promise -> ?error_handler:error_handler -> ?https:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> - handler -> unit promise + net:#Eio.Net.t -> + handler -> unit (** Like {!Dream.run}, but returns a promise that does not resolve until the server stops listening, instead of calling {{:https://ocsigen.org/lwt/latest/api/Lwt_main#VALrun} [Lwt_main.run]}. @@ -2460,7 +2455,7 @@ val request : ?target:string -> ?version:int * int -> ?headers:(string * string) list -> - string -> request + string -> request (** [Dream.request body] creates a fresh request with the given body for testing. The optional arguments set the corresponding {{!requests} request fields}. *) diff --git a/src/eml/eml.ml b/src/eml/eml.ml index 1ea43f96..dc19826a 100644 --- a/src/eml/eml.ml +++ b/src/eml/eml.ml @@ -688,17 +688,16 @@ struct init = (fun () -> print "let ___eml_write string = Dream.write response string in\n"); - finish = (fun () -> - print "Lwt.return_unit\n"); + finish = ignore; text = - Printf.ksprintf print "let%%lwt () = ___eml_write %S in\n"; + Printf.ksprintf print "___eml_write %S;\n"; format = - Printf.ksprintf print "let%%lwt () = Printf.ksprintf ___eml_write %S "; + Printf.ksprintf print "Printf.ksprintf ___eml_write %S "; format_end = (fun () -> - print " in\n"); + print ";\n"); } let stream_reason print = { @@ -707,14 +706,13 @@ struct init = (fun () -> print "let ___eml_write = string => Dream.write(response, string);\n"); - finish = (fun () -> - print "Lwt.return_unit\n"); + finish = ignore; text = - Printf.ksprintf print "let%%lwt () = ___eml_write(%S);\n"; + Printf.ksprintf print "___eml_write(%S);\n"; format = - Printf.ksprintf print "let%%lwt () = Printf.ksprintf(___eml_write, %S)"; + Printf.ksprintf print "Printf.ksprintf(___eml_write, %S)"; format_end = (fun () -> print ";\n"); diff --git a/src/graphql/graphql.ml b/src/graphql/graphql.ml index c09b3f46..eaa092a3 100644 --- a/src/graphql/graphql.ml +++ b/src/graphql/graphql.ml @@ -115,6 +115,7 @@ let complete_message id = (* TODO Take care to pass around the request Lwt.key in async, etc. *) (* TODO Test client complete racing against a stream. *) let handle_over_websocket make_context schema subscriptions request response = + Lwt_eio.Promise.await_lwt @@ let rec loop inited = match%lwt Message.read response with | None -> @@ -279,47 +280,50 @@ let graphql make_context schema = fun request -> | Some "websocket", Some "graphql-transport-ws" -> Helpers.websocket ~headers:["Sec-WebSocket-Protocol", "graphql-transport-ws"] + request (handle_over_websocket make_context schema (Hashtbl.create 16) request) | _ -> log.warning (fun log -> log ~request "Upgrade: websocket header missing"); Message.response ~status:`Not_Found Stream.empty Stream.null - |> Lwt.return end | `POST -> begin match Message.header request "Content-Type" with | Some "application/json" -> - let%lwt body = Message.body request in - (* TODO This almost certainly raises exceptions... *) - let json = Yojson.Basic.from_string body in - - begin match%lwt run_query make_context schema request json with - | Error json -> - Yojson.Basic.to_string json - |> Helpers.json - - | Ok (`Response json) -> - Yojson.Basic.to_string json - |> Helpers.json - - | Ok (`Stream _) -> - make_error "Subscriptions and streaming should use WebSocket transport" - |> Yojson.Basic.to_string - |> Helpers.json - end + Lwt_eio.Promise.await_lwt ( + let%lwt body = Message.body request in + (* TODO This almost certainly raises exceptions... *) + let json = Yojson.Basic.from_string body in + + begin match%lwt run_query make_context schema request json with + | Error json -> + Yojson.Basic.to_string json + |> Helpers.json + |> Lwt.return + + | Ok (`Response json) -> + Yojson.Basic.to_string json + |> Helpers.json + |> Lwt.return + + | Ok (`Stream _) -> + make_error "Subscriptions and streaming should use WebSocket transport" + |> Yojson.Basic.to_string + |> Helpers.json + |> Lwt.return + end + ) | _ -> log.warning (fun log -> log ~request "Content-Type not 'application/json'"); Message.response ~status:`Bad_Request Stream.empty Stream.null - |> Lwt.return end | method_ -> log.error (fun log -> log ~request "Method %s; must be GET or POST" (Method.method_to_string method_)); Message.response ~status:`Not_Found Stream.empty Stream.null - |> Lwt.return diff --git a/src/http/dune b/src/http/dune index 386935c7..4b6faf12 100644 --- a/src/http/dune +++ b/src/http/dune @@ -17,8 +17,8 @@ dream-httpaf.httpaf-lwt-unix lwt lwt.unix - lwt_ssl - ssl + lwt_eio + eio_main dream-httpaf.websocketaf ) (preprocess (pps lwt_ppx)) diff --git a/src/http/error_handler.ml b/src/http/error_handler.ml index f9c49efb..678faeeb 100644 --- a/src/http/error_handler.ml +++ b/src/http/error_handler.ml @@ -159,7 +159,7 @@ let customize template (error : Catch.error) = Then, call the template, and return the response. *) if not error.will_send_response then - Lwt.return_none + None else let debug_dump = dump error in @@ -179,13 +179,13 @@ let customize template (error : Catch.error) = (* No need to catch errors when calling the template, because every call site of the error handler already has error handlers for catching double faults. *) - let%lwt response = template error debug_dump response in - Lwt.return (Some response) + let response = template error debug_dump response in + Some response let default_template _error _debug_dump response = - Lwt.return response + response let debug_template _error debug_dump response = let status = Message.status response in @@ -193,7 +193,7 @@ let debug_template _error debug_dump response = and reason = Status.status_to_string status in Message.set_header response "Content-Type" Dream_pure.Formats.text_html; Message.set_body response (Error_template.render ~debug_dump ~code ~reason); - Lwt.return response + response let default = customize default_template @@ -228,17 +228,19 @@ let double_faults f default = is a programming error, so it's probably fine to return a generic server error. *) let respond_with_option f = + Lwt_eio.Promise.await_lwt @@ double_faults (fun () -> - f () - |> Lwt.map (function - | Some response -> response - | None -> - Message.response - ~status:`Internal_Server_Error Stream.empty Stream.null)) + Lwt_eio.run_eio @@ fun () -> + match f () with + | Some response -> response + | None -> + Message.response + ~status:`Internal_Server_Error Stream.empty Stream.null) (fun () -> Message.response ~status:`Internal_Server_Error Stream.empty Stream.null - |> Lwt.return) + |> Lwt.return + ) @@ -304,7 +306,7 @@ let httpaf Lwt.async begin fun () -> double_faults begin fun () -> - let%lwt response = user's_error_handler error in + let%lwt response = Lwt_eio.run_eio (fun () -> user's_error_handler error) in let response = match response with @@ -362,7 +364,7 @@ let h2 Lwt.async begin fun () -> double_faults begin fun () -> - let%lwt response = user's_error_handler error in + let%lwt response = Lwt_eio.run_eio (fun () -> user's_error_handler error) in let response = match response with @@ -403,7 +405,7 @@ let tls Lwt.async (fun () -> double_faults - (fun () -> Lwt.map ignore (user's_error_handler error)) + (fun () -> Lwt_eio.run_eio (fun () -> user's_error_handler error |> ignore)) Lwt.return) @@ -434,7 +436,7 @@ let websocket Lwt.async (fun () -> double_faults - (fun () -> Lwt.map ignore (user's_error_handler error)) + (fun () -> Lwt_eio.run_eio (fun () -> user's_error_handler error |> ignore)) Lwt.return) diff --git a/src/http/error_handler.mli b/src/http/error_handler.mli index 285469b5..7ab26b64 100644 --- a/src/http/error_handler.mli +++ b/src/http/error_handler.mli @@ -17,7 +17,7 @@ module Message = Dream_pure.Message val default : Catch.error_handler val debug_error_handler : Catch.error_handler val customize : - (Catch.error -> string -> Message.response -> Message.response Lwt.t) -> + (Catch.error -> string -> Message.response -> Message.response) -> Catch.error_handler @@ -35,7 +35,7 @@ val customize : val app : Catch.error_handler -> - (Catch.error -> Message.response Lwt.t) + (Catch.error -> Message.response) val httpaf : Catch.error_handler -> @@ -57,7 +57,7 @@ val websocket : val websocket_handshake : Catch.error_handler -> - (Message.request -> Message.response -> string -> Message.response Lwt.t) + (Message.request -> Message.response -> string -> Message.response) diff --git a/src/http/http.ml b/src/http/http.ml index df0e9660..8ca61eea 100644 --- a/src/http/http.ml +++ b/src/http/http.ml @@ -4,6 +4,7 @@ Copyright 2021 Anton Bachin *) +open Eio.Std module Catch = Dream__server.Catch module Content_length = Dream__server.Content_length @@ -274,7 +275,7 @@ let websocket_handler response socket = that ordinarily shouldn't be relied on by the user - this is just our last chance to tell the user that something is wrong with their app. *) (* TODO Rename conn like in the body branch. *) -let wrap_handler +let wrap_handler ~sw https (user's_error_handler : Catch.error_handler) (user's_dream_handler : Message.handler) = @@ -319,7 +320,7 @@ let wrap_handler Stream.stream body Stream.no_writer in let request : Message.request = - Helpers.request ~client ~method_ ~target ~https ~version ~headers body in + Helpers.request ~sw ~client ~method_ ~target ~https ~version ~headers body in (* Call the user's handler. If it raises an exception or returns a promise that rejects with an exception, pass the exception up to Httpaf. This @@ -334,7 +335,7 @@ let wrap_handler Lwt.async begin fun () -> Lwt.catch begin fun () -> (* Do the big call. *) - let%lwt response = user's_dream_handler request in + let%lwt response = Lwt_eio.run_eio (fun () -> user's_dream_handler request) in (* Extract the Dream response's headers. *) @@ -389,7 +390,7 @@ let wrap_handler |> function | Ok () -> Lwt.return_unit | Error error_string -> - let%lwt response = + let response = Error_handler.websocket_handshake user's_error_handler request response error_string in @@ -410,7 +411,7 @@ let wrap_handler (* TODO Factor out what is in common between the http/af and h2 handlers. *) -let wrap_handler_h2 +let wrap_handler_h2 ~sw https (_user's_error_handler : Catch.error_handler) (user's_dream_handler : Message.handler) = @@ -449,7 +450,7 @@ let wrap_handler_h2 Stream.stream body Stream.no_writer in let request : Message.request = - Helpers.request ~client ~method_ ~target ~https ~version ~headers body in + Helpers.request ~sw ~client ~method_ ~target ~https ~version ~headers body in (* Call the user's handler. If it raises an exception or returns a promise that rejects with an exception, pass the exception up to Httpaf. This @@ -464,7 +465,7 @@ let wrap_handler_h2 Lwt.async begin fun () -> Lwt.catch begin fun () -> (* Do the big call. *) - let%lwt response = user's_dream_handler request in + let%lwt response = Lwt_eio.run_eio (fun () -> user's_dream_handler request) in (* Extract the Dream response's headers. *) @@ -517,6 +518,7 @@ type tls_library = { key_file:string -> handler:Message.handler -> error_handler:Catch.error_handler -> + sw:Switch.t -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t; @@ -526,15 +528,18 @@ let no_tls = { create_handler = begin fun ~certificate_file:_ ~key_file:_ ~handler - ~error_handler -> + ~error_handler + ~sw -> Httpaf_lwt_unix.Server.create_connection_handler ?config:None - ~request_handler:(wrap_handler false error_handler handler) + ~request_handler:(wrap_handler ~sw false error_handler handler) ~error_handler:(Error_handler.httpaf error_handler) end; } let openssl = { + create_handler = fun ~certificate_file:_ -> failwith "https://github.com/savonet/ocaml-ssl/issues/76" +(* create_handler = begin fun ~certificate_file ~key_file ~handler @@ -543,15 +548,15 @@ let openssl = { let httpaf_handler = Httpaf_lwt_unix.Server.SSL.create_connection_handler ?config:None - ~request_handler:(wrap_handler true error_handler handler) - ~error_handler:(Error_handler.httpaf error_handler) + ~request_handler:(wrap_handler ~sw true error_handler handler) + ~error_handler:(Error_handler.httpaf ~sw error_handler) in let h2_handler = H2_lwt_unix.Server.SSL.create_connection_handler ?config:None ~request_handler:(wrap_handler_h2 true error_handler handler) - ~error_handler:(Error_handler.h2 error_handler) + ~error_handler:(Error_handler.h2 ~sw error_handler) in let perform_tls_handshake = @@ -590,6 +595,7 @@ let openssl = { | Some _ -> assert false end; +*) } (* TODO LATER Add ALPN + HTTP/2.0 with ocaml-tls, too. *) @@ -597,11 +603,12 @@ let ocaml_tls = { create_handler = fun ~certificate_file ~key_file ~handler - ~error_handler -> + ~error_handler + ~sw -> Httpaf_lwt_unix.Server.TLS.create_connection_handler_with_default ~certfile:certificate_file ~keyfile:key_file ?config:None - ~request_handler:(wrap_handler true error_handler handler) + ~request_handler:(wrap_handler ~sw true error_handler handler) ~error_handler:(Error_handler.httpaf error_handler) } @@ -616,12 +623,22 @@ let built_in_middleware error_handler = +let of_unix_addr = function + | Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Ipaddr.of_unix host, port) + | Unix.ADDR_UNIX path -> `Unix path + +let to_unix_addr = function + | `Tcp (host, port) -> Unix.ADDR_INET (Eio_unix.Ipaddr.to_unix host, port) + | `Unix path -> Unix.ADDR_UNIX path + + + let serve_with_details caller_function_for_error_messages tls_library + ~net ~interface ~port - ~stop ~error_handler ~certificate_file ~key_file @@ -665,36 +682,40 @@ let serve_with_details be pattern matching on the exception (but that might introduce dependency coupling), or the upstream should be patched to distinguish the errors in some useful way. *) - let httpaf_connection_handler client_address socket = - Lwt.catch - (fun () -> - httpaf_connection_handler client_address socket) - (fun exn -> - tls_error_handler client_address exn; - Lwt.return_unit) + let httpaf_connection_handler ~sw flow client_address = + let client_address = to_unix_addr client_address in + try + let fd = Eio_unix.FD.take flow |> Option.get in + let socket = Lwt_unix.of_unix_file_descr fd in + Lwt_eio.Promise.await_lwt @@ + httpaf_connection_handler ~sw client_address socket + with exn -> + tls_error_handler client_address exn in - (* Look up the low-level address corresponding to the interface. Hopefully, - this is a local interface. *) - let%lwt addresses = Lwt_unix.getaddrinfo interface (string_of_int port) [] in - match addresses with - | [] -> - Printf.ksprintf failwith "Dream.%s: no interface with address %s" - caller_function_for_error_messages interface - | address::_ -> - let listen_address = Lwt_unix.(address.ai_addr) in - - - (* Bring up the HTTP server. Wait for the server to actually get started. - Then, wait for the ~stop promise. If the ~stop promise ever resolves, stop - the server. *) - let%lwt server = - Lwt_io.establish_server_with_client_socket - listen_address - httpaf_connection_handler in + let listen_address = Lwt_eio.Promise.await_lwt @@ + (* Look up the low-level address corresponding to the interface. Hopefully, + this is a local interface. *) + let%lwt addresses = Lwt_unix.getaddrinfo interface (string_of_int port) [] in + match addresses with + | [] -> + Printf.ksprintf failwith "Dream.%s: no interface with address %s" + caller_function_for_error_messages interface + | address::_ -> + Lwt.return (of_unix_addr Lwt_unix.(address.ai_addr)) + in - let%lwt () = stop in - Lwt_io.shutdown_server server + (* Bring up the HTTP server. *) + Switch.run @@ fun sw -> + let socket = + Eio.Net.listen ~sw net listen_address + ~reuse_addr:true + ~backlog:(Lwt_unix.somaxconn () [@ocaml.warning "-3"]) + in + while true do + Eio.Net.accept_sub ~sw socket httpaf_connection_handler + ~on_error:(fun ex -> !Lwt.async_exception_hook ex) + done @@ -705,15 +726,15 @@ let serve_with_maybe_https caller_function_for_error_messages ~interface ~port - ~stop ~error_handler ~https ?certificate_file ?key_file ?certificate_string ?key_string ~builtins + ~net user's_dream_handler = - try%lwt + try (* This check will at least catch secrets like "foo" when used on a public interface. *) (* if not (is_localhost interface) then @@ -729,9 +750,9 @@ let serve_with_maybe_https serve_with_details caller_function_for_error_messages no_tls + ~net ~interface ~port - ~stop ~error_handler ~certificate_file:"" ~key_file:"" @@ -794,9 +815,9 @@ let serve_with_maybe_https serve_with_details caller_function_for_error_messages tls_library + ~net ~interface ~port - ~stop ~error_handler ~certificate_file ~key_file @@ -804,6 +825,7 @@ let serve_with_maybe_https user's_dream_handler | `Memory (certificate_string, key_string, verbose_or_silent) -> + Lwt_eio.Promise.await_lwt @@ Lwt_io.with_temp_file begin fun (certificate_file, certificate_stream) -> Lwt_io.with_temp_file begin fun (key_file, key_stream) -> @@ -819,16 +841,17 @@ let serve_with_maybe_https let%lwt () = Lwt_io.close certificate_stream in let%lwt () = Lwt_io.close key_stream in + Lwt_eio.run_eio @@ fun () -> serve_with_details caller_function_for_error_messages tls_library ~interface ~port - ~stop ~error_handler ~certificate_file ~key_file ~builtins + ~net user's_dream_handler end @@ -847,28 +870,27 @@ let serve_with_maybe_https let default_interface = "localhost" let default_port = 8080 -let never = fst (Lwt.wait ()) let serve ?(interface = default_interface) ?(port = default_port) - ?(stop = never) ?(error_handler = Error_handler.default) ?(https = false) ?certificate_file ?key_file ?(builtins = true) + ~net user's_dream_handler = serve_with_maybe_https "serve" + ~net ~interface ~port - ~stop ~error_handler - ~https:(if https then `OpenSSL else `No) + ~https:(if https then `OCaml_TLS else `No) ?certificate_file ?key_file ?certificate_string:None @@ -881,7 +903,6 @@ let serve let run ?(interface = default_interface) ?(port = default_port) - ?(stop = never) ?(error_handler = Error_handler.default) ?(https = false) ?certificate_file @@ -889,6 +910,7 @@ let run ?(builtins = true) ?(greeting = true) ?(adjust_terminal = true) + env user's_dream_handler = let () = if Sys.unix then @@ -953,14 +975,15 @@ let run end; try - Lwt_main.run begin + begin + Lwt_eio.with_event_loop ~clock:env#clock @@ fun () -> serve_with_maybe_https "run" + ~net:env#net ~interface ~port - ~stop ~error_handler - ~https:(if https then `OpenSSL else `No) + ~https:(if https then `OCaml_TLS else `No) ?certificate_file ?key_file ?certificate_string:None ?key_string:None ~builtins diff --git a/src/mirage/adapt.ml b/src/mirage/adapt.ml deleted file mode 100644 index bf5d7e54..00000000 --- a/src/mirage/adapt.ml +++ /dev/null @@ -1,79 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin - - XXX(dinosaure): same as [src/http/adapt.ml] without [address_to_string] - which - depends on [Unix]. *) - -module Dream = Dream__pure.Inmost -module Stream = Dream__pure.Stream - -(* TODO Write a test simulating client exit during SSE; this was killing the - server at some point. *) -(* TODO LATER Will also need to monitor buffer accumulation and use flush. *) -(* TODO Rewrite using Dream.next. *) -let forward_body_general - (response : Dream.response) - (_write_string : ?off:int -> ?len:int -> string -> unit) - (write_buffer : ?off:int -> ?len:int -> Stream.buffer -> unit) - http_flush - close = - let bytes_since_flush = ref 0 in - - let rec send () = - Dream.body_stream response - |> fun stream -> - Stream.read - stream - ~data - ~close - ~flush - ~ping - ~pong - - and data chunk off len _binary _fin = - write_buffer ~off ~len chunk; - bytes_since_flush := !bytes_since_flush + len; - if !bytes_since_flush >= 4096 then begin - bytes_since_flush := 0; - http_flush send - end - else - send () - - and flush () = - bytes_since_flush := 0; - http_flush send - - and ping _buffer _offset _length = - send () - - and pong _buffer _offset _length = - send () - - in - - send () - -let forward_body - (response : Dream.response) - (body : Httpaf.Body.Writer.t) = - - forward_body_general - response - (Httpaf.Body.Writer.write_string body) - (Httpaf.Body.Writer.write_bigstring body) - (Httpaf.Body.Writer.flush body) - (fun _code -> Httpaf.Body.Writer.close body) - -let forward_body_h2 - (response : Dream.response) - (body : [ `write ] H2.Body.t) = - - forward_body_general - response - (H2.Body.write_string body) - (H2.Body.write_bigstring body) - (H2.Body.flush body) - (fun _code -> H2.Body.close_writer body) diff --git a/src/mirage/dune b/src/mirage/dune deleted file mode 100644 index 6f9f8613..00000000 --- a/src/mirage/dune +++ /dev/null @@ -1,20 +0,0 @@ -(library - (public_name dream-mirage) - (name dream__mirage) - (libraries - bigarray-compat - bigstringaf - digestif - dream.cipher - dream.localhost - dream.middleware - dream-pure - dream.h2 - dream.httpaf - lwt - dream-mirage.paf - dream-mirage.paf.alpn - dream-mirage.paf.mirage - ) - (preprocess (pps lwt_ppx)) - (instrumentation (backend bisect_ppx))) diff --git a/src/mirage/error_handler.ml b/src/mirage/error_handler.ml deleted file mode 100644 index de42ac3e..00000000 --- a/src/mirage/error_handler.ml +++ /dev/null @@ -1,252 +0,0 @@ -module Dream = Dream__pure.Inmost - -let log = - Dream__middleware.Log.sub_log "dream.mirage" - -let select_log = function - | `Error -> log.error - | `Warning -> log.warning - | `Info -> log.info - | `Debug -> log.debug - -let dump (error : Dream.error) = - let buffer = Buffer.create 4096 in - let p format = Printf.bprintf buffer format in - - begin match error.condition with - | `Response response -> - let status = Dream.status response in - p "%i %s\n" (Dream.status_to_int status) (Dream.status_to_string status) - - | `String "" -> - p "(Library error without description payload)\n" - - | `String string -> - p "%s\n" string - - | `Exn exn -> - let backtrace = Printexc.get_backtrace () in - p "%s\n" (Printexc.to_string exn); - backtrace |> Dream__middleware.Log.iter_backtrace (p "%s\n") - end; - - p "\n"; - - let layer = - match error.layer with - | `TLS -> "TLS library" - | `HTTP -> "HTTP library" - | `HTTP2 -> "HTTP2 library" - | `WebSocket -> "WebSocket library" - | `App -> "Application" - in - - let blame = - match error.caused_by with - | `Server -> "Server" - | `Client -> "Client" - in - - let severity = - match error.severity with - | `Error -> "Error" - | `Warning -> "Warning" - | `Info -> "Info" - | `Debug -> "Debug" - in - - p "From: %s\n" layer; - p "Blame: %s\n" blame; - p "Severity: %s" severity; - - begin match error.client with - | None -> () - | Some client -> p "\n\nClient: %s" client - end; - - begin match error.request with - | None -> () - | Some request -> - let last = Dream.last request in - - let major, minor = Dream.version last in - p "\n\n%s %s HTTP/%i.%i" - (Dream.method_to_string (Dream.method_ last)) - (Dream.target last) - major minor; - - Dream.all_headers last - |> List.iter (fun (name, value) -> p "\n%s: %s" name value); - - let show_variables kind = - kind (fun name value first -> - if first then - p "\n"; - p "\n%s: %s" name value; - false) - true - request - |> ignore - in - show_variables Dream.fold_locals; - show_variables Dream.fold_globals - end; - - Buffer.contents buffer - -let customize template (error : Dream.error) = - - (* First, log the error. *) - - begin match error.condition with - | `Response _ -> () - | `String _ | `Exn _ as condition -> - - let client = - match error.client with - | None -> "" - | Some client -> " (" ^ client ^ ")" - in - - let layer = - match error.layer with - | `TLS -> ["TLS" ^ client] - | `HTTP -> ["HTTP" ^ client] - | `HTTP2 -> ["HTTP/2" ^ client] - | `WebSocket -> ["WebSocket" ^ client] - | `App -> [] - in - - let description, backtrace = - match condition with - | `String string -> string, "" - | `Exn exn -> - let backtrace = Printexc.get_backtrace () in - Printexc.to_string exn, backtrace - in - - let message = String.concat ": " (layer @ [description]) in - - select_log error.severity (fun log -> - log ?request:error.request "%s" message); - backtrace |> Dream__middleware.Log.iter_backtrace (fun line -> - select_log error.severity (fun log -> - log ?request:error.request "%s" line)) - end; - - (* If Dream will not send a response for this error, we are done after - logging. Otherwise, if debugging is enabled, gather a bunch of information. - Then, call the template, and return the response. *) - - if not error.will_send_response then - Lwt.return_none - - else - let debug_dump = - match error.debug with - | false -> None - | true -> Some (dump error) - in - - let response = - match error.condition with - | `Response response -> response - | _ -> - let status = - match error.caused_by with - | `Server -> `Internal_Server_Error - | `Client -> `Bad_Request - in - Dream.response ~status "" - in - - (* No need to catch errors when calling the template, because every call - site of the error handler already has error handlers for catching double - faults. *) - response - |> template debug_dump - |> Lwt.map (fun response -> Some response) - -let default_response = function - | `Server -> Dream.response ~status:`Internal_Server_Error "" - | `Client -> Dream.response ~status:`Bad_Request "" - -let default_template debug_dump response = - match debug_dump with - | None -> Lwt.return response - | Some debug_dump -> - let status = Dream.status response in - let code = Dream.status_to_int status - and reason = Dream.status_to_string status in - response - |> Dream.with_header "Content-Type" Dream__pure.Formats.text_html - |> Dream.with_body - (Dream__middleware.Error_template.render ~debug_dump ~code ~reason) - |> Lwt.return - -let default = - customize default_template - -let double_faults f default = - Lwt.catch f begin fun exn -> - let backtrace = Printexc.get_backtrace () in - log.error (fun log -> log "Error handler raised: %s" (Printexc.to_string exn)); - backtrace - |> Dream__middleware.Log.iter_backtrace (fun line -> - log.error (fun log -> log "%s" line)); - default () - end - -let httpaf app user's_error_handler = fun client_address ?request:_ error start_response -> - let condition, severity, caused_by = match error with - | `Exn exn -> - `Exn exn, - `Error, - `Server - | `Bad_request - | `Bad_gateway -> - `String "Bad request", - `Warning, - `Client - | `Internal_server_error -> - `String "Content-Length missing or negative", - `Error, - `Server in - let error = { - Dream.condition; - layer = `HTTP; - caused_by; - request = None; - response = None; - client= Some client_address; - severity; - debug = Dream.debug app; - will_send_response = true; - } in - - Lwt.async begin fun () -> - double_faults begin fun () -> - let%lwt response = user's_error_handler error in - let response = match response with - | Some response -> response - | None -> default_response caused_by in - let headers = Httpaf.Headers.of_list (Dream.all_headers response) in - let body = start_response headers in - Adapt.forward_body response body; - Lwt.return_unit - end - Lwt.return - end - -let respond_with_option f = - double_faults - (fun () -> - f () - |> Lwt.map (function - | Some response -> response - | None -> Dream.response ~status:`Internal_Server_Error "")) - (fun () -> - Dream.empty `Internal_Server_Error) - -let app user's_error_handler = fun error -> - respond_with_option (fun () -> user's_error_handler error) diff --git a/src/mirage/mirage.ml b/src/mirage/mirage.ml deleted file mode 100644 index 003abcc0..00000000 --- a/src/mirage/mirage.ml +++ /dev/null @@ -1,254 +0,0 @@ -module Dream = Dream__pure.Inmost - -open Rresult -open Lwt.Infix - -let to_dream_method meth = Httpaf.Method.to_string meth |> Dream.string_to_method -let to_httpaf_status status = Dream.status_to_int status |> Httpaf.Status.of_code -let to_h2_status status = Dream.status_to_int status |> H2.Status.of_code -let sha1 str = Digestif.SHA1.(to_raw_string (digest_string str)) -let const x = fun _ -> x -let ( >>? ) = Lwt_result.bind - -let wrap_handler_httpaf app _user's_error_handler user's_dream_handler = - let httpaf_request_handler = fun client reqd -> - let httpaf_request = Httpaf.Reqd.request reqd in - let method_ = to_dream_method httpaf_request.meth in - let target = httpaf_request.target in - let version = (httpaf_request.version.major, httpaf_request.version.minor) in - let headers = Httpaf.Headers.to_list httpaf_request.headers in - let body = Httpaf.Reqd.request_body reqd in - - let read ~data ~close ~flush:_ ~ping:_ ~pong:_ = - Httpaf.Body.Reader.schedule_read - body - ~on_eof:(fun () -> close 1000) - ~on_read:(fun buffer ~off ~len -> data buffer off len true false) - in - let close _close = - Httpaf.Body.Reader.close body in - let body = - Dream__pure.Stream.read_only ~read ~close in - - let request = Dream.request_from_http ~app ~client ~method_ ~target ~version ~headers body in - - (* Call the user's handler. If it raises an exception or returns a promise - that rejects with an exception, pass the exception up to Httpaf. This - will cause it to call its (low-level) error handler with variand `Exn _. - A well-behaved Dream app should catch all of its own exceptions and - rejections in one of its top-level middlewares. - - We don't try to log exceptions here because the behavior is not - customizable here. The handler itself is customizable (to catch all) - exceptions, and the error callback that gets leaked exceptions is also - customizable. *) - Lwt.async begin fun () -> - Lwt.catch begin fun () -> - (* Do the big call. *) - let%lwt response = user's_dream_handler request in - - (* Extract the Dream response's headers. *) - - (* This is the default function that translates the Dream response to an - http/af response and sends it. We pre-define the function, however, - because it is called from two places: - - 1. Upon a normal response, the function is called unconditionally. - 2. Upon failure to establish a WebSocket, the function is called to - transmit the resulting error response. *) - let forward_response response = - let headers = - Httpaf.Headers.of_list (Dream.all_headers response) in - - (* let version = - match Dream.version_override response with - | None -> None - | Some (major, minor) -> Some Httpaf.Version.{major; minor} - in *) - let status = - to_httpaf_status (Dream.status response) in - (* let reason = - Dream.reason_override response in *) - - let httpaf_response = - Httpaf.Response.create ~headers status in - let body = - Httpaf.Reqd.respond_with_streaming reqd httpaf_response in - - Adapt.forward_body response body; - - Lwt.return_unit - in - - forward_response response - end - @@ fun exn -> - (* TODO LATER There was something in the fork changelogs about not - requiring report_exn. Is it relevant to this? *) - Httpaf.Reqd.report_exn reqd exn; - Lwt.return_unit - end - in - - httpaf_request_handler - -let request_handler - : Dream.app -> Dream.error_handler -> Dream.handler -> string -> Alpn.reqd -> unit - = fun app - (user's_error_handler : Dream.error_handler) - (user's_dream_handler : Dream.handler) -> (); - fun client_address -> function - | Alpn.Reqd_HTTP_1_1 reqd -> wrap_handler_httpaf app user's_error_handler user's_dream_handler client_address reqd - | _ -> assert false - -let error_handler - : Dream.app -> Dream.error_handler -> string -> ?request:Alpn.request -> Alpn.server_error -> - (Alpn.headers -> Alpn.body) -> unit - = fun app - (user's_error_handler : Dream.error_handler) -> (); - fun client ?request error start_response -> - match request with - | Some (Alpn.Request_HTTP_1_1 request) -> - let start_response hdrs : Httpaf.Body.Writer.t = match start_response Alpn.(Headers_HTTP_1_1 hdrs) with - | Alpn.Body_HTTP_1_1 (Alpn.Wr, Alpn.Body_wr body) -> body - | _ -> Fmt.failwith "Impossible to respond with an h2 respond to an HTTP/1.1 client" in - Error_handler.httpaf app user's_error_handler client ?request:(Some request) error start_response - | _ -> assert false (* TODO *) - -module Make (Pclock : Mirage_clock.PCLOCK) (Time : Mirage_time.S) (Stack : Mirage_stack.V4V6) = struct - include Dream__pure.Stream - include Dream__pure.Inmost - - include Dream__middleware.Log - include Dream__middleware.Log.Make (Pclock) - include Dream__middleware.Echo - - let default_log = - Dream__middleware.Log.sub_log (Logs.Src.name Logs.default) - - let error = default_log.error - let warning = default_log.warning - let info = default_log.info - let debug = default_log.debug - - include Dream__middleware.Router - - include Dream__middleware.Session - include Dream__middleware.Session.Make (Pclock) - - include Dream__middleware.Origin_referrer_check - include Dream__middleware.Form - include Dream__middleware.Upload - include Dream__middleware.Csrf - - let content_length = - Dream__middleware.Content_length.content_length - - include Dream__middleware.Lowercase_headers - include Dream__middleware.Catch - include Dream__middleware.Request_id - include Dream__middleware.Site_prefix - - let error_template = - Error_handler.customize - - let random = - Dream__cipher.Random.random - - include Dream__pure.Formats - - let log = - Dream__middleware.Log.convenience_log - - include Dream__middleware.Tag - - let now () = Ptime.to_float_s (Ptime.v (Pclock.now_d_ps ())) - - let form = form ~now - let multipart = multipart ~now - let csrf_token = csrf_token ~now - let verify_csrf_token = verify_csrf_token ~now - let form_tag = form_tag ~now - - include Dream__pure.Formats - - include Paf_mirage.Make (Time) (Stack) - - let alpn = - let module R = (val Mimic.repr tls_protocol) in - let alpn (_, flow) = match TLS.epoch flow with - | Ok { Tls.Core.alpn_protocol; _ } -> alpn_protocol - | Error _ -> None in - let peer ((ipaddr, port), _) = Fmt.str "%a:%d" Ipaddr.pp ipaddr port in - let injection (_, flow) = R.T flow in - { Alpn.alpn; peer; injection; } - - let built_in_middleware = - Dream__pure.Inmost.pipeline [ - Dream__middleware.Lowercase_headers.lowercase_headers; - Dream__middleware.Content_length.content_length; - Dream__middleware.Catch.catch_errors; - Dream__middleware.Request_id.assign_request_id; - Dream__middleware.Site_prefix.chop_site_prefix; - ] - - let localhost_certificate = - let crts = Rresult.R.failwith_error_msg - (X509.Certificate.decode_pem_multiple (Cstruct.of_string Dream__localhost.certificate)) in - let key = Rresult.R.failwith_error_msg - (X509.Private_key.decode_pem (Cstruct.of_string Dream__localhost.key)) in - `Single (crts, key) - - let https ?stop ~port ?(prefix= "") stack - ?(cfg= Tls.Config.server ~certificates:localhost_certificate ()) - ?error_handler:(user's_error_handler : error_handler = Error_handler.default) (user's_dream_handler : handler) = - let prefix = prefix - |> Dream__pure.Formats.from_path - |> Dream__pure.Formats.drop_trailing_slash in - initialize ~setup_outputs:ignore ; - let app = Dream__pure.Inmost.new_app (Error_handler.app user's_error_handler) prefix in - let accept t = accept t >>? fun flow -> - let edn = Stack.TCP.dst flow in - TLS.server_of_flow cfg flow >>= function - | Ok flow -> Lwt.return_ok (edn, flow) - | Error err -> Lwt.return (R.error_msgf "%a" TLS.pp_write_error err) in - let user's_dream_handler = - built_in_middleware user's_dream_handler in - let error_handler = error_handler app user's_error_handler in - let request_handler = - request_handler app user's_error_handler user's_dream_handler in - let service = Alpn.service alpn ~error_handler ~request_handler accept close in - init ~port stack >>= fun t -> - let `Initialized th = serve ?stop service t in th - - let alpn protocol = - let protocol = match protocol with - | `H2 -> "h2" - | `HTTP_1_1 -> "http/1.1" in - let module R = (val Mimic.repr tcp_protocol) in - let alpn _ = Some protocol in - let peer ((ipaddr, port), _) = Fmt.str "%a:%d" Ipaddr.pp ipaddr port in - let injection (_, flow) = R.T flow in - { Alpn.alpn; peer; injection; } - - let http ?stop ~port ?(prefix= "") ?(protocol= `HTTP_1_1) stack - ?error_handler:(user's_error_handler= Error_handler.default) - user's_dream_handler = - let prefix = prefix - |> Dream__pure.Formats.from_path - |> Dream__pure.Formats.drop_trailing_slash in - initialize ~setup_outputs:ignore ; - let app = Dream__pure.Inmost.new_app (Error_handler.app user's_error_handler) prefix in - let accept t = accept t >>? fun flow -> - let edn = Stack.TCP.dst flow in - Lwt.return_ok (edn, flow) in - let user's_dream_handler = - built_in_middleware user's_dream_handler in - let error_handler = error_handler app user's_error_handler in - let request_handler = request_handler app user's_error_handler user's_dream_handler in - let service = Alpn.service (alpn protocol) ~error_handler ~request_handler accept close in - init ~port stack >>= fun t -> - let `Initialized th = serve ?stop service t in th -end - -include Dream diff --git a/src/mirage/mirage.mli b/src/mirage/mirage.mli deleted file mode 100644 index 658a6215..00000000 --- a/src/mirage/mirage.mli +++ /dev/null @@ -1,204 +0,0 @@ -type incoming -type outgoing - -type 'a message - -type request = incoming message -type response = outgoing message - -type handler = request -> response Lwt.t -type middleware = handler -> handler - -module Make - (Pclock : Mirage_clock.PCLOCK) - (Time : Mirage_time.S) - (Stack : Mirage_stack.V4V6) : sig - - type route - - type method_ = - [ `GET - | `POST - | `PUT - | `DELETE - | `HEAD - | `CONNECT - | `OPTIONS - | `TRACE - | `PATCH - | `Method of string ] - - type informational = - [ `Continue - | `Switching_Protocols ] - - type successful = - [ `OK - | `Created - | `Accepted - | `Non_Authoritative_Information - | `No_Content - | `Reset_Content - | `Partial_Content ] - - type redirection = - [ `Multiple_Choices - | `Moved_Permanently - | `Found - | `See_Other - | `Not_Modified - | `Temporary_Redirect - | `Permanent_Redirect ] - - type client_error = - [ `Bad_Request - | `Unauthorized - | `Payment_Required - | `Forbidden - | `Not_Found - | `Method_Not_Allowed - | `Not_Acceptable - | `Proxy_Authentication_Required - | `Request_Timeout - | `Conflict - | `Gone - | `Length_Required - | `Precondition_Failed - | `Payload_Too_Large - | `URI_Too_Long - | `Unsupported_Media_Type - | `Range_Not_Satisfiable - | `Expectation_Failed - | `Misdirected_Request - | `Too_Early - | `Upgrade_Required - | `Precondition_Required - | `Too_Many_Requests - | `Request_Header_Fields_Too_Large - | `Unavailable_For_Legal_Reasons ] - - type server_error = - [ `Internal_Server_Error - | `Not_Implemented - | `Bad_Gateway - | `Service_Unavailable - | `Gateway_Timeout - | `HTTP_Version_Not_Supported ] - - type standard_status = - [ informational - | successful - | redirection - | client_error - | server_error ] - - type status = - [ standard_status - | `Status of int ] - - val random : int -> string - - val log : ('a, Format.formatter, unit, unit) format4 -> 'a - - type ('a, 'b) conditional_log = - ((?request:request -> - ('a, Format.formatter, unit, 'b) format4 -> 'a) -> 'b) -> - unit - - type log_level = [ `Error | `Warning | `Info | `Debug ] - - val error : ('a, unit) conditional_log - val warning : ('a, unit) conditional_log - val info : ('a, unit) conditional_log - (* val debug : ('a, unit) conditional_log *) - - val html : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response Lwt.t - - val param : string -> request -> string - - type csrf_result = - [ `Ok - | `Expired of float - | `Wrong_session - | `Invalid ] - - val csrf_token : ?valid_for:float -> request -> string - val verify_csrf_token : request -> string -> csrf_result Lwt.t - - type 'a form_result = - [ `Ok of 'a - | `Expired of 'a * float - | `Wrong_session of 'a - | `Invalid_token of 'a - | `Missing_token of 'a - | `Many_tokens of 'a - | `Wrong_content_type ] - - type multipart_form = - (string * ((string option * string) list)) list - - val form : ?csrf:bool -> request -> (string * string) list form_result Lwt.t - val multipart : ?csrf:bool -> request -> multipart_form form_result Lwt.t - - val form_tag : - ?method_:method_ -> - ?target:string -> - ?enctype:[ `Multipart_form_data ] -> - ?csrf_token:bool -> - action:string -> request -> string - - val lowercase_headers : middleware - val content_length : middleware - - val logger : middleware - val router : route list -> middleware - - val get : string -> handler -> route - val not_found : handler - - type error = - { condition : - [ `Response of response - | `String of string - | `Exn of exn ] - ; layer : - [ `App - | `HTTP - | `HTTP2 - | `TLS - | `WebSocket ] - ; caused_by : - [ `Server - | `Client ] - ; request : request option - ; response : response option - ; client : string option - ; severity : log_level - ; debug : bool - ; will_send_response : bool } - - type error_handler = error -> response option Lwt.t - - val error_template : - (string option -> response -> response Lwt.t) -> error_handler - - val https : - ?stop:Lwt_switch.t - -> port:int - -> ?prefix:string - -> Stack.TCP.t - -> ?cfg:Tls.Config.server - -> ?error_handler:error_handler - -> handler - -> unit Lwt.t - - val http : - ?stop:Lwt_switch.t - -> port:int - -> ?prefix:string - -> ?protocol:[ `H2 | `HTTP_1_1 ] - -> Stack.TCP.t - -> ?error_handler:error_handler - -> handler - -> unit Lwt.t -end diff --git a/src/pure/dune b/src/pure/dune index 76415568..ef00d0e2 100644 --- a/src/pure/dune +++ b/src/pure/dune @@ -6,6 +6,7 @@ bigstringaf hmap lwt + eio uri ptime ) diff --git a/src/pure/message.ml b/src/pure/message.ml index 24debd91..a2c336cd 100644 --- a/src/pure/message.ml +++ b/src/pure/message.ml @@ -4,7 +4,6 @@ Copyright 2021 Anton Bachin *) - (* Type abbreviations and modules used in defining the primary types *) type 'a promise = 'a Lwt.t @@ -54,7 +53,7 @@ type response = server message (* Functions of messages *) -type handler = request -> response Lwt.t +type handler = request -> response type middleware = handler -> handler diff --git a/src/pure/message.mli b/src/pure/message.mli index 330d1270..f1b28372 100644 --- a/src/pure/message.mli +++ b/src/pure/message.mli @@ -16,7 +16,7 @@ type request = client message type response = server message type 'a promise = 'a Lwt.t -type handler = request -> response promise +type handler = request -> response type middleware = handler -> handler diff --git a/src/server/catch.ml b/src/server/catch.ml index da550b78..a02d37aa 100644 --- a/src/server/catch.ml +++ b/src/server/catch.ml @@ -34,7 +34,7 @@ type error = { will_send_response : bool; } -type error_handler = error -> Message.response option Message.promise +type error_handler = error -> Message.response option (* This error handler actually *is* a middleware, but it is just one pathway for reaching the centralized error handler provided by the user, so it is built @@ -43,12 +43,8 @@ type error_handler = error -> Message.response option Message.promise (* TODO The option return value thing is pretty awkward. *) let catch error_handler next_handler request = - Lwt.try_bind - - (fun () -> - next_handler request) - - (fun response -> + match next_handler request with + | response -> let status = Message.status response in (* TODO Overfull hbox. *) @@ -74,13 +70,13 @@ let catch error_handler next_handler request = error_handler error end else - Lwt.return response) + response (* This exception handler is partially redundant, in that the HTTP-level handlers will also catch exceptions. However, this handler is able to capture more relevant context. We leave the HTTP-level handlers for truly severe protocol-level errors and integration mistakes. *) - (fun exn -> + | exception exn -> let error = { condition = `Exn exn; layer = `App; @@ -92,4 +88,4 @@ let catch error_handler next_handler request = will_send_response = true; } in - error_handler error) + error_handler error diff --git a/src/server/content_length.ml b/src/server/content_length.ml index 4c411ea8..81568c65 100644 --- a/src/server/content_length.ml +++ b/src/server/content_length.ml @@ -18,7 +18,7 @@ let content_length next_handler request = if fst (Message.version request) <> 1 then next_handler request else - let%lwt (response : Message.response) = next_handler request in + let (response : Message.response) = next_handler request in if not (Message.has_header response "Transfer-Encoding") then Message.add_header response "Transfer-Encoding" "chunked"; - Lwt.return response + response diff --git a/src/server/dune b/src/server/dune index 9afd04a3..6a15a7ae 100644 --- a/src/server/dune +++ b/src/server/dune @@ -16,6 +16,7 @@ unstrctrd uri yojson + lwt_eio ) (preprocess (pps lwt_ppx)) (instrumentation (backend bisect_ppx))) diff --git a/src/server/echo.ml b/src/server/echo.ml index 093c652d..0a26c6c6 100644 --- a/src/server/echo.ml +++ b/src/server/echo.ml @@ -12,4 +12,3 @@ module Stream = Dream_pure.Stream let echo request = Message.response (Message.server_stream request) Stream.null - |> Lwt.return diff --git a/src/server/flash.ml b/src/server/flash.ml index 254c2567..6248e106 100644 --- a/src/server/flash.ml +++ b/src/server/flash.ml @@ -77,7 +77,7 @@ let flash_messages inner_handler request = let outbox = ref [] in Message.set_field request storage_field outbox; let existing = Cookie.cookie request flash_cookie in - let%lwt response = inner_handler request in + let response = inner_handler request in let entries = List.rev !outbox in let () = match existing, entries with @@ -101,4 +101,4 @@ let flash_messages inner_handler request = Cookie.set_cookie response flash_cookie value request ~max_age:five_minutes in - Lwt.return response + response diff --git a/src/server/helpers.ml b/src/server/helpers.ml index dae6cfdd..19bbbaeb 100644 --- a/src/server/helpers.ml +++ b/src/server/helpers.ml @@ -4,6 +4,7 @@ Copyright 2021 Anton Bachin *) +open Eio.Std module Formats = Dream_pure.Formats module Message = Dream_pure.Message @@ -46,10 +47,17 @@ let set_https request https = -let request ~client ~method_ ~target ~https ~version ~headers server_stream = +let switch_field = + Message.new_field + ~name:"dream.switch" + ~show_value:(Fmt.to_to_string Switch.dump) + () + +let request ~sw ~client ~method_ ~target ~https ~version ~headers server_stream = let request = Message.request ~method_ ~target ~version ~headers Stream.null server_stream in + Message.set_field request switch_field sw; set_client request client; set_https request https; request @@ -64,20 +72,19 @@ let html ?status ?code ?headers body = let response = Message.response ?status ?code ?headers (Stream.string body) Stream.null in Message.set_header response "Content-Type" Formats.text_html; - Lwt.return response + response let json ?status ?code ?headers body = let response = Message.response ?status ?code ?headers (Stream.string body) Stream.null in Message.set_header response "Content-Type" Formats.application_json; - Lwt.return response + response let response_with_body ?status ?code ?headers body = Message.response ?status ?code ?headers (Stream.string body) Stream.null let respond ?status ?code ?headers body = Message.response ?status ?code ?headers (Stream.string body) Stream.null - |> Lwt.return (* TODO Actually use the request and extract the site prefix. *) let redirect ?status ?code ?headers _request location = @@ -90,9 +97,15 @@ let redirect ?status ?code ?headers _request location = let response = Message.response ?status ?code ?headers Stream.empty Stream.null in Message.set_header response "Location" location; - Lwt.return response + response + +let get_switch request = + match Message.field request switch_field with + | Some sw -> sw + | None -> failwith "Missing switch field on request!" -let stream ?status ?code ?headers callback = +let stream ?status ?code ?headers request callback = + let sw = get_switch request in let reader, writer = Stream.pipe () in let client_stream = Stream.stream reader Stream.no_writer and server_stream = Stream.stream Stream.no_reader writer in @@ -100,9 +113,9 @@ let stream ?status ?code ?headers callback = Message.response ?status ?code ?headers client_stream server_stream in (* TODO Should set up an error handler for this. YES. *) (* TODO Make sure the request id is propagated to the callback. *) - let wrapped_callback _ = Lwt.async (fun () -> callback response) in + let wrapped_callback _ = Fiber.fork ~sw (fun () -> callback response) in Stream.ready server_stream ~close:wrapped_callback wrapped_callback; - Lwt.return response + response let websocket_field = Message.new_field @@ -116,7 +129,8 @@ let is_websocket response = | _ -> false (* TODO Mark the request as a WebSocket request for HTTP. *) -let websocket ?headers callback = +let websocket ?headers request callback = + let sw = get_switch request in let in_reader, in_writer = Stream.pipe () and out_reader, out_writer = Stream.pipe () in let client_stream = Stream.stream out_reader in_writer @@ -126,9 +140,9 @@ let websocket ?headers callback = ~status:`Switching_Protocols ?headers client_stream server_stream in Message.set_field response websocket_field true; (* TODO Make sure the request id is propagated to the callback. *) - let wrapped_callback _ = Lwt.async (fun () -> callback response) in + let wrapped_callback _ = Fiber.fork ~sw (fun () -> callback response) in Stream.ready server_stream ~close:wrapped_callback wrapped_callback; - Lwt.return response + response let empty ?headers status = respond ?headers ~status "" diff --git a/src/server/log.ml b/src/server/log.ml index f2a664f2..bd4064e6 100644 --- a/src/server/log.ml +++ b/src/server/log.ml @@ -493,11 +493,8 @@ struct user_agent); (* Call the rest of the app. *) - Lwt.try_bind - (fun () -> - Lwt.with_value id_lwt_key (Some id) (fun () -> - next_handler request)) - (fun response -> + match Lwt.with_value id_lwt_key (Some id) (fun () -> next_handler request) with + | response -> (* Log the elapsed time. If the response is a redirection, log the target. *) let location = @@ -531,21 +528,20 @@ struct log.info report end; - Lwt.return response) - - (fun exn -> - let backtrace = Printexc.get_backtrace () in - (* In case of exception, log the exception. We alsp log the backtrace - here, even though it is likely to be redundant, because some OCaml - libraries install exception printers that will clobber the backtrace - right during Printexc.to_string! *) - log.warning (fun log -> + response + | exception exn -> + let backtrace = Printexc.get_backtrace () in + (* In case of exception, log the exception. We alsp log the backtrace + here, even though it is likely to be redundant, because some OCaml + libraries install exception printers that will clobber the backtrace + right during Printexc.to_string! *) + log.warning (fun log -> log ~request "Aborted by: %s" (Printexc.to_string exn)); - backtrace - |> iter_backtrace (fun line -> log.warning (fun log -> log "%s" line)); + backtrace + |> iter_backtrace (fun line -> log.warning (fun log -> log "%s" line)); - Lwt.fail exn) + raise exn end diff --git a/src/server/lowercase_headers.ml b/src/server/lowercase_headers.ml index b3ff584f..fec6dced 100644 --- a/src/server/lowercase_headers.ml +++ b/src/server/lowercase_headers.ml @@ -15,9 +15,9 @@ module Message = Dream_pure.Message (* TODO This can be optimized not to convert a header if it is already lowercase. Another option is to use memoization to reduce GC pressure. *) let lowercase_headers inner_handler request = - let%lwt response = inner_handler request in + let response = inner_handler request in if fst (Message.version request) <> 1 then Message.all_headers response |> List.map (fun (name, value) -> String.lowercase_ascii name, value) |> Message.set_all_headers response; - Lwt.return response + response diff --git a/src/server/origin_referrer_check.ml b/src/server/origin_referrer_check.ml index beb9fd55..9306a612 100644 --- a/src/server/origin_referrer_check.ml +++ b/src/server/origin_referrer_check.ml @@ -32,7 +32,6 @@ let origin_referrer_check inner_handler request = log.warning (fun log -> log ~request "Origin and Referer headers both missing"); Message.response ~status:`Bad_Request Stream.empty Stream.null - |> Lwt.return (* TODO Also recommend Uri to users. *) | Some origin -> @@ -41,7 +40,6 @@ let origin_referrer_check inner_handler request = | None -> log.warning (fun log -> log ~request "Host header missing"); Message.response ~status:`Bad_Request Stream.empty Stream.null - |> Lwt.return | Some host -> @@ -76,5 +74,4 @@ let origin_referrer_check inner_handler request = log.warning (fun log -> log ~request "Origin-Host mismatch: '%s' vs. '%s'" origin host); Message.response ~status:`Bad_Request Stream.empty Stream.null - |> Lwt.return end diff --git a/src/server/session.ml b/src/server/session.ml index 45289987..19084f35 100644 --- a/src/server/session.ml +++ b/src/server/session.ml @@ -20,10 +20,10 @@ type 'a back_end = { } let middleware field back_end = fun inner_handler request -> - let%lwt session = back_end.load request in + let session = Lwt_eio.Promise.await_lwt (back_end.load request) in Message.set_field request field session; - let%lwt response = inner_handler request in - back_end.send session request response + let response = inner_handler request in + Lwt_eio.Promise.await_lwt (back_end.send session request response) let getter field request = match Message.field request field with @@ -56,8 +56,8 @@ type session = { } type operations = { - put : string -> string -> unit Lwt.t; - invalidate : unit -> unit Lwt.t; + put : string -> string -> unit; + invalidate : unit -> unit; mutable dirty : bool; } @@ -124,14 +124,12 @@ struct session.payload |> List.remove_assoc name |> fun dictionary -> (name, value)::dictionary - |> fun dictionary -> session.payload <- dictionary; - Lwt.return_unit + |> fun dictionary -> session.payload <- dictionary let invalidate hash_table ~now lifetime operations session = Hashtbl.remove hash_table !session.id; session := create hash_table (now () +. lifetime); - operations.dirty <- true; - Lwt.return_unit + operations.dirty <- true let operations ~now hash_table lifetime session dirty = let rec operations = { @@ -213,13 +211,11 @@ struct |> List.remove_assoc name |> fun dictionary -> (name, value)::dictionary |> fun dictionary -> session.payload <- dictionary; - operations.dirty <- true; - Lwt.return_unit + operations.dirty <- true let invalidate ~now lifetime operations session = session := create (now () +. lifetime); - operations.dirty <- true; - Lwt.return_unit + operations.dirty <- true let operations ~now lifetime session dirty = let rec operations = { diff --git a/src/server/site_prefix.ml b/src/server/site_prefix.ml index 6a41d2f6..e425d028 100644 --- a/src/server/site_prefix.ml +++ b/src/server/site_prefix.ml @@ -36,7 +36,6 @@ let with_site_prefix prefix = match match_site_prefix prefix (Router.path request) with | None -> Message.response ~status:`Bad_Gateway Stream.empty Stream.null - |> Lwt.return | Some path -> (* TODO This doesn't need to be recomputed on each request - can cache the result in the app. *) diff --git a/src/sql/session.ml b/src/sql/session.ml index 57b44929..ab070a3c 100644 --- a/src/sql/session.ml +++ b/src/sql/session.ml @@ -119,15 +119,19 @@ let put request (session : Session.session) name value = |> List.remove_assoc name |> fun dictionary -> (name, value)::dictionary |> fun dictionary -> session.payload <- dictionary; - Sql.sql request (fun db -> update db session) + Lwt_eio.Promise.await_lwt begin + Sql.sql request (fun db -> update db session) + end let invalidate request lifetime operations (session : Session.session ref) = - Sql.sql request begin fun db -> - let%lwt () = remove db !session.id in - let%lwt new_session = create db (Unix.gettimeofday () +. lifetime) 1 in - session := new_session; - operations.Session.dirty <- true; - Lwt.return_unit + Lwt_eio.Promise.await_lwt begin + Sql.sql request begin fun db -> + let%lwt () = remove db !session.id in + let%lwt new_session = create db (Unix.gettimeofday () +. lifetime) 1 in + session := new_session; + operations.Session.dirty <- true; + Lwt.return_unit + end end let operations request lifetime (session : Session.session ref) dirty = diff --git a/src/unix/static.ml b/src/unix/static.ml index c3c0470c..06e8764a 100644 --- a/src/unix/static.ml +++ b/src/unix/static.ml @@ -28,16 +28,16 @@ let mime_lookup filename = let from_filesystem local_root path _ = let file = Filename.concat local_root path in - Lwt.catch - (fun () -> + try + Lwt_eio.Promise.await_lwt ( Lwt_io.(with_file ~mode:Input file) (fun channel -> - let%lwt content = Lwt_io.read channel in - Message.response - ~headers:(mime_lookup path) (Stream.string content) Stream.null - |> Lwt.return)) - (fun _exn -> - Message.response ~status:`Not_Found Stream.empty Stream.null - |> Lwt.return) + let%lwt content = Lwt_io.read channel in + Message.response + ~headers:(mime_lookup path) (Stream.string content) Stream.null + |> Lwt.return) + ) + with _exn -> + Message.response ~status:`Not_Found Stream.empty Stream.null (* TODO Add ETag handling. *) (* TODO Add Content-Length handling? *) @@ -76,16 +76,14 @@ let static ?(loader = from_filesystem) local_root = fun request -> if not @@ Method.methods_equal (Message.method_ request) `GET then Message.response ~status:`Not_Found Stream.empty Stream.null - |> Lwt.return else match validate_path request with | None -> Message.response ~status:`Not_Found Stream.empty Stream.null - |> Lwt.return | Some path -> - let%lwt response = loader local_root path request in + let response = loader local_root path request in if not (Message.has_header response "Content-Type") then begin match Message.status response with | `OK @@ -97,4 +95,4 @@ let static ?(loader = from_filesystem) local_root = fun request -> | _ -> () end; - Lwt.return response + response diff --git a/src/vendor/dune b/src/vendor/dune index ac440a8b..1feae9e3 100644 --- a/src/vendor/dune +++ b/src/vendor/dune @@ -1,39 +1,6 @@ (data_only_dirs *) - -(subdir paf/lib - (library - (name paf) - (public_name dream-mirage.paf) - (modules paf) - (libraries faraday bigstringaf ke mimic))) - -(subdir paf/lib - (library - (name alpn) - (public_name dream-mirage.paf.alpn) - (modules alpn) - (libraries dream-mirage.paf dream-httpaf.httpaf dream-httpaf.h2))) - -(subdir paf/lib - (library - (name paf_mirage) - (public_name dream-mirage.paf.mirage) - (modules paf_mirage) - (libraries dream-mirage.paf tls-mirage mirage-time mirage-stack dream-mirage.paf.alpn))) - -(subdir paf/lib - (library - (name le) - (wrapped false) - (public_name dream-mirage.paf.le) - (modules lE) - (libraries dream-httpaf.httpaf dream-mirage.paf mirage-time mirage-stack duration tls-mirage emile - letsencrypt))) - - - (subdir gluten/lib (library (name gluten) @@ -61,7 +28,7 @@ (select ssl_io.ml from - (lwt_ssl -> ssl_io.real.ml) + (lwt_ssl_disabled -> ssl_io.real.ml) (-> ssl_io.dummy.ml)) (select tls_io.ml diff --git a/test/expect/pure/stream/dune b/test/expect/pure/stream/dune deleted file mode 100644 index b012b025..00000000 --- a/test/expect/pure/stream/dune +++ /dev/null @@ -1,5 +0,0 @@ -(library - (name test_expect_pure_stream) - (libraries test_expect_pure) - (inline_tests) - (preprocess (pps lwt_ppx ppx_expect))) diff --git a/test/expect/pure/stream/stream.ml b/test/expect/pure/stream/stream.ml deleted file mode 100644 index 90a89410..00000000 --- a/test/expect/pure/stream/stream.ml +++ /dev/null @@ -1,298 +0,0 @@ -(* This file is part of Dream, released under the MIT license. See LICENSE.md - for details, or visit https://github.com/aantron/dream. - - Copyright 2021 Anton Bachin *) - - - -module Stream = Dream_pure.Stream - - - -let read_and_dump stream = - Stream.read stream - ~data:(fun buffer offset length binary fin -> - Printf.printf "read: data: BINARY=%b FIN=%b %s\n" - binary fin (Bigstringaf.substring buffer ~off:offset ~len:length)) - ~close:(fun code -> - Printf.printf "read: close: CODE=%i\n" code) - ~flush:(fun () -> - print_endline "read: flush") - ~ping:(fun buffer offset length -> - Printf.printf "read: ping: %s\n" - (Bigstringaf.substring buffer ~off:offset ~len:length)) - ~pong:(fun buffer offset length -> - Printf.printf "read: pong: %s\n" - (Bigstringaf.substring buffer ~off:offset ~len:length)) - -let flush_and_dump stream = - Stream.flush stream - ~close:(fun code -> - Printf.printf "flush: close: CODE=%i\n" code) - (fun () -> - print_endline "flush: ok") - -let write_and_dump stream buffer offset length binary fin = - Stream.write stream buffer offset length binary fin - ~close:(fun code -> - Printf.printf "write: close: CODE=%i\n" code) - (fun () -> - print_endline "write: ok") - -let ping_and_dump payload stream = - let length = String.length payload in - Stream.ping stream (Bigstringaf.of_string ~off:0 ~len:length payload) 0 length - ~close:(fun code -> - Printf.printf "ping: close: CODE=%i\n" code) - (fun () -> - print_endline "ping: ok") - -let pong_and_dump payload stream = - let length = String.length payload in - Stream.pong stream (Bigstringaf.of_string ~off:0 ~len:length payload) 0 length - ~close:(fun code -> - Printf.printf "pong: close: CODE=%i\n" code) - (fun () -> - print_endline "pong: ok") - - - -(* Read-only streams. *) - -let%expect_test _ = - let stream = Stream.(stream empty no_writer) in - read_and_dump stream; - read_and_dump stream; - Stream.close stream 1005; - read_and_dump stream; - [%expect {| - read: close: CODE=1000 - read: close: CODE=1000 - read: close: CODE=1000 |}] - -let%expect_test _ = - let stream = Stream.(stream empty no_writer) in - Stream.close stream 1005; - read_and_dump stream; - [%expect {| read: close: CODE=1000 |}] - -let%expect_test _ = - let stream = Stream.(stream (string "foo") no_writer) in - read_and_dump stream; - read_and_dump stream; - read_and_dump stream; - Stream.close stream 1005; - read_and_dump stream; - [%expect {| - read: data: BINARY=true FIN=true foo - read: close: CODE=1000 - read: close: CODE=1000 - read: close: CODE=1000 |}] - -let%expect_test _ = - let stream = Stream.(stream (string "") no_writer) in - read_and_dump stream; - read_and_dump stream; - [%expect {| - read: close: CODE=1000 - read: close: CODE=1000 |}] - -let%expect_test _ = - let stream = Stream.(stream (string "foo") no_writer) in - Stream.close stream 1005; - read_and_dump stream; - [%expect {| read: close: CODE=1000 |}] - -let%expect_test _ = - let stream = Stream.(stream empty no_writer) in - (try write_and_dump stream Bigstringaf.empty 0 0 false false - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - (try flush_and_dump stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - (try ping_and_dump "foo" stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - (try pong_and_dump "bar" stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - [%expect {| - (Failure "write to a read-only stream") - (Failure "flush of a read-only stream") - (Failure "ping on a read-only stream") - (Failure "pong on a read-only stream") |}] - - - -(* Pipe: double read. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - try read_and_dump stream - with Failure _ as exn -> print_endline (Printexc.to_string exn); - [%expect {| (Failure "stream read: the previous read has not completed") |}] - - - -(* Pipe: interactions between read and close. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - print_endline "checkpoint 1"; - Stream.close stream 1005; - print_endline "checkpoint 2"; - read_and_dump stream; - print_endline "checkpoint 3"; - Stream.close stream 1000; - [%expect {| - checkpoint 1 - read: close: CODE=1005 - checkpoint 2 - read: close: CODE=1005 - checkpoint 3 |}] - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - Stream.close stream 1005; - read_and_dump stream; - read_and_dump stream; - [%expect {| - read: close: CODE=1005 - read: close: CODE=1005 |}] - - - -(* Pipe: interactions between read and flush. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - print_endline "checkpoint 1"; - flush_and_dump stream; - (try flush_and_dump stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - read_and_dump stream; - flush_and_dump stream; - [%expect {| - checkpoint 1 - read: flush - (Failure "stream flush: the previous write has not completed") - flush: ok - read: flush |}] - - - -(* Pipe: interactions between read and write. *) - -let buffer = - Bigstringaf.of_string ~off:0 ~len:3 "foo" - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - print_endline "checkpoint 1"; - write_and_dump stream buffer 0 3 false true; - (try write_and_dump stream buffer 1 1 true false - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - read_and_dump stream; - write_and_dump stream buffer 0 3 true true; - [%expect {| - checkpoint 1 - read: data: BINARY=false FIN=true foo - (Failure "stream write: the stream is not ready") - write: ok - read: data: BINARY=true FIN=true foo |}] - - - -(* Pipe: interactions between read and ping. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - print_endline "checkpoint 1"; - ping_and_dump "foo" stream; - (try ping_and_dump "bar" stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - read_and_dump stream; - ping_and_dump "baz" stream; - [%expect {| - checkpoint 1 - read: ping: foo - (Failure "stream ping: the previous write has not completed") - ping: ok - read: ping: baz |}] - - - -(* Pipe: interactions between read and pong. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - read_and_dump stream; - print_endline "checkpoint 1"; - pong_and_dump "foo" stream; - (try pong_and_dump "bar" stream - with Failure _ as exn -> print_endline (Printexc.to_string exn)); - read_and_dump stream; - pong_and_dump "baz" stream; - [%expect {| - checkpoint 1 - read: pong: foo - (Failure "stream pong: the previous write has not completed") - pong: ok - read: pong: baz |}] - - - -(* Pipe: interactions between flush and close. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - Stream.close stream 1005; - flush_and_dump stream; - [%expect {| - flush: close: CODE=1005 |}] - - - -(* Pipe: interactions between write and close. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - Stream.close stream 1005; - write_and_dump stream buffer 0 3 true false; - [%expect {| - write: close: CODE=1005 |}] - - - -(* Pipe: interactions between ping and close. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - Stream.close stream 1005; - ping_and_dump "bar" stream; - [%expect {| - ping: close: CODE=1005 |}] - - - -(* Pipe: interactions between pong and close. *) - -let%expect_test _ = - let reader, writer = Stream.pipe () in - let stream = Stream.stream reader writer in - Stream.close stream 1005; - pong_and_dump "bar" stream; - [%expect {| - pong: close: CODE=1005 |}]