Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 140 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
## ocaml-cohttp -- an OCaml library for HTTP clients and servers

[![Join the chat at https://gitter.im/mirage/ocaml-cohttp](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mirage/ocaml-cohttp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Cohttp is an OCaml library for creating HTTP daemons. It has a portable
HTTP parser, and implementations using various asynchronous programming
libraries:
Expand All @@ -14,25 +12,24 @@ libraries:
* `Cohttp_lwt` exposes an OS-independent Lwt interface, which is used
by the [Mirage](https://mirage.io/) interface to generate standalone
microkernels (use the cohttp-mirage subpackage).
* `Cohttp_lwt_xhr` compiles to a JavaScript module that maps the Cohttp
* `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.

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 activate some runtime debugging by setting `COHTTP_DEBUG` to any
value, and all requests and responses will be written to stderr. Further
debugging of the connection layer can be obtained by setting `CONDUIT_DEBUG`
to any value.
You can find help from cohttp users and maintainers at the
[discuss.ocaml.org](https://discuss.ocaml.org) forum or on the
[OCaml discord server](https://discord.gg/cCYQbqN).

## Installation

Latest stable version should be obtained from opam. Make sure to install the
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 js_of_ocaml
$ opam install cohttp-lwt-unix cohttp-async
```

You can also obtain the development release:
Expand All @@ -41,21 +38,10 @@ You can also obtain the development release:
$ opam pin add cohttp --dev-repo
```

### Findlib (Ocamlfind)

Cohttp ships with 6 findlib libraries:

* cohttp - Base `Cohttp` module. No platform specific functionality.
* cohttp-async - Async backend `Cohttp_async`
* cohttp-lwt-jsoo - Jsoo (XHR) client
* cohttp-lwt - Lwt backend without unix specifics.
* cohttp-lwt-unix - Unix based lwt backend
* cohttp-top - Print cohttp types in the toplevel (`#require "cohttp-top"`)

## Client Tutorial

Cohttp provides clients for Async, Lwt, and jsoo (Lwt based). In this tutorial,
we will use the lwt client but it should be easily translateable to Async.
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
Expand All @@ -64,7 +50,7 @@ To create a simple request, use one of the methods in `Cohttp_lwt_unix.Client`.
For example downloading the reddit frontpage:

```ocaml
(* client_example.ml *)
cat - > client_example.ml <<EOF
open Lwt
open Cohttp
open Cohttp_lwt_unix
Expand All @@ -81,19 +67,13 @@ let body =
let () =
let body = Lwt_main.run body in
print_endline ("Received body\n" ^ body)
EOF
```

Build with:

```
ocamlbuild -pkg cohttp-lwt-unix client_example.native
```

There's a few things to notice:

* We open 2 modules. `Cohttp` contains the backend independent stuff and
`Cohttp_lwt_unix` is the lwt + unix specific stuff.
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
Expand All @@ -105,37 +85,81 @@ There's a few things to notice:
* 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` is able to request an HTTPS page
Note that `Cohttp_lwt_unix`/`Cohttp_async` are able to request an HTTPS page
by default. For `Cohttp_lwt_unix`, we use [ocaml-tls](https://github.com/mirleft/ocaml-tls.git)
(but the user is able to use `lwt_ssl` if he wants). For `Cohttp_async`, we use
`async_ssl` (but the user is able to use `ocaml-tls`).
(to use `lwt_ssl` is enough to use `Cohttp_lwt_unix_ssl` from the analogously
named package, the rest of the code does not change). For `Cohttp_async`, we use
`async_ssl` (but the user is able to use `ocaml-tls` with some modifications).

Consult the following modules for reference:

* [Cohttp_lwt.Client](https://github.com/mirage/ocaml-cohttp/blob/master/cohttp-lwt/src/s.ml)
* [Cohttp_async.Client](https://github.com/mirage/ocaml-cohttp/blob/master/cohttp-async/src/client.mli)

The full documentation for the latest published version of the library is
available on the [repository github pages](https://mirage.github.io/ocaml-cohttp/).

### Compile and execute with ocamlbuild

Build and execute with:

```
$ ocamlbuild -pkg cohttp-lwt-unix client_example.native
$ ./client_example.native
```

For manual builds, it is usually enough to remember that cohttp ships with 6
findlib (`ocamlfind`) libraries:

* `cohttp` - Base `Cohttp` module. No platform specific functionality
* `cohttp-async` - Async backend `Cohttp_async`
* `cohttp-lwt` - Lwt backend without unix specifics
* `cohttp-lwt-unix` - Unix based lwt backend with `tls` support
* `cohttp-lwt-unix-ssl` - Unix based lwt backend with `lwt_ssl` support
* `cohttp-lwt-unix-nossl` - Unix based lwt backend (only `http`)
* `cohttp-lwt-jsoo` - Jsoo (XHR) client
* `cohttp-top` - Print cohttp types in the toplevel (`#require "cohttp-top"`)

### 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
```

## Docker Socket Client example

Cohttp provides a lot of utilites out of the box, but does not prevent the users
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](https://discuss.ocaml.org/t/how-to-write-a-simple-socket-based-web-client-for-docker/1760/3).

```ocaml
cat - > docker_example.ml <<EOF
open Lwt.Infix

let resolve_unix_socket = function
let resolve_unix_socket : Conduit_lwt.Endpoint.t -> 'edn option Lwt.t = function
| IP _ -> Lwt.return_none
| Domain v -> match Domain_name.to_string v with
| "docker" -> Lwt.return_some (Unix.ADDR_UNIX "/var/run/docker.sock")
| _ -> Lwt.return_none
| Domain v -> (
match Domain_name.to_string v with
| "docker" -> Lwt.return_some (Unix.ADDR_UNIX "/var/run/docker.sock")
| _ -> Lwt.return_none )

let t =
let ctx =
Conduit_lwt.add
~priority:0 (* highest priority *)
Conduit_lwt.TCP.protocol resolve_unix_socket Conduit.empty in
Cohttp_lwt_unix.Client.get ~ctx (Uri.of_string "http://docker/version") >>= fun (resp, body) ->
Conduit_lwt.add ~priority:0 (* highest priority *) Conduit_lwt.TCP.protocol
resolve_unix_socket Conduit.empty
in
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;
Expand All @@ -145,15 +169,35 @@ let t =
print_endline ("Received body\n" ^ body)

let _ = Lwt_main.run t
EOF
```

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](https://github.com/mirage/ocaml-conduit/) for examples of use and
links to up-to-date conduit documentation.


## Basic Server Tutorial

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

```ocaml
conn -> Cohttp.Request.t -> Cohttp_lwt.Body.t -> (Cohttp.Response.t * Cohttp_lwt.Body.t) Lwt.t
Expand All @@ -171,7 +215,7 @@ Here's an example of a simple cohttp server that outputs back request
information.

```ocaml
(* server_example.ml *)
cat - > server_example.ml <<EOF
open Lwt
open Cohttp
open Cohttp_lwt_unix
Expand All @@ -181,34 +225,59 @@ let server =
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 ())
( 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 tcp_config =
{
Conduit_lwt.TCP.sockaddr = Unix.ADDR_INET (Unix.inet_addr_loopback, 8000);
capacity = 40;
}
in
Server.create tcp_config Conduit_lwt.TCP.protocol Conduit_lwt.TCP.service
(Server.make ~callback ())

let () = ignore (Lwt_main.run server)
let _ = Lwt_main.run (server ())
EOF
```

Build with:
### Compile and execute with ocamlbuild

Build and execute with:
```
ocamlbuild -pkg cohttp-lwt-unix server_example.native
$ ocamlbuild -pkg cohttp-lwt-unix server_example.native
$ ./server_example.native
```

The following modules are useful references:
### Compile and execute with dune

* [Cohttp_lwt.Server](https://github.com/mirage/ocaml-cohttp/blob/master/cohttp-lwt/src/s.ml) - Common to mirage and Unix
* [Cohttp_lwt_unix.Server](https://github.com/mirage/ocaml-cohttp/blob/master/cohttp-lwt-unix/src/server.mli) - Unix specific.
* [Cohttp_async.Server](https://github.com/mirage/ocaml-cohttp/blob/master/cohttp-async/src/server.mli)
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 ./client_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](https://github.com/mirage/ocaml-conduit/)
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 testing cohttp
itself, and serve as examples of how to use cohttp. The binaries come in two
flavours - Async and Lwt based.
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}`

Expand All @@ -234,5 +303,12 @@ Assuming that the server is running in cohttp's source directory:
```
$ cohttp-curl-lwt 'http://0.0.0.0:8080/_oasis'
```

Other examples using the async api are avaliable in the
[examples/async](https://github.com/mirage/ocaml-cohttp/tree/master/examples)
folder in the sources

## Important Links
- [API Documentation](https://mirage.github.io/ocaml-cohttp/)

- [Cohttp API Documentation](https://mirage.github.io/ocaml-cohttp/)
- [Conduit API Documentation](https://mirage.github.io/ocaml-conduit/)
17 changes: 0 additions & 17 deletions examples/doc/server_lwt.ml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ open Cohttp
open Cohttp_lwt_unix

let body =
Client.get (Uri.of_string "http://www.reddit.com/") >>= fun (resp, 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);
Expand Down
25 changes: 25 additions & 0 deletions examples/lwt_unix_doc/docker_lwt.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
open Lwt.Infix

let resolve_unix_socket : Conduit_lwt.Endpoint.t -> 'edn option Lwt.t = function
| IP _ -> Lwt.return_none
| Domain v -> (
match Domain_name.to_string v with
| "docker" -> Lwt.return_some (Unix.ADDR_UNIX "/var/run/docker.sock")
| _ -> Lwt.return_none )

let t =
let ctx =
Conduit_lwt.add ~priority:0 (* highest priority *) Conduit_lwt.TCP.protocol
resolve_unix_socket Conduit.empty
in
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
8 changes: 8 additions & 0 deletions examples/lwt_unix_doc/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(executables
(names client_lwt docker_lwt server_lwt)
(libraries cohttp-lwt-unix))

(alias
(name runtest)
(package cohttp-lwt-unix)
(deps client_lwt.exe docker_lwt.exe server_lwt.exe))
Loading