1 module served.jsonrpc; 2 3 import core.thread; 4 import core.exception; 5 6 import painlessjson; 7 8 import std.container : DList, SList; 9 import std.conv; 10 import std.experimental.logger; 11 import std.json; 12 import std.stdio; 13 import std.string; 14 import std.typecons; 15 16 import served.protocol; 17 import served.filereader; 18 19 import tinyevent; 20 21 alias RequestHandler = ResponseMessage delegate(RequestMessage); 22 alias EventRequestHandler = void delegate(RequestMessage); 23 24 class RPCProcessor : Fiber 25 { 26 this(FileReader reader, File writer) 27 { 28 super(&run, 4096 * 16); 29 this.reader = reader; 30 this.writer = writer; 31 } 32 33 void stop() 34 { 35 stopped = true; 36 } 37 38 void send(ResponseMessage res) 39 { 40 auto msg = JSONValue(["jsonrpc" : JSONValue("2.0")]); 41 if (res.id.hasData) 42 msg["id"] = res.id.toJSON; 43 if (res.result.type != JSON_TYPE.NULL) 44 msg["result"] = res.result; 45 if (!res.error.isNull) 46 { 47 msg["error"] = res.error.toJSON; 48 stderr.writeln(msg["error"]); 49 } 50 send(msg); 51 } 52 53 void send(RequestMessage req) 54 { 55 send(req.toJSON); 56 } 57 58 void send(JSONValue raw) 59 { 60 if (!("jsonrpc" in raw)) 61 { 62 error(raw); 63 throw new Exception("Sent objects must have a jsonrpc"); 64 } 65 const content = raw.toString().replace("\\/", "/"); 66 // Log on client side instead! (vscode setting: "serve-d.trace.server": "verbose") 67 //trace(content); 68 string data = "Content-Length: " ~ content.length.to!string ~ "\r\n\r\n" ~ content; 69 writer.rawWrite(data); 70 writer.flush(); 71 } 72 73 void notifyMethod(string method) 74 { 75 RequestMessage req; 76 req.method = method; 77 send(req); 78 } 79 80 void notifyMethod(T)(string method, T value) 81 { 82 RequestMessage req; 83 req.method = method; 84 req.params = value.toJSON; 85 send(req); 86 } 87 88 void sendMethod(string method) 89 { 90 RequestMessage req; 91 req.id = RequestToken.random; 92 req.method = method; 93 send(req); 94 } 95 96 void sendMethod(T)(string method, T value) 97 { 98 RequestMessage req; 99 req.id = RequestToken.random; 100 req.method = method; 101 req.params = value.toJSON; 102 send(req); 103 } 104 105 ResponseMessage sendRequest(T)(string method, T value) 106 { 107 RequestMessage req; 108 req.id = RequestToken.random; 109 req.method = method; 110 req.params = value.toJSON; 111 send(req); 112 return awaitResponse(req.id); 113 } 114 115 void log(MessageType type = MessageType.log, Args...)(Args args) 116 { 117 send(JSONValue(["jsonrpc" : JSONValue("2.0"), "method" 118 : JSONValue("window/logMessage"), "params" : args.toJSON])); 119 } 120 121 bool hasData() 122 { 123 return !messageQueue.empty; 124 } 125 126 RequestMessage poll() 127 { 128 if (!hasData) 129 throw new Exception("No Data"); 130 auto ret = messageQueue.front; 131 messageQueue.removeFront(); 132 return ret; 133 } 134 135 bool running = true; 136 137 WindowFunctions window() 138 { 139 return WindowFunctions(this); 140 } 141 142 ResponseMessage awaitResponse(RequestToken tok) 143 { 144 size_t i; 145 bool found = false; 146 foreach (n, t; responseTokens) 147 { 148 if (t.handled) 149 { 150 // replace handled responses (overwrite reusable memory) 151 i = n; 152 found = true; 153 break; 154 } 155 } 156 if (!found) 157 i = responseTokens.length++; 158 responseTokens[i] = RequestWait(tok); 159 while (!responseTokens[i].got) 160 yield(); // yield until main loop placed a response 161 auto res = responseTokens[i].ret; 162 responseTokens[i].handled = true; // make memory reusable 163 return res; 164 } 165 166 private: 167 void onData(RequestMessage req) 168 { 169 messageQueue.insertBack(req); 170 } 171 172 FileReader reader; 173 File writer; 174 bool stopped; 175 DList!RequestMessage messageQueue; 176 177 struct RequestWait 178 { 179 RequestToken token; 180 bool got = false; 181 bool handled = false; 182 ResponseMessage ret; 183 } 184 185 RequestWait[] responseTokens; 186 187 void run() 188 { 189 while (!stopped) 190 { 191 bool inHeader = true; 192 size_t contentLength = 0; 193 do // dmd -O has an issue on mscoff where it forgets to emit a cmp here so this would break with while (inHeader) 194 { 195 string line = reader.yieldLine; 196 if (!line.length && contentLength > 0) 197 inHeader = false; 198 else if (line.startsWith("Content-Length:")) 199 contentLength = line["Content-Length:".length .. $].strip.to!size_t; 200 } 201 while (inHeader); 202 assert(contentLength > 0); 203 auto content = cast(string) reader.yieldData(contentLength); 204 assert(content.length == contentLength); 205 RequestMessage request; 206 bool validRequest = false; 207 try 208 { 209 auto json = parseJSON(content); 210 auto id = "id" in json; 211 bool isResponse = false; 212 if (id) 213 { 214 auto tok = RequestToken(id); 215 foreach (ref waiting; responseTokens) 216 { 217 if (!waiting.got && waiting.token == tok) 218 { 219 waiting.got = true; 220 waiting.ret.id = tok; 221 auto res = "result" in json; 222 auto err = "error" in json; 223 if (res) 224 waiting.ret.result = *res; 225 if (err) 226 waiting.ret.error = (*err).fromJSON!ResponseError; 227 isResponse = true; 228 break; 229 } 230 } 231 } 232 if (!isResponse) 233 { 234 request = RequestMessage(json); 235 validRequest = true; 236 } 237 } 238 catch (Exception e) 239 { 240 try 241 { 242 trace(e); 243 trace(content); 244 auto idx = content.indexOf("\"id\":"); 245 auto endIdx = content.indexOf(",", idx); 246 JSONValue fallback; 247 if (idx != -1 && endIdx != -1) 248 fallback = parseJSON(content[idx .. endIdx].strip); 249 else 250 fallback = JSONValue(0); 251 send(ResponseMessage(RequestToken(&fallback), ResponseError(ErrorCode.parseError))); 252 } 253 catch (Exception e) 254 { 255 errorf("Got invalid request '%s'!", content); 256 trace(e); 257 } 258 } 259 if (validRequest) 260 { 261 onData(request); 262 Fiber.yield(); 263 } 264 } 265 } 266 } 267 268 struct WindowFunctions 269 { 270 RPCProcessor rpc; 271 private bool safeShowMessage; 272 273 void showMessage(MessageType type, string message) 274 { 275 if (!safeShowMessage) 276 warningf("%s message: %s", type, message); 277 rpc.notifyMethod("window/showMessage", ShowMessageParams(type, message)); 278 safeShowMessage = false; 279 } 280 281 MessageActionItem requestMessage(MessageType type, string message, MessageActionItem[] actions) 282 { 283 auto res = rpc.sendRequest("window/showMessageRequest", 284 ShowMessageRequestParams(type, message, actions.opt)); 285 if (res.result == JSONValue.init) 286 return MessageActionItem(null); 287 return res.result.fromJSON!MessageActionItem; 288 } 289 290 string requestMessage(MessageType type, string message, string[] actions) 291 { 292 MessageActionItem[] a = new MessageActionItem[actions.length]; 293 foreach (i, action; actions) 294 a[i] = MessageActionItem(action); 295 return requestMessage(type, message, a).title; 296 } 297 298 void runOrMessage(lazy void fn, MessageType type, string message) 299 { 300 try 301 { 302 fn(); 303 } 304 catch (Exception e) 305 { 306 stderr.writeln(e); 307 showMessage(type, message); 308 } 309 } 310 311 void showErrorMessage(string message) 312 { 313 error("Error message: ", message); 314 safeShowMessage = true; 315 showMessage(MessageType.error, message); 316 } 317 318 void showWarningMessage(string message) 319 { 320 warning("Warning message: ", message); 321 safeShowMessage = true; 322 showMessage(MessageType.warning, message); 323 } 324 325 void showInformationMessage(string message) 326 { 327 info("Info message: ", message); 328 safeShowMessage = true; 329 showMessage(MessageType.info, message); 330 } 331 332 void showLogMessage(string message) 333 { 334 trace("Log message: ", message); 335 safeShowMessage = true; 336 showMessage(MessageType.log, message); 337 } 338 }