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 shared static this()
24 {
25     import dls.protocol.handlers : isHandler, pushHandler;
26     import std.algorithm : map;
27     import std.array : join, split;
28     import std.meta : AliasSeq;
29     import std.traits : hasUDA, select;
30     import std.typecons : tuple;
31     import std..string : capitalize;
32 
33     foreach (modName; AliasSeq!("general", "client", "text_document", "window", "workspace"))
34     {
35         mixin("import dls.protocol.messages" ~ (modName.length ? "." ~ modName : "") ~ ";");
36         mixin("alias mod = dls.protocol.messages" ~ (modName.length ? "." ~ modName : "") ~ ";");
37 
38         foreach (thing; __traits(allMembers, mod))
39         {
40             mixin("alias t = " ~ thing ~ ";");
41 
42             static if (isHandler!t)
43             {
44                 enum attrs = tuple(__traits(getAttributes, t));
45                 enum attrsWithDefaults = tuple(modName[0] ~ modName.split('_')
46                             .map!capitalize().join()[1 .. $], thing, attrs.expand);
47                 enum parts = tuple(attrsWithDefaults[attrs.length > 0 ? 2 : 0],
48                             attrsWithDefaults[attrs.length > 1 ? 3 : 1]);
49                 enum method = select!(parts[0].length != 0)(parts[0] ~ "/", "") ~ parts[1];
50 
51                 pushHandler(method, &t);
52             }
53         }
54     }
55 }
56 
57 final abstract class Server
58 {
59     import dls.protocol.interfaces : InitializeParams;
60 
61     static bool initialized;
62     static bool shutdown;
63     static bool exit;
64 
65     static void loop()
66     {
67         import dls.util.logger : logger;
68         import std.algorithm : findSplit;
69         import std.array : appender;
70         import std.conv : to;
71         import std.stdio : stdin;
72         import std..string : strip, stripRight;
73 
74         auto lineAppender = appender!(char[]);
75         auto charBuffer = new char[1];
76         string[string] headers;
77         string line;
78 
79         while (!stdin.eof && !exit)
80         {
81             headers.clear();
82 
83             do
84             {
85                 bool cr;
86                 bool lf;
87 
88                 lineAppender.clear();
89 
90                 do
91                 {
92                     auto res = stdin.rawRead(charBuffer);
93 
94                     if (res.length == 0)
95                     {
96                         break;
97                     }
98 
99                     lineAppender ~= res[0];
100 
101                     if (cr)
102                     {
103                         lf = res[0] == '\n';
104                     }
105 
106                     cr = res[0] == '\r';
107                 }
108                 while (!lf);
109 
110                 line = lineAppender.data.stripRight().to!string;
111                 auto parts = line.findSplit(":");
112 
113                 if (parts[1].length > 0)
114                 {
115                     headers[parts[0].to!string] = parts[2].to!string;
116                 }
117             }
118             while (line.length > 0);
119 
120             if (headers.length == 0)
121             {
122                 continue;
123             }
124 
125             if ("Content-Length" !in headers)
126             {
127                 logger.error("No valid Content-Length section in header");
128                 continue;
129             }
130 
131             static char[] buffer;
132             buffer.length = headers["Content-Length"].strip().to!size_t;
133             handleJSON(stdin.rawRead(buffer));
134         }
135     }
136 
137     private static void handleJSON(in char[] content)
138     {
139         import dls.protocol.handlers : HandlerNotFoundException,
140             NotificationHandler, RequestHandler, ResponseHandler, handler;
141         import dls.protocol.jsonrpc : ErrorCodes, InvalidParamsException,
142             NotificationMessage, RequestMessage, ResponseMessage, send,
143             sendError;
144         import dls.util.json : convertFromJSON;
145         import dls.util.logger : logger;
146         import std.json : JSONException, JSONValue, parseJSON;
147 
148         RequestMessage request;
149 
150         try
151         {
152             const json = parseJSON(content);
153 
154             if ("method" in json)
155             {
156                 if ("id" in json)
157                 {
158                     request = convertFromJSON!RequestMessage(json);
159 
160                     if (!shutdown && (initialized || request.method == "initialize"))
161                     {
162                         send(request.id, handler!RequestHandler(request.method)(request.params));
163                     }
164                     else
165                     {
166                         sendError(ErrorCodes.serverNotInitialized, request, JSONValue());
167                     }
168                 }
169                 else
170                 {
171                     auto notification = convertFromJSON!NotificationMessage(json);
172 
173                     if (initialized)
174                     {
175                         handler!NotificationHandler(notification.method)(notification.params);
176                     }
177                 }
178             }
179             else
180             {
181                 auto response = convertFromJSON!ResponseMessage(json);
182 
183                 if (response.error.isNull)
184                 {
185                     handler!ResponseHandler(response.id.str)(response.id.str, response.result);
186                 }
187                 else
188                 {
189                     logger.error(response.error.message);
190                 }
191             }
192         }
193         catch (JSONException e)
194         {
195             logger.errorf("%s: %s", ErrorCodes.parseError[0], e.message);
196             sendError(ErrorCodes.parseError, request, JSONValue(e.message));
197         }
198         catch (HandlerNotFoundException e)
199         {
200             logger.errorf("%s: %s", ErrorCodes.methodNotFound[0], e.message);
201             sendError(ErrorCodes.methodNotFound, request, JSONValue(e.message));
202         }
203         catch (InvalidParamsException e)
204         {
205             logger.errorf("%s: %s", ErrorCodes.invalidParams[0], e.message);
206             sendError(ErrorCodes.invalidParams, request, JSONValue(e.message));
207         }
208         catch (Exception e)
209         {
210             logger.errorf("%s: %s", ErrorCodes.internalError[0], e.message);
211             sendError(ErrorCodes.internalError, request, JSONValue(e.message));
212         }
213     }
214 }