mirror of https://github.com/grpc/grpc-dart.git
Handle request errors (#160)
This commit is contained in:
parent
d58659507c
commit
2b7e261f3a
|
|
@ -11,8 +11,13 @@ class EchoApp {
|
||||||
Future<void> echo(String message) async {
|
Future<void> echo(String message) async {
|
||||||
_addLeftMessage(message);
|
_addLeftMessage(message);
|
||||||
|
|
||||||
final response = await _service.echo(new EchoRequest()..message = message);
|
try {
|
||||||
_addRightMessage(response.message);
|
final response =
|
||||||
|
await _service.echo(new EchoRequest()..message = message);
|
||||||
|
_addRightMessage(response.message);
|
||||||
|
} catch (error) {
|
||||||
|
_addRightMessage(error.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void repeatEcho(String message, int count) {
|
void repeatEcho(String message, int count) {
|
||||||
|
|
@ -23,6 +28,8 @@ class EchoApp {
|
||||||
..messageInterval = 500;
|
..messageInterval = 500;
|
||||||
_service.serverStreamingEcho(request).listen((response) {
|
_service.serverStreamingEcho(request).listen((response) {
|
||||||
_addRightMessage(response.message);
|
_addRightMessage(response.message);
|
||||||
|
}, onError: (error) {
|
||||||
|
_addRightMessage(error.toString());
|
||||||
}, onDone: () => print('Closed connection to server.'));
|
}, onDone: () => print('Closed connection to server.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,8 @@ class ClientCall<Q, R> implements Response {
|
||||||
|
|
||||||
void _sendRequest(ClientConnection connection, Map<String, String> metadata) {
|
void _sendRequest(ClientConnection connection, Map<String, String> metadata) {
|
||||||
try {
|
try {
|
||||||
_stream = connection.makeRequest(path, options.timeout, metadata);
|
_stream = connection.makeRequest(
|
||||||
|
path, options.timeout, metadata, _onRequestError);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_terminateWithError(new GrpcError.unavailable('Error making call: $e'));
|
_terminateWithError(new GrpcError.unavailable('Error making call: $e'));
|
||||||
return;
|
return;
|
||||||
|
|
@ -249,7 +250,7 @@ class ClientCall<Q, R> implements Response {
|
||||||
/// Error handler for the requests stream. Something went wrong while trying
|
/// Error handler for the requests stream. Something went wrong while trying
|
||||||
/// to send the request to the server. Abort the request, and forward the
|
/// to send the request to the server. Abort the request, and forward the
|
||||||
/// error to the user code on the [_responses] stream.
|
/// error to the user code on the [_responses] stream.
|
||||||
void _onRequestError(error) {
|
void _onRequestError(error, [StackTrace stackTrace]) {
|
||||||
if (error is! GrpcError) {
|
if (error is! GrpcError) {
|
||||||
error = new GrpcError.unknown(error.toString());
|
error = new GrpcError.unknown(error.toString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@ abstract class ClientChannel {
|
||||||
/// The connection may be shared between multiple RPCs.
|
/// The connection may be shared between multiple RPCs.
|
||||||
Future<ClientConnection> getConnection() async {
|
Future<ClientConnection> getConnection() async {
|
||||||
if (_isShutdown) throw new GrpcError.unavailable('Channel shutting down.');
|
if (_isShutdown) throw new GrpcError.unavailable('Channel shutting down.');
|
||||||
return _connection ??= new ClientConnection(host, port, options, connectTransport);
|
return _connection ??=
|
||||||
|
new ClientConnection(host, port, options, connectTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiates a new RPC on this connection.
|
/// Initiates a new RPC on this connection.
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:grpc/src/client/channel.dart';
|
import 'package:grpc/src/client/channel.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
|
|
||||||
import '../shared/status.dart';
|
|
||||||
import 'call.dart';
|
import 'call.dart';
|
||||||
import 'options.dart';
|
import 'options.dart';
|
||||||
|
|
||||||
|
|
@ -99,9 +97,9 @@ class ClientConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GrpcTransportStream makeRequest(
|
GrpcTransportStream makeRequest(String path, Duration timeout,
|
||||||
String path, Duration timeout, Map<String, String> metadata) {
|
Map<String, String> metadata, ErrorHandler onRequestFailure) {
|
||||||
return _transport.makeRequest(path, timeout, metadata);
|
return _transport.makeRequest(path, timeout, metadata, onRequestFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startCall(ClientCall call) {
|
void _startCall(ClientCall call) {
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,15 @@ import 'http2_credentials.dart';
|
||||||
import 'transport.dart';
|
import 'transport.dart';
|
||||||
|
|
||||||
class Http2TransportStream extends GrpcTransportStream {
|
class Http2TransportStream extends GrpcTransportStream {
|
||||||
TransportStream _transportStream;
|
final TransportStream _transportStream;
|
||||||
StreamController<GrpcMessage> _incomingMessages;
|
final StreamController<GrpcMessage> _incomingMessages = StreamController();
|
||||||
StreamController<List<int>> _outgoingMessages;
|
final StreamController<List<int>> _outgoingMessages = StreamController();
|
||||||
|
final ErrorHandler _onError;
|
||||||
|
|
||||||
Stream<GrpcMessage> get incomingMessages => _incomingMessages.stream;
|
Stream<GrpcMessage> get incomingMessages => _incomingMessages.stream;
|
||||||
StreamSink<List<int>> get outgoingMessages => _outgoingMessages.sink;
|
StreamSink<List<int>> get outgoingMessages => _outgoingMessages.sink;
|
||||||
|
|
||||||
Http2TransportStream(this._transportStream) {
|
Http2TransportStream(this._transportStream, this._onError) {
|
||||||
_incomingMessages = new StreamController();
|
|
||||||
_outgoingMessages = new StreamController();
|
|
||||||
|
|
||||||
_transportStream.incomingMessages
|
_transportStream.incomingMessages
|
||||||
.transform(new GrpcHttpDecoder())
|
.transform(new GrpcHttpDecoder())
|
||||||
.transform(grpcDecompressor())
|
.transform(grpcDecompressor())
|
||||||
|
|
@ -56,8 +54,8 @@ class Http2TransportStream extends GrpcTransportStream {
|
||||||
cancelOnError: true);
|
cancelOnError: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onRequestError() {
|
void _onRequestError(error) {
|
||||||
// TODO: Implement errors on requests
|
_onError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -132,12 +130,12 @@ class Http2Transport extends Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GrpcTransportStream makeRequest(
|
GrpcTransportStream makeRequest(String path, Duration timeout,
|
||||||
String path, Duration timeout, Map<String, String> metadata) {
|
Map<String, String> metadata, ErrorHandler onError) {
|
||||||
final headers = createCallHeaders(
|
final headers = createCallHeaders(
|
||||||
options.credentials.isSecure, authority, path, timeout, metadata);
|
options.credentials.isSecure, authority, path, timeout, metadata);
|
||||||
final stream = transportConnection.makeRequest(headers);
|
final stream = transportConnection.makeRequest(headers);
|
||||||
return new Http2TransportStream(stream);
|
return new Http2TransportStream(stream, onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import '../../shared/message.dart';
|
||||||
|
|
||||||
typedef void SocketClosedHandler();
|
typedef void SocketClosedHandler();
|
||||||
typedef void ActiveStateHandler(bool isActive);
|
typedef void ActiveStateHandler(bool isActive);
|
||||||
|
typedef void ErrorHandler(error);
|
||||||
|
|
||||||
abstract class GrpcTransportStream {
|
abstract class GrpcTransportStream {
|
||||||
Stream<GrpcMessage> get incomingMessages;
|
Stream<GrpcMessage> get incomingMessages;
|
||||||
|
|
@ -32,8 +33,8 @@ abstract class Transport {
|
||||||
SocketClosedHandler onSocketClosed;
|
SocketClosedHandler onSocketClosed;
|
||||||
|
|
||||||
Future<void> connect();
|
Future<void> connect();
|
||||||
GrpcTransportStream makeRequest(
|
GrpcTransportStream makeRequest(String path, Duration timeout,
|
||||||
String path, Duration timeout, Map<String, String> metadata);
|
Map<String, String> metadata, ErrorHandler onRequestFailure);
|
||||||
Future<void> finish();
|
Future<void> finish();
|
||||||
Future<void> terminate();
|
Future<void> terminate();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
@ -157,4 +156,3 @@ class _GrpcWebConversionSink extends ChunkedConversionSink<ByteBuffer> {
|
||||||
_out.close();
|
_out.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import 'dart:async';
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:grpc/src/shared/status.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../shared/message.dart';
|
import '../../shared/message.dart';
|
||||||
|
|
@ -24,11 +25,12 @@ import 'transport.dart';
|
||||||
import 'web_streams.dart';
|
import 'web_streams.dart';
|
||||||
|
|
||||||
class XhrTransportStream implements GrpcTransportStream {
|
class XhrTransportStream implements GrpcTransportStream {
|
||||||
HttpRequest _request;
|
final HttpRequest _request;
|
||||||
|
final ErrorHandler _onError;
|
||||||
int _requestBytesRead = 0;
|
int _requestBytesRead = 0;
|
||||||
StreamController<ByteBuffer> _incomingProcessor;
|
final StreamController<ByteBuffer> _incomingProcessor = StreamController();
|
||||||
StreamController<GrpcMessage> _incomingMessages;
|
final StreamController<GrpcMessage> _incomingMessages = StreamController();
|
||||||
StreamController<List<int>> _outgoingMessages;
|
final StreamController<List<int>> _outgoingMessages = StreamController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<GrpcMessage> get incomingMessages => _incomingMessages.stream;
|
Stream<GrpcMessage> get incomingMessages => _incomingMessages.stream;
|
||||||
|
|
@ -36,48 +38,43 @@ class XhrTransportStream implements GrpcTransportStream {
|
||||||
@override
|
@override
|
||||||
StreamSink<List<int>> get outgoingMessages => _outgoingMessages.sink;
|
StreamSink<List<int>> get outgoingMessages => _outgoingMessages.sink;
|
||||||
|
|
||||||
XhrTransportStream(this._request) {
|
XhrTransportStream(this._request, this._onError) {
|
||||||
_incomingProcessor = StreamController();
|
|
||||||
_incomingMessages = StreamController();
|
|
||||||
_outgoingMessages = StreamController();
|
|
||||||
|
|
||||||
_incomingProcessor.stream
|
|
||||||
.transform(GrpcWebDecoder())
|
|
||||||
.transform(grpcDecompressor())
|
|
||||||
.listen(_incomingMessages.add,
|
|
||||||
onError: _incomingMessages.addError,
|
|
||||||
onDone: _incomingMessages.close);
|
|
||||||
|
|
||||||
_outgoingMessages.stream
|
_outgoingMessages.stream
|
||||||
.map(frame)
|
.map(frame)
|
||||||
.listen((data) => _request.send(data));
|
.listen((data) => _request.send(data), cancelOnError: true);
|
||||||
|
|
||||||
_request.onReadyStateChange.listen((data) {
|
_request.onReadyStateChange.listen((data) {
|
||||||
final contentType = _request.getResponseHeader('Content-Type');
|
if (_incomingMessages.isClosed) {
|
||||||
if (contentType == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
switch (_request.readyState) {
|
||||||
if (_request.readyState == HttpRequest.HEADERS_RECEIVED) {
|
case HttpRequest.HEADERS_RECEIVED:
|
||||||
if (contentType.startsWith('application/grpc')) {
|
_onHeadersReceived();
|
||||||
if (_request.response == null) {
|
break;
|
||||||
return;
|
case HttpRequest.DONE:
|
||||||
|
if (_request.status != 200) {
|
||||||
|
_onError(GrpcError.unavailable(
|
||||||
|
'XhrConnection status ${_request.status}'));
|
||||||
|
} else {
|
||||||
|
_close();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// Force a metadata message with headers
|
|
||||||
final headers = GrpcMetadata(_request.responseHeaders);
|
|
||||||
_incomingMessages.add(headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_request.readyState == HttpRequest.DONE) {
|
|
||||||
_incomingProcessor.close();
|
|
||||||
_outgoingMessages.close();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_request.onError.listen((ProgressEvent event) {
|
||||||
|
if (_incomingMessages.isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_onError(GrpcError.unavailable('XhrConnection connection-error'));
|
||||||
|
terminate();
|
||||||
|
});
|
||||||
|
|
||||||
_request.onProgress.listen((_) {
|
_request.onProgress.listen((_) {
|
||||||
// use response over responseText as most browsers don't support
|
if (_incomingMessages.isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Use response over responseText as most browsers don't support
|
||||||
// using responseText during an onProgress event.
|
// using responseText during an onProgress event.
|
||||||
final responseString = _request.response as String;
|
final responseString = _request.response as String;
|
||||||
final bytes = Uint8List.fromList(
|
final bytes = Uint8List.fromList(
|
||||||
|
|
@ -86,12 +83,48 @@ class XhrTransportStream implements GrpcTransportStream {
|
||||||
_requestBytesRead = responseString.length;
|
_requestBytesRead = responseString.length;
|
||||||
_incomingProcessor.add(bytes);
|
_incomingProcessor.add(bytes);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_incomingProcessor.stream
|
||||||
|
.transform(GrpcWebDecoder())
|
||||||
|
.transform(grpcDecompressor())
|
||||||
|
.listen(_incomingMessages.add,
|
||||||
|
onError: _onError, onDone: _incomingMessages.close);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onHeadersReceived() {
|
||||||
|
final contentType = _request.getResponseHeader('Content-Type');
|
||||||
|
if (_request.status != 200) {
|
||||||
|
_onError(
|
||||||
|
GrpcError.unavailable('XhrConnection status ${_request.status}'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (contentType == null) {
|
||||||
|
_onError(GrpcError.unavailable('XhrConnection missing Content-Type'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!contentType.startsWith('application/grpc')) {
|
||||||
|
_onError(
|
||||||
|
GrpcError.unavailable('XhrConnection bad Content-Type $contentType'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_request.response == null) {
|
||||||
|
_onError(GrpcError.unavailable('XhrConnection request null response'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a metadata message with headers.
|
||||||
|
final headers = GrpcMetadata(_request.responseHeaders);
|
||||||
|
_incomingMessages.add(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
_close() {
|
||||||
|
_incomingProcessor.close();
|
||||||
|
_outgoingMessages.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> terminate() async {
|
Future<void> terminate() async {
|
||||||
await _incomingProcessor.close();
|
_close();
|
||||||
await _outgoingMessages.close();
|
|
||||||
_request.abort();
|
_request.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,17 +157,16 @@ class XhrTransport extends Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GrpcTransportStream makeRequest(
|
GrpcTransportStream makeRequest(String path, Duration timeout,
|
||||||
String path, Duration timeout, Map<String, String> metadata) {
|
Map<String, String> metadata, ErrorHandler onError) {
|
||||||
_request = HttpRequest();
|
_request = HttpRequest();
|
||||||
_request.open('POST', '${host}:${port}${path}');
|
_request.open('POST', '${host}:${port}${path}');
|
||||||
|
|
||||||
initializeRequest(_request, metadata);
|
initializeRequest(_request, metadata);
|
||||||
|
|
||||||
return XhrTransportStream(_request);
|
return XhrTransportStream(_request, onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> terminate() async {}
|
Future<void> terminate() async {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@ Future<Transport> connectXhrTransport(
|
||||||
}
|
}
|
||||||
|
|
||||||
class GrpcWebClientChannel extends ClientChannel {
|
class GrpcWebClientChannel extends ClientChannel {
|
||||||
GrpcWebClientChannel.xhr(String host,
|
GrpcWebClientChannel.xhr(String host, {int port = 443})
|
||||||
{int port = 443}) : super(host, connectXhrTransport, port: port);
|
: super(host, connectXhrTransport, port: port);
|
||||||
}
|
}
|
||||||
|
|
@ -52,12 +52,12 @@ List<int> frame(List<int> payload) {
|
||||||
StreamTransformer<GrpcMessage, GrpcMessage> grpcDecompressor() =>
|
StreamTransformer<GrpcMessage, GrpcMessage> grpcDecompressor() =>
|
||||||
new StreamTransformer<GrpcMessage, GrpcMessage>.fromHandlers(
|
new StreamTransformer<GrpcMessage, GrpcMessage>.fromHandlers(
|
||||||
handleData: (GrpcMessage value, EventSink<GrpcMessage> sink) {
|
handleData: (GrpcMessage value, EventSink<GrpcMessage> sink) {
|
||||||
if (value is GrpcData) {
|
if (value is GrpcData) {
|
||||||
if (value.isCompressed) {
|
if (value.isCompressed) {
|
||||||
// TODO(dart-lang/grpc-dart#6): Actually handle decompression.
|
// TODO(dart-lang/grpc-dart#6): Actually handle decompression.
|
||||||
sink.add(new GrpcData(value.data, isCompressed: false));
|
sink.add(new GrpcData(value.data, isCompressed: false));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sink.add(value);
|
sink.add(value);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue