Coder Social home page Coder Social logo

mirage / ocaml-cohttp Goto Github PK

View Code? Open in Web Editor NEW
697.0 30.0 173.0 7.62 MB

An OCaml library for HTTP clients and servers using Lwt or Async

License: Other

OCaml 98.89% Makefile 0.06% JavaScript 0.08% Shell 0.08% Nix 0.89%
ocaml http http-client lwt unix

ocaml-cohttp's Introduction

ocaml-cohttp -- an OCaml library for HTTP clients and servers Main workflow

Cohttp is an OCaml library for creating HTTP daemons. It has a portable HTTP parser, and implementations using various asynchronous programming libraries:

  • Http provides essential type definitions used in Cohttp and an extremely fast http parser. It is designed to have no dependencies and make it easy for other packages to easily interoperate with Cohttp.
  • Cohttp_lwt_unix uses the Lwt library, and specifically the UNIX bindings. It uses ocaml-tls as the TLS implementation to handle HTTPS connections.
  • Cohttp_async uses the Async library and async_ssl to handle HTTPS connections.
  • Cohttp_lwt exposes an OS-independent Lwt interface, which is used by the Mirage interface to generate standalone microkernels (use the cohttp-mirage subpackage).
  • Cohttp_lwt_jsoo compiles to a JavaScript module that maps the Cohttp calls to XMLHTTPRequests. This is used to compile OCaml libraries like the GitHub bindings to JavaScript and still run efficiently.
  • Cohttp_curl uses libcurl, via ocurl, as backend. It also comes with lwt (Cohttp_curl_lwt) and async backends (Cohttp_curl_async).
  • Cohttp_eio uses eio to leverage new features from multicore ocaml 5.0.
  • Cohttp_server_lwt_unix uses lwt to implement a more efficient web server with a minimal interface.

You can implement other targets using the parser very easily. Look at the IO signature in lib/s.mli and implement that in the desired backend.

You can find help from cohttp users and maintainers at the discuss.ocaml.org forum or on the OCaml discord server.

Table of contents

Installation

Latest stable version should be obtained from opam. Make sure to install the specific backends you want as well. E.g.

$ opam install cohttp-lwt-unix cohttp-async

You can also obtain the development release:

$ opam pin add cohttp --dev-repo

Client Tutorial

Cohttp provides clients for Async, Lwt, and Js_of_ocaml (Lwt based). In this tutorial, we will use the lwt client but the example should be easily translatable to Async.

To create a simple request, use one of the methods in Cohttp_lwt_unix.Client. call is the most general, there are also http method specialized such as get, post, etc.

For example downloading the reddit frontpage:

open Lwt
open Cohttp
open Cohttp_lwt_unix

let body =
  Client.get (Uri.of_string "https://www.reddit.com/") >>= 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);
  body

let () =
  let body = Lwt_main.run body in
  print_endline ("Received body\n" ^ body)

There are a few things to notice:

  • We open 2 modules. Cohttp contains the backend independent modules and Cohttp_lwt_unix the lwt + unix specific ones.
  • Client.get accepts a Uri.t and makes an http request. Client.get also accepts optional arguments for things like header information.
  • The http response is returned in a tuple. The first element of the tuple contains the response's status code, headers, http version, etc. The second element contains the body.
  • The body is then converted to a string and is returned (after the length is printed). Note that Cohttp_lwt.Body.to_string hence it's up to us to keep a reference to the result.
  • We must trigger lwt's event loop for the request to run. Lwt_main.run will run the event loop and return with final value of body which we then print.

Note that Cohttp_lwt_unix/Cohttp_async are able to request an HTTPS page by default. For Cohttp_lwt_unix users can use ocaml-tls by installing tls-lwt or ocaml-ssl by installing lwt_ssl. The latter is the default if both are installed but it is possible to force the selection of tls with the environment variable CONDUIT_TLS=native. For Cohttp_async the default is to use async_ssl (but users are able to use ocaml-tls with some modifications).

Consult the following modules for reference:

The full documentation for the latest published version of the library is available on the repository github pages.

Compile and execute with dune

Create this dune file

cat - > dune <<EOF
(executable
 (public_name client_example)
 (name client_example)
 (libraries cohttp-lwt-unix))
EOF

