1 /**********************************************************\
2 |                                                          |
3 |                          hprose                          |
4 |                                                          |
5 | Official WebSite: http://www.hprose.com/                 |
6 |                   http://www.hprose.org/                 |
7 |                                                          |
8 \**********************************************************/
9 
10 /**********************************************************\
11  *                                                        *
12  * hprose/rpc/client.d                                    *
13  *                                                        *
14  * hprose client library for D.                           *
15  *                                                        *
16  * LastModified: Jan 13, 2016                             *
17  * Author: Ma Bingyao <andot@hprose.com>                  *
18  *                                                        *
19 \**********************************************************/
20 
21 module hprose.rpc.client;
22 
23 import hprose.io;
24 import hprose.rpc.common;
25 import hprose.rpc.context;
26 import hprose.rpc.filter;
27 import std.conv;
28 import std.stdio;
29 import std.string;
30 import std.traits;
31 import std.typecons;
32 import std.variant;
33 
34 private {
35     pure string generateMethods(alias methods, T, string namespace)(string code) {
36         static if (methods.length > 0) {
37             alias STC = ParameterStorageClass;
38             enum m = methods[0];
39             foreach(mm; __traits(getVirtualMethods, T, m)) {
40                 string name = m;
41                 ResultMode mode = ResultMode.Normal;
42                 bool simple = false;
43                 code ~= "    override ";
44                 enum attrs = __traits(getAttributes, mm);
45                 foreach (attr; attrs) {
46                     static if (is(typeof(attr) == MethodName)) {
47                         name = attr.value;
48                     }
49                     else static if (is(typeof(attr) == ResultMode)) {
50                         mode = attr;
51                     }
52                     else static if (is(typeof(attr) == Simple)) {
53                         simple = attr.value;
54                     }
55                 }
56                 alias paramTypes = ParameterTypeTuple!(mm);
57                 alias paramStors = ParameterStorageClassTuple!(mm);
58                 alias paramIds = ParameterIdentifierTuple!(mm);
59                 alias paramValues = ParameterDefaultValueTuple!(mm);
60                 alias returntype = ReturnType!(mm);
61                 alias variadic = variadicFunctionStyle!(mm);
62                 code ~= returntype.stringof ~ " " ~ m ~ "(";
63                 bool byref = false;
64                 foreach(i, p; paramTypes) {
65                     static if (i > 0) {
66                         code ~= ", ";
67                     }
68                     static if (paramStors[i] == STC.out_ || paramStors[i] == STC.ref_ || paramStors[i] == STC.return_) {
69                         byref = true;
70                     }
71                     final switch (paramStors[i]) {
72                         case STC.none: break;
73                         case STC.scope_: code ~= "scope "; break;
74                         case STC.out_: code ~= "out "; break;
75                         case STC.ref_: code ~= "ref "; break;
76                         case STC.lazy_: code ~= "lazy "; break;
77                         case STC.return_: code ~= "return ref "; break;
78                     }
79                     static if (paramIds[i] != "") {
80                         code ~= p.stringof ~ " " ~ paramIds[i];
81                     }
82                     else {
83                         code ~= p.stringof ~ " arg" ~ to!string(i);
84                     }
85                     static if (!is(paramValues[i] == void)) {
86                         code ~= " = " ~ paramValues[i].stringof;
87                     }
88                 }
89                 static if (variadic == Variadic.typesafe) {
90                     code ~= "...";
91                 }
92                 code ~= ") {\n";
93                 static if (is(returntype == void)) {
94                     static if (paramTypes.length > 0 && is(paramTypes[$-1] == return)) {
95                         alias Callback = paramTypes[$-1];
96                         alias callbackParams = ParameterTypeTuple!Callback;
97                         static if (callbackParams.length == 0) {
98                             code ~= "        invoke(\"";
99                         }
100                         else static if (callbackParams.length == 1) {
101                             code ~= "        invoke!(";
102                         }
103                         else static if (callbackParams.length > 1) {
104                             foreach(s; ParameterStorageClassTuple!Callback) {
105                                 static if (s == STC.out_ || s == STC.ref_) {
106                                     byref = true;
107                                 }
108                             }
109                             code ~= "        invoke!(" ~ to!string(byref) ~ ", ";
110                         }
111                         else {
112                             static assert(0, "can't support this callback type: " ~ Callback.stringof);
113                         }
114                         static if (callbackParams.length > 0) {
115                             code ~= "ResultMode." ~ to!string(mode) ~ ", " ~
116                                 to!string(simple) ~ ")(\"";
117                         }
118                     }
119                     else {
120                         code ~= "        invoke!(" ~ returntype.stringof ~ ", " ~
121                             to!string(byref) ~ ", " ~
122                                 "ResultMode." ~ to!string(mode) ~ ", " ~
123                                 to!string(simple) ~ ")(\"";
124                     }
125                 }
126                 else {
127                     code ~= "        return invoke!(" ~ returntype.stringof ~ ", " ~
128                         to!string(byref) ~ ", " ~
129                             "ResultMode." ~ to!string(mode) ~ ", " ~
130                             to!string(simple) ~ ")(\"";
131                 }
132                 static if (namespace != "") {
133                     code ~= namespace ~ "_";
134                 }
135                 code ~= name ~ "\"" ;
136                 foreach(i, id; paramIds) {
137                     static if (id != "") {
138                         code ~= ", " ~ id;
139                     }
140                     else {
141                         code ~= ", arg" ~ to!string(i);
142                     }
143                 }
144                 code ~= ");\n";
145                 code ~= "    }\n";
146             }
147             static if (methods.length > 1) {
148                 code = generateMethods!(tuple(methods[1..$]), T, namespace)(code);
149             }
150         }
151         return code;
152     }
153 
154     pure string generate(T, string namespace)() {
155         return generateMethods!(getAbstractMethods!(T), T, namespace)("new class T {\n") ~ "}\n";
156     }
157 
158     pure string asyncInvoke(bool byref, bool hasresult, bool hasargs)() {
159         string code = "foreach(T; Args) static assert(isSerializable!T);\n";
160         code ~= "try {\n";
161         code ~= "    auto context = new Context();\n";
162         code ~= "    auto request = doOutput!(" ~ to!string(byref) ~ ", simple)(name, context, args);\n";
163         code ~= "    sendAndReceive(request, delegate(ubyte[] response) {\n";
164         code ~= "        try {\n";
165         code ~= "            auto result = doInput!(Result, mode)(response, context, args);\n";
166         code ~= "            if (callback !is null) {\n";
167         code ~= "                callback(" ~ (hasresult ? "result" ~ (hasargs ? ", args" : "") : "") ~ ");\n";
168         code ~= "            }\n";
169         code ~= "        }\n";
170         code ~= "        catch(Exception e) {\n";
171         code ~= "            if (onError !is null) onError(name, e);\n";
172         code ~= "        }\n";
173         code ~= "    });\n";
174         code ~= "}\n";
175         code ~= "catch(Exception e) {\n";
176         code ~= "    if (onError !is null) onError(name, e);\n";
177         code ~= "}\n";
178         return code;
179     }
180 }
181 
182 abstract class Client {
183     private {
184         Filter[] _filters;
185         ubyte[] doOutput(bool byref, bool simple, Args...)(string name, Context context, ref Args args) {
186             auto bytes = new BytesIO();
187             auto writer = new Writer(bytes, simple);
188             bytes.write(TagCall);
189             writer.writeString(name);
190             if (args.length > 0 || byref) {
191                 writer.reset();
192                 writer.writeTuple(args);
193                 static if (byref) {
194                     writer.writeBool(true);
195                 }
196             }
197             bytes.write(TagEnd);
198             auto request = cast(ubyte[])(bytes.buffer);
199             bytes.close();
200             foreach(filter; _filters) {
201                 request = filter.outputFilter(request, context);
202             }
203             return request;
204         }
205         Result doInput(Result, ResultMode mode, Args...)(ubyte[]response, Context context, ref Args args) if (mode == ResultMode.Normal || is(Result == ubyte[])) {
206             foreach_reverse(filter; _filters) {
207                 response = filter.inputFilter(response, context);
208             }
209             static if (mode == ResultMode.RawWithEndTag) {
210                 return response;
211             }
212             else static if (mode == ResultMode.Raw) {
213                 return response[0..$-1];
214             }
215             else {
216                 auto bytes = new BytesIO(response);
217                 auto reader = new Reader(bytes);
218                 Result result;
219                 char tag;
220                 while((tag = bytes.read()) != TagEnd) {
221                     switch(tag) {
222                         case TagResult: {
223                             static if (mode == ResultMode.Serialized) {
224                                 result = cast(ubyte[])(reader.readRaw().buffer);
225                             }
226                             else {
227                                 reader.reset();
228                                 result = reader.unserialize!Result();
229                             }
230                             break;
231                         }
232                         case TagArgument: {
233                             reader.reset();
234                             reader.readTuple(args);
235                             break;
236                         }
237                         case TagError: {
238                             reader.reset();
239                             throw new Exception(reader.unserialize!string());
240                         }
241                         default: {
242                             throw new Exception("Wrong Response: \r\n" ~ cast(string)response);
243                         }
244                     }
245                 }
246                 bytes.close();
247                 return result;
248             }
249         }
250     }
251     protected {
252         string uri;
253         abstract ubyte[] sendAndReceive(ubyte[] request);
254         abstract void sendAndReceive(ubyte[] request, void delegate(ubyte[]) callback);
255     }
256 
257     void delegate(string name, Exception e) onError = null;
258 
259     this(string uri = "") {
260         this.uri = uri;
261         this._filters = [];
262     }
263     void useService(string uri = "") {
264         if (uri != "") {
265             this.uri = uri;
266         }
267     }
268     T useService(T, string namespace = "")(string uri = "") if (is(T == interface) || is(T == class)) {
269         useService(uri);
270         return mixin(generate!(T, namespace));
271     }
272     Result invoke(Result, bool byref = false, ResultMode mode = ResultMode.Normal, bool simple = false, Args...)
273         (string name, Args args) if (args.length > 0 && byref == false && !is(typeof(args[$-1]) == return) &&
274         (mode == ResultMode.Normal || is(Result == ubyte[]))) {
275         static if (is(Result == void)) {
276             invoke!(Result, byref, mode, simple)(name, args);
277         }
278         else {
279             return invoke!(Result, byref, mode, simple)(name, args);
280         }
281     }
282     Result invoke(Result, bool byref = false, ResultMode mode = ResultMode.Normal, bool simple = false, Args...)
283         (string name, ref Args args) if (((args.length == 0) || !is(typeof(args[$-1]) == return)) &&
284         (mode == ResultMode.Normal || is(Result == ubyte[]))) {
285         foreach(T; Args) static assert(isSerializable!(T));
286         auto context = new Context();
287         auto request = doOutput!(byref, simple)(name, context, args);
288         static if (is(Result == void)) {
289             doInput!(Variant, mode)(sendAndReceive(request), context, args);
290         }
291         else {
292             return doInput!(Result, mode)(sendAndReceive(request), context, args);
293         }
294     }
295     void invoke(Args...)
296     (string name, Args args, void delegate() callback) {
297         alias Result = Variant;
298         enum mode = ResultMode.Normal;
299         enum simple = false;
300         mixin(asyncInvoke!(false, false, false));
301     }
302     void invoke(ResultMode mode = ResultMode.Normal, bool simple = false, Callback, Args...)
303     (string name, Args args, Callback callback) if (is(Callback R == void delegate(R)) && (mode == ResultMode.Normal || is(R == ubyte[]))) {
304         alias Result = ParameterTypeTuple!callback[0];
305         mixin(asyncInvoke!(false, true, false));
306     }
307     void invoke(bool byref = false, ResultMode mode = ResultMode.Normal, bool simple = false, Result, Args...)
308     (string name, Args args, void delegate(Result result, Args args) callback) if (args.length > 0 && (mode == ResultMode.Normal || is(Result == ubyte[]))) {
309         mixin(asyncInvoke!(byref, true, true));
310     }
311     void invoke(bool byref = true, ResultMode mode = ResultMode.Normal, bool simple = false, Result, Args...)
312     (string name, ref Args args, void delegate(Result result, ref Args args) callback) if (args.length > 0 && (mode == ResultMode.Normal || is(Result == ubyte[]))) {
313         mixin(asyncInvoke!(byref, true, true));
314     }
315     void invoke(Args...)
316     (string name, Args args, void function() callback) {
317         alias Result = Variant;
318         enum mode = ResultMode.Normal;
319         enum simple = false;
320         mixin(asyncInvoke!(false, false, false));
321     }
322     void invoke(ResultMode mode = ResultMode.Normal, bool simple = false, Callback, Args...)
323     (string name, Args args, Callback callback) if (is(Callback R == void function(R)) && (mode == ResultMode.Normal || is(R == ubyte[]))) {
324         alias Result = ParameterTypeTuple!callback[0];
325         mixin(asyncInvoke!(false, true, false));
326     }
327     void invoke(bool byref = false, ResultMode mode = ResultMode.Normal, bool simple = false, Result, Args...)
328     (string name, Args args, void function(Result result, Args args) callback) if (args.length > 0 && (mode == ResultMode.Normal || is(Result == ubyte[]))) {
329         mixin(asyncInvoke!(byref, true, true));
330     }
331     void invoke(bool byref = true, ResultMode mode = ResultMode.Normal, bool simple = false, Result, Args...)
332     (string name, ref Args args, void function(Result result, ref Args args) callback) if (args.length > 0 && (mode == ResultMode.Normal || is(Result == ubyte[]))) {
333         mixin(asyncInvoke!(byref, true, true));
334     }
335     @property ref filters() {
336         return this._filters;
337     }
338 }