1 /** 2 Json-Rpc 2.0 protocol implementation. 3 4 This module has 3 entry point: 5 $(UL 6 $(LI `RawJsonRpcAutoClient`) 7 $(LI `HttpJsonRpcAutoClient`) 8 $(LI `TcpJsonRpcAutoClient`) 9 ) 10 11 Copyright: © 2018 Eliott Dumeix 12 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 13 */ 14 module rpc.protocol.json; 15 16 public import rpc.core; 17 import std.typecons: Nullable, nullable; 18 import vibe.data.json; 19 import vibe.core.log; 20 import autointf : InterfaceInfo; 21 import std.traits : hasUDA; 22 23 24 /** Json-Rpc 2.0 error. 25 */ 26 class JsonRpcError 27 { 28 public: 29 /// Error code 30 int code; 31 32 /// Error message 33 string message; 34 35 /// Optional json data 36 @optional Json data; 37 38 /// Default constructor. 39 this() @safe nothrow {} 40 41 /// Standard error constructor. 42 this(StdCodes code) 43 @safe nothrow { 44 this.code = code; 45 this.message = CODES_MESSAGE[this.code]; 46 } 47 48 /// 49 static enum StdCodes 50 { 51 parseError = -32700, /// Parse error 52 invalidRequest = -32600, /// Invalid request 53 methodNotFound = -32601, /// Method not found 54 invalidParams = -32602, /// Invalid params 55 internalError = -32603 /// Internal error 56 } 57 58 /// 59 private static immutable string[int] CODES_MESSAGE; 60 61 shared static this() 62 @safe { 63 CODES_MESSAGE[StdCodes.parseError] = "Parse error"; 64 CODES_MESSAGE[StdCodes.invalidRequest] = "Invalid Request"; 65 CODES_MESSAGE[StdCodes.methodNotFound] = "Method not found"; 66 CODES_MESSAGE[StdCodes.invalidParams] = "Invalid params"; 67 CODES_MESSAGE[StdCodes.internalError] = "Internal error"; 68 } 69 } 70 71 /** A Json-Rpc request that use TId as id type. 72 */ 73 class JsonRpcRequest(TId): IRpcRequest!TId 74 { 75 public: 76 /// 77 override @property TId requestId() { return id; } 78 79 /// json rpc string 80 string jsonrpc; 81 82 /// Json rpc method 83 string method; 84 85 // id 86 @optional TId id; 87 88 // Parameters 89 @optional Nullable!Json params; 90 91 /// 92 this() @safe 93 { 94 params = Nullable!Json.init; 95 } 96 97 /// Create a new json request. 98 static JsonRpcRequest!TId make(T)(TId id, string method, T params) 99 { 100 import vibe.data.json : serializeToJson; 101 102 auto request = new JsonRpcRequest!TId(); 103 request.id = id; 104 request.method = method; 105 request.params = serializeToJson!T(params); 106 107 return request; 108 } 109 110 /// ditto 111 static JsonRpcRequest!TId make(TId id, string method) 112 { 113 auto request = new JsonRpcRequest!TId(); 114 request.id = id; 115 request.method = method; 116 117 return request; 118 } 119 120 /// 121 bool hasParams() @safe 122 { 123 return !params.isNull; 124 } 125 126 /// Convert to Json. 127 Json toJson() const @safe 128 { 129 Json json = Json.emptyObject; 130 json["jsonrpc"] = "2.0"; 131 json["method"] = method; 132 json["id"] = id; 133 if (!params.isNull) 134 json["params"] = params.get; 135 return json; 136 } 137 138 /// Parse from Json. 139 static JsonRpcRequest fromJson(Json src) @safe 140 { 141 JsonRpcRequest request = new JsonRpcRequest(); 142 request.jsonrpc = src["jsonrpc"].to!string; 143 request.method = src["method"].to!string; 144 if (src["id"].type != Json.Type.undefined) 145 request.id = src["id"].to!TId; 146 if (src["params"].type != Json.Type.undefined) 147 request.params = src["params"].nullable; 148 return request; 149 } 150 151 /// Convert to a Json string. 152 override string toString() const @safe 153 { 154 return toJson().toString(); 155 } 156 157 /// Parse from a Json string. 158 static JsonRpcRequest fromString(string src) @safe 159 { 160 return fromJson(parseJson(src)); 161 } 162 } 163 164 /// 165 @("Test JsonRpcRequest") 166 unittest 167 { 168 import vibe.data.json; 169 170 auto r1 = new JsonRpcRequest!int(); 171 auto json = Json.emptyObject; 172 json["foo"] = 42; 173 174 r1.method = "foo"; 175 r1.toString().should == `{"method":"foo","id":0,"jsonrpc":"2.0"}`; 176 177 auto r2 = new JsonRpcRequest!int(); 178 r2.method = "foo"; 179 r2.params = json; 180 r2.toString().should == `{"params":{"foo":42},"method":"foo","id":0,"jsonrpc":"2.0"}`; 181 182 auto r3 = deserializeJson!(JsonRpcRequest!int)(r1.toString()); 183 r3.id.should == r1.id; 184 r3.params.should == r1.params; 185 r3.method.should == r1.method; 186 187 // string id: 188 auto r10 = new JsonRpcRequest!string(); 189 r10.method = "foo"; 190 r10.id = "bar"; 191 r10.toString().should == `{"method":"foo","id":"bar","jsonrpc":"2.0"}`; 192 } 193 194 /** A Json-Rpc response with an id of type TId. 195 */ 196 class JsonRpcResponse(TId): IRpcResponse 197 { 198 public: 199 /// 200 string jsonrpc; 201 202 /// id 203 Nullable!TId id; 204 205 /// Optional result. 206 @optional Nullable!Json result; 207 208 /// Optional error. 209 @optional Nullable!JsonRpcError error; 210 211 /// 212 this() @safe nothrow 213 { 214 result = Nullable!Json.init; 215 error = Nullable!JsonRpcError.init; 216 } 217 218 /// Tells if the response is an error. 219 bool isError() @safe nothrow 220 { 221 return !error.isNull; 222 } 223 224 /// Tells if the response is in success. 225 bool isSuccess() @safe nothrow 226 { 227 return !result.isNull; 228 } 229 230 /// Convert to Json. 231 Json toJson() const @safe 232 { 233 Json json = Json.emptyObject; 234 json["jsonrpc"] = "2.0"; 235 // the id must be 'null' in case of parse error 236 if (!id.isNull) 237 json["id"] = id.get; 238 else 239 json["id"] = null; 240 if (!result.isNull) 241 json["result"] = result.get; 242 if (!error.isNull) 243 json["error"] = serializeToJson!(const(JsonRpcError))(error.get); 244 return json; 245 } 246 247 /// Parse from Json. 248 static JsonRpcResponse fromJson(Json src) @safe 249 { 250 JsonRpcResponse request = new JsonRpcResponse(); 251 request.jsonrpc = src["jsonrpc"].to!string; 252 if (src["id"].type != Json.Type.undefined) 253 { 254 if (src["id"].type == Json.Type.null_) 255 request.id.nullify; 256 else 257 request.id = src["id"].to!TId; 258 } 259 if (src["result"].type != Json.Type.undefined) 260 request.result = src["result"].nullable; 261 if (src["error"].type != Json.Type.undefined) 262 request.error = deserializeJson!JsonRpcError(src["error"]).nullable; 263 return request; 264 } 265 266 /// Convert to Json string. 267 override string toString() const @safe 268 { 269 return toJson().toString(); 270 } 271 272 /// Parse from Json string. 273 static JsonRpcResponse fromString(string src) @safe 274 { 275 return fromJson(parseJson(src)); 276 } 277 } 278 279 /// 280 @("Test JsonRpcResponse") 281 unittest 282 { 283 auto r1 = new JsonRpcResponse!int(); 284 Json json = "hello"; 285 r1.result = json; 286 r1.id = 42; 287 r1.toString().should == `{"result":"hello","id":42,"jsonrpc":"2.0"}`; 288 289 auto error = new JsonRpcError(); 290 error.code = -32600; 291 error.message = "Invalid Request"; 292 293 auto r2 = new JsonRpcResponse!int(); 294 r2.error = error; 295 r2.id = 1; 296 r2.toString().should == `{"error":{"code":-32600,"message":"Invalid Request"},"id":1,"jsonrpc":"2.0"}`; 297 } 298 299 /// Encapsulate a json-rpc error response. 300 class JsonRpcMethodException: RpcException 301 { 302 import std.conv: to; 303 304 this(JsonRpcError error) @safe 305 { 306 if (error.data.type == Json.Type.object) 307 super(error.message ~ " (" ~ to!string(error.code) ~ "): " ~ error.data.toString()); 308 else 309 super(error.message ~ " (" ~ to!string(error.code) ~ ")"); 310 } 311 312 this(string msg) @safe 313 { 314 super(msg); 315 } 316 } 317 318 /// Exception to be used inside rpc handler, to throw user defined json-rpc error. 319 class JsonRpcUserException: RpcException 320 { 321 import vibe.data.json; 322 323 public: 324 int code; 325 Json data; 326 327 this(T)(int code, string msg, T data) @safe 328 { 329 super(msg); 330 this.code = code; 331 this.data = serializeToJson(data); 332 } 333 } 334 335 /// 336 package class RawJsonRpcClient(TId, 337 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 338 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) : 339 RawRpcClient!(TId, TReq, TResp) 340 { 341 import core.time : Duration; 342 import vibe.data.json; 343 import vibe.stream.operations: readAllUTF8; 344 import vibe.core.stream: InputStream, OutputStream; 345 346 private: 347 IIdGenerator!TId _idGenerator; 348 TResp[TId] _pendingResponse; 349 350 public: 351 this(OutputStream ostream, InputStream istream) @safe 352 { 353 super(ostream, istream); 354 _idGenerator = new IdGenerator!TId(); 355 } 356 357 /** Send a request with an auto-generated id. 358 Throws: 359 JSONException 360 */ 361 TResp sendRequestAndWait(TReq request, Duration timeout = Duration.max()) @safe 362 { 363 request.id = _idGenerator.getNextId(); 364 _ostream.write(request.toString()); 365 return waitForResponse(request.id, timeout); 366 } 367 368 // Process the input stream once. 369 override void tick() @safe 370 { 371 string rawJson = _istream.readAllUTF8(); 372 Json json = parseJson(rawJson); 373 374 void process(Json jsonObject) 375 { 376 auto response = deserializeJson!TResp(jsonObject); 377 _pendingResponse[response.id] = response; 378 } 379 380 // batch of commands 381 if (json.type == Json.Type.array) 382 { 383 foreach(object; json.byValue) 384 { 385 process(object); 386 } 387 } 388 else 389 { 390 process(json); 391 } 392 } 393 394 protected: 395 TResp waitForResponse(TId id, Duration timeout) @safe 396 { 397 import std.conv: to; 398 399 // check if response already received 400 if (id in _pendingResponse) 401 { 402 scope(exit) _pendingResponse.remove(id); 403 return _pendingResponse[id]; 404 } 405 else 406 { 407 throw new RpcTimeoutException("No reponse"); 408 } 409 410 } 411 } 412 413 /// Represents a Json rpc client. 414 alias IJsonRpcClient(TId, TReq: JsonRpcRequest!TId, TResp: JsonRpcResponse!TId) = IRpcClient!(TId, TReq, TResp); 415 416 /// An http json-rpc client 417 package alias HttpJsonRpcClient(TId, 418 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 419 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) = HttpRpcClient!(TId, TReq, TResp); 420 421 /// 422 package class TcpJsonRpcClient(TId, 423 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 424 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId): IJsonRpcClient!(TId, TReq, TResp) 425 { 426 import vibe.core.net : TCPConnection, TCPListener, connectTCP; 427 import vibe.stream.operations : readLine; 428 import core.time; 429 import std.conv: to; 430 431 private: 432 string _host; 433 ushort _port; 434 bool _connected; 435 IIdGenerator!TId _idGenerator; 436 JsonRpcResponse!TId[TId] _pendingResponse; 437 TCPConnection _conn; 438 RpcInterfaceSettings _settings; 439 440 public: 441 @property bool connected() { return _connected; } 442 443 this(string host, ushort port, RpcInterfaceSettings settings) 444 { 445 _host = host; 446 _port = port; 447 _settings = settings; 448 this.connect(); 449 _idGenerator = new IdGenerator!TId(); 450 } 451 452 bool connect() @safe nothrow 453 in { 454 assert(!_connected); 455 } 456 do { 457 try { 458 _conn = connectTCP(_host, _port); 459 _connected = true; 460 } catch (Exception e) { 461 _connected = false; 462 } 463 464 return _connected; 465 } 466 467 /// auto-generate id 468 TResp sendRequestAndWait(TReq request, core.time.Duration timeout = core.time.Duration.max()) @safe 469 { 470 if (!_connected) 471 throw new RpcNotConnectedException("tcp client not connected ! call connect() first."); 472 473 request.id = _idGenerator.getNextId(); 474 logTrace("tcp send request: %s", request); 475 _conn.write(request.toString() ~ _settings.linesep); 476 477 if (_conn.waitForData(timeout)) { 478 char[] raw = cast(char[]) _conn.readLine(size_t.max, _settings.linesep); 479 string json = to!string(raw); 480 logTrace("tcp server request response: %s", json); 481 auto response = deserializeJson!TResp(json); 482 return response; 483 } 484 else 485 throw new RpcTimeoutException("waitForData timeout"); 486 } 487 488 @disable void tick() @safe {} 489 } 490 491 /// Represents a Json rpc server. 492 alias IJsonRpcServer(TId, 493 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 494 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) = 495 IRpcServer!(TId, TReq, TResp); 496 497 /// 498 alias JsonRpcRequestHandler(TId, TReq=JsonRpcRequest!TId, TResp=JsonRpcResponse!TId) = 499 RpcRequestHandler!(TReq, TResp); 500 501 /// 502 class RawJsonRpcServer(TId, TReq=JsonRpcRequest!TId, TResp=JsonRpcResponse!TId): 503 RawRpcServer!(TId, TReq, TResp), 504 IRpcServerOutput!TResp 505 { 506 import vibe.stream.operations: readAllUTF8; 507 import vibe.core.stream: InputStream, OutputStream; 508 509 alias RequestHandler = JsonRpcRequestHandler!(TId, TReq, TResp); 510 511 private: 512 RequestHandler[string] _requestHandler; 513 514 public: 515 this(OutputStream ostream, InputStream istream) @safe 516 { 517 super(ostream, istream); 518 } 519 520 void registerRequestHandler(string method, RequestHandler handler) 521 { 522 _requestHandler[method] = handler; 523 } 524 525 void sendResponse(TResp reponse) @safe 526 { 527 _ostream.write(reponse.toString()); 528 } 529 530 void tick() @safe 531 { 532 string rawJson = _istream.readAllUTF8(); 533 Json json = parseJson(rawJson); 534 535 void process(Json jsonObject) 536 { 537 auto request = deserializeJson!TReq(jsonObject); 538 if (request.method in _requestHandler) 539 { 540 _requestHandler[request.method](request, this); 541 } 542 } 543 544 // batch of commands 545 if (json.type == Json.Type.array) 546 { 547 foreach(object; json.byValue) 548 { 549 process(object); 550 } 551 } 552 else 553 { 554 process(json); 555 } 556 } 557 } 558 559 /// An http json-rpc client 560 class HttpJsonRpcServer(TId, 561 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 562 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId): 563 HttpRpcServer!(TId, TReq, TResp) 564 { 565 import vibe.data.json: JSONException; 566 import vibe.http.router: URLRouter; 567 568 this(URLRouter router, string path) 569 { 570 super(router, path); 571 } 572 573 override RpcRespHandler createReponseHandler(HTTPServerResponse res) 574 @safe nothrow { 575 return new class RpcRespHandler 576 { 577 override void sendResponse(TResp reponse) @safe nothrow 578 { 579 logTrace("post request response: %s", reponse); 580 try { 581 res.writeJsonBody(reponse.toJson()); 582 } catch (Exception e) { 583 logCritical("unable to send response: %s", e.msg); 584 // TODO: add a delgate to allow the user to handle error 585 } 586 } 587 }; 588 } 589 590 @disable void tick() @safe {} 591 592 protected override TResp buildResponseFromException(Exception e) @safe nothrow 593 { 594 auto response = new TResp(); 595 if (is(typeof(e) == JSONException)) 596 { 597 response.error = new JsonRpcError(JsonRpcError.StdCodes.parseError); 598 return response; 599 } 600 else 601 { 602 response.error = new JsonRpcError(JsonRpcError.StdCodes.internalError); 603 return response; 604 } 605 } 606 607 void registerInterface(I)(I instance, RpcInterfaceSettings settings = null) 608 { 609 import std.algorithm : filter, map, all; 610 import std.array : array; 611 import std.range : front; 612 import vibe.internal.meta.uda : findFirstUDA; 613 614 alias Info = InterfaceInfo!I; 615 616 foreach (i, Func; Info.Methods) { 617 enum methodNameAtt = findFirstUDA!(RpcMethodAttribute, Func); 618 enum smethod = Info.staticMethods[i]; 619 620 auto handler = jsonRpcMethodHandler!(TId, TReq, TResp, Func, i, I)(instance); 621 622 // select rpc name (attribute or function name): 623 static if (methodNameAtt.found) 624 this.registerRequestHandler(methodNameAtt.value.method, handler); 625 else 626 this.registerRequestHandler(smethod.name, handler); 627 628 } 629 } 630 } 631 632 /// 633 class TcpJsonRpcServer(TId, 634 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 635 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId): IJsonRpcServer!(TId, TReq, TResp) 636 { 637 import vibe.core.net : TCPConnection, TCPListener, listenTCP; 638 import vibe.stream.operations : readLine; 639 import std.conv : to; 640 641 private: 642 class ResponseWriter: JsonRpcRespHandler 643 { 644 private TCPConnection _conn; 645 646 this(TCPConnection conn) 647 { 648 _conn = conn; 649 } 650 651 void sendResponse(TResp reponse) 652 @safe { 653 logTrace("tcp request response: %s", reponse); 654 try { 655 _conn.write(reponse.toString() ~ _settings.linesep); 656 } catch (Exception e) { 657 logTrace("unable to send response: %s", e.msg); 658 // TODO: add a delgate to allow the user to handle error 659 } 660 } 661 } 662 663 class TCPClient 664 { 665 private: 666 TCPConnection _connection; 667 JsonRpcRequestHandler!(TId, TReq, TResp)[string] _requestHandler; 668 669 public /*properties*/: 670 @property auto conn() { return _connection; } 671 672 public: 673 this(TCPConnection connection) 674 { 675 _connection = connection; 676 } 677 678 void run() @safe nothrow 679 { 680 try { 681 auto writer = new ResponseWriter(_connection); 682 683 while (!_connection.empty) { 684 auto json = cast(const(char)[])_connection.readLine(size_t.max, _settings.linesep); 685 logTrace("tcp request received: %s", json); 686 687 this.process(json.to!string, writer); 688 } 689 } catch (Exception e) { 690 logError("Failed to read from client: %s", e.msg); 691 if (_settings !is null) 692 _settings.errorHandler(e); 693 } 694 } 695 696 void process(string data, JsonRpcRespHandler respHandler) @safe 697 { 698 Json json = parseJson(data); 699 700 void process(Json jsonObject) 701 { 702 auto request = deserializeJson!TReq(jsonObject); 703 if (request.method in _requestHandler) 704 { 705 _requestHandler[request.method](request, respHandler); 706 } 707 } 708 709 // batch of commands 710 if (json.type == Json.Type.array) 711 { 712 foreach(object; json.byValue) 713 { 714 process(object); 715 } 716 } 717 else 718 { 719 process(json); 720 } 721 } 722 723 void registerRequestHandler(string method, JsonRpcRequestHandler!(TId, TReq, TResp) handler) @safe nothrow 724 { 725 _requestHandler[method] = handler; 726 } 727 728 void registerInterface(I)(I instance) @safe nothrow 729 { 730 import std.algorithm : filter, map, all; 731 import std.array : array; 732 import std.range : front; 733 734 alias Info = InterfaceInfo!I; 735 736 foreach (i, Func; Info.Methods) { 737 enum smethod = Info.staticMethods[i]; 738 739 // normal handler 740 auto handler = jsonRpcMethodHandler!(TId, TReq, TResp, Func, i)(instance); 741 742 this.registerRequestHandler(smethod.name, handler); 743 } 744 745 } 746 } 747 748 alias FactoryDel(I) = I delegate(TCPConnection); 749 alias NewClientDel = void delegate(TCPClient) @safe nothrow; 750 alias JsonRpcRespHandler = IRpcServerOutput!TResp; 751 JsonRpcRequestHandler!(TId, TReq, TResp)[string] _requestHandler; 752 RpcInterfaceSettings _settings; 753 NewClientDel[] _newClientDelegates; 754 755 public: 756 this(ushort port, RpcInterfaceSettings settings = new RpcInterfaceSettings()) 757 { 758 _settings = settings; 759 760 listenTCP(port, (conn) @safe nothrow { 761 logTrace("new client: %s", conn); 762 auto client = new TCPClient(conn); 763 764 foreach(newClientDel; _newClientDelegates) 765 newClientDel(client); 766 767 client.run(); 768 769 conn.close(); 770 }); 771 } 772 773 void registerInterface(I)(I instance) 774 { 775 _newClientDelegates ~= (client) @safe nothrow { 776 client.registerInterface!I(instance); 777 }; 778 } 779 780 void registerInterface(I)(FactoryDel!I factory) 781 { 782 _newClientDelegates ~= (client) { 783 // instanciate an API for each client: 784 I instance = factory(client.conn); 785 786 client.registerInterface!I(instance); 787 }; 788 } 789 790 @disable void tick() @safe {} 791 792 void registerRequestHandler(string method, JsonRpcRequestHandler!(TId, TReq, TResp) handler) 793 { 794 _requestHandler[method] = handler; 795 } 796 797 void process(string data, JsonRpcRespHandler respHandler) 798 { 799 Json json = parseJson(data); 800 801 void process(Json jsonObject) 802 { 803 auto request = deserializeJson!TReq(jsonObject); 804 if (request.method in _requestHandler) 805 { 806 _requestHandler[request.method](request, respHandler); 807 } 808 } 809 810 // batch of commands 811 if (json.type == Json.Type.array) 812 { 813 foreach(object; json.byValue) 814 { 815 process(object); 816 } 817 } 818 else 819 { 820 process(json); 821 } 822 } 823 } 824 825 /// Return an handler to match a json-rpc request on an interface method. 826 package JsonRpcRequestHandler!(TId, TReq, TResp) jsonRpcMethodHandler(TId, TReq, TResp, alias Func, size_t n, T)(T inst) 827 { 828 import std.traits; 829 import std.meta : AliasSeq; 830 import std..string : format; 831 import vibe.utils..string : sanitizeUTF8; 832 import vibe.internal.meta.funcattr : IsAttributedParameter, computeAttributedParameterCtx; 833 import vibe.internal.meta.traits : derivedMethod; 834 import vibe.internal.meta.uda : findFirstUDA; 835 836 enum Method = __traits(identifier, Func); 837 alias PTypes = ParameterTypeTuple!Func; 838 alias PDefaults = ParameterDefaultValueTuple!Func; 839 alias CFuncRaw = derivedMethod!(T, Func); 840 static if (AliasSeq!(CFuncRaw).length > 0) alias CFunc = CFuncRaw; 841 else alias CFunc = Func; 842 alias RT = ReturnType!(FunctionTypeOf!Func); 843 static const sroute = InterfaceInfo!T.staticMethods[n]; 844 enum objectParamAtt = findFirstUDA!(RpcMethodObjectParams, Func); 845 846 void handler(TReq req, IRpcServerOutput!TResp serv) @safe 847 { 848 auto response = new TResp(); 849 response.id = req.id; 850 PTypes params; 851 852 // build a custom json error object tobe sent in json rpc error response. 853 Json buildErrorData(string details) 854 @safe { 855 auto json = Json.emptyObject; 856 json["details"] = details; 857 json["request"] = req.toJson(); 858 return json; 859 } 860 861 try { 862 // check params consistency beetween rpc-request and function parameters 863 static if (PTypes.length > 1 && !objectParamAtt.found) 864 { 865 // we expect a json array 866 if (req.params.type != Json.Type.array) 867 { 868 response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams); 869 response.error.data = buildErrorData("Expected a json array for params"); 870 serv.sendResponse(response); 871 return; 872 } 873 // req.params is a json array 874 else if (req.params.length != PTypes.length) 875 { 876 response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams); 877 response.error.data = buildErrorData("Missing params"); 878 serv.sendResponse(response); 879 return; 880 } 881 } 882 else if (PTypes.length > 1 && objectParamAtt.found) 883 { 884 // in object mode, check param count 885 if (req.params.type == Json.Type.object) 886 { 887 if (req.params.length != PTypes.length) 888 { 889 response.error = new JsonRpcError(JsonRpcError.StdCodes.invalidParams); 890 response.error.data = buildErrorData("Missing entry in params"); 891 serv.sendResponse(response); 892 return; 893 } 894 } 895 } 896 897 foreach (i, PT; PTypes) { 898 enum sparam = sroute.parameters[i]; 899 900 static if (!objectParamAtt.found) 901 { 902 enum pname = sparam.name; 903 auto fieldname = sparam.name; 904 905 params[i] = deserializeJson!PT(req.params[i]); 906 } 907 else 908 { 909 enum pname = sparam.name; 910 911 if (pname in objectParamAtt.value.names) 912 params[i] = deserializeJson!PT(req.params[objectParamAtt.value.names[pname]]); 913 else 914 params[i] = deserializeJson!PT(req.params[pname]); 915 } 916 } 917 } catch (Exception e) { 918 // handleException(e, HTTPStatus.badRequest); 919 return; 920 } 921 922 try { 923 import vibe.internal.meta.funcattr; 924 925 static if (!__traits(compiles, () @safe { __traits(getMember, inst, Method)(params); })) 926 pragma(msg, "Non-@safe methods are deprecated in REST interfaces - Mark "~T.stringof~"."~Method~" as @safe."); 927 928 static if (is(RT == void)) { 929 // TODO: return null 930 } else { 931 auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period 932 933 static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ())) 934 pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe."); 935 936 static if (!__traits(compiles, () @safe { res.writeJsonBody(ret); })) 937 pragma(msg, "Non-@safe serialization of REST return types deprecated - ensure that "~RT.stringof~" is safely serializable."); 938 () @trusted { 939 // build reponse 940 response.id = req.id; 941 response.result = serializeToJson(ret); 942 serv.sendResponse(response); 943 }(); 944 } 945 } 946 // catch user-defined json-rpc errors. 947 catch (JsonRpcUserException e) 948 { 949 response.error = new JsonRpcError(); 950 response.error.code = e.code; 951 response.error.message = e.msg; 952 response.error.data = e.data; 953 serv.sendResponse(response); 954 return; 955 } 956 catch (Exception e) { 957 response.error = new JsonRpcError(); 958 response.error.code = 0; 959 response.error.message = e.msg; 960 serv.sendResponse(response); 961 return; 962 } 963 } 964 965 return &handler; 966 } 967 968 /** Base class for creating a Json RPC automatic client. 969 */ 970 package class JsonRpcAutoClient(I, 971 TId, 972 TReq: JsonRpcRequest!TId=JsonRpcRequest!TId, 973 TResp: JsonRpcResponse!TId=JsonRpcResponse!TId) 974 { 975 import std.traits; 976 977 protected: 978 IRpcClient!(TId, TReq, TResp) _client; 979 RpcInterfaceSettings _settings; 980 981 ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS args) @safe 982 { 983 import vibe.internal.meta.uda : findFirstUDA; 984 import std.traits; 985 import std.array : appender; 986 import core.time; 987 import vibe.data.json; 988 989 // retrieve some compile time informations 990 // alias Info = RpcInterface!I; 991 alias RT = ReturnType!Func; 992 alias PTT = ParameterTypeTuple!Func; 993 alias PTN = ParameterIdentifierTuple!Func; 994 995 enum objectParamAtt = findFirstUDA!(RpcMethodObjectParams, Func); 996 enum methodNameAtt = findFirstUDA!(RpcMethodAttribute, Func); 997 // parameters are rendered as array if annotated with @rpcArrayParams 998 enum bool paramsAsArray = hasRpcArrayParams!Func || (PTT.length > 1); 999 1000 try 1001 { 1002 auto jsonParams = Json.undefined; 1003 1004 // Render params as unique param or array 1005 static if (!objectParamAtt.found) 1006 { 1007 // if several params, then build an a json array 1008 static if (paramsAsArray) 1009 jsonParams = Json.emptyArray; 1010 1011 // fill the json array or the unique value 1012 static foreach (i, PT; PTT) { 1013 static if (paramsAsArray) 1014 jsonParams.appendArrayElement(serializeToJson(args[i])); 1015 else 1016 jsonParams = serializeToJson(args[i]); 1017 } 1018 } 1019 // render params as a json object by using the param name 1020 // for the key or the uda if exists 1021 else 1022 { 1023 jsonParams = Json.emptyObject; 1024 1025 // fill object 1026 static foreach (i, PT; PTT) { 1027 if (PTN[i] in objectParamAtt.value.names) 1028 jsonParams[objectParamAtt.value.names[PTN[i]]] = serializeToJson(args[i]); 1029 else 1030 jsonParams[PTN[i]] = serializeToJson(args[i]); 1031 } 1032 } 1033 1034 1035 static if (!is(RT == void)) 1036 RT jret; 1037 1038 // create a json-rpc request 1039 auto request = new TReq(); 1040 1041 static if (methodNameAtt.found) 1042 request.method = methodNameAtt.value.method; 1043 else 1044 request.method = __traits(identifier, Func); 1045 1046 request.params = jsonParams; // set rpc call params 1047 1048 auto response = _client.sendRequestAndWait(request, _settings.responseTimeout); // send packet and wait 1049 1050 if (response.isError()) 1051 { 1052 throw new JsonRpcMethodException(response.error.get); 1053 } 1054 1055 // void return type 1056 static if (is(RT == void)) 1057 { 1058 1059 } 1060 else 1061 { 1062 return deserializeJson!RT(response.result.get); 1063 } 1064 } 1065 catch (JSONException e) 1066 { 1067 throw new RpcParsingException(e.msg, e); 1068 } 1069 catch (Exception e) 1070 { 1071 throw new RpcException(e.msg, e); 1072 } 1073 } 1074 1075 public: 1076 this(IRpcClient!(TId, TReq, TResp) client, RpcInterfaceSettings settings) 1077 { 1078 _client = client; 1079 _settings = settings; 1080 } 1081 1082 @property auto client() @safe { return _client; } 1083 } 1084 1085 /// 1086 package class JsonRpcAutoAttributeClient(I) : I 1087 { 1088 import autointf; 1089 1090 private: 1091 // compile-time 1092 // Extract RPC id type from attribute: 1093 static if (hasUDA!(I, RpcIdTypeAttribute!int)) 1094 alias TId = int; 1095 else static if (hasUDA!(I, RpcIdTypeAttribute!string)) 1096 alias TId = string; 1097 else 1098 alias TId = int; 1099 1100 protected: 1101 alias TReq = JsonRpcRequest!TId; 1102 alias TResp = JsonRpcResponse!TId; 1103 alias AutoClient(I) = JsonRpcAutoClient!(I, TId, TReq, TResp); 1104 alias RpcClient = IRpcClient!(TId, TReq, TResp); 1105 AutoClient!I _autoClient; 1106 1107 pragma(inline, true) 1108 ReturnType!Func executeMethod(alias Func, ARGS...)(ARGS args) @safe 1109 { 1110 return _autoClient.executeMethod!(Func, ARGS)(args); 1111 } 1112 1113 public: 1114 this(RpcClient client, RpcInterfaceSettings settings) @safe 1115 { 1116 _autoClient = new AutoClient!I(client, settings); 1117 } 1118 1119 pragma(inline, true) 1120 @property auto client() @safe { return _autoClient.client; } 1121 1122 mixin(autoImplementMethods!(I, executeMethod)()); 1123 } 1124 1125 /** Used to create an auto-implemented raw Rpc client from an interface. 1126 1127 --- 1128 auto input = `{"jsonrpc":"2.0","id":2,"result":` ~ to!string(getValue!int) ~ "}"; 1129 auto istream = createMemoryStream(input.toBytes()); 1130 auto ostream = createMemoryOutputStream(); 1131 1132 auto api = new RawJsonRpcAutoClient!IAPI(ostream, istream); 1133 1134 // test the client side: 1135 // inputstream not processed: timeout 1136 api.add(1, 2).shouldThrowExactly!RpcException; 1137 ostream.str.should.be == JsonRpcRequest!int.make(1, "add", [1, 2]).toString(); 1138 1139 // process input stream 1140 api.client.tick(); 1141 1142 // client must send a reponse 1143 api.add(1, 2).should.be == getValue!int; 1144 --- 1145 */ 1146 class RawJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I 1147 { 1148 import vibe.core.stream: InputStream, OutputStream; 1149 1150 public: 1151 this(OutputStream ostream, InputStream istream, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe 1152 { 1153 super(new RawJsonRpcClient!TId(ostream, istream), settings); 1154 } 1155 } 1156 1157 /** Used to create an auto-implemented Http Rpc client from an interface. 1158 1159 --- 1160 interface IAPI 1161 { 1162 void send(string data); 1163 } 1164 1165 auto client = new HttpJsonRpcAutoClient!IAPI("http://127.0.0.1:8080/rpc_2"); 1166 client.send("data"); 1167 --- 1168 */ 1169 class HttpJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I 1170 { 1171 public: 1172 this(string host, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe 1173 { 1174 super(new HttpJsonRpcClient!TId(host), settings); 1175 } 1176 } 1177 1178 /** Used to create an auto-implemented Tcp Rpc client from an interface. 1179 1180 --- 1181 interface IAPI 1182 { 1183 void send(string data); 1184 } 1185 1186 auto client = new TcpJsonRpcAutoClient!IAPI("http://127.0.0.1:8080/rpc_2"); 1187 client.send("data"); 1188 --- 1189 */ 1190 class TcpJsonRpcAutoClient(I) : JsonRpcAutoAttributeClient!I 1191 { 1192 public: 1193 this(string host, ushort port, RpcInterfaceSettings settings = new RpcInterfaceSettings()) @safe 1194 { 1195 super(new TcpJsonRpcClient!TId(host, port, settings), settings); 1196 } 1197 }