then build and execute the example with

$ dune exec ./client_example.exe

Dealing with timeouts

You can use Lwt.pick to set a timeout on the execution of a thread. For example, say that you want to set a timeout on the Client.get thread in the example above, then you could modify the get call as follows

let compute ~time ~f =
  Lwt.pick
    [
      (f () >|= fun v -> `Done v)
    ; (Lwt_unix.sleep time >|= fun () -> `Timeout)
    ]

let body =
  let get () = Client.get (Uri.of_string "https://www.reddit.com/") in
  compute ~time:0.1 ~f:get >>= function
  | `Timeout -> Lwt.fail_with "Timeout expired"
  | `Done (resp, body) -> Lwt.return (resp, body)

Executing the code, which you can actually try by calling

$ dune exec examples/lwt_unix_doc/client_lwt_timeout.exe

the call will most likely fail with the following output

Fatal error: exception (Failure "Timeout expired")

Similarly, in the case of cohttp-async you can directly use Async's with_timeout function. For example,

let get_body ~uri ~timeout =
    let%bind _, body = Cohttp_async.Client.get ~interrupt:(after (sec timeout)) uri in
    Body.to_string body    

let body =
  let uri = Uri.of_string "https://www.reddit.com/" in
  let timeout = 0.1 in
  Clock.with_timeout (sec timeout) (get_body ~uri ~timeout)
  >>| function
  | `Result body -> Log.debug logger "body: %s" body
  | `Timeout  -> Log.debug logger "Timeout with url:%s" url

Managing sessions

Managing sessions and saving cookies across requests is not directly supported by cohttp. It is not hard to roll out a custom solution, but an alternative is to use the session library, which is compatible with cohttp.

Multipart form data

Multipart form data is not supported out of the box but is provided by external libraries:

Creating custom resolver: a Docker Socket Client example

Cohttp provides a lot of utilities out of the box, but does not prevent the users to dig in and customise it for their needs. The following is an example of a unix socket client to communicate with Docker.

open Lwt.Infix
open Cohttp

