RPC: add HTTP methods and Content-Type headers.

This commit is contained in:
Guillem Rieu 2016-12-11 16:34:58 +01:00 committed by Benjamin Canou
parent a2758253ea
commit cc44053229
14 changed files with 390 additions and 64 deletions

View File

@ -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

View File

@ -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/%/, \

View File

@ -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)

View File

@ -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

View File

@ -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 ->

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 ->

View File

@ -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))

View File

@ -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 ->

View File

@ -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 }
] ]

View File

@ -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 @@