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/httpservice.d                               *
13  *                                                        *
14  * hprose http service library for D.                     *
15  *                                                        *
16  * LastModified: Aug 3, 2016                              *
17  * Author: Ma Bingyao <andot@hprose.com>                  *
18  *                                                        *
19 \**********************************************************/
20 
21 module hprose.rpc.httpservice;
22 
23 import hprose.rpc.service;
24 import hprose.rpc.common;
25 import hprose.rpc.httpcontext;
26 import std.conv;
27 import std.file;
28 import std.stdio;
29 import std.traits;
30 import std.typecons;
31 import std.variant;
32 import vibe.http.router;
33 import vibe.http.server;
34 import vibe.stream.operations;
35 
36 alias OnSendHeader = void delegate(HttpContext context);
37 
38 class HttpService: Service {
39     private {
40         bool[string] origins;
41 
42         void sendHeader(HttpContext context) {
43             if (onSendHeader !is null) {
44                 onSendHeader(context);
45             }
46             HTTPServerRequest req = context.request;
47             HTTPServerResponse res = context.response;
48             res.headers["Content-Type"] = "text/plain";
49             if (p3p) {
50                 res.headers["P3P"] = "CP=\"CAO DSP COR CUR ADM DEV TAI PSA PSD " ~
51                     "IVAi IVDi CONi TELo OTPi OUR DELi SAMi " ~
52                     "OTRi UNRi PUBi IND PHY ONL UNI PUR FIN " ~
53                     "COM NAV INT DEM CNT STA POL HEA PRE GOV\"";
54             }
55             if (crossDomain) {
56                 string origin = req.headers["Origin"];
57                 if (origin != "" && origin != "null") {
58                     if (origins.length == 0 || origin in origins) {
59                         res.headers["Access-Control-Allow-Origin"] = origin;
60                         res.headers["Access-Control-Allow-Credentials"] = "true";
61                     }
62                 }
63                 else {
64                     res.headers["Access-Control-Allow-Origin"] = "*";
65                 }
66             }
67         }
68     }
69 
70     bool get;
71     bool p3p;
72     bool crossDomain;
73     OnSendHeader onSendHeader;
74 
75     void handler(HTTPServerRequest req, HTTPServerResponse res) {
76         HttpContext context = new HttpContext(req, res);
77         sendHeader(context);
78         switch(req.method) {
79             case HTTPMethod.GET: {
80                 if (get) {
81                     res.writeBody(doFunctionList());
82                 }
83                 else {
84                     res.statusCode = HTTPStatus.forbidden;
85                 }
86                 break;
87             }
88             case HTTPMethod.POST: {
89                 res.writeBody(handle(req.bodyReader.readAll(), context));
90                 break;
91             }
92             default: break;
93         }
94     }
95 }
96 
97 class HttpServer: HttpService {
98     private {
99         string _crossDomainXmlFile;
100         string _clientAccessPolicyXmlFile;
101         string _lastModified = null;
102         string _etag = null;
103     }
104 
105     @property string crossDomainXmlFile() {
106         return _crossDomainXmlFile;
107     }
108 
109     @property string crossDomainXmlFile(string file) {
110         _crossDomainXmlFile = file;
111         crossDomainXmlContext = readText(file);
112         return file;
113     }
114 
115     @property string clientAccessPolicyXmlFile() {
116         return _clientAccessPolicyXmlFile;
117     }
118     
119     @property string clientAccessPolicyXmlFile(string file) {
120         _clientAccessPolicyXmlFile = file;
121         clientAccessPolicyXmlContent = readText(file);
122         return file;
123     }
124     
125     string crossDomainXmlContext;
126     string clientAccessPolicyXmlContent;
127     HTTPServerSettings settings = new HTTPServerSettings();
128 
129     void crossDomainXmlHandler(HTTPServerRequest req, HTTPServerResponse res) {
130         if (req.headers["If-Modified-Since"] == _lastModified &&
131             req.headers["If-None-Match"] == _etag) {
132             res.statusCode = HTTPStatus.notModified;
133         }
134         else {
135             res.headers["Last-Modified"] = _lastModified;
136             res.headers["Etag"] = _etag;
137             res.writeBody(crossDomainXmlContext, "text/xml");
138         }
139     }
140 
141     void clientAccessPolicyXmlHandler(HTTPServerRequest req, HTTPServerResponse res) {
142         if (req.headers["If-Modified-Since"] == _lastModified &&
143             req.headers["If-None-Match"] == _etag) {
144             res.statusCode = HTTPStatus.notModified;
145         }
146         else {
147             res.headers["Last-Modified"] = _lastModified;
148             res.headers["Etag"] = _etag;
149             res.writeBody(clientAccessPolicyXmlContent, "text/xml");
150         }
151     }
152 
153     HTTPListener start(string path = "/") {
154         URLRouter router = new URLRouter();
155         router.get("/crossdomain.xml", &crossDomainXmlHandler);
156         router.get("/clientaccesspolicy.xml", &clientAccessPolicyXmlHandler);
157         router.any(path, &handler);
158 
159         return listenHTTP(settings, router);
160     }
161 }
162 
163 unittest {
164     import hprose.rpc.httpclient;
165     import hprose.rpc.context;
166     import hprose.rpc.filter;
167     import std.datetime;
168 
169     string hello(string name) {
170         return "hello " ~ name ~ "!";
171     }
172 
173     string goodbye(string name) {
174         return "goodbye " ~ name ~ "!";
175     }
176 
177     Variant missfunc(string name, Variant[] args) {
178         if (name == "mul") {
179             return args[0] * args[1];
180         }
181         else if (name == "div") {
182             return args[0] / args[1];
183         }
184         else {
185             return Variant(null);
186         }
187     }
188 
189     int inc(ref int n, HttpContext context) {
190         auto req = context.request;
191         auto res = context.response;
192         n++;
193         return n;
194     }
195 
196     class BaseTest {
197         int add(int a, int b) {
198             return a + b;
199         }
200         int sub(int a, int b) {
201             return a - b;
202         }
203     }
204     class Test: BaseTest {
205         int sum(int[] nums) {
206             int sum = 0;
207             foreach (x; nums) {
208                 sum += x;
209             }
210             return sum;
211         }
212         static string[] test() {
213             return ["Tom", "Jerry"];
214         }
215         static Variant[string] test2() {
216             return ["name": Variant("张三"), "age": Variant(18)];
217         }
218     }
219 
220     Test test = new Test();
221 
222     // Server
223     HttpServer server = new HttpServer();
224     server.add!("hello")(&hello);
225     server.add!(["goodbye", "inc"])(&goodbye, &inc);
226     server.add!(["add", "sub", "sum"])(test);
227     server.add!("test", Test)(); // add Test.test method to the server
228     server.add!(Test, "test")(); // add all static methods on Test with prefix "test" to the server
229     server.addMissingFunction(&missfunc);
230     server.use(delegate Variant(string name, ref Variant[] args, Context context, NextInvokeHandler next) {
231             writeln(name);
232             writeln(args);
233             Variant result = next(name, args, context);
234             writeln(result);
235             return result;
236         }, delegate Variant(string name, ref Variant[] args, Context context, NextInvokeHandler next) {
237             writeln(Clock.currStdTime());
238             Variant result = next(name, args, context);
239             writeln(Clock.currStdTime());
240             return result;
241         });
242     server.use!"beforeFilter"(delegate ubyte[](ubyte[] request, Context context, NextFilterHandler next) {
243             writeln("beforeFilter");
244             writeln(cast(string)request);
245             ubyte[] response = next(request, context);
246             writeln("beforeFilter");
247             writeln(cast(string)response);
248             writeln();
249             return response;
250         });
251     server.use!"afterFilter"(delegate ubyte[](ubyte[] request, Context context, NextFilterHandler next) {
252             writeln("afterFilter");
253             writeln(cast(string)request);
254             ubyte[] response = next(request, context);
255             writeln("afterFilter");
256             writeln(cast(string)response);
257             return response;
258         });
259     server.settings.bindAddresses = ["127.0.0.1"];
260     server.settings.port = 4444;
261     server.settings.sessionStore = new MemorySessionStore();
262     server.start();
263 
264     // Client
265     interface Hello {
266         @Simple() string hello(string name);
267         string goodbye(string name);
268         int add(int a, int b);
269         int sub(int a, int b = 3);
270         int mul(int a, int b);
271         int div(int a, int b);
272         int sum(int[] nums...);
273         int inc(ref int n);
274         string[] test();
275         Variant[string] test2();
276     }
277 
278     auto client = new HttpClient("http://127.0.0.1:4444/");
279     Hello proxy = client.useService!Hello();
280 
281     Hello proxy2 = client.useService!(Hello, "test")();
282 
283 //    client.filters ~= new class Filter {
284 //        override ubyte[] inputFilter(ubyte[] data, Context context) {
285 //            writeln(cast(string)data);
286 //            return data;
287 //        }
288 //
289 //        override ubyte[] outputFilter(ubyte[] data, Context context) {
290 //            writeln(cast(string)data);
291 //            return data;
292 //        };
293 //    };
294 
295     assert(proxy.hello("world") == "hello world!");
296     assert(proxy.goodbye("world") == "goodbye world!");
297     assert(proxy.add(1, 2) == 3);
298     assert(proxy.sub(1, 2) == -1);
299     assert(proxy.mul(1, 2) == 2);
300     assert(proxy.div(2, 2) == 1);
301     assert(proxy.sum(1, 2, 3) == 6);
302     assert(proxy.test() == ["Tom", "Jerry"]);
303     int n = 0;
304     assert(proxy.inc(n) == 1);
305     assert(proxy.inc(n) == 2);
306     assert(proxy.inc(n) == 3);
307     assert(n == 3);
308     assert(proxy2.test() == ["Tom", "Jerry"]);
309     assert(proxy2.test2() == ["name": Variant("张三"), "age": Variant(18)]);
310 
311 }