let ctx =
  let resolver =
    let h = Hashtbl.create 1 in
    Hashtbl.add h "docker" (`Unix_domain_socket "/var/run/docker.sock");
    Resolver_lwt_unix.static h
  in
  Cohttp_lwt_unix.Client.custom_ctx ~resolver ()

let t =
  Cohttp_lwt_unix.Client.get ~ctx (Uri.of_string "http://docker/version")
  >>= fun (resp, body) ->
  let open Cohttp in
  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)

let _ = Lwt_main.run t

The main issue there is there no way to resolve a socket address, so you need to create a custom resolver to map a hostname to the Unix domain socket.

To build and execute with dune, first create the following dune file

$ cat - > dune <<EOF
(executable
 (public_name docker_example)
 (name docker_example)
 (libraries cohttp-lwt-unix conduit-lwt))
EOF

then run the example with

$ dune exec ./docker_example.exe

Even though conduit is transitively there, for this example we are explicitly mentioning it to emphasize that we are creating a new Conduit resolver. Refer to conduit's README for examples of use and links to up-to-date conduit documentation.

Dealing with redirects

This examples has been adapted from a script on the ocaml.org website, and shows an explicit way to deal with redirects in cohttp-lwt-unix.

let rec http_get_and_follow ~max_redirects uri =
  let open Lwt.Syntax in
  let* ans = Cohttp_lwt_unix.Client.get uri in
  follow_redirect ~max_redirects uri ans

and follow_redirect ~max_redirects 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 request_uri response
  | `Found | `Temporary_redirect ->
      handle_redirect ~permanent:false ~max_redirects request_uri response
  | `Not_found | `Gone -> Lwt.fail_with "Not found"
  | status ->
      Lwt.fail_with
        (Printf.sprintf "Unhandled status: %s"
           (Cohttp.Code.string_of_status status))

and handle_redirect ~permanent ~max_redirects request_uri response =
  if max_redirects <= 0 then Lwt.fail_with "Too many redirects"
  else
    let headers = Http.Response.headers response in
    let location = Http.Header.get headers "location" in
    match location with
    | None -> Lwt.fail_with "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 uri ~max_redirects:(max_redirects - 1)

The following example, adapted from blue-http, does a similar thing with cohttp-async (and ppx_let).

open Core_kernel
open Async_kernel

let with_redirects ~max_redirects uri f =
  let seen_uris = Hash_set.create (module String) in
  let rec loop ~max_redirects uri =
    Hash_set.add seen_uris (Uri.to_string uri);
    let%bind ((response, response_body) as res) = f uri in
    let status_code =
      Cohttp.(Response.status response |> Code.code_of_status)
    in
    if Cohttp.Code.is_redirection status_code then (
      match Cohttp.(Response.headers response |> Header.get_location) with
      | Some new_uri when Uri.to_string new_uri |> Hash_set.mem seen_uris ->
          return res
      | Some new_uri ->
          if max_redirects > 0 then
            (* Cohttp leaks connections if we don't drain the response body *)
            Cohttp_async.Body.drain response_body >>= fun () ->
            loop ~max_redirects:(max_redirects - 1) new_uri
          else (
            Log.Global.debug ~tags:[]
              "Ignoring %d redirect from %s to %s: redirect limit exceeded"
              status_code (Uri.to_string uri) (Uri.to_string new_uri);
            return res)
      | None ->
          Log.Global.debug ~tags:[]
            "Ignoring %d redirect from %s: there is no Location header"
            status_code (Uri.to_string uri);
          return res)
    else return res
  in
  loop ~max_redirects uri

You can read a bit more on the rationale behind the absence of this functionality in the API here.

Basic Server Tutorial

Implementing a server in cohttp using the Lwt backend (for Async is very similar) is mostly equivalent to implementing a function of type :

conn -> Http.Request.t -> Cohttp_lwt.Body.t -> (Http.Response.t * Cohttp_lwt.Body.t) Lwt.t

The parameters are self explanatory but we'll summarize them quickly here:

  • conn - contains connection information
  • Http.Request.t - Request information such as method, uri, headers, etc.
  • Cohttp_lwt.Body.t - Contains the request body. You must manually decode the request body into json, form encoded pairs, etc. For cohttp, the body is simply binary data.

Here's an example of a simple cohttp server that outputs back request information.

open Lwt
open Cohttp
open Cohttp_lwt_unix

let server =
  let callback _conn req body =
    let uri = req |> Request.uri |> Uri.to_string in
    let meth = req |> Request.meth |> Code.string_of_method in
    let headers = req |> Request.headers |> Header.to_string in
    ( body |> Cohttp_lwt.Body.to_string >|= fun body ->
      Printf.sprintf "Uri: %s\nMethod: %s\nHeaders\nHeaders: %s\nBody: %s" uri
        meth headers body )
    >>= fun body -> Server.respond_string ~status:`OK ~body ()
  in
  Server.create ~mode:(`TCP (`Port 8000)) (Server.make ~callback ())

let () = ignore (Lwt_main.run server)

Compile and execute with dune

Create this dune file

cat - > dune <<EOF
(executable
 (public_name server_example)
 (name server_example)
 (libraries cohttp-lwt-unix conduit-lwt))
EOF

then build and execute the example with

$ dune exec ./server_example.exe

As in the previous example, here we are explicitly mentioning conduit-lwt to emphasize that we are relying on Conduit to specify the protocols and the services. Refer to conduit's README for examples of use and links to up-to-date conduit documentation.

Installed Binaries

Cohttp comes with a few simple binaries that are handy, useful also to test cohttp itself, and can serve as examples of how to use the library. All binaries come in two flavours - Async and Lwt.

  • $ cohttp-curl-{lwt,async}

This is a simple curl utility implemented using cohttp. An example of an invocation is:

$ cohttp-curl-lwt -v -X GET "https://www.reddit.com/"
  • $ cohttp-server-{lwt,async}

This binary acts in a similar fashion to the Python SimpleHTTPServer. Just run cohttp-server-async in a directory and it will open up a local port and serve the files over HTTP.

$ cohttp-server-async

Assuming that the server is running in cohttp's source directory:

$ cohttp-curl-lwt 'http://0.0.0.0:8080/README.md'

Other examples using the async api are available in the cohttp-async/examples folder in the sources.

Debugging

You can activate some runtime debugging for the servers by setting COHTTP_DEBUG to any value different from 0 or false, and it will set a default debug-level logger on stdout.

