RPC: add HTTP methods and Content-Type headers.
This commit is contained in:
parent
a2758253ea
commit
cc44053229
@ -37,6 +37,7 @@ PKG git
|
|||||||
PKG ipv6-multicast
|
PKG ipv6-multicast
|
||||||
PKG irmin
|
PKG irmin
|
||||||
PKG lwt
|
PKG lwt
|
||||||
|
PKG magic-mime
|
||||||
PKG mtime.os
|
PKG mtime.os
|
||||||
PKG nocrypto
|
PKG nocrypto
|
||||||
PKG ocplib-endian
|
PKG ocplib-endian
|
||||||
|
@ -522,7 +522,8 @@ WEBCLIENT_IMPLS := \
|
|||||||
webclient_main.ml
|
webclient_main.ml
|
||||||
|
|
||||||
CLIENT_PACKAGES := \
|
CLIENT_PACKAGES := \
|
||||||
${NODE_PACKAGES}
|
${NODE_PACKAGES} \
|
||||||
|
magic-mime \
|
||||||
|
|
||||||
EMBEDDED_CLIENT_PROTOCOLS := \
|
EMBEDDED_CLIENT_PROTOCOLS := \
|
||||||
$(patsubst client/embedded/%/, \
|
$(patsubst client/embedded/%/, \
|
||||||
|
@ -308,7 +308,7 @@ let call url cctxt =
|
|||||||
| Error msg ->
|
| Error msg ->
|
||||||
cctxt.error "%s" msg
|
cctxt.error "%s" msg
|
||||||
| Ok json ->
|
| Ok json ->
|
||||||
Client_node_rpcs.get_json cctxt args json >>= fun json ->
|
Client_node_rpcs.get_json cctxt `POST args json >>= fun json ->
|
||||||
cctxt.message
|
cctxt.message
|
||||||
"Output:\n%s\n%!" (Data_encoding_ezjsonm.to_string json)
|
"Output:\n%s\n%!" (Data_encoding_ezjsonm.to_string json)
|
||||||
end
|
end
|
||||||
@ -325,7 +325,7 @@ let call_with_json url json (cctxt: Client_commands.context) =
|
|||||||
err
|
err
|
||||||
| Ok json ->
|
| Ok json ->
|
||||||
let open RPC.Description in
|
let open RPC.Description in
|
||||||
Client_node_rpcs.get_json cctxt args json >>= fun json ->
|
Client_node_rpcs.get_json cctxt `POST args json >>= fun json ->
|
||||||
cctxt.message
|
cctxt.message
|
||||||
"Output:\n%s\n%!" (Data_encoding_ezjsonm.to_string json)
|
"Output:\n%s\n%!" (Data_encoding_ezjsonm.to_string json)
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ let log_response { log } cpt code ans =
|
|||||||
log "requests" "<<<<%d: %s\n%s\n" cpt (Cohttp.Code.string_of_status code) ans
|
log "requests" "<<<<%d: %s\n%s\n" cpt (Cohttp.Code.string_of_status code) ans
|
||||||
|
|
||||||
let cpt = ref 0
|
let cpt = ref 0
|
||||||
let make_request cctxt service json =
|
let make_request cctxt meth service json =
|
||||||
incr cpt ;
|
incr cpt ;
|
||||||
let cpt = !cpt in
|
let cpt = !cpt in
|
||||||
let scheme = if Client_config.tls#get then "https" else "http" in
|
let scheme = if Client_config.tls#get then "https" else "http" in
|
||||||
@ -35,7 +35,7 @@ let make_request cctxt service json =
|
|||||||
catch
|
catch
|
||||||
(fun () ->
|
(fun () ->
|
||||||
let body = Cohttp_lwt_body.of_string reqbody in
|
let body = Cohttp_lwt_body.of_string reqbody in
|
||||||
Cohttp_lwt_unix.Client.post ~body uri >>= fun (code, ansbody) ->
|
Cohttp_lwt_unix.Client.call meth ~body uri >>= fun (code, ansbody) ->
|
||||||
log_request cctxt cpt string_uri reqbody >>= fun () ->
|
log_request cctxt cpt string_uri reqbody >>= fun () ->
|
||||||
return (cpt, Unix.gettimeofday () -. tzero,
|
return (cpt, Unix.gettimeofday () -. tzero,
|
||||||
code.Cohttp.Response.status, ansbody))
|
code.Cohttp.Response.status, ansbody))
|
||||||
@ -45,8 +45,8 @@ let make_request cctxt service json =
|
|||||||
| e -> Printexc.to_string e in
|
| e -> Printexc.to_string e in
|
||||||
cctxt.error "cannot connect to the RPC server (%s)" msg)
|
cctxt.error "cannot connect to the RPC server (%s)" msg)
|
||||||
|
|
||||||
let get_streamed_json cctxt service json =
|
let get_streamed_json cctxt meth service json =
|
||||||
make_request cctxt service json >>= fun (_cpt, time, code, ansbody) ->
|
make_request cctxt meth service json >>= fun (_cpt, time, code, ansbody) ->
|
||||||
let ansbody = Cohttp_lwt_body.to_stream ansbody in
|
let ansbody = Cohttp_lwt_body.to_stream ansbody in
|
||||||
match code, ansbody with
|
match code, ansbody with
|
||||||
| #Cohttp.Code.success_status, ansbody ->
|
| #Cohttp.Code.success_status, ansbody ->
|
||||||
@ -73,8 +73,8 @@ let get_streamed_json cctxt service json =
|
|||||||
cctxt.error "the RPC server returned a non-success status (%s)"
|
cctxt.error "the RPC server returned a non-success status (%s)"
|
||||||
(Cohttp.Code.string_of_status err)
|
(Cohttp.Code.string_of_status err)
|
||||||
|
|
||||||
let get_json cctxt service json =
|
let get_json cctxt meth service json =
|
||||||
make_request cctxt service json >>= fun (cpt, time, code, ansbody) ->
|
make_request cctxt meth service json >>= fun (cpt, time, code, ansbody) ->
|
||||||
Cohttp_lwt_body.to_string ansbody >>= fun ansbody ->
|
Cohttp_lwt_body.to_string ansbody >>= fun ansbody ->
|
||||||
match code, ansbody with
|
match code, ansbody with
|
||||||
| #Cohttp.Code.success_status, ansbody -> begin
|
| #Cohttp.Code.success_status, ansbody -> begin
|
||||||
@ -108,23 +108,23 @@ let parse_answer cctxt service path json =
|
|||||||
| Ok v -> return v
|
| Ok v -> return v
|
||||||
|
|
||||||
let call_service0 cctxt service arg =
|
let call_service0 cctxt service arg =
|
||||||
let path, arg = RPC.forge_request service () arg in
|
let meth, path, arg = RPC.forge_request service () arg in
|
||||||
get_json cctxt path arg >>= fun json ->
|
get_json cctxt meth path arg >>= fun json ->
|
||||||
parse_answer cctxt service path json
|
parse_answer cctxt service path json
|
||||||
|
|
||||||
let call_service1 cctxt service a1 arg =
|
let call_service1 cctxt service a1 arg =
|
||||||
let path, arg = RPC.forge_request service ((), a1) arg in
|
let meth, path, arg = RPC.forge_request service ((), a1) arg in
|
||||||
get_json cctxt path arg >>= fun json ->
|
get_json cctxt meth path arg >>= fun json ->
|
||||||
parse_answer cctxt service path json
|
parse_answer cctxt service path json
|
||||||
|
|
||||||
let call_service2 cctxt service a1 a2 arg =
|
let call_service2 cctxt service a1 a2 arg =
|
||||||
let path, arg = RPC.forge_request service (((), a1), a2) arg in
|
let meth, path, arg = RPC.forge_request service (((), a1), a2) arg in
|
||||||
get_json cctxt path arg >>= fun json ->
|
get_json cctxt meth path arg >>= fun json ->
|
||||||
parse_answer cctxt service path json
|
parse_answer cctxt service path json
|
||||||
|
|
||||||
let call_streamed_service0 cctxt service arg =
|
let call_streamed_service0 cctxt service arg =
|
||||||
let path, arg = RPC.forge_request service () arg in
|
let meth, path, arg = RPC.forge_request service () arg in
|
||||||
get_streamed_json cctxt path arg >|= fun st ->
|
get_streamed_json cctxt meth path arg >|= fun st ->
|
||||||
Lwt_stream.map_s (parse_answer cctxt service path) st
|
Lwt_stream.map_s (parse_answer cctxt service path) st
|
||||||
|
|
||||||
module Services = Node_rpc_services
|
module Services = Node_rpc_services
|
||||||
@ -150,8 +150,8 @@ let complete cctxt ?block prefix =
|
|||||||
| Some block ->
|
| Some block ->
|
||||||
call_service2 cctxt Services.Blocks.complete block prefix ()
|
call_service2 cctxt Services.Blocks.complete block prefix ()
|
||||||
let describe cctxt ?recurse path =
|
let describe cctxt ?recurse path =
|
||||||
let prefix, arg = RPC.forge_request Services.describe () recurse in
|
let meth, prefix, arg = RPC.forge_request Services.describe () recurse in
|
||||||
get_json cctxt (prefix @ path) arg >>=
|
get_json cctxt meth (prefix @ path) arg >>=
|
||||||
parse_answer cctxt Services.describe prefix
|
parse_answer cctxt Services.describe prefix
|
||||||
|
|
||||||
module Blocks = struct
|
module Blocks = struct
|
||||||
|
@ -185,7 +185,7 @@ val describe:
|
|||||||
|
|
||||||
val get_json:
|
val get_json:
|
||||||
Client_commands.context ->
|
Client_commands.context ->
|
||||||
string list -> Data_encoding.json -> Data_encoding.json Lwt.t
|
RPC.meth -> string list -> Data_encoding.json -> Data_encoding.json Lwt.t
|
||||||
|
|
||||||
val call_service0:
|
val call_service0:
|
||||||
Client_commands.context ->
|
Client_commands.context ->
|
||||||
|
@ -26,7 +26,8 @@ let call_service service params input =
|
|||||||
(module Json_repr_browser.Repr)
|
(module Json_repr_browser.Repr)
|
||||||
(module Json_repr.Ezjsonm)
|
(module Json_repr.Ezjsonm)
|
||||||
(Js._JSON##parse body) in
|
(Js._JSON##parse body) in
|
||||||
let path, json = RPC.forge_request service params input in
|
let meth, path, json = RPC.forge_request service params input in
|
||||||
|
let meth_str = RPC.string_of_method meth in
|
||||||
let url = String.concat "/" path in
|
let url = String.concat "/" path in
|
||||||
let xhr = XmlHttpRequest.create () in
|
let xhr = XmlHttpRequest.create () in
|
||||||
let t, u = Lwt.wait () in
|
let t, u = Lwt.wait () in
|
||||||
@ -34,7 +35,7 @@ let call_service service params input =
|
|||||||
if xhr##.readyState = XmlHttpRequest.DONE then
|
if xhr##.readyState = XmlHttpRequest.DONE then
|
||||||
let response = read_json_body xhr##.responseText in
|
let response = read_json_body xhr##.responseText in
|
||||||
Lwt.wakeup u response) ;
|
Lwt.wakeup u response) ;
|
||||||
xhr##_open (Js.string "POST") (Js.string url) Js._true ;
|
xhr##_open (Js.string meth_str) (Js.string url) Js._true ;
|
||||||
xhr##send (Js.Opt.return (write_json_body json)) ;
|
xhr##send (Js.Opt.return (write_json_body json)) ;
|
||||||
t >>= fun json ->
|
t >>= fun json ->
|
||||||
match RPC.read_answer service json with
|
match RPC.read_answer service json with
|
||||||
|
@ -7,18 +7,247 @@
|
|||||||
(* *)
|
(* *)
|
||||||
(**************************************************************************)
|
(**************************************************************************)
|
||||||
|
|
||||||
|
open Lwt.Infix
|
||||||
|
|
||||||
module Arg = Resto.Arg
|
module Arg = Resto.Arg
|
||||||
module Path = Resto.Path
|
module Path = Resto.Path
|
||||||
module Description = Resto.Description
|
|
||||||
let read_answer = Resto.read_answer
|
(* Services *)
|
||||||
let forge_request = Resto.forge_request
|
|
||||||
let service ?description ~input ~output path =
|
(* HTTP methods as defined in Cohttp.Code *)
|
||||||
|
type meth = [
|
||||||
|
| `GET
|
||||||
|
| `POST
|
||||||
|
| `HEAD
|
||||||
|
| `DELETE
|
||||||
|
| `PATCH
|
||||||
|
| `PUT
|
||||||
|
| `OPTIONS
|
||||||
|
| `TRACE
|
||||||
|
| `CONNECT
|
||||||
|
| `Other of string
|
||||||
|
]
|
||||||
|
|
||||||
|
type ('prefix, 'params, 'input, 'output) service =
|
||||||
|
meth * ('prefix, 'params, 'input, 'output) Resto.service
|
||||||
|
|
||||||
|
(* The default HTTP method for services *)
|
||||||
|
let default_meth = `POST
|
||||||
|
|
||||||
|
(* Commonly used REST HTTP methods *)
|
||||||
|
let rest_meths = [`GET; `POST; `HEAD; `DELETE; `PATCH; `PUT; `OPTIONS]
|
||||||
|
|
||||||
|
let string_of_method = function
|
||||||
|
| `GET -> "GET"
|
||||||
|
| `POST -> "POST"
|
||||||
|
| `HEAD -> "HEAD"
|
||||||
|
| `DELETE -> "DELETE"
|
||||||
|
| `PATCH -> "PATCH"
|
||||||
|
| `PUT -> "PUT"
|
||||||
|
| `OPTIONS -> "OPTIONS"
|
||||||
|
| `TRACE -> "TRACE"
|
||||||
|
| `CONNECT -> "CONNECT"
|
||||||
|
| `Other s -> s
|
||||||
|
|
||||||
|
let service ?(meth = default_meth) ?description ~input ~output path =
|
||||||
|
(meth,
|
||||||
Resto.service
|
Resto.service
|
||||||
?description
|
?description
|
||||||
~input:(Data_encoding.Json.convert input)
|
~input:(Data_encoding.Json.convert input)
|
||||||
~output:(Data_encoding.Json.convert output)
|
~output:(Data_encoding.Json.convert output)
|
||||||
path
|
path)
|
||||||
type ('prefix, 'params, 'input, 'output) service =
|
|
||||||
('prefix, 'params, 'input, 'output) Resto.service
|
|
||||||
|
|
||||||
include RestoDirectory
|
(* REST services *)
|
||||||
|
|
||||||
|
(* GET service: no input body *)
|
||||||
|
let get_service ?description ~output path =
|
||||||
|
service ~meth:`GET ?description
|
||||||
|
~input:Data_encoding.empty ~output
|
||||||
|
path
|
||||||
|
|
||||||
|
(* HEAD service: same as GET, but without output body *)
|
||||||
|
let head_service ?description path =
|
||||||
|
service ~meth:`HEAD ?description
|
||||||
|
~input:Data_encoding.empty ~output:Data_encoding.empty
|
||||||
|
path
|
||||||
|
|
||||||
|
let post_service ?description ~input ~output path =
|
||||||
|
service ~meth:`POST ?description ~input ~output path
|
||||||
|
|
||||||
|
let put_service ?description ~input ~output path =
|
||||||
|
service ~meth:`PUT ?description ~input ~output path
|
||||||
|
|
||||||
|
let delete_service ?description ~input ~output path =
|
||||||
|
service ~meth:`DELETE ?description ~input ~output path
|
||||||
|
|
||||||
|
let prefix p (meth, s) = (meth, RestoDirectory.prefix p s)
|
||||||
|
|
||||||
|
let forge_request (meth, service) params input =
|
||||||
|
let path, arg = Resto.forge_request service params input in
|
||||||
|
meth, path, arg
|
||||||
|
|
||||||
|
let read_answer (_meth, service) json =
|
||||||
|
Resto.read_answer service json
|
||||||
|
|
||||||
|
module Description = struct
|
||||||
|
|
||||||
|
include Resto.Description
|
||||||
|
|
||||||
|
let service ?(meth = default_meth) ?description path =
|
||||||
|
(meth, Resto.Description.service ?description path)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
module Answer = RestoDirectory.Answer
|
||||||
|
|
||||||
|
type step =
|
||||||
|
| Static of string
|
||||||
|
| Dynamic of Arg.descr
|
||||||
|
|
||||||
|
type conflict =
|
||||||
|
| CService
|
||||||
|
| CDir
|
||||||
|
| CBuilder
|
||||||
|
| CCustom
|
||||||
|
| CTypes of Arg.descr * Arg.descr
|
||||||
|
| CType of Arg.descr * string list
|
||||||
|
|
||||||
|
exception Conflict of step list * conflict
|
||||||
|
exception Cannot_parse of Arg.descr * string * string list
|
||||||
|
|
||||||
|
(* Dispatch *)
|
||||||
|
|
||||||
|
type 'prefix directory = (meth * 'prefix RestoDirectory.directory) list
|
||||||
|
|
||||||
|
let empty = []
|
||||||
|
|
||||||
|
let map_dirs f dirs =
|
||||||
|
List.map (fun (meth, dir) -> (meth, f ~meth dir)) dirs
|
||||||
|
|
||||||
|
let map f dirs =
|
||||||
|
map_dirs (fun ~meth:_ dir -> RestoDirectory.map f dir) dirs
|
||||||
|
|
||||||
|
let prefix path dirs =
|
||||||
|
map_dirs (fun ~meth:_ dir -> RestoDirectory.prefix path dir) dirs
|
||||||
|
|
||||||
|
let merge dirs1 dirs2 =
|
||||||
|
let compare (meth1, _dir1) (meth2, _dir2) = compare meth1 meth2 in
|
||||||
|
let f (meth1, dir1) (_, dir2) = (meth1, RestoDirectory.merge dir1 dir2) in
|
||||||
|
Utils.merge_list2 ~compare ~f dirs1 dirs2
|
||||||
|
|
||||||
|
(*****************************************************************************
|
||||||
|
* Registration
|
||||||
|
****************************************************************************)
|
||||||
|
|
||||||
|
(** [replace_assoc ~init ~f k l] searches for value corresponding to [k] in an
|
||||||
|
association list, and replaces it with [f value]. If not found, a new pair
|
||||||
|
[(k, f init)] is added to the list. *)
|
||||||
|
(* TODO: move to Utils? *)
|
||||||
|
let replace_assoc ?(finalize = List.rev) ~init ~f key l =
|
||||||
|
let rec aux acc = function
|
||||||
|
| [] -> finalize ((key, f init) :: acc)
|
||||||
|
| (k, v) :: tl when k = key -> finalize ((key, f v) :: acc) @ tl
|
||||||
|
| hd :: tl -> aux (hd :: acc) tl
|
||||||
|
in
|
||||||
|
aux [] l
|
||||||
|
|
||||||
|
(* Register [service] to the directory with corresponding [meth] using [reg] *)
|
||||||
|
let register dirs (meth, service) handler =
|
||||||
|
let init = RestoDirectory.empty in
|
||||||
|
let f dir = RestoDirectory.register dir service handler in
|
||||||
|
replace_assoc ~init ~f meth dirs
|
||||||
|
|
||||||
|
(* Register dynamic directory *)
|
||||||
|
|
||||||
|
(* By default, the [builder] function of dynamic directory is registered for
|
||||||
|
HTTP methods listed in [rest_meths] *)
|
||||||
|
let register_dynamic_directory
|
||||||
|
?(meths = rest_meths) ?descr init_dirs path builder =
|
||||||
|
let builder' ~meth prefix =
|
||||||
|
builder prefix >>= fun dirs ->
|
||||||
|
Lwt.return (List.assoc meth dirs)
|
||||||
|
in
|
||||||
|
let init = RestoDirectory.empty in
|
||||||
|
List.fold_left (fun dirs meth ->
|
||||||
|
let f dir =
|
||||||
|
RestoDirectory.register_dynamic_directory
|
||||||
|
?descr dir path (builder' ~meth)
|
||||||
|
in
|
||||||
|
replace_assoc ~init ~f meth dirs)
|
||||||
|
init_dirs meths
|
||||||
|
|
||||||
|
(* Register custom lookup *)
|
||||||
|
|
||||||
|
type custom_lookup = RestoDirectory.custom_lookup
|
||||||
|
|
||||||
|
let register_custom_lookup ?(meth = default_meth) ?descr dirs s f =
|
||||||
|
let init = RestoDirectory.empty in
|
||||||
|
let f dir = RestoDirectory.register_custom_lookup ?descr dir s f in
|
||||||
|
replace_assoc ~init ~f meth dirs
|
||||||
|
|
||||||
|
(* Register description service *)
|
||||||
|
|
||||||
|
let register_describe_directory_service dirs (meth, service) =
|
||||||
|
let init = RestoDirectory.empty in
|
||||||
|
let f dir = RestoDirectory.register_describe_directory_service dir service in
|
||||||
|
replace_assoc ~init ~f meth dirs
|
||||||
|
|
||||||
|
(*****************************************************************************
|
||||||
|
* Lookup
|
||||||
|
****************************************************************************)
|
||||||
|
|
||||||
|
let lookup dirs ?(meth = default_meth) args path =
|
||||||
|
let dir = List.assoc meth dirs in
|
||||||
|
RestoDirectory.lookup dir args path
|
||||||
|
|
||||||
|
(*****************************************************************************
|
||||||
|
* Currying
|
||||||
|
****************************************************************************)
|
||||||
|
|
||||||
|
(* Service registration *)
|
||||||
|
|
||||||
|
let register0 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry Z f)
|
||||||
|
|
||||||
|
let register1 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry (S Z) f)
|
||||||
|
|
||||||
|
let register2 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry (S (S Z)) f)
|
||||||
|
|
||||||
|
let register3 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry (S (S (S Z))) f)
|
||||||
|
|
||||||
|
let register4 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry (S (S (S (S Z)))) f)
|
||||||
|
|
||||||
|
let register5 root s f =
|
||||||
|
register root s RestoDirectory.Internal.(curry (S (S (S (S (S Z))))) f)
|
||||||
|
|
||||||
|
(* Dynamic directory registration *)
|
||||||
|
|
||||||
|
let register_dynamic_directory1 ?descr root s f =
|
||||||
|
register_dynamic_directory
|
||||||
|
?descr root s RestoDirectory.Internal.(curry (S Z) f)
|
||||||
|
|
||||||
|
let register_dynamic_directory2 ?descr root s f =
|
||||||
|
register_dynamic_directory
|
||||||
|
?descr root s RestoDirectory.Internal.(curry (S (S Z)) f)
|
||||||
|
|
||||||
|
let register_dynamic_directory3 ?descr root s f =
|
||||||
|
register_dynamic_directory
|
||||||
|
?descr root s RestoDirectory.Internal.(curry (S (S (S Z))) f)
|
||||||
|
|
||||||
|
(* Custom lookup registration *)
|
||||||
|
|
||||||
|
let register_custom_lookup1 ?meth ?descr root s f =
|
||||||
|
register_custom_lookup ?meth ?descr root s
|
||||||
|
RestoDirectory.Internal.(curry (S Z) f)
|
||||||
|
|
||||||
|
let register_custom_lookup2 ?meth ?descr root s f =
|
||||||
|
register_custom_lookup ?meth ?descr root s
|
||||||
|
RestoDirectory.Internal.(curry (S (S Z)) f)
|
||||||
|
|
||||||
|
let register_custom_lookup3 ?meth ?descr root s f =
|
||||||
|
register_custom_lookup ?meth ?descr root s
|
||||||
|
RestoDirectory.Internal.(curry (S (S (S Z))) f)
|
||||||
|
@ -59,10 +59,60 @@ module Path : sig
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(** HTTP methods as defined in Cohttp.Code *)
|
||||||
|
|
||||||
|
type meth = [
|
||||||
|
| `GET
|
||||||
|
| `POST
|
||||||
|
| `HEAD
|
||||||
|
| `DELETE
|
||||||
|
| `PATCH
|
||||||
|
| `PUT
|
||||||
|
| `OPTIONS
|
||||||
|
| `TRACE
|
||||||
|
| `CONNECT
|
||||||
|
| `Other of string
|
||||||
|
]
|
||||||
|
|
||||||
|
val string_of_method : meth -> string
|
||||||
|
|
||||||
(** Services. *)
|
(** Services. *)
|
||||||
type ('prefix, 'params, 'input, 'output) service
|
type ('prefix, 'params, 'input, 'output) service
|
||||||
|
|
||||||
val service:
|
val service:
|
||||||
|
?meth: meth ->
|
||||||
|
?description: string ->
|
||||||
|
input: 'input Data_encoding.t ->
|
||||||
|
output: 'output Data_encoding.t ->
|
||||||
|
('prefix, 'params) Path.path ->
|
||||||
|
('prefix, 'params, 'input, 'output) service
|
||||||
|
|
||||||
|
val get_service:
|
||||||
|
?description: string ->
|
||||||
|
output: 'output Data_encoding.t ->
|
||||||
|
('prefix, 'params) Path.path ->
|
||||||
|
('prefix, 'params, unit, 'output) service
|
||||||
|
|
||||||
|
val head_service:
|
||||||
|
?description: string ->
|
||||||
|
('prefix, 'params) Path.path ->
|
||||||
|
('prefix, 'params, unit, unit) service
|
||||||
|
|
||||||
|
val post_service:
|
||||||
|
?description: string ->
|
||||||
|
input: 'input Data_encoding.t ->
|
||||||
|
output: 'output Data_encoding.t ->
|
||||||
|
('prefix, 'params) Path.path ->
|
||||||
|
('prefix, 'params, 'input, 'output) service
|
||||||
|
|
||||||
|
val put_service:
|
||||||
|
?description: string ->
|
||||||
|
input: 'input Data_encoding.t ->
|
||||||
|
output: 'output Data_encoding.t ->
|
||||||
|
('prefix, 'params) Path.path ->
|
||||||
|
('prefix, 'params, 'input, 'output) service
|
||||||
|
|
||||||
|
val delete_service:
|
||||||
?description: string ->
|
?description: string ->
|
||||||
input: 'input Data_encoding.t ->
|
input: 'input Data_encoding.t ->
|
||||||
output: 'output Data_encoding.t ->
|
output: 'output Data_encoding.t ->
|
||||||
@ -76,7 +126,7 @@ val prefix:
|
|||||||
|
|
||||||
val forge_request:
|
val forge_request:
|
||||||
(unit, 'params, 'input, 'output) service ->
|
(unit, 'params, 'input, 'output) service ->
|
||||||
'params -> 'input -> string list * Data_encoding.json
|
'params -> 'input -> meth * string list * Data_encoding.json
|
||||||
|
|
||||||
val read_answer:
|
val read_answer:
|
||||||
(unit, 'params, 'input, 'output) service ->
|
(unit, 'params, 'input, 'output) service ->
|
||||||
@ -105,6 +155,7 @@ module Description : sig
|
|||||||
| Arg of Arg.descr * directory_descr
|
| Arg of Arg.descr * directory_descr
|
||||||
|
|
||||||
val service:
|
val service:
|
||||||
|
?meth: meth ->
|
||||||
?description:string ->
|
?description:string ->
|
||||||
('prefix, 'params) Path.path ->
|
('prefix, 'params) Path.path ->
|
||||||
('prefix, 'params, bool option, directory_descr) service
|
('prefix, 'params, bool option, directory_descr) service
|
||||||
@ -141,7 +192,7 @@ end
|
|||||||
(** Dispatch tree *)
|
(** Dispatch tree *)
|
||||||
type 'prefix directory
|
type 'prefix directory
|
||||||
|
|
||||||
(** Empty tree *)
|
(** Empty list of dispatch trees *)
|
||||||
val empty: 'prefix directory
|
val empty: 'prefix directory
|
||||||
|
|
||||||
val map: ('a -> 'b) -> 'b directory -> 'a directory
|
val map: ('a -> 'b) -> 'b directory -> 'a directory
|
||||||
@ -206,9 +257,11 @@ val register5:
|
|||||||
|
|
||||||
(** Registring dynamic subtree. *)
|
(** Registring dynamic subtree. *)
|
||||||
val register_dynamic_directory:
|
val register_dynamic_directory:
|
||||||
|
?meths:meth list ->
|
||||||
?descr:string ->
|
?descr:string ->
|
||||||
'prefix directory ->
|
'prefix directory ->
|
||||||
('prefix, 'a) Path.path -> ('a -> 'a directory Lwt.t) ->
|
('prefix, 'a) Path.path ->
|
||||||
|
('a -> 'a directory Lwt.t) ->
|
||||||
'prefix directory
|
'prefix directory
|
||||||
|
|
||||||
(** Registring dynamic subtree. (Curryfied variant) *)
|
(** Registring dynamic subtree. (Curryfied variant) *)
|
||||||
@ -234,13 +287,14 @@ val register_dynamic_directory3:
|
|||||||
'prefix directory
|
'prefix directory
|
||||||
|
|
||||||
(** Registring custom directory lookup. *)
|
(** Registring custom directory lookup. *)
|
||||||
type custom_lookup =
|
type custom_lookup = RestoDirectory.custom_lookup
|
||||||
| CustomService of Description.service_descr *
|
(* | CustomService of Description.service_descr * *)
|
||||||
( Data_encoding.json option ->
|
(* ( Data_encoding.json option -> *)
|
||||||
Data_encoding.json Answer.answer Lwt.t )
|
(* Data_encoding.json Answer.answer Lwt.t ) *)
|
||||||
| CustomDirectory of Description.directory_descr
|
(* | CustomDirectory of Description.directory_descr *)
|
||||||
|
|
||||||
val register_custom_lookup:
|
val register_custom_lookup:
|
||||||
|
?meth:meth ->
|
||||||
?descr:string ->
|
?descr:string ->
|
||||||
'prefix directory ->
|
'prefix directory ->
|
||||||
('prefix, 'params) Path.path ->
|
('prefix, 'params) Path.path ->
|
||||||
@ -248,6 +302,7 @@ val register_custom_lookup:
|
|||||||
'prefix directory
|
'prefix directory
|
||||||
|
|
||||||
val register_custom_lookup1:
|
val register_custom_lookup1:
|
||||||
|
?meth:meth ->
|
||||||
?descr:string ->
|
?descr:string ->
|
||||||
'prefix directory ->
|
'prefix directory ->
|
||||||
('prefix, unit * 'a) Path.path ->
|
('prefix, unit * 'a) Path.path ->
|
||||||
@ -255,6 +310,7 @@ val register_custom_lookup1:
|
|||||||
'prefix directory
|
'prefix directory
|
||||||
|
|
||||||
val register_custom_lookup2:
|
val register_custom_lookup2:
|
||||||
|
?meth:meth ->
|
||||||
?descr:string ->
|
?descr:string ->
|
||||||
'prefix directory ->
|
'prefix directory ->
|
||||||
('prefix, (unit * 'a) * 'b) Path.path ->
|
('prefix, (unit * 'a) * 'b) Path.path ->
|
||||||
@ -262,6 +318,7 @@ val register_custom_lookup2:
|
|||||||
'prefix directory
|
'prefix directory
|
||||||
|
|
||||||
val register_custom_lookup3:
|
val register_custom_lookup3:
|
||||||
|
?meth:meth ->
|
||||||
?descr:string ->
|
?descr:string ->
|
||||||
'prefix directory ->
|
'prefix directory ->
|
||||||
('prefix, ((unit * 'a) * 'b) * 'c) Path.path ->
|
('prefix, ((unit * 'a) * 'b) * 'c) Path.path ->
|
||||||
@ -278,5 +335,5 @@ exception Cannot_parse of Arg.descr * string * string list
|
|||||||
|
|
||||||
(** Resolve a service. *)
|
(** Resolve a service. *)
|
||||||
val lookup:
|
val lookup:
|
||||||
'prefix directory -> 'prefix -> string list ->
|
'prefix directory -> ?meth:meth -> 'prefix -> string list ->
|
||||||
(Data_encoding.json option -> Data_encoding.json Answer.answer Lwt.t) Lwt.t
|
(Data_encoding.json option -> Data_encoding.json Answer.answer Lwt.t) Lwt.t
|
||||||
|
@ -82,7 +82,12 @@ let launch ?pre_hook ?post_hook ?(host="::") mode root cors_allowed_origins cors
|
|||||||
Lwt.catch
|
Lwt.catch
|
||||||
(fun () ->
|
(fun () ->
|
||||||
hook (Uri.path (Cohttp.Request.uri req))
|
hook (Uri.path (Cohttp.Request.uri req))
|
||||||
>>= fun { Answer.code ; body } ->
|
>>= fun (content_type, { Answer.code ; body }) ->
|
||||||
|
let headers =
|
||||||
|
match content_type with
|
||||||
|
| None -> Cohttp.Header.init ()
|
||||||
|
| Some ct -> Cohttp.Header.init_with "Content-Type" ct
|
||||||
|
in
|
||||||
if code = 404 && not answer_404 then
|
if code = 404 && not answer_404 then
|
||||||
Lwt.return None
|
Lwt.return None
|
||||||
else
|
else
|
||||||
@ -96,7 +101,7 @@ let launch ?pre_hook ?post_hook ?(host="::") mode root cors_allowed_origins cors
|
|||||||
create_stream io con (fun s -> s) s in
|
create_stream io con (fun s -> s) s in
|
||||||
Cohttp_lwt_body.of_stream stream in
|
Cohttp_lwt_body.of_stream stream in
|
||||||
Lwt.return_some
|
Lwt.return_some
|
||||||
(Response.make ~flush:true ~status:(`Code code) (),
|
(Response.make ~flush:true ~status:(`Code code) ~headers (),
|
||||||
body))
|
body))
|
||||||
(function
|
(function
|
||||||
| Not_found -> Lwt.return None
|
| Not_found -> Lwt.return None
|
||||||
@ -114,16 +119,20 @@ let launch ?pre_hook ?post_hook ?(host="::") mode root cors_allowed_origins cors
|
|||||||
| Some res ->
|
| Some res ->
|
||||||
Lwt.return res
|
Lwt.return res
|
||||||
| None ->
|
| None ->
|
||||||
lookup root () path >>= fun handler ->
|
lookup root ~meth:req.meth () path >>= fun handler ->
|
||||||
begin
|
begin
|
||||||
match req.meth with
|
match req.meth with
|
||||||
| `POST -> begin
|
| `POST
|
||||||
|
| `PUT
|
||||||
|
| `PATCH
|
||||||
|
| `DELETE -> begin
|
||||||
Cohttp_lwt_body.to_string body >>= fun body ->
|
Cohttp_lwt_body.to_string body >>= fun body ->
|
||||||
match Data_encoding_ezjsonm.from_string body with
|
match Data_encoding_ezjsonm.from_string body with
|
||||||
| Error msg -> Lwt.fail (Cannot_parse_body msg)
|
| Error msg -> Lwt.fail (Cannot_parse_body msg)
|
||||||
| Ok body -> Lwt.return (Some body)
|
| Ok body -> Lwt.return (Some body)
|
||||||
end
|
end
|
||||||
| `GET -> Lwt.return None
|
| `GET
|
||||||
|
| `HEAD -> Lwt.return None
|
||||||
| `OPTIONS -> Lwt.fail Options_preflight
|
| `OPTIONS -> Lwt.fail Options_preflight
|
||||||
| _ -> Lwt.fail Invalid_method
|
| _ -> Lwt.fail Invalid_method
|
||||||
end >>= fun body ->
|
end >>= fun body ->
|
||||||
@ -142,7 +151,12 @@ let launch ?pre_hook ?post_hook ?(host="::") mode root cors_allowed_origins cors
|
|||||||
(if Cohttp.Code.is_error code
|
(if Cohttp.Code.is_error code
|
||||||
then "failed"
|
then "failed"
|
||||||
else "success") >>= fun () ->
|
else "success") >>= fun () ->
|
||||||
let headers = make_cors_headers cors_allowed_headers cors_allowed_origins origin_header in
|
let headers =
|
||||||
|
Cohttp.Header.init_with "Content-Type" "application/json" in
|
||||||
|
let headers =
|
||||||
|
make_cors_headers ~headers
|
||||||
|
cors_allowed_headers cors_allowed_origins origin_header
|
||||||
|
in
|
||||||
Lwt.return (Response.make
|
Lwt.return (Response.make
|
||||||
~flush:true ~status:(`Code code) ~headers (), body))
|
~flush:true ~status:(`Code code) ~headers (), body))
|
||||||
(function
|
(function
|
||||||
|
@ -34,10 +34,12 @@ type server
|
|||||||
before resolving each request, to delegate the answering to
|
before resolving each request, to delegate the answering to
|
||||||
another resolution mechanism. Its result is ignored if the return
|
another resolution mechanism. Its result is ignored if the return
|
||||||
code is [404]. The optional [post_hook] is called if both the
|
code is [404]. The optional [post_hook] is called if both the
|
||||||
[pre_hook] and the serviced answered with a [404] code. *)
|
[pre_hook] and the serviced answered with a [404] code. [pre_hook] and
|
||||||
|
[post_hook] return a pair made of an optional Content-Type value and the
|
||||||
|
answer. *)
|
||||||
val launch :
|
val launch :
|
||||||
?pre_hook: (string -> string RPC.Answer.answer Lwt.t) ->
|
?pre_hook: (string -> (string option * string RPC.Answer.answer) Lwt.t) ->
|
||||||
?post_hook: (string -> string RPC.Answer.answer Lwt.t) ->
|
?post_hook: (string -> (string option * string RPC.Answer.answer) Lwt.t) ->
|
||||||
?host:string ->
|
?host:string ->
|
||||||
Conduit_lwt_unix.server ->
|
Conduit_lwt_unix.server ->
|
||||||
unit RPC.directory ->
|
unit RPC.directory ->
|
||||||
|
@ -19,12 +19,13 @@ module Error = struct
|
|||||||
RPC.Path.(root / "errors")
|
RPC.Path.(root / "errors")
|
||||||
|
|
||||||
let encoding =
|
let encoding =
|
||||||
let path, _ = RPC.forge_request service () () in
|
let meth, path, _ = RPC.forge_request service () () in
|
||||||
describe
|
describe
|
||||||
~description:
|
~description:
|
||||||
(Printf.sprintf
|
(Printf.sprintf
|
||||||
"The full list of error is available with \
|
"The full list of error is available with \
|
||||||
the global RPC `/%s`" (String.concat "/" path))
|
the global RPC `%s /%s`"
|
||||||
|
(RPC.string_of_method meth) (String.concat "/" path))
|
||||||
(conv
|
(conv
|
||||||
~schema:Json_schema.any
|
~schema:Json_schema.any
|
||||||
(fun exn -> `A (List.map json_of_error exn))
|
(fun exn -> `A (List.map json_of_error exn))
|
||||||
|
@ -57,7 +57,22 @@ end
|
|||||||
(** Services. *)
|
(** Services. *)
|
||||||
type ('prefix, 'params, 'input, 'output) service
|
type ('prefix, 'params, 'input, 'output) service
|
||||||
|
|
||||||
|
(** HTTP methods as defined in Cohttp.Code *)
|
||||||
|
type meth = [
|
||||||
|
| `GET
|
||||||
|
| `POST
|
||||||
|
| `HEAD
|
||||||
|
| `DELETE
|
||||||
|
| `PATCH
|
||||||
|
| `PUT
|
||||||
|
| `OPTIONS
|
||||||
|
| `TRACE
|
||||||
|
| `CONNECT
|
||||||
|
| `Other of string
|
||||||
|
]
|
||||||
|
|
||||||
val service:
|
val service:
|
||||||
|
?meth: meth ->
|
||||||
?description: string ->
|
?description: string ->
|
||||||
input: 'input Data_encoding.t ->
|
input: 'input Data_encoding.t ->
|
||||||
output: 'output Data_encoding.t ->
|
output: 'output Data_encoding.t ->
|
||||||
|
@ -37,6 +37,7 @@ depends: [
|
|||||||
"tyxml"
|
"tyxml"
|
||||||
"js_of_ocaml"
|
"js_of_ocaml"
|
||||||
"sodium" {>= "0.3.0" }
|
"sodium" {>= "0.3.0" }
|
||||||
|
"magic-mime"
|
||||||
"kaputt" # { test }
|
"kaputt" # { test }
|
||||||
"bisect_ppx" # { test }
|
"bisect_ppx" # { test }
|
||||||
]
|
]
|
||||||
|
@ -125,14 +125,14 @@ let root =
|
|||||||
Lwt.return directory) in
|
Lwt.return directory) in
|
||||||
root
|
root
|
||||||
|
|
||||||
let find_static_file path =
|
let find_static_file path_str =
|
||||||
let path = OCamlRes.Path.of_string path in
|
let path = OCamlRes.Path.of_string path_str in
|
||||||
let index path = match path with
|
let index path = match path with
|
||||||
| ([], None) -> ([], Some ("index", Some "html"))
|
| [], None -> "text/html", ([], Some ("index", Some "html"))
|
||||||
| oth -> oth in
|
| oth -> Magic_mime.lookup path_str, oth in
|
||||||
match path with
|
match path with
|
||||||
| ("block" :: block :: path, file) ->
|
| ("block" :: block :: path, file) ->
|
||||||
let path = index (path, file) in
|
let content_type, path = index (path, file) in
|
||||||
(match Node_rpc_services.Blocks.parse_block block with
|
(match Node_rpc_services.Blocks.parse_block block with
|
||||||
| Ok block ->
|
| Ok block ->
|
||||||
block_protocol Client_commands.ignore_context block >>= fun version ->
|
block_protocol Client_commands.ignore_context block >>= fun version ->
|
||||||
@ -140,29 +140,33 @@ let find_static_file path =
|
|||||||
(try
|
(try
|
||||||
let root =
|
let root =
|
||||||
Webclient_version.find_contextual_static_files version in
|
Webclient_version.find_contextual_static_files version in
|
||||||
Some (OCamlRes.Res.find path root)
|
Some (content_type, OCamlRes.Res.find path root)
|
||||||
with Not_found -> None)
|
with Not_found -> None)
|
||||||
| Error _ -> Lwt.return None)
|
| Error _ -> Lwt.return None)
|
||||||
| _ ->
|
| _ ->
|
||||||
Lwt.return
|
Lwt.return
|
||||||
(try
|
(try
|
||||||
Some (OCamlRes.Res.find (index path) Webclient_static.root)
|
let content_type, path = index path in
|
||||||
|
Some (content_type, OCamlRes.Res.find path Webclient_static.root)
|
||||||
with Not_found -> None)
|
with Not_found -> None)
|
||||||
|
|
||||||
let http_proxy mode =
|
let http_proxy mode =
|
||||||
let pre_hook path =
|
let pre_hook path =
|
||||||
find_static_file path >>= function
|
find_static_file path >>= function
|
||||||
| Some body ->
|
| Some (content_type, body) ->
|
||||||
Lwt.return { RPC.Answer.code = 200 ; body = RPC.Answer.Single body }
|
Lwt.return
|
||||||
|
(Some content_type,
|
||||||
|
{ RPC.Answer.code = 200 ; body = RPC.Answer.Single body })
|
||||||
| None ->
|
| None ->
|
||||||
Lwt.return { RPC.Answer.code = 404 ; body = RPC.Answer.Empty } in
|
Lwt.return
|
||||||
|
(None, { RPC.Answer.code = 404 ; body = RPC.Answer.Empty }) in
|
||||||
let post_hook _ =
|
let post_hook _ =
|
||||||
(find_static_file "not_found.html" >>= function
|
(find_static_file "not_found.html" >>= function
|
||||||
| Some body ->
|
| Some (content_type, body) ->
|
||||||
Lwt.return (RPC.Answer.Single body)
|
Lwt.return (Some content_type, RPC.Answer.Single body)
|
||||||
| None ->
|
| None ->
|
||||||
Lwt.return (RPC.Answer.Empty)) >>= fun body ->
|
Lwt.return (None, RPC.Answer.Empty)) >>= fun (content_type, body) ->
|
||||||
Lwt.return { RPC.Answer.code = 404 ; body } in
|
Lwt.return (content_type, { RPC.Answer.code = 404 ; body }) in
|
||||||
RPC_server.launch ~pre_hook ~post_hook mode root [] []
|
RPC_server.launch ~pre_hook ~post_hook mode root [] []
|
||||||
|
|
||||||
let web_port = Client_config.in_both_groups @@
|
let web_port = Client_config.in_both_groups @@
|
||||||
|
Loading…
Reference in New Issue
Block a user