1 /*
2  *Copyright (C) 2018 Laurent Tréguier
3  *
4  *This file is part of DLS.
5  *
6  *DLS is free software: you can redistribute it and/or modify
7  *it under the terms of the GNU General Public License as published by
8  *the Free Software Foundation, either version 3 of the License, or
9  *(at your option) any later version.
10  *
11  *DLS is distributed in the hope that it will be useful,
12  *but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *GNU General Public License for more details.
15  *
16  *You should have received a copy of the GNU General Public License
17  *along with DLS.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 module dls.server;
22 
23 private immutable contentLengthHeaderName = "Content-Length";
24 
25 shared static this()
26 {
27     import dls.protocol.handlers : pushHandler;
28     import dls.util.setup : initialSetup;
29     import std.algorithm : map;
30     import std.array : join, split;
31     import std.meta : Alias, AliasSeq;
32     import std.traits : hasUDA, isSomeFunction, select;
33     import std.typecons : tuple;
34     import std..string : capitalize;
35 
36     initialSetup();
37 
38     foreach (modName; AliasSeq!("general", "client", "text_document", "window",
39             "workspace", "other"))
40     {
41         mixin("import dls.protocol.messages." ~ modName ~ ";");
42         mixin("alias mod = dls.protocol.messages." ~ modName ~ ";");
43 
44         foreach (thing; __traits(allMembers, mod))
45         {
46             alias t = Alias!(__traits(getMember, mod, thing));
47 
48             static if (__traits(getProtection, t) == "public" && isSomeFunction!t)
49             {
50                 enum attrs = tuple(__traits(getAttributes, t));
51                 enum attrsWithDefaults = tuple(modName[0] ~ modName.split('_')
52                             .map!capitalize().join()[1 .. $], thing, attrs.expand);
53                 enum parts = tuple(attrsWithDefaults[attrs.length > 0 ? 2 : 0],
54                             attrsWithDefaults[attrs.length > 1 ? 3 : 1]);
55                 enum method = select!(parts[0].length != 0)(parts[0] ~ "/", "") ~ parts[1];
56 
57                 pushHandler(method, &t);
58             }
59         }
60     }
61 }
62 
63 final abstract class Server
64 {
65     import dls.protocol.interfaces : InitializeParams;
66     import dls.util.disposable_fiber : DisposableFiber;
67     import std.container : DList;
68     import std.json : JSONValue;
69 
70     static bool exit;
71     private static bool _initialized;
72     private static DisposableFiber[string] _requestsFibers;
73     private static DList!DisposableFiber _fibers;
74 
75     @property static bool initialized()
76     {
77         return _initialized;
78     }
79 
80     @property static void initialized(bool i)
81     {
82         import dls.protocol.logger : logger;
83 
84         _initialized = i;
85 
86         if (!_initialized)
87         {
88             logger.info("Cancelling pending requests");
89 
90             foreach (fiber; _requestsFibers.byValue)
91             {
92                 fiber.dispose();
93             }
94         }
95     }
96 
97     static void cancel(JSONValue id)
98     {
99         import dls.protocol.logger : logger;
100 
101         immutable idString = id.toString();
102 
103         if (idString in _requestsFibers
104                 && _requestsFibers[idString].state != DisposableFiber.State.TERM)
105         {
106             logger.info("Cancelling request %s", idString);
107             _requestsFibers[idString].dispose();
108         }
109     }
110 
111     static void loop()
112     {
113         import dls.protocol.logger : logger;
114         import dls.protocol.messages.general : shutdown;
115         import dls.util.communicator : communicator;
116         import std.algorithm : findSplit;
117         import std.array : appender;
118         import std.conv : to;
119         import std..string : strip, stripRight;
120 
121         auto lineAppender = appender!(char[]);
122         string[string] headers;
123         string line;
124 
125         while (!exit && communicator.hasData())
126         {
127             headers.clear();
128 
129             do
130             {
131                 bool cr;
132                 bool lf;
133 
134                 lineAppender.clear();
135 
136                 do
137                 {
138                     auto res = communicator.read(1);
139 
140                     if (res.length == 0)
141                     {
142                         break;
143                     }
144 
145                     lineAppender ~= res[0];
146 
147                     if (cr)
148                     {
149                         lf = res[0] == '\n';
150                     }
151 
152                     cr = res[0] == '\r';
153                 }
154                 while (!lf);
155 
156                 line = lineAppender.data.stripRight().idup;
157                 auto parts = line.findSplit(":");
158 
159                 if (parts[1].length > 0)
160                 {
161                     headers[parts[0]] = parts[2];
162                 }
163             }
164             while (line.length > 0);
165 
166             if (headers.length == 0)
167             {
168                 continue;
169             }
170 
171             if (contentLengthHeaderName !in headers)
172             {
173                 logger.error("No valid %s section in header", contentLengthHeaderName);
174                 continue;
175             }
176 
177             const content = communicator.read(headers[contentLengthHeaderName].strip().to!size_t);
178             auto fiber = new DisposableFiber({ handleJSON(content); });
179 
180             fiber.call();
181             _fibers.insertBack(fiber);
182 
183             while (!_initialized || !communicator.hasPendingData())
184             {
185                 while (!_fibers.empty && _fibers.front.state == DisposableFiber.State.TERM)
186                 {
187                     foreach (id, f; _requestsFibers)
188                     {
189                         if (f == _fibers.front)
190                         {
191                             _requestsFibers.remove(id);
192                             break;
193                         }
194                     }
195 
196                     _fibers.removeFront();
197                 }
198 
199                 if (_fibers.empty)
200                 {
201                     break;
202                 }
203                 else
204                 {
205                     _fibers.front.call();
206                 }
207             }
208         }
209 
210         if (_initialized)
211         {
212             logger.warning("No shutdown request or exit notification received");
213             shutdown(JSONValue());
214             _initialized = true;
215         }
216     }
217 
218     private static void handleJSON(const char[] content)
219     {
220         import dls.protocol.errors : InvalidParamsException;
221         import dls.protocol.handlers : HandlerNotFoundException,
222             NotificationHandler, RequestHandler, ResponseHandler, handler;
223         import dls.protocol.jsonrpc : ErrorCodes, NotificationMessage,
224             RequestMessage, ResponseMessage, send, sendError;
225         import dls.protocol.logger : logger;
226         import dls.protocol.state : initOptions;
227         import dls.util.disposable_fiber : FiberDisposedException;
228         import dls.util.json : convertFromJSON;
229         import std.algorithm : startsWith;
230         import std.json : JSONException, parseJSON;
231 
232         RequestMessage request;
233         NotificationMessage notification;
234 
235         void findAndExecuteHandler()
236         {
237             try
238             {
239                 immutable json = parseJSON(content);
240 
241                 if ("method" in json)
242                 {
243                     if ("id" in json)
244                     {
245                         request = convertFromJSON!RequestMessage(json);
246                         _requestsFibers[request.id.toString()] = DisposableFiber.getThis();
247                         logger.log("Received request %s: %s", request.id, request.method);
248 
249                         if (_initialized || request.method == "initialize")
250                         {
251                             send(request.id,
252                                     handler!RequestHandler(request.method)(request.params));
253                         }
254                         else
255                         {
256                             sendError(ErrorCodes.serverNotInitialized, request, JSONValue());
257                         }
258                     }
259                     else
260                     {
261                         notification = convertFromJSON!NotificationMessage(json);
262                         logger.log("Received notification: %s", notification.method);
263 
264                         if (_initialized || notification.method == "exit")
265                         {
266                             handler!NotificationHandler(notification.method)(notification.params);
267                         }
268                     }
269                 }
270                 else
271                 {
272                     auto response = convertFromJSON!ResponseMessage(json);
273                     logger.log("Received response for request %s", response.id);
274 
275                     if (response.error.isNull)
276                     {
277                         handler!ResponseHandler(response.id.str)(response.id.str, response.result);
278                     }
279                     else
280                     {
281                         logger.error(response.error.message);
282                     }
283                 }
284             }
285             catch (FiberDisposedException)
286             {
287                 sendError(ErrorCodes.requestCancelled, request, JSONValue());
288             }
289             catch (JSONException e)
290             {
291                 logger.error("%s: %s", ErrorCodes.parseError[0], e.toString());
292                 sendError(ErrorCodes.parseError, request, JSONValue(e.toString()));
293             }
294             catch (HandlerNotFoundException e)
295             {
296                 if (notification is null || !notification.method.startsWith("$/"))
297                 {
298                     logger.error("%s: %s", ErrorCodes.methodNotFound[0], e.toString());
299                     sendError(ErrorCodes.methodNotFound, request, JSONValue(e.toString()));
300                 }
301             }
302             catch (InvalidParamsException e)
303             {
304                 logger.error("%s: %s", ErrorCodes.invalidParams[0], e.toString());
305                 sendError(ErrorCodes.invalidParams, request, JSONValue(e.toString()));
306             }
307             catch (Exception e)
308             {
309                 logger.error("%s: %s", ErrorCodes.internalError[0], e.toString());
310                 sendError(ErrorCodes.internalError, request, JSONValue(e.toString()));
311             }
312         }
313 
314         if (initOptions.catchErrors)
315         {
316             try
317             {
318                 findAndExecuteHandler();
319             }
320             catch (Error e)
321             {
322                 logger.error("%s: %s", ErrorCodes.internalError[0], e.toString());
323                 sendError(ErrorCodes.internalError, request, JSONValue(e.toString()));
324             }
325         }
326         else
327         {
328             findAndExecuteHandler();
329         }
330     }
331 }