Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library json_rpc_2.client; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:stack_trace/stack_trace.dart'; | |
| 10 | |
| 11 import 'exception.dart'; | |
| 12 import 'two_way_stream.dart'; | |
| 13 import 'utils.dart'; | |
| 14 | |
| 15 class Client { | |
|
Bob Nystrom
2014/11/03 18:43:55
Doc comment.
nweiz
2014/11/04 02:22:45
Done.
| |
| 16 final TwoWayStream _streams; | |
| 17 | |
| 18 /// The next request id. | |
| 19 var _id = 0; | |
| 20 | |
| 21 /// The current batch of requests to be sent together. | |
| 22 /// | |
| 23 /// Each element is a JSON-serializable object. | |
| 24 List _batch; | |
| 25 | |
| 26 /// The map of request ids for pending requests to [Completer]s that will be | |
| 27 /// completed with those requests' responses. | |
| 28 final _pendingRequests = new Map<int, Completer>(); | |
| 29 | |
| 30 /// Creates a [Client] that writes requests to [requests] and reads responses | |
| 31 /// from [responses]. | |
| 32 /// | |
| 33 /// If [responses] is a [StreamSink] as well as a [Stream] (for example, a | |
| 34 /// `WebSocket`), [requests] may be omitted. | |
| 35 /// | |
| 36 /// Note that the client won't begin listening to [responses] until | |
| 37 /// [Client.listen] is called. | |
| 38 Client(Stream<String> responses, [StreamSink<String> requests]) | |
| 39 : _streams = new TwoWayStream( | |
| 40 "Client", responses, "responses", requests, "requests"); | |
| 41 | |
| 42 | |
| 43 /// Creates a [Client] that writes decoded responses to [responses] and reads | |
| 44 /// decoded requests from [requests]. | |
| 45 /// | |
| 46 /// Unlike [new Client], this doesn't read or write JSON strings. Instead, it | |
| 47 /// reads and writes decoded maps or lists. | |
| 48 /// | |
| 49 /// If [responses] is a [StreamSink] as well as a [Stream], [requests] may be | |
| 50 /// omitted. | |
| 51 /// | |
| 52 /// Note that the client won't begin listening to [responses] until | |
| 53 /// [Client.listen] is called. | |
| 54 Client.withoutJson(Stream responses, [StreamSink requests]) | |
| 55 : _streams = new TwoWayStream.withoutJson( | |
| 56 "Client", responses, "responses", requests, "requests"); | |
| 57 | |
| 58 /// Users of the library should not use this constructor. | |
| 59 Client.internal(this._streams); | |
| 60 | |
| 61 /// Starts listening to the underlying stream. | |
| 62 /// | |
| 63 /// Returns a [Future] that will complete when the stream is closed or when it | |
| 64 /// has an error. | |
| 65 /// | |
| 66 /// [listen] may only be called once. | |
| 67 Future listen() => _streams.listen(_handleResponse); | |
| 68 | |
| 69 /// Closes the server's request sink and response subscription. | |
| 70 /// | |
| 71 /// Returns a [Future] that completes when all resources have been released. | |
| 72 /// | |
| 73 /// A client can't be closed before [listen] has been called. | |
| 74 Future close() => _streams.close(); | |
| 75 | |
| 76 /// Sends a JSON-RPC 2 request to invoke the given [method]. | |
| 77 /// | |
| 78 /// If passed, [parameters] is the parameters for the method. This must be | |
| 79 /// either an [Iterable] (to pass parameters by position) or a [Map] with | |
| 80 /// [String] keys (to pass parameters by name). Either way, it must be | |
| 81 /// JSON-serializable. | |
| 82 /// | |
| 83 /// If the request succeeds, this returns the response result as a decoded | |
| 84 /// JSON-serializable object. If it fails, it throws an [RpcException] | |
| 85 /// describing the failure. | |
| 86 Future sendRequest(String method, [parameters]) { | |
| 87 var id = _id++; | |
| 88 _send(method, parameters, id); | |
| 89 | |
| 90 var completer = new Completer.sync(); | |
| 91 _pendingRequests[id] = completer; | |
| 92 return completer.future; | |
| 93 } | |
| 94 | |
| 95 /// Sends a JSON-RPC 2 request to invoke the given [method] without expecting | |
| 96 /// a response. | |
| 97 /// | |
| 98 /// If passed, [parameters] is the parameters for the method. This must be | |
| 99 /// either an [Iterable] (to pass parameters by position) or a [Map] with | |
| 100 /// [String] keys (to pass parameters by name). Either way, it must be | |
| 101 /// JSON-serializable. | |
| 102 /// | |
| 103 /// Since this is just a notification to which the server isn't expected to | |
| 104 /// send a response, it has no return value. | |
|
Bob Nystrom
2014/11/03 18:43:55
What about errors? Is it possible to send a notifi
nweiz
2014/11/04 02:22:45
No, if a notification causes errors those errors a
| |
| 105 void sendNotification(String method, [parameters]) => | |
| 106 _send(method, parameters); | |
| 107 | |
| 108 /// A helper method for [sendRequest] and [sendNotification]. | |
| 109 /// | |
| 110 /// Sends a request to invoke [method] with [parameters]. If [id] is given, | |
| 111 /// the request uses that id. | |
| 112 void _send(String method, parameters, [int id]) { | |
| 113 if (parameters is Iterable) parameters = parameters.toList(); | |
| 114 if (parameters is! Map && parameters is! List) { | |
| 115 throw new ArgumentError('Only maps and lists may be used as JSON-RPC ' | |
| 116 'parameters, was "$parameters".'); | |
| 117 } | |
| 118 | |
| 119 var message = { | |
| 120 "jsonrpc": "2.0", | |
| 121 "method": method | |
| 122 }; | |
| 123 if (id != null) message["id"] = id; | |
| 124 if (parameters != null) message["parameters"] = parameters; | |
| 125 | |
| 126 if (_batch != null) { | |
| 127 _batch.add(message); | |
| 128 } else { | |
| 129 _streams.add(message); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 /// Runs [callback] and batches any requests sent until it returns. | |
| 134 /// | |
| 135 /// A batch of requests is sent in a single message on the underlying stream, | |
| 136 /// and the responses are likewise sent back in a single message. | |
| 137 /// | |
| 138 /// [callback] may be synchronous or asynchronous. If it returns a [Future], | |
| 139 /// requests will be batched until that Future returns; otherwise, requests | |
| 140 /// will only be batched while synchronously executing [callback]. | |
| 141 /// | |
| 142 /// If this is called in the context of another [withBatch] call, it just | |
| 143 /// invokes [callback] without creating another batch. | |
|
Bob Nystrom
2014/11/03 18:43:55
Clarify that the batch doesn't end until the outer
nweiz
2014/11/04 02:22:46
Done.
| |
| 144 withBatch(callback()) { | |
| 145 if (_batch != null) return callback(); | |
| 146 | |
| 147 _batch = new Queue(); | |
| 148 return tryFinally(callback, () { | |
| 149 _batch.forEach(_streams.add); | |
| 150 _batch = null; | |
| 151 }); | |
| 152 } | |
| 153 | |
| 154 /// Handles a decoded response from the server. | |
| 155 void _handleResponse(response) { | |
| 156 if (response is List) { | |
| 157 response.forEach(_handleResponse); | |
|
Bob Nystrom
2014/11/03 18:43:55
The recursive call means that multiple layers of n
nweiz
2014/11/04 02:22:46
No, changed.
| |
| 158 return; | |
| 159 } | |
| 160 | |
| 161 if (!_isResponseValid(response)) return; | |
| 162 var completer = _pendingRequests.remove(response["id"]); | |
| 163 if (response.containsKey("result")) { | |
|
Bob Nystrom
2014/11/03 18:43:55
If it doesn't have a result, that means it's a not
nweiz
2014/11/04 02:22:45
No, that means it's an error so we call completeEr
| |
| 164 completer.complete(response["result"]); | |
| 165 } else { | |
| 166 completer.completeError(new RpcException( | |
| 167 response["code"], response["message"], data: response["data"]), | |
| 168 new Chain.current()); | |
| 169 } | |
| 170 } | |
| 171 | |
| 172 /// Determines whether the server's response is valid per the spec. | |
| 173 bool _isResponseValid(response) { | |
| 174 if (response is! Map) return false; | |
| 175 if (response["jsonrpc"] != "2.0") return false; | |
| 176 if (!_pendingRequests.containsKey(response["id"])) return false; | |
| 177 if (response.containsKey("result")) return true; | |
| 178 | |
| 179 if (!response.containsKey("error")) return false; | |
| 180 var error = response["error"]; | |
| 181 if (error is! Map) return false; | |
| 182 if (error["code"] is! int) return false; | |
| 183 if (error["message"] is! String) return false; | |
| 184 return true; | |
| 185 } | |
| 186 } | |
| OLD | NEW |