Source file jsonrpc2.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
open Common_
module J = Yojson.Safe
module Err = Jsonrpc.Response.Error
type json = Yojson.Safe.t
module type IO = Sigs.IO
module type S = sig
module IO : IO
type t
include module type of Server.Make (IO)
val create :
?on_received:(json -> unit) ->
?on_sent:(json -> unit) ->
ic:IO.in_channel ->
oc:IO.out_channel ->
server ->
t
val create_stdio :
?on_received:(json -> unit) -> ?on_sent:(json -> unit) -> env:IO.env -> server -> t
val send_server_notification : t -> Lsp.Server_notification.t -> unit IO.t
val send_server_request :
t ->
'from_server Lsp.Server_request.t ->
(('from_server, Jsonrpc.Response.Error.t) result -> unit IO.t) ->
Req_id.t IO.t
val run : ?shutdown:(unit -> bool) -> t -> unit IO.t
end
module Make (IO : IO) : S with module IO = IO = struct
module IO = IO
include Server.Make (IO)
open IO
type json = J.t
module ErrorCode = Jsonrpc.Response.Error.Code
exception E of ErrorCode.t * string
let ( let*? ) x f =
let* x = x in
match x with
| Ok x -> f x
| Error _ as err -> IO.return err
type t = {
ic: IO.in_channel;
oc: IO.out_channel;
on_sent: json -> unit;
on_received: json -> unit;
s: server;
id_counter: int Atomic.t;
pending_responses: (Req_id.t, server_request_handler_pair) Hashtbl.t;
}
let create ?(on_received = ignore) ?(on_sent = ignore) ~ic ~oc server : t =
{
ic;
oc;
s = server;
id_counter = Atomic.make 0;
on_sent;
on_received;
pending_responses = Hashtbl.create 8;
}
let create_stdio ?on_received ?on_sent ~env server : t =
create ?on_received ?on_sent ~ic:(IO.stdin env) ~oc:(IO.stdout env) server
let send_json_ (self : t) (j : json) : unit IO.t =
self.on_sent j;
let json = J.to_string j in
Log.debug (fun k ->
k "jsonrpc2: send json (%dB): %s" (String.length json) json);
let full_s =
Printf.sprintf "Content-Length: %d\r\n\r\n%s" (String.length json) json
in
IO.write_string self.oc full_s
let send_response (self : t) (m : Jsonrpc.Response.t) : unit IO.t =
let json = Jsonrpc.Response.yojson_of_t m in
send_json_ self json
let send_server_notif (self : t) (m : Jsonrpc.Notification.t) : unit IO.t =
let json = Jsonrpc.Notification.yojson_of_t m in
send_json_ self json
(** Send a server request to the LSP client. Invariant: you should call
[register_server_request_response_handler] before calling this method to
ensure that [handle_response] will have a registered handler for this
response. *)
let send_server_req (self : t) (m : Jsonrpc.Request.t) : unit IO.t =
let json = Jsonrpc.Request.yojson_of_t m in
send_json_ self json
(** Returns a new, unused [Req_id.t] to send a server request. *)
let fresh_lsp_id (self : t) : Req_id.t =
let id = Atomic.fetch_and_add self.id_counter 1 in
`Int id
(** Registers a new handler for a request response. The return indicates
whether a value was inserted or not (in which case it's already present). *)
let register_server_request_response_handler (self : t) (id : Req_id.t)
(handler : server_request_handler_pair) : bool =
if Hashtbl.mem self.pending_responses id then
false
else (
let () = Hashtbl.add self.pending_responses id handler in
true
)
let try_ f =
IO.catch
(fun () ->
let+ x = f () in
Ok x)
(fun e bt -> IO.return (Error (e, bt)))
(** Sends a server notification to the LSP client. *)
let send_server_notification (self : t) (n : Lsp.Server_notification.t) :
unit IO.t =
let msg = Lsp.Server_notification.to_jsonrpc n in
send_server_notif self msg
(** Given a [server_request_handler_pair] consisting of some server request
and its handler, sends this request to the LSP client and adds the handler
to a table of pending responses. The request will later be handled by
[handle_response], which will call the provided handler and delete it from
the table of pending responses. *)
let server_request (self : t) (req : server_request_handler_pair) :
Req_id.t IO.t =
let (Request_and_handler (r, _)) = req in
let id = fresh_lsp_id self in
let msg = Lsp.Server_request.to_jsonrpc_request r ~id in
let has_inserted = register_server_request_response_handler self id req in
if has_inserted then
let* () = send_server_req self msg in
return id
else
IO.failwith "failed to register server request: id was already used"
(** Wraps some action and, in case the [IO.t] request has failed, logs the
failure to the LSP client. *)
let with_error_handler (self : t) (action : unit -> unit IO.t) : unit IO.t =
IO.catch action (fun exn bt ->
let message =
spf "LSP handler failed with %s\n%s" (Printexc.to_string exn)
(Printexc.raw_backtrace_to_string bt)
in
Log.err (fun k -> k "%s" message);
let msg =
Lsp.Types.LogMessageParams.create ~type_:Lsp.Types.MessageType.Error
~message
in
let msg =
Lsp.Server_notification.LogMessage msg
|> Lsp.Server_notification.to_jsonrpc
in
send_server_notif self msg)
let handle_notification (self : t) (n : Jsonrpc.Notification.t) : unit IO.t =
let@ _sp =
Trace.with_span ~__FILE__ ~__LINE__ "linol.handle-notification"
in
match Lsp.Client_notification.of_jsonrpc n with
| Ok n ->
let@ () = with_error_handler self in
self.s#on_notification n
~notify_back:(send_server_notification self)
~server_request:(server_request self)
| Error e -> IO.failwith (spf "cannot decode notification: %s" e)
let handle_request (self : t) (r : Jsonrpc.Request.t) : unit IO.t =
let protect ~id f =
IO.catch f (fun e bt ->
let message =
spf "%s\n%s" (Printexc.to_string e)
(Printexc.raw_backtrace_to_string bt)
in
Log.err (fun k -> k "error in request handler: %s" message);
let r =
Jsonrpc.Response.error id
(Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.InternalError ~message ())
in
send_response self r)
in
let id = r.id in
IO.catch
(fun () ->
match Lsp.Client_request.of_jsonrpc r with
| Ok (Lsp.Client_request.E r) ->
let@ () = protect ~id in
let* reply =
self.s#on_request r ~id
~notify_back:(send_server_notification self)
~server_request:(server_request self)
in
let response =
match reply with
| Ok reply ->
let reply_json = Lsp.Client_request.yojson_of_result r reply in
Jsonrpc.Response.ok id reply_json
| Error message ->
Jsonrpc.Response.error id
(Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.InternalError ~message ())
in
send_response self response
| Error e -> IO.failwith (spf "cannot decode request: %s" e))
(fun e bt ->
let message =
spf "%s\n%s" (Printexc.to_string e)
(Printexc.raw_backtrace_to_string bt)
in
Log.err (fun k -> k "error: %s" message);
let r =
Jsonrpc.Response.error id
(Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.InternalError ~message ())
in
send_response self r)
let handle_response (self : t) (r : Jsonrpc.Response.t) : unit IO.t =
match Hashtbl.find_opt self.pending_responses r.id with
| None ->
IO.failwith
@@ Printf.sprintf "server request not found for response of id %s"
@@ Req_id.to_string r.id
| Some (Request_and_handler (req, handler)) ->
let () = Hashtbl.remove self.pending_responses r.id in
(match r.result with
| Error err -> with_error_handler self (fun () -> handler @@ Error err)
| Ok json ->
let r = Lsp.Server_request.response_of_json req json in
with_error_handler self (fun () -> handler @@ Ok r))
let handle_batch_response (_self : t) (_rs : Jsonrpc.Response.t list) :
unit IO.t =
IO.failwith "Unhandled: jsonrpc batch response"
let handle_batch_call (_self : t)
(_cs :
[ `Notification of Jsonrpc.Notification.t
| `Request of Jsonrpc.Request.t
]
list) : unit IO.t =
IO.failwith "Unhandled: jsonrpc batch call"
let fix_null_in_params (j : J.t) : J.t =
let open J.Util in
match j with
| `Assoc assoc as t when t |> member "params" |> J.equal `Null ->
let f = function
| "params", `Null -> "params", `Assoc []
| x -> x
in
`Assoc (List.map f assoc)
| _ -> j
let read_msg (self : t) :
(Jsonrpc.Packet.t, exn * Printexc.raw_backtrace) result IO.t =
let rec acc =
let*? line = try_ @@ fun () -> IO.read_line self.ic in
match String.trim line with
| "" -> IO.return (Ok acc)
| line ->
(match
let i = String.index line ':' in
if i < 0 || String.get line (i + 1) <> ' ' then raise Not_found;
let key = String.lowercase_ascii @@ String.sub line 0 i in
let v =
String.lowercase_ascii
@@ String.trim
(String.sub line (i + 1) (String.length line - i - 1))
in
key, v
with
| pair -> read_headers (pair :: acc)
| exception _ ->
let bt = Printexc.get_raw_backtrace () in
let exn = E (ErrorCode.ParseError, spf "invalid header: %S" line) in
IO.return (Error (exn, bt)))
in
let*? = read_headers [] in
Log.debug (fun k ->
k "jsonrpc2: read headers: [%s]"
(String.concat ";"
@@ List.map (fun (a, b) -> Printf.sprintf "(%S,%S)" a b) headers));
let ok =
match List.assoc "content-type" headers with
| "utf8" | "utf-8" -> true
| _ -> false
| exception Not_found -> true
in
if ok then (
match int_of_string (List.assoc "content-length" headers) with
| n ->
Log.debug (fun k -> k "jsonrpc2: read %d bytes..." n);
let buf = Bytes.make n '\000' in
let*? () = try_ @@ fun () -> IO.read self.ic buf 0 n in
let*? j =
Fun.id @@ try_
@@ fun () -> IO.return @@ J.from_string (Bytes.unsafe_to_string buf)
in
self.on_received j;
Log.debug (fun k -> k "got json %s" (J.to_string j));
(match Jsonrpc.Packet.t_of_yojson @@ fix_null_in_params j with
| m -> IO.return @@ Ok m
| exception exn ->
let bt = Printexc.get_raw_backtrace () in
Log.err (fun k ->
k "cannot decode json message: %s\n%s" (Printexc.to_string exn)
(Printexc.raw_backtrace_to_string bt));
let exn = E (ErrorCode.ParseError, "cannot decode json") in
IO.return (Error (exn, bt)))
| exception _ ->
let bt = Printexc.get_raw_backtrace () in
IO.return
@@ Error (E (ErrorCode.ParseError, "missing content-length' header"), bt)
) else (
let bt = Printexc.get_callstack 10 in
IO.return
@@ Error (E (ErrorCode.InvalidRequest, "content-type must be 'utf-8'"), bt)
)
let send_server_request (self : t) (req : 'from_server Lsp.Server_request.t)
(cb : ('from_server, Jsonrpc.Response.Error.t) result -> unit IO.t) :
Req_id.t IO.t =
server_request self (Request_and_handler (req, cb))
(** [shutdown ()] is called after processing each request to check if the server
could wait for new messages.
When launching an LSP server using [Server.Make.server], the
natural choice for it is [s#get_status = `ReceivedExit] *)
let run ?(shutdown = fun _ -> false) (self : t) : unit IO.t =
let async f =
self.s#spawn_query_handler f;
IO.return ()
in
let process_msg r =
let module M = Jsonrpc.Packet in
match r with
| M.Notification n ->
handle_notification self n
| M.Request r -> async (fun () -> handle_request self r)
| M.Response r -> async (fun () -> handle_response self r)
| M.Batch_response rs -> async (fun () -> handle_batch_response self rs)
| M.Batch_call cs -> async (fun () -> handle_batch_call self cs)
in
let rec loop () =
if shutdown () then
IO.return ()
else
let* r = read_msg self in
match r with
| Ok r ->
let* () = process_msg r in
loop ()
| Error (End_of_file, _) -> IO.return ()
| Error (e, bt) -> IO.fail e bt
in
loop ()
end