14
14
// limitations under the License.
15
15
16
16
import 'dart:async' ;
17
- import 'dart:html ' ;
17
+ import 'dart:js_interop ' ;
18
18
import 'dart:typed_data' ;
19
19
20
20
import 'package:meta/meta.dart' ;
21
+ import 'package:web/web.dart' ;
21
22
22
23
import '../../client/call.dart' ;
23
24
import '../../shared/message.dart' ;
@@ -30,7 +31,7 @@ import 'web_streams.dart';
30
31
const _contentTypeKey = 'Content-Type' ;
31
32
32
33
class XhrTransportStream implements GrpcTransportStream {
33
- final HttpRequest _request;
34
+ final XMLHttpRequest _request;
34
35
final ErrorHandler _onError;
35
36
final Function (XhrTransportStream stream) _onDone;
36
37
bool _headersReceived = false ;
@@ -45,23 +46,22 @@ class XhrTransportStream implements GrpcTransportStream {
45
46
@override
46
47
StreamSink <List <int >> get outgoingMessages => _outgoingMessages.sink;
47
48
48
- XhrTransportStream (this ._request,
49
- {required ErrorHandler onError, required onDone})
49
+ XhrTransportStream (this ._request, {required ErrorHandler onError, required onDone})
50
50
: _onError = onError,
51
51
_onDone = onDone {
52
52
_outgoingMessages.stream
53
53
.map (frame)
54
- .listen ((data) => _request.send (data), cancelOnError: true );
54
+ .listen ((data) => _request.send (Int8List . fromList ( data).toJS) , cancelOnError: true , onError : _onError );
55
55
56
- _request.onReadyStateChange.listen ((data ) {
56
+ _request.onReadyStateChange.listen ((_ ) {
57
57
if (_incomingProcessor.isClosed) {
58
58
return ;
59
59
}
60
60
switch (_request.readyState) {
61
- case HttpRequest . HEADERS_RECEIVED :
61
+ case 2 :
62
62
_onHeadersReceived ();
63
63
break ;
64
- case HttpRequest . DONE :
64
+ case 4 :
65
65
_onRequestDone ();
66
66
_close ();
67
67
break ;
@@ -72,36 +72,29 @@ class XhrTransportStream implements GrpcTransportStream {
72
72
if (_incomingProcessor.isClosed) {
73
73
return ;
74
74
}
75
- _onError (GrpcError .unavailable ('XhrConnection connection-error' ),
76
- StackTrace .current);
75
+ _onError (GrpcError .unavailable ('XhrConnection connection-error' ), StackTrace .current);
77
76
terminate ();
78
77
});
79
78
80
79
_request.onProgress.listen ((_) {
81
80
if (_incomingProcessor.isClosed) {
82
81
return ;
83
82
}
84
- // Use response over responseText as most browsers don't support
85
- // using responseText during an onProgress event.
86
- final responseString = _request.response as String ;
87
- final bytes = Uint8List .fromList (
88
- responseString.substring (_requestBytesRead).codeUnits)
89
- .buffer;
90
- _requestBytesRead = responseString.length;
83
+ final responseText = _request.responseText;
84
+ final bytes = Uint8List .fromList (responseText.substring (_requestBytesRead).codeUnits).buffer;
85
+ _requestBytesRead = responseText.length;
91
86
_incomingProcessor.add (bytes);
92
87
});
93
88
94
89
_incomingProcessor.stream
95
90
.transform (GrpcWebDecoder ())
96
91
.transform (grpcDecompressor ())
97
- .listen (_incomingMessages.add,
98
- onError: _onError, onDone: _incomingMessages.close);
92
+ .listen (_incomingMessages.add, onError: _onError, onDone: _incomingMessages.close);
99
93
}
100
94
101
95
bool _validateResponseState () {
102
96
try {
103
- validateHttpStatusAndContentType (
104
- _request.status, _request.responseHeaders,
97
+ validateHttpStatusAndContentType (_request.status, _parseHeaders (_request.getAllResponseHeaders ()),
105
98
rawResponse: _request.responseText);
106
99
return true ;
107
100
} catch (e, st) {
@@ -115,17 +108,15 @@ class XhrTransportStream implements GrpcTransportStream {
115
108
if (! _validateResponseState ()) {
116
109
return ;
117
110
}
118
- _incomingMessages.add (GrpcMetadata (_request.responseHeaders ));
111
+ _incomingMessages.add (GrpcMetadata (_parseHeaders ( _request.getAllResponseHeaders ()) ));
119
112
}
120
113
121
114
void _onRequestDone () {
122
115
if (! _headersReceived && ! _validateResponseState ()) {
123
116
return ;
124
117
}
125
- if (_request.response == null ) {
126
- _onError (
127
- GrpcError .unavailable ('XhrConnection request null response' , null ,
128
- _request.responseText),
118
+ if (_request.status != 200 ) {
119
+ _onError (GrpcError .unavailable ('Request failed with status: ${_request .status }' , null , _request.responseText),
129
120
StackTrace .current);
130
121
return ;
131
122
}
@@ -137,6 +128,20 @@ class XhrTransportStream implements GrpcTransportStream {
137
128
_onDone (this );
138
129
}
139
130
131
+ Map <String , String > _parseHeaders (String rawHeaders) {
132
+ final headers = < String , String > {};
133
+ final lines = rawHeaders.split ('\r\n ' );
134
+ for (var line in lines) {
135
+ final index = line.indexOf (': ' );
136
+ if (index != - 1 ) {
137
+ final key = line.substring (0 , index);
138
+ final value = line.substring (index + 2 );
139
+ headers[key] = value;
140
+ }
141
+ }
142
+ return headers;
143
+ }
144
+
140
145
@override
141
146
Future <void > terminate () async {
142
147
_close ();
@@ -153,24 +158,24 @@ class XhrClientConnection implements ClientConnection {
153
158
154
159
@override
155
160
String get authority => uri.authority;
161
+
156
162
@override
157
163
String get scheme => uri.scheme;
158
164
159
- void _initializeRequest (HttpRequest request, Map <String , String > metadata) {
160
- for ( final header in metadata.keys ) {
161
- request.setRequestHeader (header, metadata[header] ! );
162
- }
165
+ void _initializeRequest (XMLHttpRequest request, Map <String , String > metadata) {
166
+ metadata.forEach ((key, value ) {
167
+ request.setRequestHeader (key, value );
168
+ });
163
169
// Overriding the mimetype allows us to stream and parse the data
164
170
request.overrideMimeType ('text/plain; charset=x-user-defined' );
165
171
request.responseType = 'text' ;
166
172
}
167
173
168
174
@visibleForTesting
169
- HttpRequest createHttpRequest () => HttpRequest ();
175
+ XMLHttpRequest createHttpRequest () => XMLHttpRequest ();
170
176
171
177
@override
172
- GrpcTransportStream makeRequest (String path, Duration ? timeout,
173
- Map <String , String > metadata, ErrorHandler onError,
178
+ GrpcTransportStream makeRequest (String path, Duration ? timeout, Map <String , String > metadata, ErrorHandler onError,
174
179
{CallOptions ? callOptions}) {
175
180
// gRPC-web headers.
176
181
if (_getContentTypeHeader (metadata) == null ) {
@@ -180,8 +185,7 @@ class XhrClientConnection implements ClientConnection {
180
185
}
181
186
182
187
var requestUri = uri.resolve (path);
183
- if (callOptions is WebCallOptions &&
184
- callOptions.bypassCorsPreflight == true ) {
188
+ if (callOptions is WebCallOptions && callOptions.bypassCorsPreflight == true ) {
185
189
requestUri = cors.moveHttpHeadersToQueryParam (metadata, requestUri);
186
190
}
187
191
@@ -193,8 +197,7 @@ class XhrClientConnection implements ClientConnection {
193
197
// Must set headers after calling open().
194
198
_initializeRequest (request, metadata);
195
199
196
- final transportStream =
197
- XhrTransportStream (request, onError: onError, onDone: _removeStream);
200
+ final transportStream = XhrTransportStream (request, onError: onError, onDone: _removeStream);
198
201
_requests.add (transportStream);
199
202
return transportStream;
200
203
}
0 commit comments