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