From: Stephane Glondu Date: Sat, 14 Mar 2026 08:43:02 +0000 (+0100) Subject: New upstream version 6.1.0 X-Git-Tag: archive/raspbian/6.2.2-1+rpi1^2~5^2~4 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=f44dba896005bada806d143628139adc8f54fd0a;p=ocaml-cohttp.git New upstream version 6.1.0 --- diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 9727c48..03ea1b1 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -5,8 +5,6 @@ on: jobs: tests: runs-on: ubuntu-latest - env: - NIXPKGS_ALLOW_INSECURE: 1 steps: - name: Checkout tree uses: actions/checkout@v4 @@ -14,5 +12,7 @@ jobs: submodules: true - uses: cachix/install-nix-action@v30 with: - nix_path: nixpkgs=channel:nixos-unstable - - run: nix develop -c dune build @runtest @check + extra_nix_config: | + extra-substituters = https://anmonteiro.nix-cache.workers.dev + extra-trusted-public-keys = ocaml.nix-cache.com-1:/xI2h2+56rwFfKyyFVbkJSeGqSIYMC/Je+7XXqGKDIY= + - run: nix develop -L .# -c dune build @runtest @check diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index c407e41..ceca122 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -69,7 +69,7 @@ jobs: - ubuntu-latest - macos-latest ocaml-compiler: - - "5" + - "5.2" local-packages: - | *.opam @@ -151,7 +151,7 @@ jobs: os: - ubuntu-latest ocaml-compiler: - - "5" + - "5.2" local-packages: - | *.opam diff --git a/.ocamlformat b/.ocamlformat index 1595ace..291f8a0 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1,4 +1,4 @@ -version = 0.26.2 +version=0.27.0 profile=conventional break-infix=fit-or-vertical parse-docstrings=true diff --git a/CHANGES.md b/CHANGES.md index b0baa2b..5f70bd8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +# v6.1.0 (2025-03-03) + +- cohttp-lwt-unix: Add http/https proxy support for client requests (@art-w @MisterDA, #1080) +- cohttp-mirage: Support conduit 8.0.0 (@hannesm, #1104) + ## v6.0.0 (2024-11-21) - bump minimum dune version to 3.8 (@avsm) @@ -16,7 +21,7 @@ - cohttp: `Cohttp.Request.make_for_client` no longer allows setting both `~chunked:true` and `~body_length`. - cohttp-lwt-unix: Don't blow up when certificates are not available and no-network requests are made. (akuhlens #1027) - + Makes `cohttp-lwt.S.default_ctx` lazy. + + Makes `cohttp-lwt.S.default_ctx` lazy. ## v6.0.0~beta2 (2024-01-05) diff --git a/cohttp-async.opam b/cohttp-async.opam index 4a8e5d6..5234580 100644 --- a/cohttp-async.opam +++ b/cohttp-async.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation for the Async concurrency library" @@ -26,7 +26,7 @@ doc: "https://mirage.github.io/ocaml-cohttp/" bug-reports: "https://github.com/mirage/ocaml-cohttp/issues" depends: [ "dune" {>= "3.8"} - "ocaml" {>= "4.14"} + "ocaml" {>= "4.14" & < "5.3.0"} "http" {= version} "cohttp" {= version} "async_kernel" {>= "v0.16.0"} diff --git a/cohttp-async/bin/cohttp_server_async.ml b/cohttp-async/bin/cohttp_server_async.ml index 8398e02..5ecaa22 100644 --- a/cohttp-async/bin/cohttp_server_async.ml +++ b/cohttp-async/bin/cohttp_server_async.ml @@ -116,9 +116,9 @@ let start_server docroot port index cert_file key_file verbose () = Server.create ~on_handler_error: (`Call - (fun addr exn -> - Logs.err (fun f -> f "Error from %s" (Socket.Address.to_string addr)); - Logs.err (fun f -> f "%s" @@ Exn.to_string exn))) + (fun addr exn -> + Logs.err (fun f -> f "Error from %s" (Socket.Address.to_string addr)); + Logs.err (fun f -> f "%s" @@ Exn.to_string exn))) ~mode (Tcp.Where_to_listen.of_port port) (handler ~info ~docroot ~index) diff --git a/cohttp-async/test/test_async_integration.ml b/cohttp-async/test/test_async_integration.ml index b700b1a..a2cfb9a 100644 --- a/cohttp-async/test/test_async_integration.ml +++ b/cohttp-async/test/test_async_integration.ml @@ -28,27 +28,27 @@ let server = ] (* pipelined_chunk *) @ (response_bodies |> List.map ~f:(Fn.compose const ok)) - @ (* large response chunked *) - [ - (fun _ _ -> - let body = - let r, w = Pipe.create () in - let chunk = chunk chunk_size in - for _ = 0 to chunks - 1 do - Pipe.write_without_pushback w chunk - done; - Pipe.close w; - r - in - Server.respond_with_pipe ~code:`OK body >>| response); - (* pipelined_expert *) - expert (fun _ic oc -> - Async_unix.Writer.write oc "8\r\nexpert 1\r\n0\r\n\r\n"; - Async_unix.Writer.flushed oc); - expert (fun ic oc -> - Async_unix.Writer.write oc "8\r\nexpert 2\r\n0\r\n\r\n"; - Async_unix.Writer.flushed oc >>= fun () -> Async_unix.Reader.close ic); - ] + (* large response chunked *) + @ [ + (fun _ _ -> + let body = + let r, w = Pipe.create () in + let chunk = chunk chunk_size in + for _ = 0 to chunks - 1 do + Pipe.write_without_pushback w chunk + done; + Pipe.close w; + r + in + Server.respond_with_pipe ~code:`OK body >>| response); + (* pipelined_expert *) + expert (fun _ic oc -> + Async_unix.Writer.write oc "8\r\nexpert 1\r\n0\r\n\r\n"; + Async_unix.Writer.flushed oc); + expert (fun ic oc -> + Async_unix.Writer.write oc "8\r\nexpert 2\r\n0\r\n\r\n"; + Async_unix.Writer.flushed oc >>= fun () -> Async_unix.Reader.close ic); + ] |> response_sequence let ts = diff --git a/cohttp-bench.opam b/cohttp-bench.opam index e187ec9..3f7dbcd 100644 --- a/cohttp-bench.opam +++ b/cohttp-bench.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Benchmarks binaries for Cohttp" diff --git a/cohttp-curl-async.opam b/cohttp-curl-async.opam index e9e1e74..d855be4 100644 --- a/cohttp-curl-async.opam +++ b/cohttp-curl-async.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Cohttp client using Curl & Async as the backend" diff --git a/cohttp-curl-lwt.opam b/cohttp-curl-lwt.opam index 66aaf78..49b43c8 100644 --- a/cohttp-curl-lwt.opam +++ b/cohttp-curl-lwt.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Cohttp client using Curl & Lwt as the backend" diff --git a/cohttp-curl.opam b/cohttp-curl.opam index 5b0a91a..dcd07e1 100644 --- a/cohttp-curl.opam +++ b/cohttp-curl.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Shared code between the individual cohttp-curl clients" diff --git a/cohttp-eio.opam b/cohttp-eio.opam index 3f454df..d3ef96d 100644 --- a/cohttp-eio.opam +++ b/cohttp-eio.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation with eio backend" @@ -30,7 +30,7 @@ depends: [ "logs" "uri" "tls-eio" {with-test & >= "1.0.0"} - "mirage-crypto-rng-eio" {with-test & >= "0.11.2"} + "mirage-crypto-rng" {with-test & >= "1.2.0"} "ca-certs" {with-test & >= "1.0.0"} "fmt" "ptime" diff --git a/cohttp-eio/examples/client_tls.ml b/cohttp-eio/examples/client_tls.ml index 5289c7c..dc32986 100644 --- a/cohttp-eio/examples/client_tls.ml +++ b/cohttp-eio/examples/client_tls.ml @@ -26,7 +26,7 @@ let https ~authenticator = let () = Eio_main.run @@ fun env -> - Mirage_crypto_rng_eio.run (module Mirage_crypto_rng.Fortuna) env @@ fun () -> + Mirage_crypto_rng_unix.use_default (); let client = Client.make ~https:(Some (https ~authenticator)) env#net in Eio.Switch.run @@ fun sw -> let resp, body = diff --git a/cohttp-eio/examples/dune b/cohttp-eio/examples/dune index 9b0f207..8786ecb 100644 --- a/cohttp-eio/examples/dune +++ b/cohttp-eio/examples/dune @@ -10,7 +10,7 @@ logs.threaded tls-eio ca-certs - mirage-crypto-rng-eio)) + mirage-crypto-rng.unix)) (alias (name runtest) diff --git a/cohttp-lwt-jsoo.opam b/cohttp-lwt-jsoo.opam index 06ae32f..e6d779c 100644 --- a/cohttp-lwt-jsoo.opam +++ b/cohttp-lwt-jsoo.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation for the Js_of_ocaml JavaScript compiler" diff --git a/cohttp-lwt-jsoo/src/cohttp_lwt_jsoo.ml b/cohttp-lwt-jsoo/src/cohttp_lwt_jsoo.ml index 2d68024..9c1bd25 100644 --- a/cohttp-lwt-jsoo/src/cohttp_lwt_jsoo.ml +++ b/cohttp-lwt-jsoo/src/cohttp_lwt_jsoo.ml @@ -63,11 +63,11 @@ module Body_builder (P : Params) = struct let chunkerizer () = if !pos = body_len then Lwt.return C.Transfer.Done else if !pos + P.chunk_size >= body_len then ( - let str = text ## (substring_toEnd !pos) in + let str = text##(substring_toEnd !pos) in pos := body_len; Lwt.return (C.Transfer.Final_chunk (P.convert_body_string str))) else - let str = text ## (substring !pos (!pos + P.chunk_size)) in + let str = text##(substring !pos (!pos + P.chunk_size)) in pos := !pos + P.chunk_size; Lwt.return (C.Transfer.Chunk (P.convert_body_string str)) in @@ -205,11 +205,10 @@ module Make_client_async (P : Params) = Make_api (struct xml##.responseType := Js.string "arraybuffer"; let (res : (Http.Response.t Lwt.t * CLB.t) Lwt.t), wake = Lwt.task () in let () = - xml - ## (_open - (Js.string (C.Code.string_of_method meth)) - (Js.string (Uri.to_string uri)) - Js._true) + xml##(_open + (Js.string (C.Code.string_of_method meth)) + (Js.string (Uri.to_string uri)) + Js._true) (* asynchronous call *) in (* set request headers *) @@ -221,7 +220,7 @@ module Make_client_async (P : Params) = Make_api (struct (fun k v -> (* some headers lead to errors in the javascript console, should we filter then out here? *) - xml ## (setRequestHeader (Js.string k) (Js.string v))) + xml##(setRequestHeader (Js.string k) (Js.string v))) headers in @@ -264,7 +263,7 @@ module Make_client_async (P : Params) = Make_api (struct (* perform call *) (match body with - | None -> Lwt.return xml ## (send Js.null) + | None -> Lwt.return xml##(send Js.null) | Some body -> CLB.to_string body >>= fun body -> let bs = binary_string body in @@ -292,11 +291,10 @@ module Make_client_sync (P : Params) = Make_api (struct if Lazy.force xhr_response_supported then xml##.responseType := Js.string "arraybuffer"; let () = - xml - ## (_open - (Js.string (C.Code.string_of_method meth)) - (Js.string (Uri.to_string uri)) - Js._false) + xml##(_open + (Js.string (C.Code.string_of_method meth)) + (Js.string (Uri.to_string uri)) + Js._false) (* synchronous call *) in (* set request headers *) @@ -308,16 +306,16 @@ module Make_client_sync (P : Params) = Make_api (struct (fun k v -> (* some headers lead to errors in the javascript console, should we filter then out here? *) - xml ## (setRequestHeader (Js.string k) (Js.string v))) + xml##(setRequestHeader (Js.string k) (Js.string v))) headers in (* perform call *) (match body with - | None -> Lwt.return xml ## (send Js.null) + | None -> Lwt.return xml##(send Js.null) | Some body -> CLB.to_string body >|= fun body -> let bs = binary_string body in - xml ## (send (Js.Opt.return (Obj.magic bs)))) + xml##(send (Js.Opt.return (Obj.magic bs)))) >>= fun _body -> let body = Bb.construct_body xml in (* (re-)construct the response *) diff --git a/cohttp-lwt-unix.opam b/cohttp-lwt-unix.opam index 1128ae5..5255559 100644 --- a/cohttp-lwt-unix.opam +++ b/cohttp-lwt-unix.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation for Unix and Windows using Lwt" @@ -35,8 +35,8 @@ depends: [ "cohttp-lwt" {= version} "cmdliner" {>= "1.1.0"} "lwt" {>= "3.0.0"} - "conduit-lwt" {>= "5.0.0"} - "conduit-lwt-unix" {>= "5.0.0"} + "conduit-lwt" {>= "7.1.0"} + "conduit-lwt-unix" {>= "7.1.0"} "fmt" {>= "0.8.2"} "base-unix" "ppx_sexp_conv" {>= "v0.13.0"} diff --git a/cohttp-lwt-unix/examples/client_lwt_proxy.ml b/cohttp-lwt-unix/examples/client_lwt_proxy.ml new file mode 100644 index 0000000..0293003 --- /dev/null +++ b/cohttp-lwt-unix/examples/client_lwt_proxy.ml @@ -0,0 +1,145 @@ +open Lwt +open Cohttp +open Cohttp_lwt_unix + +let () = + if not @@ Debug.debug_active () then ( + Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ~utf_8:true (); + Logs.set_level ~all:true (Some Logs.Debug); + Logs.set_reporter Debug.default_reporter) + +let proxy_uri = ref None +let uri = ref [] +let proxy_authorization = ref None +let set_proxy_uri uri = proxy_uri := Some (Uri.of_string uri) + +let set_proxy_authorization auth = + proxy_authorization := + Some (Cohttp.Auth.credential_of_string ("Basic " ^ Base64.encode_exn auth)) + +let usage_msg = + {|Usage: test_client_proxy -proxy +Examples: +$ test_client_proxy -proxy http://localhost:8080 http://example.com +$ test_client_proxy -proxy https://localhost:8080 https://example.com +Options:|} + +let anon_fun args = uri := !uri @ [ args ] + +let speclist = + [ + ("-proxy", Arg.String set_proxy_uri, " Proxy uri"); + ("-proxyauth", Arg.String set_proxy_authorization, " Proxy authorization"); + ] + +(* Boilerplate code to handle redirects *) + +let rec http_get_and_follow ~max_redirects ?headers uri = + let open Lwt.Syntax in + let* ans = Cohttp_lwt_unix.Client.get ?headers uri in + follow_redirect ~max_redirects ?headers uri ans + +and follow_redirect ~max_redirects ?headers request_uri (response, body) = + let open Lwt.Syntax in + let status = Http.Response.status response in + (* The unconsumed body would otherwise leak memory *) + let* () = + if status <> `OK then Cohttp_lwt.Body.drain_body body else Lwt.return_unit + in + match status with + | `OK -> Lwt.return (response, body) + | `Permanent_redirect | `Moved_permanently -> + handle_redirect ~permanent:true ~max_redirects ?headers request_uri + response + | `Found | `Temporary_redirect -> + handle_redirect ~permanent:false ~max_redirects ?headers request_uri + response + | `Not_found | `Gone -> failwith "Not found" + | status -> + Printf.ksprintf failwith "Unhandled status: %s" + (Cohttp.Code.string_of_status status) + +and handle_redirect ~permanent ~max_redirects ?headers request_uri response = + if max_redirects <= 0 then failwith "Too many redirects" + else + let headers' = Http.Response.headers response in + let location = Http.Header.get headers' "location" in + match location with + | None -> failwith "Redirection without Location header" + | Some url -> + let open Lwt.Syntax in + let uri = Uri.of_string url in + let* () = + if permanent then + Logs_lwt.warn (fun m -> + m "Permanent redirection from %s to %s" + (Uri.to_string request_uri) + url) + else Lwt.return_unit + in + http_get_and_follow ?headers uri ~max_redirects:(max_redirects - 1) + +(* Interesting stuff *) + +let getenv_opt k = + match Sys.getenv_opt k with + | Some v -> Some (k, Uri.of_string v) + | None -> None + +let getenv_opt_case k = + match getenv_opt (String.lowercase_ascii k) with + | None -> getenv_opt (String.uppercase_ascii k) + | v -> v + +let main ~proxy ~uri ~credential () = + let all_proxy, no_proxy, scheme_proxy = + match proxy with + | None -> + ( Option.map Uri.of_string (Sys.getenv_opt "ALL_PROXY"), + Sys.getenv_opt "NO_PROXY", + [ + getenv_opt_case "httpunix_proxy"; + getenv_opt_case "https_proxy"; + getenv_opt "http_proxy"; + ] + |> List.filter_map (function + | Some (k, v) -> Some (String.(sub k 0 (rindex k '_')), v) + | n -> n) ) + | v -> (v, None, []) + in + + let proxy_headers = + Option.map + (fun credential -> + Http.Header.init_with "Proxy-Authorization" + (Cohttp.Auth.string_of_credential credential)) + credential + in + + let module Cache = Cohttp_lwt_unix.Connection_proxy in + let cache = + Cache.create ?all_proxy ~scheme_proxy ?no_proxy ?proxy_headers () + in + Client.set_cache (Cache.call cache); + + http_get_and_follow ~max_redirects:2 (Uri.of_string uri) + >>= fun (resp, body) -> + let code = resp |> Response.status |> Code.code_of_status in + Printf.printf "Response code: %d\n" code; + Printf.printf "Headers: %s\n" (resp |> Response.headers |> Header.to_string); + body |> Cohttp_lwt.Body.to_string >|= fun body -> + Printf.printf "Body of length: %d\n" (String.length body); + print_endline ("Received body\n" ^ body) + +(* Argument parsing *) + +let () = + Arg.parse speclist anon_fun usage_msg; + if List.length !uri <> 1 then ( + prerr_endline "Expected a single resource uri."; + prerr_endline usage_msg; + exit 1); + let proxy = !proxy_uri + and uri = List.hd !uri + and credential = !proxy_authorization in + Lwt_main.run (main ~proxy ~uri ~credential ()) diff --git a/cohttp-lwt-unix/examples/dune b/cohttp-lwt-unix/examples/dune index c323e76..b596cd6 100644 --- a/cohttp-lwt-unix/examples/dune +++ b/cohttp-lwt-unix/examples/dune @@ -1,8 +1,13 @@ (executables - (names client_lwt client_lwt_timeout docker_lwt server_lwt) - (libraries cohttp-lwt-unix)) + (names client_lwt client_lwt_timeout docker_lwt server_lwt client_lwt_proxy) + (libraries cohttp-lwt-unix fmt.tty)) (alias (name runtest) (package cohttp-lwt-unix) - (deps client_lwt.exe client_lwt_timeout.exe docker_lwt.exe server_lwt.exe)) + (deps + client_lwt.exe + client_lwt_timeout.exe + docker_lwt.exe + server_lwt.exe + client_lwt_proxy.exe)) diff --git a/cohttp-lwt-unix/src/cohttp_lwt_unix.ml b/cohttp-lwt-unix/src/cohttp_lwt_unix.ml index 1202020..db82391 100644 --- a/cohttp-lwt-unix/src/cohttp_lwt_unix.ml +++ b/cohttp-lwt-unix/src/cohttp_lwt_unix.ml @@ -38,6 +38,14 @@ module Connection_cache = let sleep_ns ns = Lwt_unix.sleep (Int64.to_float ns /. 1_000_000_000.) end) +module Connection_proxy = + Cohttp_lwt.Connection_cache.Make_proxy + (Connection) + (struct + (* : Mirage_time.S *) + let sleep_ns ns = Lwt_unix.sleep (Int64.to_float ns /. 1_000_000_000.) + end) + module Client : sig (** The [Client] module implements the full UNIX HTTP client interface, including the UNIX-specific functions defined in {!C}. *) diff --git a/cohttp-lwt-unix/src/net.ml b/cohttp-lwt-unix/src/net.ml index cd0edef..6a49c8e 100644 --- a/cohttp-lwt-unix/src/net.ml +++ b/cohttp-lwt-unix/src/net.ml @@ -35,15 +35,21 @@ let default_ctx = } type endp = Conduit.endp +type client = Conduit_lwt_unix.client let resolve ~ctx uri = Resolver_lwt.resolve_uri ~uri ctx.resolver -let connect_endp ~ctx:{ ctx; _ } endp = - Conduit_lwt_unix.endp_to_client ~ctx endp >>= fun client -> +let tunnel hostname (channels : IO.ic * IO.oc) : client = + `TLS_tunnel (`Hostname hostname, (fst channels).chan, snd channels) + +let connect_client ~ctx:{ ctx; _ } client = Conduit_lwt_unix.connect ~ctx client >|= fun (flow, ic, oc) -> let ic = Input_channel.create ic in (flow, ic, oc) +let connect_endp ~ctx endp = + Conduit_lwt_unix.endp_to_client ~ctx:ctx.ctx endp >>= connect_client ~ctx + let connect_uri ~ctx uri = resolve ~ctx uri >>= connect_endp ~ctx let close c = diff --git a/cohttp-lwt-unix/src/net.mli b/cohttp-lwt-unix/src/net.mli index c3f216b..42c09fd 100644 --- a/cohttp-lwt-unix/src/net.mli +++ b/cohttp-lwt-unix/src/net.mli @@ -24,6 +24,7 @@ include with module IO = Io and type ctx := ctx and type endp = Conduit.endp + and type client = Conduit_lwt_unix.client val init : ?ctx:Conduit_lwt_unix.ctx -> ?resolver:Resolver_lwt.t -> unit -> ctx (** [init ?ctx ?resolver ()] is a network context that is the same as the diff --git a/cohttp-lwt-unix/test/test_client.ml b/cohttp-lwt-unix/test/test_client.ml index 8ab5c34..47d7f6e 100644 --- a/cohttp-lwt-unix/test/test_client.ml +++ b/cohttp-lwt-unix/test/test_client.ml @@ -114,7 +114,10 @@ let test_client uri = assert_equal ~printer:Fun.id "Spring" body; (* simple request function accepting custom requests. *) - let handler ?headers ?body meth uri = Client.call ?headers ?body meth uri in + let handler ?headers ?body ?absolute_form meth uri = + ignore absolute_form; + Client.call ?headers ?body meth uri + in tests handler uri (* The Client.{call, get, put, ...} functions by default use a new @@ -143,7 +146,8 @@ let test_non_persistent uri = (* the resolved endpoint may be buffered to avoid stressing the resolver: *) Connection.Net.resolve ~ctx:(Lazy.force Connection.Net.default_ctx) uri >>= fun endp -> - let handler ?headers ?body meth uri = + let handler ?headers ?body ?absolute_form meth uri = + ignore absolute_form; Connection.connect ~persistent:false endp >>= fun connection -> Connection.call connection ?headers ?body meth uri in @@ -159,7 +163,8 @@ let test_unknown uri = Connection.connect ~persistent:false endp >>= fun c -> let connection = ref c in (* reference to open connection *) - let rec handler ?headers ?body meth uri = + let rec handler ?headers ?body ?absolute_form meth uri = + ignore absolute_form; Lwt.catch (fun () -> Connection.call !connection ?headers ?body meth uri) (function diff --git a/cohttp-lwt-unix/test/test_sanity.ml b/cohttp-lwt-unix/test/test_sanity.ml index 90adbdf..5c8202f 100644 --- a/cohttp-lwt-unix/test/test_sanity.ml +++ b/cohttp-lwt-unix/test/test_sanity.ml @@ -50,22 +50,22 @@ let server = (fun _ _ -> Lwt.return (`Expert - (let headers = - Http.( - Header.add_transfer_encoding (Header.init ()) Transfer.Chunked) - in - ( Http.Response.make ~headers (), - fun _ic oc -> Lwt_io.write oc "8\r\nexpert 1\r\n0\r\n\r\n" )))); + (let headers = + Http.( + Header.add_transfer_encoding (Header.init ()) Transfer.Chunked) + in + ( Http.Response.make ~headers (), + fun _ic oc -> Lwt_io.write oc "8\r\nexpert 1\r\n0\r\n\r\n" )))); (fun _ _ -> Lwt.return (`Expert - ( (* Alternatively, cohttp.response.make injects the Chunked encoding when no + ( (* Alternatively, cohttp.response.make injects the Chunked encoding when no encoding is already in the headers. *) - Cohttp.Response.make (), - fun ic oc -> - Lwt_io.write oc "8\r\nexpert 2\r\n0\r\n\r\n" >>= fun () -> - Lwt_io.flush oc >>= fun () -> - Cohttp_lwt_unix.Private.Input_channel.close ic ))); + Cohttp.Response.make (), + fun ic oc -> + Lwt_io.write oc "8\r\nexpert 2\r\n0\r\n\r\n" >>= fun () -> + Lwt_io.flush oc >>= fun () -> + Cohttp_lwt_unix.Private.Input_channel.close ic ))); ] |> response_sequence diff --git a/cohttp-lwt.opam b/cohttp-lwt.opam index 3624545..487026e 100644 --- a/cohttp-lwt.opam +++ b/cohttp-lwt.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation using the Lwt concurrency library" @@ -32,6 +32,7 @@ depends: [ "cohttp" {= version} "lwt" {>= "5.4.0"} "sexplib0" + "ipaddr" {>= "5.6.0"} "ppx_sexp_conv" {>= "v0.13.0"} "logs" "uri" {>= "2.0.0"} diff --git a/cohttp-lwt/src/connection.ml b/cohttp-lwt/src/connection.ml index 879ed08..0c454a7 100644 --- a/cohttp-lwt/src/connection.ml +++ b/cohttp-lwt/src/connection.ml @@ -33,6 +33,7 @@ module Make (Net : S.Net) : S.Connection with module Net = Net = struct (* enable warning when https://github.com/mirage/ocaml-conduit/pull/319 is released *) type req_resr = { + absolute_form : bool; uri : Uri.t; meth : Cohttp.Code.meth; headers : Header.t; @@ -164,12 +165,15 @@ module Make (Net : S.Net) : S.Connection with module Net = Net = struct queue_fail connection connection.in_flight e; Lwt.return_unit) - let call connection ?headers ?(body = `Empty) meth uri = + let call connection ?headers ?(body = `Empty) ?(absolute_form = false) meth + uri = let headers = match headers with Some h -> h | None -> Header.init () in match connection.state with | Connecting _ | Full _ -> let res, res_r = Lwt.wait () in - Queue.push { uri; meth; headers; body; res_r } connection.waiting; + Queue.push + { absolute_form; uri; meth; headers; body; res_r } + connection.waiting; Lwt_condition.broadcast connection.condition (); res | Closing _ | Half _ | Closed | Failed _ -> raise Retry @@ -193,7 +197,7 @@ module Make (Net : S.Net) : S.Connection with module Net = Net = struct *) Lwt.return_unit | Full (ic, oc) | Closing (ic, oc) -> - let ({ uri; meth; headers; body; res_r } as work) = + let ({ absolute_form; uri; meth; headers; body; res_r } as work) = Queue.take connection.waiting in @@ -222,7 +226,7 @@ module Make (Net : S.Net) : S.Connection with module Net = Net = struct else headers in - let req = Request.make ~encoding ~meth ~headers uri in + let req = Request.make ~encoding ~meth ~headers ~absolute_form uri in Queue.push work connection.in_flight; @@ -293,6 +297,34 @@ module Make (Net : S.Net) : S.Connection with module Net = Net = struct on_failure; connection + let create_tunnel ?(finalise = fun _ -> Lwt.return_unit) + ?(ctx = Lazy.force Net.default_ctx) proxy remote_host = + match proxy.state with + | Full (ic, oc) -> + let client = Net.tunnel remote_host (ic, oc) in + let channels = + Net.connect_client ~ctx client >>= fun (_, ic, oc) -> return (ic, oc) + in + let connection = + { + finalise; + in_flight = Queue.create (); + waiting = Queue.create (); + state = Connecting channels; + condition = Lwt_condition.create (); + persistent = `True; + } + in + let on_failure e = connection.state <- Failed e in + Lwt.on_any channels + (fun channels -> + connection.state <- Full channels; + Lwt.dont_wait (fun () -> reader connection) on_failure; + Lwt.dont_wait (fun () -> writer connection) on_failure) + on_failure; + connection + | _ -> failwith "Proxy connection wasn't in right state." + let connect ?finalise ?persistent ?ctx uri = let connection = create ?finalise ?persistent ?ctx uri in match connection.state with diff --git a/cohttp-lwt/src/connection_cache.ml b/cohttp-lwt/src/connection_cache.ml index 0dd4643..695c520 100644 --- a/cohttp-lwt/src/connection_cache.ml +++ b/cohttp-lwt/src/connection_cache.ml @@ -1,15 +1,6 @@ exception Retry = Connection.Retry -(** This functor establishes a new connection for each request. *) -module Make_no_cache (Connection : S.Connection) : sig - include S.Connection_cache - - val create : ?ctx:Connection.Net.ctx -> unit -> t - (** [create ?ctx ()] creates a connection for handling a single request. The - connection accepts only a single request and will automatically be closed - as soon as possible. - @param ctx See {!Connection.Net.ctx} *) -end = struct +module Make_no_cache (Connection : S.Connection) = struct module Net = Connection.Net module IO = Net.IO open IO @@ -18,12 +9,15 @@ end = struct let call = Fun.id - let create ?(ctx = Lazy.force Net.default_ctx) () ?headers ?body meth uri = + let create ?(ctx = Lazy.force Net.default_ctx) () ?headers ?body + ?absolute_form meth uri = Net.resolve ~ctx uri (* TODO: Support chunked encoding without ~persistent:true ? *) >>= Connection.connect ~ctx ~persistent:true >>= fun connection -> - let res = Connection.call connection ?headers ?body meth uri in + let res = + Connection.call connection ?headers ?body ?absolute_form meth uri + in (* this can be simplified when https://github.com/mirage/ocaml-conduit/pull/319 is released. *) Lwt.dont_wait (fun () -> @@ -38,38 +32,7 @@ end = struct res end -(** This functor keeps a cache of connections for reuse. Connections are reused - based on their remote {!type:Conduit.endp} (effectively IP / port). *) -module Make (Connection : S.Connection) (Sleep : S.Sleep) : sig - include S.Connection_cache - - val create : - ?ctx:Connection.Net.ctx -> - ?keep:int64 -> - ?retry:int -> - ?parallel:int -> - ?depth:int -> - unit -> - t - (** Create a new connection cache - - @param ctx Conduit context to use. See {!type:Connection.Net.ctx}. - @param keep Number of nanoseconds to keep an idle connection around. - @param retry - Number of times a {e gracefully} failed request is automatically - retried. {e graceful} means failed with {!exception:Connection.Retry}. - Requests with a [`Stream] {!module:Body} cannot be retried - automatically. Such requests will fail with - {!exception:Connection.Retry} and a new {!module:Body} will need to be - provided to retry. - @param parallel - maximum number of connections to establish to a single endpoint. Beware: - A single hostname may resolve to multiple endpoints. In such a case - connections may be created in excess to what was intended. - @param depth - maximum number of requests to queue and / or send on a single - connection. *) -end = struct +module Make (Connection : S.Connection) (Sleep : S.Sleep) = struct module Net = Connection.Net module IO = Net.IO open IO @@ -83,10 +46,11 @@ end = struct retry : int; parallel : int; depth : int; + proxy : Uri.t option; } let create ?(ctx = Lazy.force Net.default_ctx) ?(keep = 60_000_000_000L) - ?(retry = 2) ?(parallel = 4) ?(depth = 100) () = + ?(retry = 2) ?(parallel = 4) ?(depth = 100) ?proxy () = { cache = Hashtbl.create ~random:true 10; ctx; @@ -94,6 +58,7 @@ end = struct retry; parallel; depth; + proxy; } let rec get_connection self endp = @@ -109,7 +74,8 @@ end = struct Lwt.return_unit in let create () = - let connection = Connection.create ~finalise ~ctx:self.ctx endp + let connection = + Connection.create ~persistent:true ~finalise ~ctx:self.ctx endp and timeout = ref Lwt.return_unit in let rec busy () = Lwt.cancel !timeout; @@ -152,12 +118,31 @@ end = struct (fun _ -> get_connection self endp) (fun _ -> get_connection self endp)) - let call self ?headers ?body meth uri = - Net.resolve ~ctx:self.ctx uri >>= fun endp -> + let prepare self ?headers ?absolute_form meth uri = + match self.proxy with + | None -> + let absolute_form = Option.value ~default:false absolute_form in + Net.resolve ~ctx:self.ctx uri >>= fun endp -> + Lwt.return (endp, absolute_form, headers) + | Some proxy_uri -> + let absolute_form = + Option.value + ~default: + (not + (meth = `CONNECT + || (meth = `OPTIONS && Uri.path_and_query uri = "*"))) + absolute_form + in + Net.resolve ~ctx:self.ctx proxy_uri >>= fun endp -> + Lwt.return (endp, absolute_form, headers) + + let call self ?headers ?body ?absolute_form meth uri = + prepare self ?headers ?absolute_form meth uri + >>= fun (endp, absolute_form, headers) -> let rec request retry = get_connection self endp >>= fun conn -> Lwt.catch - (fun () -> Connection.call conn ?headers ?body meth uri) + (fun () -> Connection.call conn ?headers ?body ~absolute_form meth uri) (function | Retry -> ( match body with @@ -168,3 +153,310 @@ end = struct in request self.retry end + +module Make_tunnel (Connection : S.Connection) (Sleep : S.Sleep) : sig + include S.Connection_cache + + val create : + ?ctx:Connection.Net.ctx -> + ?keep:int64 -> + ?retry:int -> + ?parallel:int -> + ?depth:int -> + ?proxy_headers:Http.Header.t -> + Uri.t -> + unit -> + t +end = struct + module Net = Connection.Net + module IO = Net.IO + open IO + + type ctx = Net.ctx + type tunnel = { proxy : Connection.t; remote : Connection.t } + + type t = { + cache : (string, tunnel) Hashtbl.t; (* remote host * tunnel *) + proxy_uri : Uri.t; + mutable proxy_endp : Net.endp option; + proxy_headers : Http.Header.t; + ctx : ctx; + keep : int64; + retry : int; + parallel : int; + depth : int; + } + + let proxy_default_scheme uri = + match Uri.scheme uri with + | None -> Uri.with_scheme uri (Some "http") + | _ -> uri + + let create ?(ctx = Lazy.force Net.default_ctx) ?(keep = 60_000_000_000L) + ?(retry = 2) ?(parallel = 4) ?(depth = 100) + ?(proxy_headers = Http.Header.init ()) proxy_uri () = + if Uri.host proxy_uri = None then + Printf.ksprintf invalid_arg "No host was provided in URI %s." + (Uri.to_string proxy_uri); + { + cache = Hashtbl.create ~random:true 10; + proxy_uri = proxy_default_scheme proxy_uri; + proxy_endp = None; + proxy_headers; + ctx; + keep; + retry; + parallel; + depth; + } + + let rec request conn ?headers ?body ?absolute_form meth uri retry = + Lwt.catch + (fun () -> Connection.call conn ?headers ?body ?absolute_form meth uri) + (function + | Retry -> ( + match body with + | Some (`Stream _) -> Lwt.fail Retry + | None | Some `Empty | Some (`String _) | Some (`Strings _) -> + if retry <= 0 then Lwt.fail Retry + else + request conn ?headers ?body ?absolute_form meth uri (retry - 1) + ) + | e -> Lwt.fail e) + + let rec get_connection self ~proxy_endp ~remote ~remote_host ~remote_uri = + let finalise connection = + let rec remove keep = + let current = Hashtbl.find self.cache remote in + Hashtbl.remove self.cache remote; + if current.proxy == connection || current.remote == connection then + List.iter (Hashtbl.add self.cache remote) keep + else remove (current :: keep) + in + remove []; + Lwt.return_unit + in + let create () = + let proxy = + Connection.create ~persistent:true ~finalise ~ctx:self.ctx proxy_endp + in + request proxy ~headers:self.proxy_headers `CONNECT remote_uri self.retry + >>= fun (resp, _body) -> + let code = resp |> Http.Response.status |> Cohttp.Code.code_of_status in + if not (Cohttp.Code.is_success code) then + Printf.ksprintf failwith "Could not setup tunnel. Response code: %d\n" + code; + let remote = + Connection.create_tunnel ~finalise ~ctx:self.ctx proxy remote_host + and timeout = ref Lwt.return_unit in + let rec busy () = + Lwt.cancel !timeout; + if Connection.length remote = 0 then ( + timeout := + Sleep.sleep_ns self.keep >>= fun () -> + Connection.close remote; + Connection.close proxy; + (* failure is ignored *) + Lwt.return_unit); + Lwt.on_termination (Connection.notify remote) busy + in + busy (); + Lwt.return { proxy; remote } + in + match Hashtbl.find_all self.cache remote with + | [] -> + create () >>= fun tunnel -> + Hashtbl.add self.cache remote tunnel; + Lwt.return tunnel + | tunnels -> ( + let rec search length = function + | [ a ] -> (a, length + 1) + | a :: b :: tl + when Connection.length a.remote < Connection.length b.remote -> + search (length + 1) (a :: tl) + | _ :: tl -> search (length + 1) tl + | [] -> assert false + in + match search 0 tunnels with + | shallowest, _ when Connection.length shallowest.remote = 0 -> + Lwt.return shallowest + | _, length when length < self.parallel -> + create () >>= fun tunnel -> + Hashtbl.add self.cache remote tunnel; + Lwt.return tunnel + | shallowest, _ when Connection.length shallowest.remote < self.depth -> + Lwt.return shallowest + | _ -> + Lwt.try_bind + (fun () -> + Lwt.choose + (List.map + (fun { remote; _ } -> Connection.notify remote) + tunnels)) + (fun _ -> + get_connection self ~proxy_endp ~remote ~remote_host ~remote_uri) + (fun _ -> + get_connection self ~proxy_endp ~remote ~remote_host ~remote_uri) + ) + + let call self ?headers ?body ?absolute_form meth uri = + (match self.proxy_endp with + | None -> + Net.resolve ~ctx:self.ctx self.proxy_uri >>= fun proxy_endp -> + self.proxy_endp <- Some proxy_endp; + Lwt.return proxy_endp + | Some proxy_endp -> Lwt.return proxy_endp) + >>= fun proxy_endp -> + let remote_port = + match Uri_services.tcp_port_of_uri uri with + | Some p -> p + | None -> failwith "A port is required for the CONNECT method." + in + let remote_host = Option.get (Uri.host uri) in + let remote = remote_host ^ ":" ^ string_of_int remote_port + and remote_uri = Uri.with_port uri (Some remote_port) in + get_connection self ~proxy_endp ~remote ~remote_host ~remote_uri + >>= fun tunnel -> + request tunnel.remote ?headers ?body ?absolute_form meth uri self.retry +end + +type no_proxy_pattern = Name of string | Ipaddr_prefix of Ipaddr.Prefix.t +type no_proxy = Wildcard | Patterns of no_proxy_pattern list + +let trim_dots ~first_leading s = + let len = String.length s in + let i = ref 0 in + if first_leading && !i < len && String.unsafe_get s !i = '.' then incr i; + let j = ref (len - 1) in + while !j >= !i && String.unsafe_get s !j = '.' do + decr j + done; + if !j >= !i then String.sub s !i (!j - !i + 1) else "" + +let strncasecompare a b n = + let a = String.(sub a 0 (min (length a) n) |> lowercase_ascii) + and b = String.(sub b 0 (min (length b) n) |> lowercase_ascii) in + String.compare a b = 0 + +let no_proxy_from_env no_proxy = + if no_proxy = "*" then Wildcard + else + let patterns = + no_proxy + |> String.split_on_char ',' + |> List.filter_map (fun pattern -> + if pattern = "" then None else Some (String.trim pattern)) + |> List.map (fun pattern -> + match Ipaddr.of_string pattern with + | Ok addr -> Ipaddr_prefix (Ipaddr.Prefix.of_addr addr) + | Error _ -> ( + match Ipaddr.Prefix.of_string pattern with + | Ok prefix -> Ipaddr_prefix prefix + | Error _ -> Name (trim_dots ~first_leading:true pattern))) + in + Patterns patterns + +let check_no_proxy_patterns host = function + | Wildcard -> true + | _ when String.length host = 0 -> true + | Patterns patterns -> ( + match Ipaddr.of_string host with + | Ok hostip -> + List.exists + (function + | Name _ -> false + | Ipaddr_prefix network -> Ipaddr.Prefix.mem hostip network) + patterns + | Error _ -> + let name = trim_dots ~first_leading:false host in + List.exists + (function + | Ipaddr_prefix _ -> false + | Name pattern -> + let patternlen = String.length pattern + and namelen = String.length name in + if patternlen = namelen then + strncasecompare pattern name namelen + else if patternlen < namelen then + name.[namelen - patternlen - 1] = '.' + && strncasecompare pattern + (String.sub name (namelen - patternlen) + (patternlen - namelen - patternlen)) + patternlen + else false) + patterns) + +let tunnel_schemes = [ "https" ] + +module Make_proxy (Connection : S.Connection) (Sleep : S.Sleep) = struct + module Connection_cache = Make (Connection) (Sleep) + module Connection_tunnel = Make_tunnel (Connection) (Sleep) + + type proxy = Direct of Connection_cache.t | Tunnel of Connection_tunnel.t + + type t = { + proxies : (string * proxy) list; + direct : proxy option; + tunnel : proxy option; + no_proxy : Connection_cache.t; + no_proxy_patterns : no_proxy; + } + + let create ?ctx ?keep ?retry ?parallel ?depth ?(scheme_proxy = []) ?all_proxy + ?no_proxy ?proxy_headers () = + let create_default () = + Connection_cache.create ?ctx ?keep ?retry ?parallel ?depth () + and create_direct proxy = + Connection_cache.create ?ctx ?keep ?retry ?parallel ?depth ~proxy () + and create_tunnel proxy_uri = + Connection_tunnel.create ?ctx ?keep ?retry ?parallel ?depth ?proxy_headers + proxy_uri () + in + let no_proxy_patterns = + match no_proxy with + | None -> Patterns [] + | Some no_proxy -> no_proxy_from_env no_proxy + in + let no_proxy = create_default () in + let proxies = + List.map + (fun (scheme, uri) -> + let proxy = + if List.mem scheme tunnel_schemes then Tunnel (create_tunnel uri) + else Direct (create_direct uri) + in + (scheme, proxy)) + scheme_proxy + in + let direct, tunnel = + match all_proxy with + | Some uri -> + (Some (Direct (create_direct uri)), Some (Tunnel (create_tunnel uri))) + | None -> (None, None) + in + { no_proxy; direct; tunnel; proxies; no_proxy_patterns } + + let call self ?headers ?body ?absolute_form meth uri = + let proxy = + if + check_no_proxy_patterns + (Uri.host_with_default ~default:"" uri) + self.no_proxy_patterns + then None + (* Connection_cache.call self.no_proxy ?headers ?body ?absolute_form meth uri *) + else + let scheme = Option.value ~default:"" (Uri.scheme uri) in + match List.assoc scheme self.proxies with + | proxy -> Some proxy + | exception Not_found -> + if List.mem scheme tunnel_schemes then self.tunnel else self.direct + in + match proxy with + | None -> + Connection_cache.call self.no_proxy ?headers ?body ?absolute_form meth + uri + | Some (Tunnel proxy) -> + Connection_tunnel.call proxy ?headers ?body ?absolute_form meth uri + | Some (Direct proxy) -> + Connection_cache.call proxy ?headers ?body ?absolute_form meth uri +end diff --git a/cohttp-lwt/src/connection_cache.mli b/cohttp-lwt/src/connection_cache.mli new file mode 100644 index 0000000..60708df --- /dev/null +++ b/cohttp-lwt/src/connection_cache.mli @@ -0,0 +1,96 @@ +(** This functor establishes a new connection for each request. *) +module Make_no_cache (Connection : S.Connection) : sig + include S.Connection_cache + + val create : ?ctx:Connection.Net.ctx -> unit -> t + (** [create ?ctx ()] creates a connection for handling a single request. The + connection accepts only a single request and will automatically be closed + as soon as possible. + @param ctx See {!Connection.Net.ctx} *) +end + +(** This functor keeps a cache of connections for reuse. Connections are reused + based on their remote {!type:Conduit.endp} (effectively IP / port). *) +module Make (Connection : S.Connection) (Sleep : S.Sleep) : sig + include S.Connection_cache + + val create : + ?ctx:Connection.Net.ctx -> + ?keep:int64 -> + ?retry:int -> + ?parallel:int -> + ?depth:int -> + ?proxy:Uri.t -> + unit -> + t + (** Create a new connection cache + + @param ctx Conduit context to use. See {!type:Connection.Net.ctx}. + @param keep Number of nanoseconds to keep an idle connection around. + @param retry + Number of times a {e gracefully} failed request is automatically + retried. {e graceful} means failed with {!exception:Connection.Retry}. + Requests with a [`Stream] {!module:Body} cannot be retried + automatically. Such requests will fail with + {!exception:Connection.Retry} and a new {!module:Body} will need to be + provided to retry. + @param parallel + maximum number of connections to establish to a single endpoint. Beware: + A single hostname may resolve to multiple endpoints. In such a case + connections may be created in excess to what was intended. + @param depth + maximum number of requests to queue and / or send on a single + connection. + @param proxy A direct (non-tunneling) proxy to use. *) +end +[@@warning "-unused-functor-parameter"] + +(** This functor keeps a cache of connections for reuse. Connections are reused + based on their remote {!type:Conduit.endp} (effectively IP / port). It also + supports automatically connecting and reconnecting to direct and tunneling + proxies, based on the remote URI scheme (HTTP will select direct proxies, + HTTPS tunneling proxies). *) +module Make_proxy (Connection : S.Connection) (Sleep : S.Sleep) : sig + include S.Connection_cache + + val create : + ?ctx:Connection.Net.ctx -> + ?keep:int64 -> + ?retry:int -> + ?parallel:int -> + ?depth:int -> + ?scheme_proxy:(string * Uri.t) list -> + ?all_proxy:Uri.t -> + ?no_proxy:string -> + ?proxy_headers:Http.Header.t -> + unit -> + t + (** Create a new connection cache. The outer connections to the proxy and the + inner connections share the same parameters. + + @param ctx Conduit context to use. See {!type:Connection.Net.ctx}. + @param keep Number of nanoseconds to keep an idle connection around. + @param retry + Number of times a {e gracefully} failed request is automatically + retried. {e graceful} means failed with {!exception:Connection.Retry}. + Requests with a [`Stream] {!module:Body} cannot be retried + automatically. Such requests will fail with + {!exception:Connection.Retry} and a new {!module:Body} will need to be + provided to retry. + @param parallel + maximum number of connections to establish to a single endpoint. Beware: + A single hostname may resolve to multiple endpoints. In such a case + connections may be created in excess to what was intended. + @param depth + maximum number of requests to queue and / or send on a single + connection. + @param scheme_proxy The proxy URI associated to each (remote) scheme. + @param all_proxy + The default proxy to use. Proxy for specific schemes have precedence + over this. + @param no_proxy + Disable proxies for specific hosts, specified as curl's [NO_PROXY]. + @see + @param proxy_headers Headers to pass to the proxy. *) +end +[@@warning "-unused-functor-parameter"] diff --git a/cohttp-lwt/src/dune b/cohttp-lwt/src/dune index 12c5df2..d2d0621 100644 --- a/cohttp-lwt/src/dune +++ b/cohttp-lwt/src/dune @@ -4,4 +4,4 @@ (synopsis "Lwt backend") (preprocess (pps ppx_sexp_conv)) - (libraries lwt uri http_bytebuffer cohttp logs logs.lwt)) + (libraries lwt uri uri.services http_bytebuffer cohttp logs logs.lwt ipaddr)) diff --git a/cohttp-lwt/src/s.ml b/cohttp-lwt/src/s.ml index 18d8f7c..1043c55 100644 --- a/cohttp-lwt/src/s.ml +++ b/cohttp-lwt/src/s.ml @@ -20,6 +20,7 @@ end module type Net = sig module IO : IO + type client type endp type ctx [@@deriving sexp_of] @@ -40,6 +41,8 @@ module type Net = sig (** [resolve ~ctx uri] resolves [uri] into an endpoint description. This is [Resolver_lwt.resolve_uri ~uri ctx.resolver]. *) + val tunnel : string -> IO.ic * IO.oc -> client + val connect_uri : ctx:ctx -> Uri.t -> (IO.conn * IO.ic * IO.oc) IO.t (** [connect_uri ~ctx uri] starts a {i flow} on the given [uri]. The choice of the protocol (with or without encryption) is done by the {i scheme} of the @@ -62,6 +65,7 @@ module type Net = sig (** [connect_endp ~ctx endp] starts a {i flow} to the given [endp]. [endp] describes address and protocol of the endpoint to connect to. *) + val connect_client : ctx:ctx -> client -> (IO.conn * IO.ic * IO.oc) IO.t val close_in : IO.ic -> unit val close_out : IO.oc -> unit val close : IO.ic -> IO.oc -> unit @@ -76,6 +80,7 @@ end type call = ?headers:Http.Header.t -> ?body:Body.t -> + ?absolute_form:bool -> Http.Method.t -> Uri.t -> (Cohttp.Response.t * Body.t) Lwt.t @@ -89,15 +94,14 @@ type call = module can be used to consume [response_body]. Use {!Body.drain_body} if you don't consume the body by other means. - Leaks are detected by the GC and logged as debug messages, these can be - enabled activating the debug logging. For example, this can be done as - follows in - [cohttp-lwt-unix] + Leaks are detected by the GC and logged as debug messages, these can be + enabled activating the debug logging. For example, this can be done as + follows in [cohttp-lwt-unix] - {[ - Cohttp_lwt_unix.Debug.activate_debug (); - Logs.set_level (Some Logs.Warning) - ]} + {[ + Cohttp_lwt_unix.Debug.activate_debug (); + Logs.set_level (Some Logs.Warning) + ]} @raise {!Connection.Retry} on recoverable errors like the remote endpoint closing the connection @@ -138,6 +142,9 @@ module type Connection = sig @param ctx See [Net.ctx] @param endp The remote address, port and protocol to connect to. *) + val create_tunnel : + ?finalise:(t -> unit Net.IO.t) -> ?ctx:Net.ctx -> t -> string -> t + val connect : ?finalise:(t -> unit Net.IO.t) -> ?persistent:bool -> @@ -191,9 +198,9 @@ module type Client = sig {!val:Connection_cache.Make_no_cache.create} is used to resolve uri and create a dedicated connection with [ctx]. - In most cases you should use the more specific helper calls in the - interface rather than invoke this function directly. See {!head}, {!get} - and {!post} for some examples. *) + In most cases you should use the more specific helper calls in the + interface rather than invoke this function directly. See {!head}, {!get} + and {!post} for some examples. *) include Cohttp.Generic.Client.S with type 'a io = 'a Lwt.t diff --git a/cohttp-mirage.opam b/cohttp-mirage.opam index 334df04..ef93290 100644 --- a/cohttp-mirage.opam +++ b/cohttp-mirage.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP implementation for the MirageOS unikernel" @@ -32,8 +32,8 @@ depends: [ "ocaml" {>= "4.08"} "mirage-flow" {>= "2.0.0"} "mirage-channel" {>= "4.0.0"} - "conduit" {>= "2.0.2"} - "conduit-mirage" {>= "2.3.0"} + "conduit" {>= "8.0.0"} + "conduit-mirage" {>= "8.0.0"} "mirage-kv" {>= "3.0.0"} "lwt" {>= "2.4.3"} "cohttp-lwt" {= version} diff --git a/cohttp-mirage/src/client.ml b/cohttp-mirage/src/client.ml index 15a00fe..1f06e74 100644 --- a/cohttp-mirage/src/client.ml +++ b/cohttp-mirage/src/client.ml @@ -14,15 +14,11 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * cohttp v6.0.0 + * cohttp v6.1.0 *) -module Make - (P : Mirage_clock.PCLOCK) - (R : Resolver_mirage.S) - (S : Conduit_mirage.S) = -struct - module Net = Net.Make (P) (R) (S) +module Make (R : Resolver_mirage.S) (S : Conduit_mirage.S) = struct + module Net = Net.Make (R) (S) module Connection = Cohttp_lwt.Connection.Make (Net) include Cohttp_lwt.Client.Make (Connection) diff --git a/cohttp-mirage/src/client.mli b/cohttp-mirage/src/client.mli index a67cb23..b4d1d9e 100644 --- a/cohttp-mirage/src/client.mli +++ b/cohttp-mirage/src/client.mli @@ -1,7 +1,4 @@ -module Make - (_ : Mirage_clock.PCLOCK) - (R : Resolver_mirage.S) - (S : Conduit_mirage.S) : sig +module Make (R : Resolver_mirage.S) (S : Conduit_mirage.S) : sig module Connection : Cohttp_lwt.S.Connection include Cohttp_lwt.S.Client with type ctx = Connection.Net.ctx diff --git a/cohttp-mirage/src/io.ml b/cohttp-mirage/src/io.ml index 5b2830d..cfd7147 100644 --- a/cohttp-mirage/src/io.ml +++ b/cohttp-mirage/src/io.ml @@ -14,7 +14,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * cohttp v6.0.0 + * cohttp v6.1.0 *) open Lwt.Infix diff --git a/cohttp-mirage/src/io.mli b/cohttp-mirage/src/io.mli index a6ea44e..b9422ca 100644 --- a/cohttp-mirage/src/io.mli +++ b/cohttp-mirage/src/io.mli @@ -14,7 +14,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * cohttp v6.0.0 + * cohttp v6.1.0 *) (** Cohttp IO implementation using Mirage channels. *) diff --git a/cohttp-mirage/src/net.ml b/cohttp-mirage/src/net.ml index 72964fc..8375888 100644 --- a/cohttp-mirage/src/net.ml +++ b/cohttp-mirage/src/net.ml @@ -1,11 +1,6 @@ -module Make - (P : Mirage_clock.PCLOCK) - (R : Resolver_mirage.S) - (S : Conduit_mirage.S) = -struct +module Make (R : Resolver_mirage.S) (S : Conduit_mirage.S) = struct module Channel = Mirage_channel.Make (S.Flow) module Input_channel = Input_channel.Make (Channel) - module Endpoint = Conduit_mirage.Endpoint (P) module IO = Io.Make (Channel) open IO @@ -21,11 +16,15 @@ struct lazy { resolver = R.localhost; conduit = None; authenticator = None } type endp = Conduit.endp + type client + let tunnel = failwith "Unimplemented" + let connect_client = failwith "Unimplemented" let resolve ~ctx uri = R.resolve_uri ~uri ctx.resolver let connect_endp ~ctx endp = - Endpoint.client ?tls_authenticator:ctx.authenticator endp >>= fun client -> + Conduit_mirage.Endpoint.client ?tls_authenticator:ctx.authenticator endp + >>= fun client -> match ctx.conduit with | None -> failwith "conduit not initialised" | Some c -> diff --git a/cohttp-mirage/src/net.mli b/cohttp-mirage/src/net.mli index 0a64dd7..ee48f2f 100644 --- a/cohttp-mirage/src/net.mli +++ b/cohttp-mirage/src/net.mli @@ -1,7 +1,4 @@ -module Make - (_ : Mirage_clock.PCLOCK) - (R : Resolver_mirage.S) - (S : Conduit_mirage.S) : sig +module Make (R : Resolver_mirage.S) (S : Conduit_mirage.S) : sig type ctx = { resolver : R.t; conduit : S.t option; diff --git a/cohttp-mirage/src/static.ml b/cohttp-mirage/src/static.ml index ccddd31..8b4adde 100644 --- a/cohttp-mirage/src/static.ml +++ b/cohttp-mirage/src/static.ml @@ -14,7 +14,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * cohttp v6.0.0 + * cohttp v6.1.0 *) module Key = Mirage_kv.Key diff --git a/cohttp-mirage/src/static.mli b/cohttp-mirage/src/static.mli index 4bd3f0e..6de49c7 100644 --- a/cohttp-mirage/src/static.mli +++ b/cohttp-mirage/src/static.mli @@ -14,7 +14,7 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * cohttp v6.0.0 + * cohttp v6.1.0 *) (** Serve static HTTP sites from a Mirage key-value store. *) diff --git a/cohttp-server-lwt-unix.opam b/cohttp-server-lwt-unix.opam index 787420e..f462a7f 100644 --- a/cohttp-server-lwt-unix.opam +++ b/cohttp-server-lwt-unix.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Lightweight Cohttp + Lwt based HTTP server" diff --git a/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.ml b/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.ml index 2a6da71..054ac93 100644 --- a/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.ml +++ b/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.ml @@ -205,11 +205,11 @@ module Context = struct Int64.of_string_opt ("0x" ^ hex) let step_chunked : - 'a. - t -> - f:(Body.Substring.t -> 'acc -> 'acc Lwt.t) -> - init:'acc -> - 'acc option Lwt.t = + 'a. + t -> + f:(Body.Substring.t -> 'acc -> 'acc Lwt.t) -> + init:'acc -> + 'acc option Lwt.t = fun t ~f ~init -> Input_channel.read_line_opt t.ic >>= function | None -> Lwt.return_none (* TODO invalid input *) diff --git a/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.mli b/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.mli index b0c76e3..4eb6809 100644 --- a/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.mli +++ b/cohttp-server-lwt-unix/src/cohttp_server_lwt_unix.mli @@ -61,7 +61,8 @@ module Body : sig (** [stream ?encoding f] respond with body generated by repeatedly applying [f]. When [f] returns [None], it will be considered terminated. - [?encoding] is the encoding to use. By default this is [Encoding.chunked]. *) + [?encoding] is the encoding to use. By default this is [Encoding.chunked]. + *) end module Context : sig diff --git a/cohttp-top.opam b/cohttp-top.opam index d1cbe2e..fe47786 100644 --- a/cohttp-top.opam +++ b/cohttp-top.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "CoHTTP toplevel pretty printers for HTTP types" diff --git a/cohttp.opam b/cohttp.opam index d4958c2..3552273 100644 --- a/cohttp.opam +++ b/cohttp.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "An OCaml library for HTTP clients and servers" diff --git a/cohttp/src/accept.mli b/cohttp/src/accept.mli index 6095fd3..585502a 100644 --- a/cohttp/src/accept.mli +++ b/cohttp/src/accept.mli @@ -50,7 +50,8 @@ type encoding = Accept_types.encoding = (** Basic language range tag. ["en-gb"] is represented as [Language ["en"; "gb"]]. - @see the specification. *) + @see the specification. +*) type language = Accept_types.language = Language of string list | AnyLanguage [@@deriving sexp] diff --git a/cohttp/src/code.mli b/cohttp/src/code.mli index f645561..d6d9792 100644 --- a/cohttp/src/code.mli +++ b/cohttp/src/code.mli @@ -171,7 +171,8 @@ val reason_phrase_of_code : int -> string (** Give a description of the given int code. *) val is_informational : int -> bool -(** Is the given int code belong to the class of "informational" return code ? *) +(** Is the given int code belong to the class of "informational" return code ? +*) val is_success : int -> bool (** Is the given int code belong to the class of "success" return code ? *) diff --git a/cohttp/src/dune b/cohttp/src/dune index bdc14c0..34636e5 100644 --- a/cohttp/src/dune +++ b/cohttp/src/dune @@ -17,7 +17,8 @@ sexplib0 stringext uri - uri-sexp)) + uri-sexp + uri.services)) (ocamllex accept_lexer) diff --git a/cohttp/src/header.mli b/cohttp/src/header.mli index 7ed2115..0405e2d 100644 --- a/cohttp/src/header.mli +++ b/cohttp/src/header.mli @@ -40,7 +40,8 @@ val to_list : t -> (string * string) list (** [to_list h] converts HTTP headers [h] to a list. Order and case is preserved. - {e Invariant (with case insensitive comparison):} [to_list (of_list l) = l] *) + {e Invariant (with case insensitive comparison):} [to_list (of_list l) = l] +*) val init_with : string -> string -> t (** [init_with k v] construct a fresh HTTP headers with a single header with @@ -118,7 +119,8 @@ val get_multi_concat : ?list_value_only:bool -> t -> string -> string option the last value paired with [k] in [h]. {e Invariant:} - [forall h, k not a list-value header. get_multi_concat ~list-value-only:true h k = get h k] *) + [forall h, k not a list-value header. get_multi_concat ~list-value-only:true + h k = get h k] *) val update : t -> string -> (string option -> string option) -> t (** [update h k f] returns an header list containing the same headers as [h], @@ -190,7 +192,8 @@ val clean_dup : t -> t Finally, following {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2}, the - header [Set-cookie] is treated as an exception and ignored by [clean_dup]. *) + header [Set-cookie] is treated as an exception and ignored by [clean_dup]. +*) val get_content_range : t -> Int64.t option val get_media_type : t -> string option @@ -213,7 +216,8 @@ val add_links : t -> Link.t list -> t val get_links : t -> Link.t list val user_agent : string -(** The User-Agent header used by this library, including the version of cohttp. *) +(** The User-Agent header used by this library, including the version of cohttp. +*) val prepend_user_agent : t -> string -> t (** Prepend [user_agent] to the product token already declared in the diff --git a/cohttp/src/request.ml b/cohttp/src/request.ml index 05ea645..cd5f937 100644 --- a/cohttp/src/request.ml +++ b/cohttp/src/request.ml @@ -42,16 +42,26 @@ let version t = t.version let encoding t = Header.get_transfer_encoding t.headers let make ?(meth = `GET) ?(version = `HTTP_1_1) ?encoding - ?(headers = Header.init ()) uri = - let headers = - Header.add_unless_exists headers "host" - (match Uri.scheme uri with - | Some "httpunix" -> "" - | _ -> ( - Uri.host_with_default ~default:"localhost" uri - ^ - match Uri.port uri with Some p -> ":" ^ string_of_int p | None -> "")) + ?(headers = Header.init ()) ?(absolute_form = false) uri = + let port () = + match Uri.port uri with + | Some p -> ":" ^ string_of_int p + | None when meth = `CONNECT -> ( + match Uri_services.tcp_port_of_uri uri with + | None -> failwith "A port is required for the CONNECT method." + | Some p -> ":" ^ string_of_int p) + | None -> "" + in + let host = + match Header.get headers "host" with + | None -> ( + match Uri.scheme uri with + | Some "httpunix" -> "" + | _ -> Uri.host_with_default ~default:"localhost" uri ^ port ()) + | Some host -> if String.contains host ':' then host else host ^ port () in + + let headers = Header.replace headers "host" host in let headers = Header.add_unless_exists headers "user-agent" Header.user_agent in @@ -66,7 +76,9 @@ let make ?(meth = `GET) ?(version = `HTTP_1_1) ?encoding Header.add_authorization headers auth | _, _, _ -> headers in - let resource = Uri.path_and_query uri in + let resource = + if absolute_form then Uri.to_string uri else Uri.path_and_query uri + in let headers = match encoding with | None -> headers @@ -80,7 +92,7 @@ let is_keep_alive t = Http.Request.is_keep_alive t adding content headers if appropriate. @param chunked Forces chunked encoding *) -let make_for_client ?headers ?chunked ?body_length meth uri = +let make_for_client ?headers ?chunked ?body_length ?absolute_form meth uri = let encoding = match (chunked, body_length) with | Some true, None -> Transfer.Chunked @@ -89,7 +101,7 @@ let make_for_client ?headers ?chunked ?body_length meth uri = | Some true, Some _ -> invalid_arg "cannot set both ?chunked and ?body_length:" in - make ~meth ~encoding ?headers uri + make ~meth ~encoding ?headers ?absolute_form uri let pp_hum ppf r = Format.fprintf ppf "%s" (r |> sexp_of_t |> Sexplib0.Sexp.to_string_hum) @@ -119,7 +131,9 @@ let uri { resource; headers; meth; _ } = | Some _ -> ( Uri.( (* we have an absoluteURI *) - match path uri with "" -> with_path uri "/" | _ -> uri)) + match path uri with + | "" -> with_path uri "/" + | _ -> uri)) | None -> ( let empty = Uri.of_string "" in let empty_base = Uri.of_string "///" in @@ -181,7 +195,9 @@ module Make (IO : S.IO) = struct let fst_line = Printf.sprintf "%s %s %s\r\n" (Http.Method.to_string req.meth) - (if req.resource = "" then "/" else req.resource) + (if req.meth = `CONNECT then Option.get (Header.get req.headers "host") + else if req.resource = "" then "/" + else req.resource) (Http.Version.to_string req.version) in IO.write oc fst_line >>= fun _ -> Header_IO.write req.headers oc diff --git a/cohttp/src/s.ml b/cohttp/src/s.ml index 915433f..8daf316 100644 --- a/cohttp/src/s.ml +++ b/cohttp/src/s.ml @@ -102,6 +102,7 @@ module type Request = sig ?version:Code.version -> ?encoding:Transfer.encoding -> ?headers:Header.t -> + ?absolute_form:bool -> Uri.t -> t (** [make ()] is a value of {!type:t}. The default values for the request, if @@ -119,6 +120,7 @@ module type Request = sig ?headers:Header.t -> ?chunked:bool -> ?body_length:int64 -> + ?absolute_form:bool -> Code.meth -> Uri.t -> t diff --git a/cohttp/test/test_accept.ml b/cohttp/test/test_accept.ml index 9148300..1f33928 100644 --- a/cohttp/test/test_accept.ml +++ b/cohttp/test/test_accept.ml @@ -16,16 +16,14 @@ module A = Cohttp.Accept -let suite_of : - type a. +let suite_of : type a. (string option -> a) -> a Alcotest.testable -> (string * a) list -> _ list = fun pf t -> List.map (fun (s, expected) -> let test () = Alcotest.check t s (pf (Some s)) expected in (s, `Quick, test)) -let suite_of_fail : - type a. +let suite_of_fail : type a. (string option -> a) -> a Alcotest.testable -> (string * exn) list -> _ list = fun pf _ -> @@ -41,8 +39,8 @@ let suite_to_string_of : type a. (a -> string) -> (a * string) list -> _ list = let test () = Alcotest.(check string expected_str expected_str (pf v)) in (expected_str, `Quick, test)) -let suite_to_string_of_fail : - type a. (a -> string) -> (a * string * exn) list -> _ list = +let suite_to_string_of_fail : type a. + (a -> string) -> (a * string * exn) list -> _ list = fun pf -> List.map (fun (v, descr, e) -> let test () = Alcotest.(check_raises descr e (fun () -> ignore (pf v))) in diff --git a/dune-project b/dune-project index 2a6ce3c..f03e57b 100644 --- a/dune-project +++ b/dune-project @@ -1,7 +1,7 @@ (lang dune 3.8) (name cohttp) -(version v6.0.0) +(version v6.1.0) (license ISC) @@ -79,6 +79,8 @@ (lwt (>= 5.4.0)) sexplib0 + (ipaddr + (>= 5.6.0)) (ppx_sexp_conv (>= v0.13.0)) logs @@ -104,9 +106,9 @@ (lwt (>= 3.0.0)) (conduit-lwt - (>= 5.0.0)) + (>= 7.1.0)) (conduit-lwt-unix - (>= 5.0.0)) + (>= 7.1.0)) (fmt (>= 0.8.2)) base-unix @@ -172,7 +174,7 @@ "An implementation of an HTTP client and server using the Async\nconcurrency library. See the `Cohttp_async` module for information\non how to use this. The package also installs `cohttp-curl-async`\nand a `cohttp-server-async` binaries for quick uses of a HTTP(S)\nclient and server respectively.\n") (depends (ocaml - (>= 4.14)) + (and (>= 4.14) (< 5.3.0))) (http (= :version)) (cohttp @@ -217,9 +219,9 @@ (mirage-channel (>= 4.0.0)) (conduit - (>= 2.0.2)) + (>= 8.0.0)) (conduit-mirage - (>= 2.3.0)) + (>= 8.0.0)) (mirage-kv (>= 3.0.0)) (lwt @@ -381,7 +383,7 @@ logs uri (tls-eio (and :with-test (>= 1.0.0))) - (mirage-crypto-rng-eio (and :with-test (>= 0.11.2))) + (mirage-crypto-rng (and :with-test (>= 1.2.0))) (ca-certs (and :with-test (>= "1.0.0"))) fmt ptime diff --git a/flake.lock b/flake.lock index 95e638b..c5a6bd7 100644 --- a/flake.lock +++ b/flake.lock @@ -5,29 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -38,15 +20,14 @@ }, "nixpkgs": { "inputs": { - "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1725916231, - "narHash": "sha256-kaU41Z43Uv2As0Sor8FPACJfWjkbUsWnZMtbCgqicvU=", + "lastModified": 1734153276, + "narHash": "sha256-/cvtpMFp0HArEpFi0PrPMsheauc3IJ7qWpSHnw8so2M=", "owner": "nix-ocaml", "repo": "nix-overlays", - "rev": "d63aa7b62251c70bbf0a28a67c30555077a2b758", + "rev": "4247b28ce426ccdea09a1ec014fa52785bc7ba1d", "type": "github" }, "original": { @@ -57,17 +38,17 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1725857262, - "narHash": "sha256-m9n0PncgZepVgmjOO1rfVXMgUACDOwZbhjSRjJ/NUpM=", + "lastModified": 1734100912, + "narHash": "sha256-93T/KB1ppdhnaV4u5uSwO6HutSq2RzcnkqVX9YKYslE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5af6aefbcc55670e36663fd1f8a796e1e323001a", + "rev": "2a7ebf12140f6d97941d5f8cc38e9323212ecbad", "type": "github" }, "original": { "owner": "NixOS", "repo": "nixpkgs", - "rev": "5af6aefbcc55670e36663fd1f8a796e1e323001a", + "rev": "2a7ebf12140f6d97941d5f8cc38e9323212ecbad", "type": "github" } }, @@ -91,21 +72,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index c02dfce..da89e0e 100644 --- a/flake.nix +++ b/flake.nix @@ -37,13 +37,14 @@ default = http; http = pkg { pname = "http"; + propagatedBuildInputs = [ ppx_expect ]; checkInputs = [ alcotest base_quickcheck ppx_expect crowbar ]; }; cohttp = pkg { pname = "cohttp"; checkInputs = [ fmt alcotest ]; propagatedBuildInputs = [ - stringext http re uri uri-sexp logs sexplib0 ppx_sexp_conv + base64 stringext http re uri uri-sexp logs sexplib0 ppx_sexp_conv ]; }; cohttp-top = pkg { @@ -56,8 +57,9 @@ }; cohttp-curl-lwt = pkg { pname = "cohttp-curl-lwt"; - checkInputs = [ cohttp-lwt-unix cohttp cohttp-lwt conduit-lwt ounit2 uri ]; - propagatedBuildInputs = [ ocurl http stringext lwt ]; + checkInputs = [ alcotest cohttp-lwt-unix cohttp cohttp-lwt conduit-lwt ounit2 uri ]; + propagatedBuildInputs = [ ocurl cohttp-curl http stringext lwt ]; + __darwinAllowLocalNetworking = true; }; cohttp-curl-async = pkg { pname = "cohttp-curl-async"; @@ -94,6 +96,7 @@ http cohttp cohttp-lwt cmdliner lwt conduit-lwt conduit-lwt-unix fmt ppx_sexp_conv magic-mime logs ]; + __darwinAllowLocalNetworking = true; }; cohttp-server-lwt-unix = pkg { pname = "cohttp-server-lwt-unix"; diff --git a/http.opam b/http.opam index 2aef68f..0909a06 100644 --- a/http.opam +++ b/http.opam @@ -1,4 +1,4 @@ -version: "6.0.0" +version: "6.1.0" # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Type definitions of HTTP essentials" diff --git a/http/src/http.mli b/http/src/http.mli index b2ddc08..32713cf 100644 --- a/http/src/http.mli +++ b/http/src/http.mli @@ -278,7 +278,8 @@ module Header : sig returned value is the last value paired with [k] in [h]. {e Invariant:} - [forall h, k not a list-value header. get_multi_concat ~list-value-only:true h k = get h k] *) + [forall h, k not a list-value header. get_multi_concat + ~list-value-only:true h k = get h k] *) val update : t -> string -> (string option -> string option) -> t (** [update h k f] returns an header list containing the same headers as [h], @@ -357,7 +358,8 @@ module Header : sig Finally, following {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2}, the - header [Set-cookie] is treated as an exception and ignored by [clean_dup]. *) + header [Set-cookie] is treated as an exception and ignored by [clean_dup]. + *) val get_content_range : t -> Int64.t option val get_connection_close : t -> bool