Since both Cohttp and Conduit use Logs for debugging output, you can enable custom debugging in your code (if needed). For example, if you intend to make use of the COHTTP_DEBUG env variable, you could simply use

let () =
  if not @@ Debug.debug_active () then (
    Fmt_tty.setup_std_outputs ();
    Logs.set_level ~all:true level;
    Logs.set_reporter Debug.default_reporter);

Of course you are free to completely override it and use your own reporters, for example by adding something like the following to your code (courtesy of @dinosaure).

let reporter ppf =
  let report src level ~over k msgf =
    let k _ =
      over () ;
      k () in
    let with_metadata header _tags k ppf fmt =
      Format.kfprintf k ppf
        ("%a[%a]: " ^^ fmt ^^ "\n%!")
        Logs_fmt.pp_header (level, header)
        Fmt.(styled `Magenta string)
        (Logs.Src.name src) in
    msgf @@ fun ?header ?tags fmt -> with_metadata header tags k ppf fmt in
  { Logs.report }

let () =
  Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ~utf_8:true ();
  Logs.set_reporter (reporter Fmt.stderr);
  Logs.set_level ~all:true (Some Logs.Debug)

Note that you can selectively filter out the logs produced by cohttp-lwt and cohttp-lwt-unix internals as follows.

let () =
  (* Set log level v for all loggers, this does also affect cohttp internal loggers *)
  Logs.set_level ~all:true level;
  (* Disable all cohttp-lwt and cohttp-lwt-unix logs *)
  List.iter (fun src ->
      match Logs.Src.name src with
      | "cohttp.lwt.io" | "cohttp.lwt.server" -> Logs.Src.set_level src None
      | _ -> ())
  @@ Logs.Src.list ()

Important Links

ocaml-cohttp's People

Contributors

andreas avatar andrewray avatar anuragsoni avatar avsm avatar barko avatar bikallem avatar cgaebel avatar dinosaure avatar djs55 avatar dsheets avatar duckpilot avatar emillon avatar hannesm avatar j0sh avatar lyrm avatar mefyl avatar misterda avatar msaffer-js avatar mseri avatar objmagic avatar raphael-proust avatar rgrinberg avatar samoht avatar seliopou avatar smorimoto avatar talex5 avatar vbmithr avatar yallop avatar yomimono avatar zoggy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ocaml-cohttp's Issues

Body.body_of_string does not print the integrality of the body

Something like

open Cohttp_lwt_unix

let uri = Uri.of_string "http://www.google.com" in
Lwt_main.run ((fun () ->
lwt res = Client.get uri in match res with
| None -> failwith "Nothing returned"
| Some (response, body) -> lwt body_str = Body.string_of_body body in Printf.printf "%s\n" body_str) ())

Does not print all the body, but prints only a part of it.

Cohttp_lwt_body.t vs Cohttp_lwt_body.contents option

I'm doing a very basic HTTP post

open Core.Std
open Lwt

let m = "foo bar baz"

(* let body_from_string s = Option.value_exn (Cohttp_lwt_body.body_of_string s) *)
let body_from_string = Cohttp_lwt_body.body_of_string


let make_request m = Cohttp_lwt_unix.Client.post ~body:(body_from_string m)
        (Uri.of_string "https://localhost") >>= function
                | Some (_, body) -> Cohttp_lwt_body.string_of_body body
                | None -> return ""

I would expect to be able to use Cohttp_lwt_body.body_of_string to directly build a post. Instead, I get type errors like:

Error: This expression has type
         Cohttp_lwt_body.t = Cohttp_lwt_body.contents option
       but an expression was expected of type Cohttp_lwt_body.contents

I'm not sure where a type of Cohttp_lwt_body.contents is expected.

Is this a bug in the library or am I using it incorrectly?

Here are (hopefully) the relevant lines from opam list -i

cohttp               0.9.14  HTTP library for Lwt, Async and Mirage
core              109.55.00  Industrial strength alternative to OCaml's standard library
core_bench        109.55.00  Benchmarking library
core_extended     109.55.00  Extra components that are not as closely vetted or as stable as Core
core_kernel       109.55.00  Industrial strength alternative to OCaml's standard library
lwt                   2.4.4  A cooperative threads library for OCaml

Fails to build with async 119.13.00

It looks like a simple interface change in async broke something but I haven't had a chance to dig any deeper.

==== ERROR [while installing cohttp.0.9.5] ====
# opam-version    1.0.0 (1.0.0)
# os              darwin
# command         make PREFIX=/Users/dlobraico/.opam/4.00.1+short-types
# path            /Users/dlobraico/.opam/4.00.1+short-types/build/cohttp.0.9.5
# exit-code       2
# env-file        /Users/dlobraico/.opam/4.00.1+short-types/build/cohttp.0.9.5/cohttp-5f1023.env
# stdout-file     /Users/dlobraico/.opam/4.00.1+short-types/build/cohttp.0.9.5/cohttp-5f1023.out
# stderr-file     /Users/dlobraico/.opam/4.00.1+short-types/build/cohttp.0.9.5/cohttp-5f1023.err
### stdout ###
...[truncated]
ocamlfind ocamldep -package uri.services -package uri -package threads -package re -package async_unix -package async_core -package async -modules async/cohttp_async.ml > async/cohttp_async.ml.depends
ocamlfind ocamlc -c -g -annot -I lib -package uri.services -package uri -package threads -package re -package async_unix -package async_core -package async -thread -I async -I lib -o async/cohttp_async_raw.cmo async/cohttp_async_raw.ml
ocamlfind ocamlc -c -g -annot -I lib -package uri.services -package uri -package threads -package re -package async_unix -package async_core -package async -thread -I async -I lib -o async/cohttp_async.cmo async/cohttp_async.ml
+ ocamlfind ocamlc -c -g -annot -I lib -package uri.services -package uri -package threads -package re -package async_unix -package async_core -package async -thread -I async -I lib -o async/cohttp_async.cmo async/cohttp_async.ml
File "async/cohttp_async.ml", line 89, characters 14-21:
Error: This pattern matches values of type 'a * 'b
       but a pattern was expected which matches values of type
         ([ `Active ], Async_extra.Import.Socket.Address.Inet.t) Socket.t *
         Response.ic * Response.oc
