1 /** 2 Json-Rpc 2.0 protocol implementation. 3 4 Copyright: © 2018 Eliott Dumeix 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 */ 7 module rpc.protocol.json; 8 9 import rpc.core; 10 import std.typecons: Nullable, nullable; 11 import vibe.data.json; 12 import vibe.core.log; 13 import autointf : InterfaceInfo; 14 15 16 /** Json-Rpc 2.0 error. 17 */ 18 class JsonRPCError 19 { 20 public: 21 int code; 22 string message; 23 @optional Json data; 24 25 /// Default constructor. 26 this() @safe nothrow {} 27 28 /// Standard error constructor. 29 this(StdCodes code) 30 @safe nothrow { 31 this.code = code; 32 this.message = CODES_MESSAGE[this.code]; 33 } 34 35 static enum StdCodes 36 { 37 parseError = -32700, 38 invalidRequest = -32600, 39 methodNotFound = -32601, 40 invalidParams = -32602, 41 internalError = -32603 42 } 43 44 private static immutable string[int] CODES_MESSAGE; 45 46 static this() 47 @safe { 48 CODES_MESSAGE[StdCodes.parseError] = "Parse error"; 49 CODES_MESSAGE[StdCodes.invalidRequest] = "Invalid Request"; 50 CODES_MESSAGE[StdCodes.methodNotFound] = "Method not found"; 51 CODES_MESSAGE[StdCodes.invalidParams] = "Invalid params"; 52 CODES_MESSAGE[StdCodes.internalError] = "Internal error"; 53 } 54 } 55 56 /** Json-Rpc request. 57 58 Template_Params: 59 TId = The type used to identify rpc request. 60 */ 61 class JsonRPCRequest(TId): IRPCRequest!TId 62 { 63 public: 64 @property TId requestId() { return id; } 65 string jsonrpc; 66 string method; 67 @optional TId id; 68 @optional Nullable!Json params; 69 70 this() @safe 71 { 72 params = Nullable!Json.init; 73 } 74 75 static JsonRPCRequest!TId make(T)(TId id, string method, T params) 76 { 77 import vibe.data.json : serializeToJson; 78 79 auto request = new JsonRPCRequest!TId(); 80 request.id = id; 81 request.method = method; 82 request.params = serializeToJson!T(params); 83 84 return request; 85 } 86 87 static JsonRPCRequest!TId make(TId id, string method) 88 { 89 import vibe.data.json : serializeToJson; 90 91 auto request = new JsonRPCRequest!TId(); 92 request.id = id; 93 request.method = method; 94 95 return request; 96 } 97 98 bool hasParams() @safe 99 { 100 return !params.isNull; 101 } 102 103 Json toJson() const @safe 104 { 105 Json json = Json.emptyObject; 106 json["jsonrpc"] = "2.0"; 107 json["method"] = method; 108 json["id"] = id; 109 if (!params.isNull) 110 json["params"] = params.get; 111 return json; 112 } 113 114 static JsonRPCRequest fromJson(Json src) @safe 115 { 116 JsonRPCRequest request = new JsonRPCRequest(); 117 request.jsonrpc = src["jsonrpc"].to!string; 118 request.method = src["method"].to!string; 119 if (src["id"].type != Json.Type.undefined) 120 request.id = src["id"].to!TId; 121 if (src["params"].type != Json.Type.undefined) 122 request.params = src["params"].nullable; 123 return request; 124 } 125 126 override string toString() const @safe 127 { 128 return toJson().toString(); 129 } 130 131 132 static JsonRPCRequest fromString(string src) @safe 133 { 134 return fromJson(parseJson(src)); 135 } 136 } 137 138 @("Test JsonRpcRequest") 139 unittest 140 { 141 import vibe.data.json; 142 143 auto r1 = new JsonRPCRequest!int(); 144 auto json = Json.emptyObject; 145 json["foo"] = 42; 146 147 r1.method = "foo"; 148 assert(`{"jsonrpc":"2.0","id":0,"method":"foo"}` == r1.toString()); 149 150 auto r2 = new JsonRPCRequest!int(); 151 r2.method = "foo"; 152 r2.params = json; 153 assert(`{"jsonrpc":"2.0","id":0,"method":"foo","params":{"foo":42}}` == r2.toString()); 154 155 auto r3 = deserializeJson!(JsonRPCRequest!int)(r1.toString()); 156 assert(r3.id == r1.id); 157 assert(r3.params == r1.params); 158 assert(r3.method == r1.method); 159 160 // string id: 161 auto r10 = new JsonRPCRequest!string(); 162 r10.method = "foo"; 163 r10.id = "bar"; 164 assert(`{"jsonrpc":"2.0","id":"bar","method":"foo"}` == r10.toString()); 165 } 166 167 /** Json-Rpc response. 168 169 Template_Params: 170 TId = The type used to identify rpc request. 171 */ 172 class JsonRPCResponse(TId): IRPCResponse 173 { 174 public: 175 string jsonrpc; 176 Nullable!TId id; 177 @optional Nullable!Json result; 178 @optional Nullable!JsonRPCError error; 179 180 this() @safe nothrow 181 { 182 result = Nullable!Json.init; 183 error = Nullable!JsonRPCError.init; 184 } 185 186 bool isError() @safe nothrow 187 { 188 return !error.isNull; 189 } 190 191 bool isSuccess() @safe nothrow 192 { 193 return !result.isNull; 194 } 195 196 Json toJson() const @safe 197 { 198 Json json = Json.emptyObject; 199 json["jsonrpc"] = "2.0"; 200 // the id must be 'null' in case of parse error 201 if (!id.isNull) 202 json["id"] = id.get; 203 else 204 json["id"] = null; 205 if (!result.isNull) 206 json["result"] = result.get; 207 if (!error.isNull) 208 json["error"] = serializeToJson!(const(JsonRPCError))(error.get); 209 return json; 210 } 211 212 static JsonRPCResponse fromJson(Json src) @safe 213 { 214 JsonRPCResponse request = new JsonRPCResponse(); 215 request.jsonrpc = src["jsonrpc"].to!string; 216 if (src["id"].type != Json.Type.undefined) 217 { 218 if (src["id"].type == Json.Type.null_) 219 request.id.nullify; 220 else 221 request.id = src["id"].to!TId; 222 } 223 if (src["result"].type != Json.Type.undefined) 224 request.result = src["result"].nullable; 225 if (src["error"].type != Json.Type.undefined) 226 request.error = deserializeJson!JsonRPCError(src["error"]).nullable; 227 return request; 228 } 229 230 override string toString() const @safe 231 { 232 return toJson().toString(); 233 } 234 235 static JsonRPCResponse fromString(string src) @safe 236 { 237 return fromJson(parseJson(src)); 238 } 239 } 240 241 @("Test JsonRpcResponse") 242 unittest 243 { 244 auto r1 = new JsonRPCResponse!int(); 245 Json json = "hello"; 246 r1.result = json; 247 r1.id = 42; 248 assert(`{"jsonrpc":"2.0","result":"hello","id":42}` == r1.toString()); 249 250 auto error = new JsonRPCError(); 251 error.code = -32600; 252 error.message = "Invalid Request"; 253 254 auto r2 = new JsonRPCResponse!int(); 255 r2.error = error; 256 r2.id = 1; 257 assert(`{"jsonrpc":"2.0","id":1,"error":{"message":"Invalid Request","code":-32600}}` == r2.toString()); 258 } 259 260 /// Encapsulate a json-rpc error response. 261 class JsonRPCMethodException: RPCException 262 { 263 import std.conv: to; 264 265 this(JsonRPCError error) @safe 266 { 267 if (error.data.type == Json.Type.object) 268 super(error.message ~ " (" ~ to!string(error.code) ~ "): " ~ error.data.toString()); 269 else 270 super(error.message ~ " (" ~ to!string(error.code) ~ ")"); 271 } 272 273 this(string msg) @safe 274 { 275 super(msg); 276 } 277 } 278 279 /// Exception to be used inside rpc handler, to throw user defined json-rpc error. 280 class JsonRPCUserException: RPCException 281 { 282 import vibe.data.json; 283 284 public: 285 int code; 286 Json data; 287 288 this(T)(int code, string msg, T data) @safe 289 { 290 super(msg); 291 this.code = code; 292 this.data = serializeToJson(data); 293 } 294 } 295 296 297 298 class RawJsonRPCClient(TId): RawRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId) 299 { 300 import core.time : Duration; 301 import vibe.data.json; 302 import vibe.stream.operations: readAllUTF8; 303 import vibe.core.stream: InputStream, OutputStream; 304 305 private: 306 IIdGenerator!TId _idGenerator; 307 JsonRPCResponse!TId[TId] _pendingResponse; 308 309 public: 310 this(OutputStream ostream, InputStream istream) @safe 311 { 312 super(ostream, istream); 313 _idGenerator = new IdGenerator!TId(); 314 } 315 316 /** Send a request with an auto-generated id. 317 Throws: 318 JSONException 319 */ 320 JsonRPCResponse!TId sendRequestAndWait(JsonRPCRequest!TId request, Duration timeout = Duration.max()) @safe 321 { 322 request.id = _idGenerator.getNextId(); 323 _ostream.write(request.toString()); 324 return waitForResponse(request.id, timeout); 325 } 326 327 // Process the input stream once. 328 void tick() @safe 329 { 330 string rawJson = _istream.readAllUTF8(); 331 Json json = parseJson(rawJson); 332 333 void process(Json jsonObject) 334 { 335 auto response = deserializeJson!(JsonRPCResponse!TId)(jsonObject); 336 _pendingResponse[response.id] = response; 337 } 338 339 // batch of commands 340 if (json.type == Json.Type.array) 341 { 342 foreach(object; json.byValue) 343 { 344 process(object); 345 } 346 } 347 else 348 { 349 process(json); 350 } 351 } 352 353 protected: 354 JsonRPCResponse!TId waitForResponse(TId id, Duration timeout) @safe 355 { 356 import std.conv: to; 357 358 // check if response already received 359 if (id in _pendingResponse) 360 { 361 scope(exit) _pendingResponse.remove(id); 362 return _pendingResponse[id]; 363 } 364 else 365 { 366 throw new RPCTimeoutException("No reponse"); 367 } 368 369 } 370 } 371 372 alias IJsonRPCClient(TId) = IRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId); 373 374 /// An http json-rpc client 375 alias HTTPJsonRPCClient(TId) = HttpRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId); 376 377 class TCPJsonRPCClient(TId): IJsonRPCClient!TId 378 { 379 import vibe.core.net : TCPConnection, TCPListener, connectTCP; 380 import vibe.stream.operations : readLine; 381 import core.time; 382 import std.conv: to; 383 384 private: 385 string _host; 386 ushort _port; 387 bool _connected; 388 IIdGenerator!TId _idGenerator; 389 JsonRPCResponse!TId[TId] _pendingResponse; 390 TCPConnection _conn; 391 392 public: 393 @property bool connected() { return _connected; } 394 395 this(string host, ushort port) 396 { 397 _host = host; 398 _port = port; 399 this.connect(); 400 _idGenerator = new IdGenerator!TId(); 401 } 402 403 bool connect() @safe nothrow 404 in { 405 assert(!_connected); 406 } 407 do { 408 try { 409 _conn = connectTCP(_host, _port); 410 _connected = true; 411 } catch (Exception e) { 412 _connected = false; 413 } 414 415 return _connected; 416 } 417 418 /// auto-generate id 419 JsonRPCResponse!TId sendRequestAndWait(JsonRPCRequest!TId request, core.time.Duration timeout = core.time.Duration.max()) @safe 420 { 421 if (!_connected) 422 throw new RPCNotConnectedException("tcp client not connected ! call connect() first."); 423 424 request.id = _idGenerator.getNextId(); 425 logTrace("tcp send request: %s", request); 426 _conn.write(request.toString() ~ "\r\n"); 427 428 if (_conn.waitForData(timeout)) { 429 char[] raw = cast(char[]) _conn.readLine(); 430 string json = to!string(raw); 431 logTrace("tcp server request response: %s", json); 432 auto response = deserializeJson!(JsonRPCResponse!TId)(json); 433 return response; 434 } 435 else 436 throw new RPCTimeoutException("waitForData timeout"); 437 } 438 439 @disable void tick() @safe {} 440 } 441 442 443 444 alias IJsonRPCServer(TId) = IRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId); 445 446 alias JsonRPCRequestHandler(TId) = RPCRequestHandler!(JsonRPCRequest!TId, JsonRPCResponse!TId); 447 448 class RawJsonRPCServer(TId): RawRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId), 449 IRPCServerOutput!(JsonRPCResponse!TId) 450 { 451 import vibe.stream.operations: readAllUTF8; 452 import vibe.core.stream: InputStream, OutputStream; 453 454 private: 455 JsonRPCRequestHandler!TId[string] _requestHandler; 456 457 public: 458 this(OutputStream ostream, InputStream istream) @safe 459 { 460 super(ostream, istream); 461 } 462 463 void registerRequestHandler(string method, JsonRPCRequestHandler!TId handler) 464 { 465 _requestHandler[method] = handler; 466 } 467 468 void sendResponse(JsonRPCResponse!TId reponse) @safe 469 { 470 _ostream.write(reponse.toString()); 471 } 472 473 void tick() @safe 474 { 475 string rawJson = _istream.readAllUTF8(); 476 Json json = parseJson(rawJson); 477 478 void process(Json jsonObject) 479 { 480 auto request = deserializeJson!(JsonRPCRequest!TId)(jsonObject); 481 if (request.method in _requestHandler) 482 { 483 _requestHandler[request.method](request, this); 484 } 485 } 486 487 // batch of commands 488 if (json.type == Json.Type.array) 489 { 490 foreach(object; json.byValue) 491 { 492 process(object); 493 } 494 } 495 else 496 { 497 process(json); 498 } 499 } 500 } 501 502 /** An http json-rpc server. 503 504 Template_Params: 505 TId = The type to use for request and response json-rpc id. 506 */ 507 508 /// An http json-rpc client 509 class HTTPJsonRPCServer(TId): HttpRPCServer!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId) 510 { 511 import vibe.data.json: JSONException; 512 import vibe.http.router: URLRouter; 513 514 this(URLRouter router, string path) 515 { 516 super(router, path); 517 } 518 519 @disable void tick() @safe {} 520 521 protected override JsonRPCResponse!TId buildResponseFromException(Exception e) @safe nothrow 522 { 523 auto response = new JsonRPCResponse!TId(); 524 if (is(typeof(e) == JSONException)) 525 { 526 response.error = new JsonRPCError(JsonRPCError.StdCodes.parseError); 527 return response; 528 } 529 else 530 { 531 response.error = new JsonRPCError(JsonRPCError.StdCodes.internalError); 532 return response; 533 } 534 } 535 536 void registerInterface(I)(I instance, RPCInterfaceSettings settings = null) 537 { 538 import std.algorithm : filter, map, all; 539 import std.array : array; 540 import std.range : front; 541 import vibe.internal.meta.uda : findFirstUDA; 542 543 alias Info = InterfaceInfo!I; 544 InterfaceInfo!I* info = new Info(); 545 546 foreach (i, ovrld; Info.SubInterfaceFunctions) { 547 enum fname = __traits(identifier, Info.SubInterfaceFunctions[i]); 548 alias R = ReturnType!ovrld; 549 550 static if (isInstanceOf!(Collection, R)) { 551 auto ret = __traits(getMember, instance, fname)(R.ParentIDs.init); 552 router.registerRestInterface!(R.Interface)(ret.m_interface, info.subInterfaces[i].settings); 553 } else { 554 auto ret = __traits(getMember, instance, fname)(); 555 router.registerRestInterface!R(ret, info.subInterfaces[i].settings); 556 } 557 } 558 559 foreach (i, Func; Info.Methods) { 560 enum methodNameAtt = findFirstUDA!(RPCMethodAttribute, Func); 561 enum smethod = Info.staticMethods[i]; 562 563 auto handler = jsonRpcMethodHandler!(TId, Func, i, I)(instance, *info); 564 565 // select rpc name (attribute or function name): 566 static if (methodNameAtt.found) 567 this.registerRequestHandler(methodNameAtt.value.method, handler); 568 else 569 this.registerRequestHandler(smethod.name, handler); 570 571 } 572 } 573 } 574 575 class TCPJsonRPCServer(TId): IJsonRPCServer!TId 576 { 577 import vibe.core.net : TCPConnection, TCPListener, listenTCP; 578 import vibe.stream.operations : readLine; 579 580 private: 581 alias JsonRpcRespHandler = IRPCServerOutput!(JsonRPCResponse!TId); 582 JsonRPCRequestHandler!TId[string] _requestHandler; 583 RPCInterfaceSettings _settings; 584 585 class ResponseWriter: JsonRpcRespHandler 586 { 587 private TCPConnection _conn; 588 589 this(TCPConnection conn) 590 { 591 _conn = conn; 592 } 593 594 void sendResponse(JsonRPCResponse!TId reponse) 595 @safe { 596 logTrace("tcp request response: %s", reponse); 597 try { 598 _conn.write(reponse.toString() ~ "\r\n"); 599 } catch (Exception e) { 600 logTrace("unable to send response: %s", e.msg); 601 // TODO: add a delgate to allow the user to handle error 602 } 603 } 604 } 605 606 public: 607 this(ushort port, RPCInterfaceSettings settings = null) 608 { 609 _settings = settings; 610 611 listenTCP(port, (conn) { 612 logTrace("new client: %s", conn); 613 try { 614 615 auto writer = new ResponseWriter(conn); 616 617 while (!conn.empty) { 618 auto json = cast(const(char)[])conn.readLine(); 619 logTrace("tcp request received: %s", json); 620 621 this.process(cast(string) json, writer); 622 } 623 } catch (Exception e) { 624 logError("Failed to read from client: %s", e.msg); 625 if (_settings !is null) 626 _settings.errorHandler(e); 627 } 628 629 conn.close(); 630 }); 631 } 632 633 void registerInterface(I)(I instance, RPCInterfaceSettings settings = null) 634 { 635 import std.algorithm : filter, map, all; 636 import std.array : array; 637 import std.range : front; 638 639 alias Info = InterfaceInfo!I; 640 InterfaceInfo!I* info = new Info(); 641 642 foreach (i, Func; Info.Methods) { 643 enum smethod = Info.staticMethods[i]; 644 645 // normal handler 646 auto handler = jsonRpcMethodHandler!(TId, Func, i)(instance, *info); 647 648 this.registerRequestHandler(smethod.name, handler); 649 } 650 651 } 652 653 @disable void tick() @safe {} 654 655 void registerRequestHandler(string method, JsonRPCRequestHandler!TId handler) 656 { 657 _requestHandler[method] = handler; 658 } 659 660 void process(string data, JsonRpcRespHandler respHandler) 661 { 662 Json json = parseJson(data); 663 664 void process(Json jsonObject) 665 { 666 auto request = deserializeJson!(JsonRPCRequest!TId)(jsonObject); 667 if (request.method in _requestHandler) 668 { 669 _requestHandler[request.method](request, respHandler); 670 } 671 } 672 673 // batch of commands 674 if (json.type == Json.Type.array) 675 { 676 foreach(object; json.byValue) 677 { 678 process(object); 679 } 680 } 681 else 682 { 683 process(json); 684 } 685 } 686 } 687 688 689 /// Return an handler to match a json-rpc request on an interface method. 690 public JsonRPCRequestHandler!TId jsonRpcMethodHandler(TId, alias Func, size_t n, T)(T inst, ref InterfaceInfo!T intf) 691 { 692 import std.traits; 693 import std.meta : AliasSeq; 694 import std.string : format; 695 import vibe.utils.string : sanitizeUTF8; 696 import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx; 697 import vibe.internal.meta.traits : derivedMethod; 698 699 enum Method = __traits(identifier, Func); 700 alias PTypes = ParameterTypeTuple!Func; 701 alias PDefaults = ParameterDefaultValueTuple!Func; 702 alias CFuncRaw = derivedMethod!(T, Func); 703 static if (AliasSeq!(CFuncRaw).length > 0) alias CFunc = CFuncRaw; 704 else alias CFunc = Func; 705 alias RT = ReturnType!(FunctionTypeOf!Func); 706 static const sroute = InterfaceInfo!T.staticMethods[n]; 707 auto method = intf.methods[n]; 708 709 void handler(JsonRPCRequest!TId req, IRPCServerOutput!(JsonRPCResponse!TId) serv) @safe 710 { 711 auto response = new JsonRPCResponse!TId(); 712 response.id = req.id; 713 PTypes params; 714 715 // build a custom json error object tobe sent in json rpc error response. 716 Json buildErrorData(string details) 717 @safe { 718 auto json = Json.emptyObject; 719 json["details"] = details; 720 json["request"] = req.toJson(); 721 return json; 722 } 723 724 try { 725 // check params consistency beetween rpc-request and function parameters 726 if (PTypes.length > 1) 727 { 728 // we expect a json array 729 if (req.params.type != Json.Type.array) 730 { 731 response.error = new JsonRPCError(JsonRPCError.StdCodes.invalidParams); 732 response.error.data = buildErrorData("Expected a json array for params"); 733 serv.sendResponse(response); 734 return; 735 } 736 // req.params is a json array 737 else if (req.params.length != PTypes.length) 738 { 739 response.error = new JsonRPCError(JsonRPCError.StdCodes.invalidParams); 740 response.error.data = buildErrorData("Missing params"); 741 serv.sendResponse(response); 742 return; 743 } 744 } 745 746 foreach (i, PT; PTypes) { 747 enum sparam = sroute.parameters[i]; 748 749 enum pname = sparam.name; 750 auto fieldname = sparam.name; 751 static if (isInstanceOf!(Nullable, PT)) PT v; 752 else Nullable!PT v; 753 754 v = deserializeJson!PT(req.params[i]); 755 756 params[i] = v; 757 } 758 } catch (Exception e) { 759 //handleException(e, HTTPStatus.badRequest); 760 return; 761 } 762 763 try { 764 import vibe.internal.meta.funcattr; 765 766 static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); })) 767 pragma(msg, "Non-@safe methods are deprecated in REST interfaces - Mark "~T.stringof~"."~Method~" as @safe."); 768 769 static if (is(RT == void)) { 770 // TODO: return null 771 } else { 772 auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period 773 774 static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ())) 775 pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe."); 776 777 static if (!__traits(compiles, () @safe { res.writeJsonBody(ret); })) 778 pragma(msg, "Non-@safe serialization of REST return types deprecated - ensure that "~RT.stringof~" is safely serializable."); 779 () @trusted { 780 // build reponse 781 response.id = req.id; 782 response.result = serializeToJson(ret); 783 serv.sendResponse(response); 784 }(); 785 } 786 } 787 // catch user-defined json-rpc errors. 788 catch (JsonRPCUserException e) 789 { 790 response.error = new JsonRPCError(); 791 response.error.code = e.code; 792 response.error.message = e.msg; 793 response.error.data = e.data; 794 serv.sendResponse(response); 795 return; 796 } 797 catch (Exception e) { 798 //returnHeaders(); 799 //handleException(e, HTTPStatus.internalServerError); 800 response.error = new JsonRPCError(); 801 response.error.code = 0; 802 response.error.message = e.msg; 803 serv.sendResponse(response); 804 return; 805 } 806 } 807 808 return &handler; 809 } 810 811 /** Base class to create a Json RPC automatic client. 812 */ 813 abstract class JsonRPCAutoClient(I) : I 814 { 815 import std.traits : hasUDA; 816 817 private: 818 // The json rpc id type to use: string or int 819 static if (hasUDA!(I, RPCIdTypeAttribute!int)) 820 alias TId = int; 821 else static if (hasUDA!(I, RPCIdTypeAttribute!string)) 822 alias TId = string; 823 else 824 alias TId = int; 825 826 protected: 827 IRPCClient!(TId, JsonRPCRequest!TId, JsonRPCResponse!TId) _client; 828 RPCInterfaceSettings _settings; 829 830 RT executeMethod(I, RT, int n, ARGS...)(ref InterfaceInfo!I info, ARGS args) @safe 831 { 832 import vibe.internal.meta.uda : findFirstUDA; 833 import std.traits; 834 import std.array : appender; 835 import core.time; 836 import vibe.data.json; 837 838 // retrieve some compile time informations 839 // alias Info = RpcInterface!I; 840 alias Func = info.Methods[n]; 841 alias RT = ReturnType!Func; 842 alias PTT = ParameterTypeTuple!Func; 843 enum sroute = info.staticMethods[n]; 844 auto method = info.methods[n]; 845 846 enum objectParamAtt = findFirstUDA!(RPCMethodObjectParams, Func); 847 enum methodNameAtt = findFirstUDA!(RPCMethodAttribute, Func); 848 849 try 850 { 851 auto jsonParams = Json.undefined; 852 853 // Render params as unique param or array 854 static if (!objectParamAtt.found) 855 { 856 // if several params, then build an a json array 857 if (PTT.length > 1) 858 jsonParams = Json.emptyArray; 859 860 // fill the json array or the unique value 861 foreach (i, PT; PTT) { 862 if (PTT.length > 1) 863 jsonParams.appendArrayElement(serializeToJson(args[i])); 864 else 865 jsonParams = serializeToJson(args[i]); 866 } 867 } 868 // render params as a json object by using the param name 869 // for the key or the uda if exists 870 else 871 { 872 jsonParams = Json.emptyObject; 873 874 // fill object 875 foreach (i, PT; PTT) { 876 if (sroute.parameters[i].name in objectParamAtt.value.names) 877 jsonParams[objectParamAtt.value.names[sroute.parameters[i].name]] = serializeToJson(args[i]); 878 else 879 jsonParams[sroute.parameters[i].name] = serializeToJson(args[i]); 880 } 881 } 882 883 884 static if (!is(RT == void)) 885 RT jret; 886 887 // create a json-rpc request 888 auto request = new JsonRPCRequest!TId(); 889 static if (methodNameAtt.found) 890 request.method = methodNameAtt.value.method; 891 else 892 request.method = method.name; 893 request.params = jsonParams; // set rpc call params 894 895 auto response = _client.sendRequestAndWait(request, _settings.responseTimeout); // send packet and wait 896 897 if (response.isError()) 898 { 899 throw new JsonRPCMethodException(response.error); 900 } 901 902 // void return type 903 static if (is(RT == void)) 904 { 905 906 } 907 else 908 { 909 return deserializeJson!RT(response.result); 910 } 911 } 912 catch (JSONException e) 913 { 914 throw new RPCParsingException(e.msg, e); 915 } 916 catch (Exception e) 917 { 918 throw new RPCException(e.msg, e); 919 } 920 } 921 922 public: 923 @property auto client() @safe { return _client; } 924 925 // mixin(autoImplementMethods!I()); 926 } 927 928 class RawJsonRPCAutoClient(I) : JsonRPCAutoClient!I 929 { 930 import vibe.core.stream: InputStream, OutputStream; 931 import autointf; 932 933 public: 934 this(OutputStream ostream, InputStream istream) @safe 935 { 936 _client = new RawJsonRPCClient!TId(ostream, istream); 937 _settings = new RPCInterfaceSettings(); 938 } 939 940 mixin(autoImplementMethods!I()); 941 } 942 943 class HTTPJsonRPCAutoClient(I) : JsonRPCAutoClient!I 944 { 945 import autointf; 946 947 public: 948 this(string host) @safe 949 { 950 _client = new HTTPJsonRPCClient!TId(host); 951 _settings = new RPCInterfaceSettings(); 952 } 953 954 mixin(autoImplementMethods!I()); 955 } 956 957 class TCPJsonRPCAutoClient(I) : JsonRPCAutoClient!I 958 { 959 import autointf; 960 961 public: 962 this(string host, ushort port) @safe 963 { 964 _client = new TCPJsonRPCClient!TId(host, port); 965 _settings = new RPCInterfaceSettings(); 966 } 967 968 mixin(autoImplementMethods!I()); 969 }