14
votes

I'm trying to do something that should be simple: make a GET request to a url. However, when I search for examples of how to do this I often wind up with near-gibberish like this.

Does anyone know how to make a simple HTTP request using OCaml? I'm an OCaml newbie with some Haskell exp.

NOTE:

A solution using the lowest possible level OCaml would be ideal. I've seen the Cohttp library used, but I'm more interested in a native (?) HTTP OCaml lib or something along those lines.

In response to @antron, a solution using the lowest possible level native OCaml would be much appreciated. I'm led to believe that this will involve the Unix library. But if there is another solution that does not involve 3rd party libraries it would be just as welcome.

4
As per comment on my answer recommending Cohttp, there is no such library in OCaml. The OCaml standard library is very austere, and it is common to use community libraries for many tasks. If you want to see the list of libraries packaged with OCaml, it is here: caml.inria.fr/pub/docs/manual-ocaml/libref/index.html - antron
If you want the "lowest possible level" in OCaml, the answer will be sockets, (e.g. standard module Unix) and then it won't be simple anymore. If you want that low a level, please clarify your question. - antron
It blows my mind that anyone would downvote a question like this, its bread and butter coding for the majority of developers as the stack overflow developer survey shows. @dopatraman I recommend also taking a look at hyegar.com/2015/10/20/so-youre-learning-ocaml to get productive quickly. - EdgarArout
@antron updated the question. - dopatraman
@dopatraman how much background do you have with socket programming? You want to use socket, connect, send, recv and close in Unix, and perhaps even select, but I don't know if this is immediately meaningful to you, or you want a detailed answer. It is basically C systems programming in OCaml at that point, if you choose not to use any HTTP library or a concurrency monad (which are also not found in the standard library). - antron

4 Answers

9
votes

Use the Cohttp library. See the Client example.

The relevant line is:

Cohttp_lwt_unix.Client.get (Uri.of_string "http://www.reddit.com/")

This gives you a pair of (response, body) inside the Lwt monad. response is basically a record, and body is a stream. The rest of the example is just printing some interesting bits of those.

3
votes

Perhaps the most basic way to send a GET request in OCaml is to use the Unix library and the basic input/output routines from Pervasives.

Here is a very simple example:

let ip = Unix.((gethostbyname "caml.inria.fr").h_addr_list.(0))
let addr = Unix.ADDR_INET (ip, 80)

let sock = Unix.(socket PF_INET SOCK_STREAM 0)
let _ = Unix.connect sock addr

let in_ch = Unix.in_channel_of_descr sock
let out_ch = Unix.out_channel_of_descr sock

let _ =
  output_string out_ch
    "GET /pub/docs/manual-ocaml/index.html HTTP/1.1\r\n\
     Host: caml.inria.fr\r\n\
     User-Agent: OCaml\r\n\
     Connection: close\r\n\
     \r\n";
  flush out_ch

let _ =
  try
    while true do
      print_string (input_line in_ch)
    done
  with End_of_file ->
    Unix.close sock

The Unix. prefixes are not necessary if one puts open Unix at the top of the file, but I preferred to leave them in for clarity.

The program can be compiled to byte code with ocamlc unix.cma -o get get.ml.

I agree with @ChriS' suggestion to read Leroy and Rémy's Unix system programming in OCaml (I've included a link to the online version); it's a great book.

2
votes

For low level Unix programming in OCaml (even if you don't know much about it), I recommend the excellent book Unix System Programming in OCaml. It will tell you how to write the client you want.

0
votes

For those searching a quite independent solution, I found one which should at least work on any Unix* like OS.

From Rosetta Code:

let try_finalise f x finally y =
  let res = try f x with e -> finally y; raise e in
  finally y;
  res

let rec restart_on_EINTR f x =
  try f x with Unix.Unix_error (Unix.EINTR, _, _) -> restart_on_EINTR f x

let double_fork_treatment server service (client_descr, _ as client) =
  let treat () =
    match Unix.fork () with
    | 0 ->
        if Unix.fork () <> 0 then exit 0;
        Unix.close server; service client; exit 0
    | k ->
        ignore (restart_on_EINTR (Unix.waitpid []) k)
  in
  try_finalise treat () Unix.close client_descr

let install_tcp_server_socket addr =
  let s = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  try
    Unix.bind s addr;
    Unix.listen s 10;
    s
  with e -> Unix.close s; raise e

let tcp_server treat_connection addr =
  ignore (Sys.signal Sys.sigpipe Sys.Signal_ignore);
  let server_sock = install_tcp_server_socket addr in
  while true do
    let client = restart_on_EINTR Unix.accept server_sock in 
    treat_connection server_sock client 
  done

let server () =
  let port = 8080 in
  let host = (Unix.gethostbyname (Unix.gethostname())).Unix.h_addr_list.(0) in 
  let addr = Unix.ADDR_INET (host, port) in
  let treat sock (client_sock, client_addr as client) =
    let service (s, _) =
      let response = "\
        HTTP/1.1 200 OK\r\n\
        Content-Type: text/html; charset=UTF-8\r\n\r\n\
        <html><head><title>Goodbye, world!</title>\
        <style>body { background-color: #0FF }\
        h1 { font-size:3em; color: black; }</style></head>\
        <body><h1>Goodbye, world!</h1></body></html>\r\n"
      in
      Unix.write s response 0 (String.length response);
    in
    double_fork_treatment sock service client
  in
  tcp_server treat addr

let _ =
  Unix.handle_unix_error server ()

It's not a one liner but it's relative straight forward.