Command exited with code 2.
### stderr ###
E: Failure("Command ''/Users/dlobraico/.opam/4.00.1+short-types/bin/ocamlbuild' lib/cohttp.cma lib/cohttp.cmxa lib/cohttp.a lib/cohttp.cmxs async/cohttp_async.cma async/cohttp_async.cmxa async/cohttp_async.a async/cohttp_async.cmxs lib_test/test_accept.native lib_test/test_header.native lib_test/test_net_async.native lib_test/test_net_async_server.native -tag debug' terminated with error code 10")
make: *** [build] Error 1

'opam install cohttp' failed.

memory leak in async version of server?

I have noticed that long running servers using the async version of cohttp use increasing amounts of memory over time. I isolated the issue using a simple server and client shown below. It seems 132KB is consumed on every request. Am I doing something wrong?

test_server.ml:
open Core.Std
open Cohttp
open Cohttp_async
open Async.Std
let server_port = 10999
let server_addr = "localhost"

let http_callback ~body address request =
  Cohttp_async.Server.respond_with_string "response"
;;                                                                                                                                                                        

let () =                                                                                                                                                                       
  don't_wait_for (
    Server.create (Tcp.on_port server_port) (http_callback) ~max_connections:1 >>|          
    fun _server -> ()
  );                                                                                                                                                                      
  never_returns (Scheduler.go ())
;;                             
client.sh:
#!/bin/bash                                                                                                                                                                                                                                                       
while true; do curl localhost:10999; sleep 5; done

I watch the vm usage using e.g.:
watch -n 1 "cat /proc/$(pgrep test_server.exe)/status"

and it shows a steady increase every 5 seconds when the curl call happens. I am using 0.9.10.

Please clarify LICENSE

Similar issue like with ocaml-re: ocaml/ocaml-re#5
If it is really meant to have the exception could you update the LICENSE file, and the individual files? (assuming all the authors will consent to that)

Thanks,

Add README usage examples

Hi. Thanks for your work : )

Would it be possible to add some simple documentation to the README? I.e very basic stuff like making basic requests. I've found it nearly impossible to find any easy to use libraries for OCaml that handle simple HTTP requests for newcomers like myself.

