123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)moduletypeS=sigmoduletypeLOGGER=sigtyperequestvallog_empty_request:Uri.t->requestLwt.tvallog_request:?media:Media_type.t->'aData_encoding.t->Uri.t->string->requestLwt.tvallog_response:request->?media:Media_type.t->'aData_encoding.t->Cohttp.Code.status_code->stringLwt.tLazy.t->unitLwt.tendtypelogger=(moduleLOGGER)valnull_logger:loggervaltimings_logger:gettimeofday:(unit->float)->Format.formatter->loggervalfull_logger:Format.formatter->loggertypeconfig={media_type:Media_type.tlist;endpoint:Uri.t;logger:logger;}valconfig_encoding:configData_encoding.tvaldefault_config:configclasshttp_ctxt:config->Media_type.tlist->RPC_context.json(**/**)valcall_service:Media_type.tlist->?logger:logger->?headers:(string*string)list->base:Uri.t->([<Resto.meth],unit,'p,'q,'i,'o)RPC_service.t->'p->'q->'i->'otzresultLwt.tvalcall_streamed_service:Media_type.tlist->?logger:logger->?headers:(string*string)list->base:Uri.t->([<Resto.meth],unit,'p,'q,'i,'o)RPC_service.t->on_chunk:('o->unit)->on_close:(unit->unit)->'p->'q->'i->(unit->unit)tzresultLwt.tvalgeneric_json_call:?headers:(string*string)list->?body:Data_encoding.json->[<RPC_service.meth]->Uri.t->(Data_encoding.json,Data_encoding.jsonoption)RPC_context.rest_resultLwt.ttypecontent_type=Media_type.Content_type.ttypecontent=Cohttp_lwt.Body.t*content_typeoption*Media_type.toptionvalgeneric_media_type_call:?headers:(string*string)list->accept:Media_type.tlist->?body:Data_encoding.json->[<RPC_service.meth]->Uri.t->RPC_context.generic_call_resulttzresultLwt.tvalgeneric_call:?headers:(string*string)list->?accept:Media_type.tlist->?body:Cohttp_lwt.Body.t->?media:Media_type.t->[<RPC_service.meth]->Uri.t->(content,content)RPC_context.rest_resultLwt.tendmoduleMake(Client:Resto_cohttp_client.Client.CALL)=structmoduleClient=Resto_cohttp_client.Client.Make(RPC_encoding)(Client)moduletypeLOGGER=Client.LOGGERtypelogger=(moduleLOGGER)letnull_logger=Client.null_loggerlettimings_logger=Client.timings_loggerletfull_logger=Client.full_loggertypecontent_type=Media_type.Content_type.ttypecontent=Cohttp_lwt.Body.t*content_typeoption*Media_type.toptionletrequest_failedmethurierror=letmeth=(meth:[<RPC_service.meth]:>RPC_service.meth)infail(RPC_client_errors.Request_failed{meth;uri;error})letgeneric_call?headers?accept?body?mediamethuri:(content,content)RPC_context.rest_resultLwt.t=Client.generic_callmeth?headers?accept?body?mediauri>>=function|`Ok(Somev)->return(`Okv)|`OkNone->request_failedmethuriEmpty_answer|(`Conflict_|`Error_|`Forbidden_|`Unauthorized_|`Not_found_|`Gone_)asv->returnv|`Unexpected_status_code(code,(content,_,media_type))->letmedia_type=Option.mapMedia_type.namemedia_typeinCohttp_lwt.Body.to_stringcontent>>=funcontent->request_failedmethuri(Unexpected_status_code{code;content;media_type})|`Method_not_allowedallowed->letallowed=List.filter_mapRPC_service.meth_of_stringallowedinrequest_failedmethuri(Method_not_allowedallowed)|`Unsupported_media_type->letmedia=Option.mapMedia_type.namemediainrequest_failedmethuri(Unsupported_media_typemedia)|`Not_acceptableacceptable->letproposed=Option.foldaccept~none:""~some:Media_type.accept_headerinrequest_failedmethuri(Not_acceptable{proposed;acceptable})|`Bad_requestmsg->request_failedmethuri(Bad_requestmsg)|`Connection_failedmsg->request_failedmethuri(Connection_failedmsg)|`OCaml_exceptionmsg->request_failedmethuri(OCaml_exceptionmsg)|`Unauthorized_hosthost->request_failedmethuri(Unauthorized_hosthost)lethandle_error(body,content_type,_)f=Cohttp_lwt.Body.is_emptybody>>=funempty->ifemptythenreturn(content_type,fNone)elseCohttp_lwt.Body.to_stringbody>>=funbody->return(content_type,f(Somebody))letjsonify_othermethuricontent_typeerror:(Data_encoding.json,Data_encoding.jsonoption)RPC_context.rest_resultLwt.t=letjsonify_bodystring_body=matchcontent_typewith|Some("application","json")|None->(matchData_encoding.Json.from_stringstring_bodywith|Okjson_body->returnjson_body|Errormsg->request_failedmethuri(Unexpected_content{content=string_body;media_type=Media_type.(namejson);error=msg;}))|Somecontent_type->request_failedmethuri(Unexpected_content_type{received=Format.asprintf"%a"Media_type.Content_type.ppcontent_type;acceptable=[Media_type.(namejson)];body=string_body;})inletjsonify_body_opt=function|None->return_none|Somestring_body->jsonify_bodystring_body>>=?funjson_body->return_somejson_bodyinmatcherrorwith|`Conflicts->jsonify_body_opts>|=?funs->`Conflicts|`Errors->jsonify_body_opts>|=?funs->`Errors|`Forbiddens->jsonify_body_opts>|=?funs->`Forbiddens|`Not_founds->jsonify_body_opts>|=?funs->`Not_founds|`Gones->jsonify_body_opts>|=?funs->`Gones|`Unauthorizeds->jsonify_body_opts>|=?funs->`Unauthorizeds|`Oks->jsonify_bodys>|=?funs->`Oksletpost_process_error_responsesresponsemethuriaccept=matchresponsewith|`Conflictbody->handle_errorbody(funv->`Conflictv)|`Errorbody->handle_errorbody(funv->`Errorv)|`Forbiddenbody->handle_errorbody(funv->`Forbiddenv)|`Not_foundbody->(* The client's proxy mode matches on the `Not_found returned here,
to detect that a local RPC is unavailable at generic_json_call,
and hence that delegation to the endpoint must be done. *)handle_errorbody(funv->`Not_foundv)|`Gonebody->handle_errorbody(funv->`Gonev)|`Unauthorizedbody->handle_errorbody(funv->`Unauthorizedv)|`Ok(body,(Some_ascontent_type),_)->Cohttp_lwt.Body.to_stringbody>>=funbody->request_failedmethuri(Unexpected_content_type{received=Format.asprintf"%a"(Format.pp_print_optionMedia_type.Content_type.pp)content_type;acceptable=List.mapMedia_type.nameaccept;body;})|`Ok(body,None,_)->Cohttp_lwt.Body.to_stringbody>>=funbody->return(None,`Okbody)letpost_process_json_response~bodymethuri=matchData_encoding.Json.from_stringbodywith|Okjson->returnjson|Errormsg->request_failedmethuri(Unexpected_content{content=body;media_type=Media_type.(namejson);error=msg})letpost_process_bson_response~bodymethuri=matchJson_repr_bson.bytes_to_bson~laziness:false~copy:false(Bytes.of_stringbody)with|exceptionJson_repr_bson.Bson_decoding_error(msg,_,pos)->leterror=Format.asprintf"(at offset: %d) %s"posmsginrequest_failedmethuri(Unexpected_content{content=body;media_type=Media_type.(namebson);error})|bson->return(Json_repr.convert(moduleJson_repr_bson.Repr)(moduleJson_repr.Ezjsonm)bson)letgeneric_json_call?headers?bodymethuri=letbody=Option.map(funb->Cohttp_lwt.Body.of_string(Data_encoding.Json.to_stringb))bodyinletmedia=Media_type.jsoningeneric_call?headers?bodymeth~accept:Media_type.[json;bson]~mediauri>>=?funresponse->matchresponsewith|`Ok(body,Some("application","json"),_)->Cohttp_lwt.Body.to_stringbody>>=funbody->post_process_json_response~bodymethuri>>=?funbody->return(`Okbody)|`Ok(body,Some("application","bson"),_)->Cohttp_lwt.Body.to_stringbody>>=funbody->post_process_bson_response~bodymethuri>>=?funbody->return(`Okbody)|_->post_process_error_responsesresponsemethuriMedia_type.[json;bson]>>=?fun(content_type,other)->jsonify_othermethuricontent_typeother(* This function checks that the content type of the answer belongs to accepted ones in [accept]. If not, it is processed as an error. If the answer lacks content-type, the response is decoded as JSON if possible. *)letgeneric_media_type_call?headers~accept?bodymethuri:RPC_context.generic_call_resulttzresultLwt.t=letbody=Option.map(funb->Cohttp_lwt.Body.of_string(Data_encoding.Json.to_stringb))bodyinletmedia=Media_type.jsoningeneric_callmeth?headers~accept?body~mediauri>>=?funresponse->matchresponsewith|`Ok(body,Some("application","octet-stream"),_)whenList.mem~equal:(==)Media_type.octet_streamaccept->(Cohttp_lwt.Body.to_stringbody>>=funbody->(* The binary RPCs are prefixed with a size header, we remove it here. *)matchData_encoding.Binary.of_string_optData_encoding.stringbodywith|Someresponse->return(`Binary(`Okresponse))|None->return(`Binary(`Error(Somebody))))|`Ok(body,Some("application","json"),_)whenList.mem~equal:(==)Media_type.jsonaccept->Cohttp_lwt.Body.to_stringbody>>=funbody->post_process_json_response~bodymethuri>>=?funbody->return(`Json(`Okbody))|`Ok(body,Some("application","bson"),_)whenList.mem~equal:(==)Media_type.bsonaccept->Cohttp_lwt.Body.to_stringbody>>=funbody->post_process_bson_response~bodymethuri>>=?funbody->return(`Json(`Okbody))|_->(post_process_error_responsesresponsemethuriaccept>>=?fun(content_type,other_resp)->(* We attempt to decode in JSON. It might
work. *)jsonify_othermethuricontent_typeother_resp>>=function|Okjsonified->return(`Jsonjsonified)|Error_->return(`Other(content_type,other_resp)))lethandleaccept(meth,uri,ans)=matchanswith|`Ok(Somev)->returnv|`OkNone->request_failedmethuriEmpty_answer|`GoneNone->fail(RPC_context.Gone{meth;uri})|`Not_foundNone->(* The client's proxy mode matches on the error raised here,
to detect that a local RPC is unavailable at call_service and
call_streamed_service, and hence that delegation
to the endpoint must be done. *)fail(RPC_context.Not_found{meth;uri})|`Conflict(Someerr)|`Error(Someerr)|`Forbidden(Someerr)|`Unauthorized(Someerr)|`Gone(Someerr)|`Not_found(Someerr)->Lwt.return_errorerr|`UnauthorizedNone->request_failedmethuriUnauthorized_uri|`ForbiddenNone->request_failedmethuriForbidden|`ConflictNone|`ErrorNone->fail(RPC_context.Generic_error{meth;uri})|`Unexpected_status_code(code,(content,_,media_type))->letmedia_type=Option.mapMedia_type.namemedia_typeinCohttp_lwt.Body.to_stringcontent>>=funcontent->request_failedmethuri(Unexpected_status_code{code;content;media_type})|`Method_not_allowedallowed->letallowed=List.filter_mapRPC_service.meth_of_stringallowedinrequest_failedmethuri(Method_not_allowedallowed)|`Unsupported_media_type->letname=matchMedia_type.first_complete_mediaacceptwith|None->None|Some((l,r),_)->Some(l^"/"^r)inrequest_failedmethuri(Unsupported_media_typename)|`Not_acceptableacceptable->letproposed=Media_type.accept_headeracceptinrequest_failedmethuri(Not_acceptable{proposed;acceptable})|`Bad_requestmsg->request_failedmethuri(Bad_requestmsg)|`Unexpected_content((content,media_type),error)|`Unexpected_error_content((content,media_type),error)->letmedia_type=Media_type.namemedia_typeinrequest_failedmethuri(Unexpected_content{content;media_type;error})|`Unexpected_error_content_type(body,media)|`Unexpected_content_type(body,media)->Cohttp_lwt.Body.to_stringbody>>=funbody->letreceived=Option.foldmedia~none:""~some:(fun(l,r)->l^"/"^r)inrequest_failedmethuri(Unexpected_content_type{received;acceptable=List.mapMedia_type.nameaccept;body})|`Connection_failedmsg->request_failedmethuri(Connection_failedmsg)|`OCaml_exceptionmsg->request_failedmethuri(OCaml_exceptionmsg)|`Unauthorized_hosthost->request_failedmethuri(Unauthorized_hosthost)letcall_streamed_service(typepqio)accept?logger?headers~base(service:(_,_,p,q,i,o)RPC_service.t)~on_chunk~on_close(params:p)(query:q)(body:i):(unit->unit)tzresultLwt.t=Client.call_streamed_serviceaccept?logger?headers~base~on_chunk~on_closeserviceparamsquerybody>>=funans->handleacceptansletcall_service(typepqio)accept?logger?headers~base(service:(_,_,p,q,i,o)RPC_service.t)(params:p)(query:q)(body:i):otzresultLwt.t=Client.call_service?logger?headers~baseacceptserviceparamsquerybody>>=funans->handleacceptanstypeconfig={media_type:Media_type.tlist;endpoint:Uri.t;logger:logger;}letconfig_encoding=letopenData_encodinginconv(fun{media_type;endpoint;logger=_}->(media_type,endpoint))(fun(media_type,endpoint)->{media_type;endpoint;logger=null_logger})(obj2(req"media-type"(listMedia_type.encoding))(req"endpoint"RPC_encoding.uri_encoding))letdefault_config={media_type=Media_type.all_media_types;endpoint=Uri.of_string"http://localhost:8732";logger=null_logger;}classhttp_ctxtconfigmedia_types:RPC_context.json=letbase=config.endpointinletlogger=config.loggerinletcallmethurif=letpath=Uri.pathuriandquery=Uri.queryuriinletprefix=Uri.pathbaseinletprefixed_path=ifprefix=""thenpathelseprefix^"/"^pathinleturi=Uri.with_pathbaseprefixed_pathinleturi=Uri.with_queryuriqueryinfmethuriinobjectmethodgeneric_json_callmeth?bodyuri=callmethuri(generic_json_call?body)methodgeneric_media_type_callmeth?bodyuri=callmethuri(generic_media_type_call?body~accept:config.media_type)methodcall_service:'m'p'q'i'o.(([<Resto.meth]as'm),unit,'p,'q,'i,'o)RPC_service.t->'p->'q->'i->'otzresultLwt.t=funserviceparamsquerybody->call_servicemedia_types~logger~baseserviceparamsquerybodymethodcall_streamed_service:'m'p'q'i'o.(([<Resto.meth]as'm),unit,'p,'q,'i,'o)RPC_service.t->on_chunk:('o->unit)->on_close:(unit->unit)->'p->'q->'i->(unit->unit)tzresultLwt.t=funservice~on_chunk~on_closeparamsquerybody->call_streamed_servicemedia_typesservice~logger~base~on_chunk~on_closeparamsquerybodymethodbase=baseendend