feat: add lwt wrapper

This commit is contained in:
Simon Cruanes 2019-09-14 10:00:38 -05:00
parent 80fc740e39
commit bf31f32a5b
9 changed files with 256 additions and 138 deletions

27
ezcurl-lwt.opam Normal file
View file

@ -0,0 +1,27 @@
opam-version: "2.0"
name: "ezcurl-lwt"
version: "0.1"
authors: ["Simon Cruanes"]
maintainer: "simon.cruanes.2007@m4x.org"
license: "MIT"
synopsis: "Friendly wrapper around OCurl, Lwt version"
build: [
["dune" "build" "@install" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name] {with-doc}
["dune" "runtest" "-p" name] {with-test}
]
depends: [
"base-bytes"
"result"
"ocurl"
"ezcurl"
"lwt"
"dune" {build}
"odoc" {with-doc}
"ocaml" { >= "4.03.0" }
]
tags: [ "curl" "web" "http" "client" "lwt" ]
homepage: "https://github.com/c-cube/ezcurl/"
doc: "https://c-cube.github.io/ezcurl/doc/1.2"
bug-reports: "https://github.com/c-cube/ezcurl/issues"
dev-repo: "git+https://github.com/c-cube/ezcurl.git"

View file

@ -1,67 +0,0 @@
module Config : sig
type t
val default : t
val verbose : bool -> t -> t
val authmethod : Curl.curlAuth list -> t -> t
val max_redirects : int -> t -> t
val follow_location : bool -> t -> t
val username : string -> t -> t
val password : string -> t -> t
end
type t
val make :
?set_opts:(Curl.t -> unit) ->
unit -> t
val delete : t -> unit
val with_client :
?set_opts:(Curl.t -> unit) ->
(t -> 'a) -> 'a
(** Make a temporary client, call the function with it, then cleanup *)
(* TODO: duphandle is deprecated, how do we iterate on options?
val copy : t -> t
*)
type response_info = {
ri_response_time: float;
ri_redirect_count: int;
}
type response = {
code: int;
headers: (string * string) list;
body: string;
info: response_info;
}
type meth =
| GET
| POST of Curl.curlHTTPPost list
| PUT
val string_of_meth : meth -> string
val http :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
meth:meth ->
unit ->
(response, Curl.curlCode * string) result
val get :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
unit ->
(response, Curl.curlCode * string) result

View file

@ -1,5 +1,4 @@
let opt_map ~f = function None -> None | Some x -> Some (f x)
let opt_iter ~f = function None -> () | Some x -> f x
module Config = struct
@ -88,6 +87,48 @@ type response = {
info: response_info;
}
type meth =
| GET
| POST of Curl.curlHTTPPost list
| PUT
let string_of_meth = function
| GET -> "GET"
| POST _ -> "POST"
| PUT -> "PUT"
module type IO = sig
type 'a t
val return : 'a -> 'a t
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
val fail : exn -> 'a t
val perform : Curl.t -> Curl.curlCode t
end
module type S = sig
type 'a io
val http :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
meth:meth ->
unit ->
(response, Curl.curlCode * string) result io
val get :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
unit ->
(response, Curl.curlCode * string) result io
end
exception Parse_error of Curl.curlCode * string
let mk_res (self:t) headers body : (response,_) result =
@ -114,77 +155,74 @@ let mk_res (self:t) headers body : (response,_) result =
with Parse_error (e, msg) ->
Error (e, Curl.strerror e ^ ": " ^ msg)
type meth =
| GET
| POST of Curl.curlHTTPPost list
| PUT
module Make(IO : IO)
(* : S with module IO = IO *)
= struct
open IO
let string_of_meth = function
| GET -> "GET"
| POST _ -> "POST"
| PUT -> "PUT"
type 'a io = 'a IO.t
let http
?(tries=1) ?client ?(config=Config.default) ?(headers=[]) ~url ~meth ()
: _ result =
let do_cleanup, self = match client with
| None -> true, make()
| Some c ->
Curl.reset c;
false, c
in
_apply_config self config;
(* local state *)
let tries = max tries 1 in (* at least one attempt *)
let body = ref "" in
let resp_headers = ref [] in
let resp_headers_done = ref false in (* once we get "\r\n" header line *)
Curl.set_url self url;
begin match meth with
| POST l -> Curl.set_httppost self l;
| GET -> Curl.set_httpget self true;
| PUT -> Curl.set_put self true;
end;
_set_headers self headers;
Curl.set_headerfunction self
(fun s0 ->
let s = String.trim s0 in
(* Printf.printf "got header %S\n%!" s0; *)
if s0 = "\r\n" then (
resp_headers_done := true;
) else (
(* redirection: drop previous headers *)
if !resp_headers_done then (
resp_headers_done := false;
resp_headers := [];
let http
?(tries=1) ?client ?(config=Config.default) ?(headers=[]) ~url ~meth ()
: _ result io =
let do_cleanup, self = match client with
| None -> true, make()
| Some c ->
Curl.reset c;
false, c
in
_apply_config self config;
(* local state *)
let tries = max tries 1 in (* at least one attempt *)
let body = ref "" in
let resp_headers = ref [] in
let resp_headers_done = ref false in (* once we get "\r\n" header line *)
Curl.set_url self url;
begin match meth with
| POST l -> Curl.set_httppost self l;
| GET -> Curl.set_httpget self true;
| PUT -> Curl.set_put self true;
end;
_set_headers self headers;
Curl.set_headerfunction self
(fun s0 ->
let s = String.trim s0 in
(* Printf.printf "got header %S\n%!" s0; *)
if s0 = "\r\n" then (
resp_headers_done := true;
) else (
(* redirection: drop previous headers *)
if !resp_headers_done then (
resp_headers_done := false;
resp_headers := [];
);
resp_headers := s :: !resp_headers;
);
resp_headers := s :: !resp_headers;
);
String.length s0);
Curl.set_writefunction self
(fun s ->
body := (if !body = "" then s else !body ^ s);
String.length s);
let rec loop i =
match Curl.perform self with
| () ->
let r = mk_res self (List.rev !resp_headers) !body in
if do_cleanup then Curl.cleanup self;
r
| exception Curl.CurlException (Curl.CURLE_AGAIN, _, _) when i > 1 ->
(* another try *)
loop (i-1)
| exception Curl.CurlException (c, _, _) ->
if do_cleanup then Curl.cleanup self;
Error (c, Curl.strerror c)
in
loop tries
String.length s0);
Curl.set_writefunction self
(fun s ->
body := (if !body = "" then s else !body ^ s);
String.length s);
let rec loop i =
IO.perform self >>= function
| Curl.CURLE_OK ->
let r = mk_res self (List.rev !resp_headers) !body in
if do_cleanup then Curl.cleanup self;
return r
| Curl.CURLE_AGAIN when i > 1 ->
loop (i-1) (* try again *)
| c ->
if do_cleanup then Curl.cleanup self;
return (Error (c, Curl.strerror c))
in
loop tries
let get ?tries ?client ?config ?headers ~url () : _ result =
http ?tries ?client ?config ?headers ~url ~meth:GET ()
let get ?tries ?client ?config ?headers ~url () : _ result io =
http ?tries ?client ?config ?headers ~url ~meth:GET ()
(* TODO
let post ?verbose ?tries ?client ?auth ?username ?password ~url () : _ result =
call ?verbose ?tries ?client ?auth ?username ?password ~url ~meth:GET ()
*)
(* TODO
let post ?verbose ?tries ?client ?auth ?username ?password ~url () : _ result =
call ?verbose ?tries ?client ?auth ?username ?password ~url ~meth:GET ()
*)
end

85
src/core/Ezcurl_core.mli Normal file
View file

@ -0,0 +1,85 @@
(** {1 Core signatures and implementation} *)
module Config : sig
type t
val default : t
val verbose : bool -> t -> t
val authmethod : Curl.curlAuth list -> t -> t
val max_redirects : int -> t -> t
val follow_location : bool -> t -> t
val username : string -> t -> t
val password : string -> t -> t
end
type t = Curl.t
val make :
?set_opts:(t -> unit) ->
unit -> t
val delete : t -> unit
val with_client :
?set_opts:(t -> unit) ->
(t -> 'a) -> 'a
(** Make a temporary client, call the function with it, then cleanup *)
(* TODO: duphandle is deprecated, how do we iterate on options?
val copy : t -> t
*)
type response_info = {
ri_response_time: float;
ri_redirect_count: int;
}
type response = {
code: int;
headers: (string * string) list;
body: string;
info: response_info;
}
type meth =
| GET
| POST of Curl.curlHTTPPost list
| PUT
val string_of_meth : meth -> string
(** {2 Underlying IO Monad} *)
module type IO = sig
type 'a t
val return : 'a -> 'a t
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
val fail : exn -> 'a t
val perform : Curl.t -> Curl.curlCode t
end
(** {2 Main Signature} *)
module type S = sig
type 'a io
val http :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
meth:meth ->
unit ->
(response, Curl.curlCode * string) result io
val get :
?tries:int ->
?client:t ->
?config:Config.t ->
?headers:(string*string) list ->
url:string ->
unit ->
(response, Curl.curlCode * string) result io
end
module Make(IO : IO) : S with type 'a io = 'a IO.t

6
src/core/dune Normal file
View file

@ -0,0 +1,6 @@
(library
(name ezcurl_core)
(public_name ezcurl.core)
(flags :standard -warn-error -32)
(libraries curl))

7
src/lwt/Ezcurl_lwt.ml Normal file
View file

@ -0,0 +1,7 @@
include Ezcurl_core
include Make(struct
include Lwt
let perform = Curl_lwt.perform
end)

6
src/lwt/dune Normal file
View file

@ -0,0 +1,6 @@
(library
(name ezcurl_lwt)
(public_name ezcurl-lwt)
(flags :standard -warn-error -32)
(libraries curl.lwt ezcurl.core))

16
src/sync/Ezcurl.ml Normal file
View file

@ -0,0 +1,16 @@
(** {1 Synchronous API} *)
include Ezcurl_core
include Ezcurl_core.Make(struct
type 'a t = 'a
let return x = x
let (>>=) x f = f x
let (>|=) x f = f x
let fail e = raise e
let perform c =
try Curl.perform c; Curl.CURLE_OK
with Curl.CurlException (c, _, _) -> c
end)

View file

@ -1,4 +1,4 @@
(library
(name ezcurl)
(flags :standard -warn-error -32)
(libraries curl))
(libraries curl ezcurl.core))