I'm thinking something along the lines of this which gives examples on how to do basic things:

https://github.com/dakrone/clj-http/blob/master/Readme.md

Thanks

Cookies: work better with Header.t

Right now the Cookie module works with string * string pairs, but it would be easier if it used the Header module directly (it was written before Header existed).

DoS test

Use one of the HTTP stress testers to spot DoS attacks (like unbounded header requests) before 1.0

Can we add sexp to all types?

I'm finding that it would really help with debugging for me currently. Of course this is a trivial change and I don't mind doing it myself but I'd like to know first if anyone has any objections.

regress test for chunked-encoding off-by-one

This is almost certainly using a very old version of Cohttp, but we should add a regression test to verify that this doesn't affect current Cohttp servers.

See lavv17/lftp#51 for the details of the bug.

In a nutshell

get http://mjambon.com/releases/cppo/cppo-0.9.3.tar.gz

a content-length calculation with a HEAD request is different from a chunked-encoding transfer.

Does not compile under uri 1.3.2

I get this error:

File "lib/header.ml", line 1:
Error: The implementation lib/header.ml
       does not match the interface lib/header.cmi:
       ...
       At position module Make(IO) : <here>
       Values do not match:
         val parse_form :
           string list StringMap.t ->
           IO.ic -> (string * string list) list IO.t
       is not included in
         val parse_form : t -> IO.ic -> (string * string) list IO.t
       File "lib/header.ml", line 165, characters 6-16: Actual declaration
Command exited with code 2.

OASIS: Use InternalModules

OASIS has an InternalModules directive in order to define internal modules not to be exported by the library. Maybe cohttp should use it for some of its modules that should not be exported ?

Add a way to set TCP_NODELAY socket option

Related to #52; both issues are important for real-time applications.

Since Lwt_io performs its own buffering, I think it may be acceptable to just always set TCP_NODELAY on the underlying socket.

Abort request if connection is closed

I'm using a server based on lwt.

I have this code (simplified) in callback:

let stream : string Lwt_stream.t = ... in
return (resp, Cohttp_lwt_body.body_of_stream stream)

stream is a very long-running stream (it's for server-sent events). Right now, even if the remote end closes the connection, cohttp will hold a reference to stream until I terminate it. This results in a memory leak.

It would be very nice if server detected when the remote end has closed the connection and released the reference to the body.

I'm somewhat at a loss why it does not happen now. Lwt_io.write should raise if it can't write to the socket, and that should propagate through Request all the way back up to Cohttp_lwt.Server. But for some reason, this does not happen.

Cohttp_async: post body is always empty

I could just be misunderstanding how to properly write to the pipe. But let's say I just want to POST a string to a given url. What am I doing wrong with the following attempt? (the server keeps reporting that nothing is posted)

open Core.Std
open Async.Std

(* this function is just used to gather the result pipe into a string for printing to stdout *)
let read_entire_pipe pr =
  let buf = Buffer.create 0 in
  let rec f () =
    Deferred.bind (Pipe.read pr) (function
      | `Eof -> (print_endline "eof"; return (Buffer.contents buf))
      | `Ok s -> (Buffer.add_string buf s; f ())
    )
  in
  f ()
;;

let post_string request =
  let body = Pipe.init (fun w -> Pipe.write w request) in
  let uri = Uri.of_string "http://posttestserver.com/post.php" in
  Cohttp_async.Client.call ~body `POST uri >>= function
    | None -> failwith "none"
    | Some (response, None) -> failwith "empty"
    | Some (response, Some reader) ->
        read_entire_pipe reader >>= fun s ->
          print_endline s; return ()
;;

let () =
  don't_wait_for (post_string "foo");
  never_returns (Scheduler.go())
;;

[feature wish] High-level function for performing a GET that follows redirects

There's a whole bunch of ways to perform redirections: 301, other 3xx HTTP codes (temporarily moved, etc.), Location: header, etc. etc.. I kind of wish for an extra "follow_redirects" parameter for the Cohttp_lwt_unix.Client.get function, that doesn't require me to do that myself. Curl has that, and it's bloody convenient :-).

Cheers,

~ jonathan

client return types not consistent between lwt and async backends

We have:

Cohttp_async.get : ... Uri.t -> (Response.t * string Pipe.Reader.t) Deferred.t
Cohtt_lwt_unix.get : ... Uri.t -> (Response.t * Cohttp_lwt_body.t) option Lwt.t

What does the None case in the lwt version indicate? And why isn't this needed in the async version?

Cookie/Response/Request could use some functional setters

For example updating the expiration of Set_cookie_hdr.t value is not easy. I have similar annoyances with Request/Response and headers. Easiest way to fix this is to add the fieldslib dependency and with fields to the types. Or just add the boilerplate if a new dependency is a no-no.

Cohttp dies with SIGPIPE when responding lots of data and quick browser refresh.

This code can be used to reproduce the bug:

module C = Cohttp
module CU = Cohttp_lwt_unix
module CB = Cohttp_lwt_body

let (>>=) = Lwt.bind

let random_string len =
  let open Cryptokit in
  let rng = Random.device_rng "/dev/urandom" in
  let str = Random.string rng len in
  let trans = Base64.encode_compact_pad () in
  transform_string trans str

let mycallback conn_id ?body req =
  let open Cohttp_lwt_unix.Server in
  let response = Response.make ~status:`OK () in
  Lwt.return (response, CB.body_of_string (random_string 100000))

let conn_closed conn_id () = ()

let config = Cohttp_lwt_unix.Server.({
    callback=mycallback;
    conn_closed=conn_closed
  })

let _ =
  Lwt_main.run (
    Cohttp_lwt_unix.Server.create
      ~address:"localhost" ~port:8080 config
  )

Then go to http://localhost:8080 with your browser and hit refresh like a maniac :p

PUT?

Shouldn't there be support for PUT? It doesn't seem like using `POST instead would work.

lib/code.mli:

type meth = [ `GET | `POST | `HEAD | `DELETE | `PATCH ]

lwt/cohttp_lwt_unix.ml:

let put ?body ?chunked ?headers uri = call ?headers ?body ?chunked `POST uri 

cohttp_lwt: uniform SSL API

(as discussed on #mirage)

Right now there is an HTTPS client but no server.

I wrote some example code to built an HTTPS server: https://github.com/smondet/cohttps-server-example

  • could be integrated as SSL_server (but the API is difficult to define → many options/parameters + error management + server & client certificates)
  • SSL could be abstracted out of Cohttp in a plugable way

Exception raised when doing a large number of Client.get

utop $ let rec perform_get n =
lwt () = Lwt_io.printf "%d\n" n in
lwt res = Cohttp_lwt_unix.Client.get (Uri.of_string "http://luminar.eu.org") in
perform_get (n+1)
;;
val perform_get : int -> 'a Lwt.t = <fun>
utop $ perform_get 0;;
0                                                                                                                         
1                                                                                                                         
2
[...]
1000
1001
1002
1003
1004
Exception: Failure "cant resolve hostname: luminar.eu.org".
Fatal error: exception Sys_error("/home/vb/.opam/system/lib/ssl: Too many open files")
(Program not linked with -g, cannot print stack backtrace)
utop: main: cannot save history to "/home/vb/.utop-history": open: Too many open files                  

When doing a library based on cohttp, I still want to have the choice of the backend.

Please refactor cohttp such that it become possible to create a library that depend on it, and that can still choose which backend to use.

Currently, unless I'm completely wrong, we need to use either Cohttp_lwt_unix or Cohttp_async to use the library.

What I want is a way to do Client = Cohttp_client(Async_backend) or Client = Cohttp_client(Lwt_backend) in my library, such that my library can be a functor over Cohttp_backend.S (names of modules are my imagination but you should guess what I want).

If it is already possible, please explain me how!

Thanks

Client.post_form

A lot of websites require clients to put the content length of what they POST.
What about upgrading post_form into somthing like

    let post_form ?headers ~params uri =
      let headers = Header.add_opt headers "content-type" "application/x-www-form-urlencoded" in
      let q = List.map (fun (k,v) -> k, [v]) (Header.to_list params) in
      let body = Cohttp_lwt_body.body_of_string (Uri.encoded_of_query q) in
      let body_len, body = Cohttp_lwt_body.get_length body in
      let headers = Header.add headers "content-length" (string_of_int body_len) in
      post ~headers ?body uri

?

Unable to get a 700ko body with Cohttp

I ran that in utop:

let uri = Uri.of_string "https://mtgox.com/api/1/BTCUSD/trades";
Cohttp_lwt_unix.Client.get uri;;

The last function never finishes. The body of this page is about 700ko big. Could anybody try to reproduce this issue ? Cohttp works with other URLs for me.

Why does Cohttp.lwt download `www.lastminute.com` so slow?

I am using Cohttp.lwt to download the html from http://www.lastminute.com.

It is very slow and seems not finishing forever.

The code is like this:

let u1 = "http://www.lastminute.com"

let u2 = "http://www.bbc.co.uk"

(* a simple function to access the content of the response *)
let content = function
  | Some (_, body) ->  Cohttp_lwt_body.string_of_body body
  | _ -> return ""

(* launch both requests in parallel *)
let t = Lwt_list.map_p Cohttp_lwt_unix.Client.get
  (List.map Uri.of_string [u1])

(* maps the result through the content function *)
let t2 = t >>= Lwt_list.map_p content

let t3 = t2 >>= Lwt_list.iter_p (Lwt_io.printf "%s")  

(* launch the event loop *)
let v = Lwt_main.run t3

If I use the same code to download from http://www.bbc.co.uk or http://www.google.com or stackoverflow.com etc, it is fast as normal.


Eventually, it finished.

<HTML><HEAD>
<TITLE>Gateway Timeout - In read </TITLE>
</HEAD><BODY>
<H1>Gateway Timeout</H1>
The proxy server did not receive a timely response from the upstream server.<P>
Reference&#32;&#35;1&#46;a128434d&#46;1371477050&#46;26237681
</BODY></HTML>

But again, why it says Gateway Timeout? I paste the url http://www.lastminute.com in browser, it does not have any problem.


Can I know why? Why does lastminute.com takes so long with Cohttp? And why does it give an error like that?

Edit

I guess I should let Cohttp_lwt_unix.Client.get auto-redirect? How can I set that?

http 1.0 responses with no headers do not seem to be parsed

I have come across HTTP 1.0 servers that send responses to GET requests that have no headers, and a non-empty body. Apparently this is allowed. The server is supposed to close the connection when it is done sending the body.

However, Cohttp_lwt_unix.Client.get returns an empty body if there are no headers.

Cohttp.IO.S should not define the (>>) operator.

In Lwt the (>>) operator is defined in the syntax extension and not like in
https://github.com/avsm/ocaml-cohttp/blob/master/lwt/cohttp_lwt_unix_io.ml#L29
because this way it is buggy, or at least very difficult to reason about (the eager evaluation goes in the wrong order, which is officially unspecified anyway).

Here is the counter example showing the problem:

utop # open Lwt
let (>>) m n = m >>= fun _ -> n
let () =
  Lwt_main.run begin
     Lwt_io.printf "Bouh 1\n%!" >>
     Lwt_io.printf "Bouh 2\n%!" >>
     return ()
  end
;;
Bouh 2                                                                                                                        
Bouh 1                                                                                                                        
val ( >> ) : 'a t -> 'b t -> 'b t = <fun>              

This was spotted on the Ocsigen mailing-list (with @agarwal and @jpdeplaix).

HTTPS support?

Is HTTPS supported by ocaml-cohttp? If not, is there some tweak to make it work for HTTPS? For I'm working on a project that has to deal with that.

[doc] Better documentation for the Header module

Hi,

I've been bitten bit this, but I didn't waste too much time since I suspected what was going on :-).

The header module should probably mention that the header names are lowercase. Servers usually write headers as "Location: ", so that's what I intuitively wrote. Alternatively, the implementation should first lowercase the parameter passed to function such as Header.get.

Cheers,

~ jonathan

New release and update to Opam

Could you please make a new release and push an update to Opam? cohttp currently will not install in opam due to the async_core issue which you pushed the patch in 2 days ago.

Thanks!

cookie expiration needs to be implemented

I need this functionality myself, but from the source code:

(* We need a non-Unix date/time implementation to support other expiration types *)

What's the reasoning why Unix cannot be used? Is it for mirage compatibility?. As a compromise can we implement max-age instead? I'd like to have max-age anyway.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.