Showing
18 changed files
with
276 additions
and
83 deletions
1 | +## [3.22.0] | ||
2 | +- Added: more multipart options. Now you can send as multipart: | ||
3 | + | ||
4 | +File: | ||
5 | +'file':MultipartFile(File('./images/avatar.png'), filename: 'avatar.png'), | ||
6 | + | ||
7 | +String path: | ||
8 | +'file':MultipartFile('./images/avatar.png', filename: 'avatar.png'), | ||
9 | + | ||
10 | +Or bytes (Flutter web work only with bytes): | ||
11 | +'file':MultipartFile(File('file').readAsBytesSync(), filename: 'avatar.png'), | ||
12 | + | ||
13 | +- Improve: auto jsonDecode occurs only if response.header.contentType is "application/json" | ||
14 | + | ||
15 | +- Added: Upload Progress to MultipartRequest | ||
16 | +- Added support to List<MultipartFile> (@jasonlaw) | ||
17 | +- Improve and fix requests types (@eduardoflorence) | ||
18 | +- Fix HeaderValue variables with same name (@haidang93) | ||
19 | + | ||
1 | ## [3.21.3] | 20 | ## [3.21.3] |
2 | - Improve multipart file and defaultDecoder on GetConnect | 21 | - Improve multipart file and defaultDecoder on GetConnect |
3 | 22 |
@@ -127,6 +127,7 @@ class GetConnect extends GetConnectInterface { | @@ -127,6 +127,7 @@ class GetConnect extends GetConnectInterface { | ||
127 | Map<String, String> headers, | 127 | Map<String, String> headers, |
128 | Map<String, dynamic> query, | 128 | Map<String, dynamic> query, |
129 | Decoder<T> decoder, | 129 | Decoder<T> decoder, |
130 | + Progress uploadProgress, | ||
130 | }) { | 131 | }) { |
131 | _checkIfDisposed(); | 132 | _checkIfDisposed(); |
132 | return httpClient.post<T>( | 133 | return httpClient.post<T>( |
@@ -136,6 +137,7 @@ class GetConnect extends GetConnectInterface { | @@ -136,6 +137,7 @@ class GetConnect extends GetConnectInterface { | ||
136 | contentType: contentType, | 137 | contentType: contentType, |
137 | query: query, | 138 | query: query, |
138 | decoder: decoder, | 139 | decoder: decoder, |
140 | + uploadProgress: uploadProgress, | ||
139 | ); | 141 | ); |
140 | } | 142 | } |
141 | 143 | ||
@@ -147,6 +149,7 @@ class GetConnect extends GetConnectInterface { | @@ -147,6 +149,7 @@ class GetConnect extends GetConnectInterface { | ||
147 | Map<String, String> headers, | 149 | Map<String, String> headers, |
148 | Map<String, dynamic> query, | 150 | Map<String, dynamic> query, |
149 | Decoder<T> decoder, | 151 | Decoder<T> decoder, |
152 | + Progress uploadProgress, | ||
150 | }) { | 153 | }) { |
151 | _checkIfDisposed(); | 154 | _checkIfDisposed(); |
152 | return httpClient.put<T>( | 155 | return httpClient.put<T>( |
@@ -156,6 +159,7 @@ class GetConnect extends GetConnectInterface { | @@ -156,6 +159,7 @@ class GetConnect extends GetConnectInterface { | ||
156 | contentType: contentType, | 159 | contentType: contentType, |
157 | query: query, | 160 | query: query, |
158 | decoder: decoder, | 161 | decoder: decoder, |
162 | + uploadProgress: uploadProgress, | ||
159 | ); | 163 | ); |
160 | } | 164 | } |
161 | 165 | ||
@@ -168,6 +172,7 @@ class GetConnect extends GetConnectInterface { | @@ -168,6 +172,7 @@ class GetConnect extends GetConnectInterface { | ||
168 | Map<String, String> headers, | 172 | Map<String, String> headers, |
169 | Map<String, dynamic> query, | 173 | Map<String, dynamic> query, |
170 | Decoder<T> decoder, | 174 | Decoder<T> decoder, |
175 | + Progress uploadProgress, | ||
171 | }) { | 176 | }) { |
172 | _checkIfDisposed(); | 177 | _checkIfDisposed(); |
173 | return httpClient.request<T>( | 178 | return httpClient.request<T>( |
@@ -178,6 +183,7 @@ class GetConnect extends GetConnectInterface { | @@ -178,6 +183,7 @@ class GetConnect extends GetConnectInterface { | ||
178 | contentType: contentType, | 183 | contentType: contentType, |
179 | query: query, | 184 | query: query, |
180 | decoder: decoder, | 185 | decoder: decoder, |
186 | + uploadProgress: uploadProgress, | ||
181 | ); | 187 | ); |
182 | } | 188 | } |
183 | 189 |
1 | import 'dart:async'; | 1 | import 'dart:async'; |
2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
3 | - | ||
4 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; |
5 | 4 | ||
6 | import '../src/certificates/certificates.dart'; | 5 | import '../src/certificates/certificates.dart'; |
7 | import '../src/exceptions/exceptions.dart'; | 6 | import '../src/exceptions/exceptions.dart'; |
8 | -import '../src/http_impl/http_request_stub.dart' | ||
9 | - if (dart.library.html) 'http_impl/http_request_html.dart' | ||
10 | - if (dart.library.io) 'http_impl/http_request_io.dart'; | ||
11 | -import '../src/http_impl/request_base.dart'; | ||
12 | import '../src/multipart/form_data.dart'; | 7 | import '../src/multipart/form_data.dart'; |
13 | import '../src/request/request.dart'; | 8 | import '../src/request/request.dart'; |
14 | import '../src/response/response.dart'; | 9 | import '../src/response/response.dart'; |
15 | import '../src/status/http_status.dart'; | 10 | import '../src/status/http_status.dart'; |
11 | +import 'http/interface/request_base.dart'; | ||
12 | +import 'http/stub/http_request_stub.dart' | ||
13 | + if (dart.library.html) 'http/html/http_request_html.dart' | ||
14 | + if (dart.library.io) 'http/io/http_request_io.dart'; | ||
16 | import 'interceptors/get_modifiers.dart'; | 15 | import 'interceptors/get_modifiers.dart'; |
17 | 16 | ||
18 | typedef Decoder<T> = T Function(dynamic data); | 17 | typedef Decoder<T> = T Function(dynamic data); |
19 | 18 | ||
19 | +typedef Progress = Function(double percent); | ||
20 | + | ||
20 | class GetHttpClient { | 21 | class GetHttpClient { |
21 | String userAgent; | 22 | String userAgent; |
22 | String baseUrl; | 23 | String baseUrl; |
@@ -90,9 +91,10 @@ class GetHttpClient { | @@ -90,9 +91,10 @@ class GetHttpClient { | ||
90 | String method, | 91 | String method, |
91 | Map<String, dynamic> query, | 92 | Map<String, dynamic> query, |
92 | Decoder<T> decoder, | 93 | Decoder<T> decoder, |
94 | + Progress uploadProgress, | ||
93 | ) async { | 95 | ) async { |
94 | List<int> bodyBytes; | 96 | List<int> bodyBytes; |
95 | - BodyBytes bodyStream; | 97 | + BodyBytesStream bodyStream; |
96 | final headers = <String, String>{}; | 98 | final headers = <String, String>{}; |
97 | 99 | ||
98 | headers['user-agent'] = userAgent; | 100 | headers['user-agent'] = userAgent; |
@@ -125,11 +127,10 @@ class GetHttpClient { | @@ -125,11 +127,10 @@ class GetHttpClient { | ||
125 | } | 127 | } |
126 | 128 | ||
127 | if (bodyBytes != null) { | 129 | if (bodyBytes != null) { |
128 | - bodyStream = BodyBytes.fromBytes(bodyBytes); | 130 | + bodyStream = _trackProgress(bodyBytes, uploadProgress); |
129 | } | 131 | } |
130 | 132 | ||
131 | final uri = _createUri(url, query); | 133 | final uri = _createUri(url, query); |
132 | - | ||
133 | return Request<T>( | 134 | return Request<T>( |
134 | method: method, | 135 | method: method, |
135 | url: uri, | 136 | url: uri, |
@@ -141,6 +142,27 @@ class GetHttpClient { | @@ -141,6 +142,27 @@ class GetHttpClient { | ||
141 | ); | 142 | ); |
142 | } | 143 | } |
143 | 144 | ||
145 | + BodyBytesStream _trackProgress( | ||
146 | + List<int> bodyBytes, | ||
147 | + Progress uploadProgress, | ||
148 | + ) { | ||
149 | + var total = 0; | ||
150 | + var length = bodyBytes.length; | ||
151 | + | ||
152 | + var byteStream = | ||
153 | + Stream.fromIterable(bodyBytes.map((i) => [i])).transform<List<int>>( | ||
154 | + StreamTransformer.fromHandlers(handleData: (data, sink) { | ||
155 | + total += data.length; | ||
156 | + if (uploadProgress != null) { | ||
157 | + var percent = total / length * 100; | ||
158 | + uploadProgress(percent); | ||
159 | + } | ||
160 | + sink.add(data); | ||
161 | + }), | ||
162 | + ); | ||
163 | + return BodyBytesStream(byteStream); | ||
164 | + } | ||
165 | + | ||
144 | void _setSimpleHeaders( | 166 | void _setSimpleHeaders( |
145 | Map<String, String> headers, | 167 | Map<String, String> headers, |
146 | String contentType, | 168 | String contentType, |
@@ -187,6 +209,8 @@ class GetHttpClient { | @@ -187,6 +209,8 @@ class GetHttpClient { | ||
187 | headers: response.headers, | 209 | headers: response.headers, |
188 | statusCode: response.statusCode, | 210 | statusCode: response.statusCode, |
189 | body: response.body, | 211 | body: response.body, |
212 | + bodyBytes: response.bodyBytes, | ||
213 | + bodyString: response.bodyString, | ||
190 | statusText: response.statusText, | 214 | statusText: response.statusText, |
191 | ); | 215 | ); |
192 | } | 216 | } |
@@ -232,6 +256,7 @@ class GetHttpClient { | @@ -232,6 +256,7 @@ class GetHttpClient { | ||
232 | @required dynamic body, | 256 | @required dynamic body, |
233 | Map<String, dynamic> query, | 257 | Map<String, dynamic> query, |
234 | Decoder<T> decoder, | 258 | Decoder<T> decoder, |
259 | + @required Progress uploadProgress, | ||
235 | }) { | 260 | }) { |
236 | return _requestWithBody<T>( | 261 | return _requestWithBody<T>( |
237 | url, | 262 | url, |
@@ -240,6 +265,7 @@ class GetHttpClient { | @@ -240,6 +265,7 @@ class GetHttpClient { | ||
240 | 'post', | 265 | 'post', |
241 | query, | 266 | query, |
242 | decoder ?? (defaultDecoder as Decoder<T>), | 267 | decoder ?? (defaultDecoder as Decoder<T>), |
268 | + uploadProgress, | ||
243 | ); | 269 | ); |
244 | } | 270 | } |
245 | 271 | ||
@@ -250,6 +276,7 @@ class GetHttpClient { | @@ -250,6 +276,7 @@ class GetHttpClient { | ||
250 | @required dynamic body, | 276 | @required dynamic body, |
251 | @required Map<String, dynamic> query, | 277 | @required Map<String, dynamic> query, |
252 | Decoder<T> decoder, | 278 | Decoder<T> decoder, |
279 | + @required Progress uploadProgress, | ||
253 | }) { | 280 | }) { |
254 | return _requestWithBody<T>( | 281 | return _requestWithBody<T>( |
255 | url, | 282 | url, |
@@ -258,6 +285,7 @@ class GetHttpClient { | @@ -258,6 +285,7 @@ class GetHttpClient { | ||
258 | method, | 285 | method, |
259 | query, | 286 | query, |
260 | decoder ?? (defaultDecoder as Decoder<T>), | 287 | decoder ?? (defaultDecoder as Decoder<T>), |
288 | + uploadProgress, | ||
261 | ); | 289 | ); |
262 | } | 290 | } |
263 | 291 | ||
@@ -267,6 +295,7 @@ class GetHttpClient { | @@ -267,6 +295,7 @@ class GetHttpClient { | ||
267 | @required dynamic body, | 295 | @required dynamic body, |
268 | @required Map<String, dynamic> query, | 296 | @required Map<String, dynamic> query, |
269 | Decoder<T> decoder, | 297 | Decoder<T> decoder, |
298 | + @required Progress uploadProgress, | ||
270 | }) { | 299 | }) { |
271 | return _requestWithBody<T>( | 300 | return _requestWithBody<T>( |
272 | url, | 301 | url, |
@@ -275,6 +304,7 @@ class GetHttpClient { | @@ -275,6 +304,7 @@ class GetHttpClient { | ||
275 | 'put', | 304 | 'put', |
276 | query, | 305 | query, |
277 | decoder ?? (defaultDecoder as Decoder<T>), | 306 | decoder ?? (defaultDecoder as Decoder<T>), |
307 | + uploadProgress, | ||
278 | ); | 308 | ); |
279 | } | 309 | } |
280 | 310 | ||
@@ -303,6 +333,7 @@ class GetHttpClient { | @@ -303,6 +333,7 @@ class GetHttpClient { | ||
303 | Map<String, String> headers, | 333 | Map<String, String> headers, |
304 | Map<String, dynamic> query, | 334 | Map<String, dynamic> query, |
305 | Decoder<T> decoder, | 335 | Decoder<T> decoder, |
336 | + Progress uploadProgress, | ||
306 | // List<MultipartFile> files, | 337 | // List<MultipartFile> files, |
307 | }) async { | 338 | }) async { |
308 | try { | 339 | try { |
@@ -313,7 +344,7 @@ class GetHttpClient { | @@ -313,7 +344,7 @@ class GetHttpClient { | ||
313 | body: body, | 344 | body: body, |
314 | query: query, | 345 | query: query, |
315 | decoder: decoder, | 346 | decoder: decoder, |
316 | - // files: files, | 347 | + uploadProgress: uploadProgress, |
317 | ), | 348 | ), |
318 | headers: headers, | 349 | headers: headers, |
319 | ); | 350 | ); |
@@ -323,9 +354,6 @@ class GetHttpClient { | @@ -323,9 +354,6 @@ class GetHttpClient { | ||
323 | throw GetHttpException(e.toString()); | 354 | throw GetHttpException(e.toString()); |
324 | } | 355 | } |
325 | return Future.value(Response<T>( | 356 | return Future.value(Response<T>( |
326 | - request: null, | ||
327 | - statusCode: null, | ||
328 | - body: null, | ||
329 | statusText: 'Can not connect to server. Reason: $e', | 357 | statusText: 'Can not connect to server. Reason: $e', |
330 | )); | 358 | )); |
331 | } | 359 | } |
@@ -339,6 +367,7 @@ class GetHttpClient { | @@ -339,6 +367,7 @@ class GetHttpClient { | ||
339 | Map<String, String> headers, | 367 | Map<String, String> headers, |
340 | Map<String, dynamic> query, | 368 | Map<String, dynamic> query, |
341 | Decoder<T> decoder, | 369 | Decoder<T> decoder, |
370 | + Progress uploadProgress, | ||
342 | }) async { | 371 | }) async { |
343 | try { | 372 | try { |
344 | var response = await _performRequest<T>( | 373 | var response = await _performRequest<T>( |
@@ -349,6 +378,7 @@ class GetHttpClient { | @@ -349,6 +378,7 @@ class GetHttpClient { | ||
349 | query: query, | 378 | query: query, |
350 | body: body, | 379 | body: body, |
351 | decoder: decoder, | 380 | decoder: decoder, |
381 | + uploadProgress: uploadProgress, | ||
352 | ), | 382 | ), |
353 | headers: headers, | 383 | headers: headers, |
354 | ); | 384 | ); |
@@ -358,9 +388,6 @@ class GetHttpClient { | @@ -358,9 +388,6 @@ class GetHttpClient { | ||
358 | throw GetHttpException(e.toString()); | 388 | throw GetHttpException(e.toString()); |
359 | } | 389 | } |
360 | return Future.value(Response<T>( | 390 | return Future.value(Response<T>( |
361 | - request: null, | ||
362 | - statusCode: null, | ||
363 | - body: null, | ||
364 | statusText: 'Can not connect to server. Reason: $e', | 391 | statusText: 'Can not connect to server. Reason: $e', |
365 | )); | 392 | )); |
366 | } | 393 | } |
@@ -373,6 +400,7 @@ class GetHttpClient { | @@ -373,6 +400,7 @@ class GetHttpClient { | ||
373 | Map<String, String> headers, | 400 | Map<String, String> headers, |
374 | Map<String, dynamic> query, | 401 | Map<String, dynamic> query, |
375 | Decoder<T> decoder, | 402 | Decoder<T> decoder, |
403 | + Progress uploadProgress, | ||
376 | }) async { | 404 | }) async { |
377 | try { | 405 | try { |
378 | var response = await _performRequest<T>( | 406 | var response = await _performRequest<T>( |
@@ -382,6 +410,7 @@ class GetHttpClient { | @@ -382,6 +410,7 @@ class GetHttpClient { | ||
382 | query: query, | 410 | query: query, |
383 | body: body, | 411 | body: body, |
384 | decoder: decoder, | 412 | decoder: decoder, |
413 | + uploadProgress: uploadProgress, | ||
385 | ), | 414 | ), |
386 | headers: headers, | 415 | headers: headers, |
387 | ); | 416 | ); |
@@ -391,9 +420,6 @@ class GetHttpClient { | @@ -391,9 +420,6 @@ class GetHttpClient { | ||
391 | throw GetHttpException(e.toString()); | 420 | throw GetHttpException(e.toString()); |
392 | } | 421 | } |
393 | return Future.value(Response<T>( | 422 | return Future.value(Response<T>( |
394 | - request: null, | ||
395 | - statusCode: null, | ||
396 | - body: null, | ||
397 | statusText: 'Can not connect to server. Reason: $e', | 423 | statusText: 'Can not connect to server. Reason: $e', |
398 | )); | 424 | )); |
399 | } | 425 | } |
@@ -417,14 +443,72 @@ class GetHttpClient { | @@ -417,14 +443,72 @@ class GetHttpClient { | ||
417 | throw GetHttpException(e.toString()); | 443 | throw GetHttpException(e.toString()); |
418 | } | 444 | } |
419 | return Future.value(Response<T>( | 445 | return Future.value(Response<T>( |
420 | - request: null, | ||
421 | - statusCode: null, | ||
422 | - body: null, | ||
423 | statusText: 'Can not connect to server. Reason: $e', | 446 | statusText: 'Can not connect to server. Reason: $e', |
424 | )); | 447 | )); |
425 | } | 448 | } |
426 | } | 449 | } |
427 | 450 | ||
451 | + // Future<Response<T>> download<T>( | ||
452 | + // String url, | ||
453 | + // String path, { | ||
454 | + // Map<String, String> headers, | ||
455 | + // String contentType = 'application/octet-stream', | ||
456 | + // Map<String, dynamic> query, | ||
457 | + // }) async { | ||
458 | + // try { | ||
459 | + // var response = await _performRequest<T>( | ||
460 | + // () => _get<T>(url, contentType, query, null), | ||
461 | + // headers: headers, | ||
462 | + // ); | ||
463 | + // response.bodyBytes.listen((value) {}); | ||
464 | + // return response; | ||
465 | + // } on Exception catch (e) { | ||
466 | + // if (!errorSafety) { | ||
467 | + // throw GetHttpException(e.toString()); | ||
468 | + // } | ||
469 | + // return Future.value(Response<T>( | ||
470 | + // statusText: 'Can not connect to server. Reason: $e', | ||
471 | + // )); | ||
472 | + // } | ||
473 | + | ||
474 | + // int byteCount = 0; | ||
475 | + // int totalBytes = httpResponse.contentLength; | ||
476 | + | ||
477 | + // Directory appDocDir = await getApplicationDocumentsDirectory(); | ||
478 | + // String appDocPath = appDocDir.path; | ||
479 | + | ||
480 | + // File file = File(path); | ||
481 | + | ||
482 | + // var raf = file.openSync(mode: FileMode.write); | ||
483 | + | ||
484 | + // Completer completer = Completer<String>(); | ||
485 | + | ||
486 | + // httpResponse.listen( | ||
487 | + // (data) { | ||
488 | + // byteCount += data.length; | ||
489 | + | ||
490 | + // raf.writeFromSync(data); | ||
491 | + | ||
492 | + // if (onDownloadProgress != null) { | ||
493 | + // onDownloadProgress(byteCount, totalBytes); | ||
494 | + // } | ||
495 | + // }, | ||
496 | + // onDone: () { | ||
497 | + // raf.closeSync(); | ||
498 | + | ||
499 | + // completer.complete(file.path); | ||
500 | + // }, | ||
501 | + // onError: (e) { | ||
502 | + // raf.closeSync(); | ||
503 | + // file.deleteSync(); | ||
504 | + // completer.completeError(e); | ||
505 | + // }, | ||
506 | + // cancelOnError: true, | ||
507 | + // ); | ||
508 | + | ||
509 | + // return completer.future; | ||
510 | + // } | ||
511 | + | ||
428 | Future<Response<T>> delete<T>( | 512 | Future<Response<T>> delete<T>( |
429 | String url, { | 513 | String url, { |
430 | Map<String, String> headers, | 514 | Map<String, String> headers, |
@@ -443,9 +527,6 @@ class GetHttpClient { | @@ -443,9 +527,6 @@ class GetHttpClient { | ||
443 | throw GetHttpException(e.toString()); | 527 | throw GetHttpException(e.toString()); |
444 | } | 528 | } |
445 | return Future.value(Response<T>( | 529 | return Future.value(Response<T>( |
446 | - request: null, | ||
447 | - statusCode: null, | ||
448 | - body: null, | ||
449 | statusText: 'Can not connect to server. Reason: $e', | 530 | statusText: 'Can not connect to server. Reason: $e', |
450 | )); | 531 | )); |
451 | } | 532 | } |
1 | +// import 'dart:html' as html; | ||
2 | + | ||
3 | +List<int> fileToBytes(dynamic data) { | ||
4 | + if (data is List<int>) { | ||
5 | + return data; | ||
6 | + } else { | ||
7 | + throw FormatException('File is not [File] or [String] or [List<int>]'); | ||
8 | + } | ||
9 | +} | ||
10 | + | ||
11 | +// void writeOnFile(List<int> bytes) { | ||
12 | +// var blob = html.Blob(["data"], 'text/plain', 'native'); | ||
13 | +// var anchorElement = html.AnchorElement( | ||
14 | +// href: html.Url.createObjectUrlFromBlob(blob).toString(), | ||
15 | +// ) | ||
16 | +// ..setAttribute("download", "data.txt") | ||
17 | +// ..click(); | ||
18 | +// } |
1 | import 'dart:async'; | 1 | import 'dart:async'; |
2 | import 'dart:html' as html; | 2 | import 'dart:html' as html; |
3 | import 'dart:typed_data'; | 3 | import 'dart:typed_data'; |
4 | -import '../certificates/certificates.dart'; | ||
5 | -import '../exceptions/exceptions.dart'; | ||
6 | -import '../request/request.dart'; | ||
7 | -import '../response/response.dart'; | ||
8 | -import 'body_decoder.dart'; | ||
9 | -import 'request_base.dart'; | 4 | + |
5 | +import '../../certificates/certificates.dart'; | ||
6 | +import '../../exceptions/exceptions.dart'; | ||
7 | +import '../../request/request.dart'; | ||
8 | +import '../../response/response.dart'; | ||
9 | +import '../interface/request_base.dart'; | ||
10 | +import '../utils/body_decoder.dart'; | ||
10 | 11 | ||
11 | /// A `dart:html` implementation of `HttpRequestBase`. | 12 | /// A `dart:html` implementation of `HttpRequestBase`. |
12 | class HttpRequestImpl implements HttpRequestBase { | 13 | class HttpRequestImpl implements HttpRequestBase { |
@@ -44,12 +45,24 @@ class HttpRequestImpl implements HttpRequestBase { | @@ -44,12 +45,24 @@ class HttpRequestImpl implements HttpRequestBase { | ||
44 | var reader = html.FileReader(); | 45 | var reader = html.FileReader(); |
45 | 46 | ||
46 | reader.onLoad.first.then((_) async { | 47 | reader.onLoad.first.then((_) async { |
47 | - var bodyBytes = BodyBytes.fromBytes(reader.result as Uint8List); | 48 | + var bodyBytes = BodyBytesStream.fromBytes(reader.result as Uint8List); |
48 | 49 | ||
49 | final stringBody = | 50 | final stringBody = |
50 | await bodyBytesToString(bodyBytes, xhr.responseHeaders); | 51 | await bodyBytesToString(bodyBytes, xhr.responseHeaders); |
51 | 52 | ||
52 | - final body = bodyDecoded<T>(request, stringBody); | 53 | + String contentType; |
54 | + | ||
55 | + if (xhr.responseHeaders.containsKey('content-type')) { | ||
56 | + contentType = xhr.responseHeaders['content-type']; | ||
57 | + } else { | ||
58 | + contentType = 'application/json'; | ||
59 | + } | ||
60 | + // xhr.responseHeaders.containsKey(key) | ||
61 | + final body = bodyDecoded<T>( | ||
62 | + request, | ||
63 | + stringBody, | ||
64 | + contentType, | ||
65 | + ); | ||
53 | 66 | ||
54 | final response = Response<T>( | 67 | final response = Response<T>( |
55 | bodyBytes: bodyBytes, | 68 | bodyBytes: bodyBytes, |
@@ -58,6 +71,7 @@ class HttpRequestImpl implements HttpRequestBase { | @@ -58,6 +71,7 @@ class HttpRequestImpl implements HttpRequestBase { | ||
58 | headers: xhr.responseHeaders, | 71 | headers: xhr.responseHeaders, |
59 | statusText: xhr.statusText, | 72 | statusText: xhr.statusText, |
60 | body: body, | 73 | body: body, |
74 | + bodyString: stringBody, | ||
61 | ); | 75 | ); |
62 | completer.complete(response); | 76 | completer.complete(response); |
63 | }); | 77 | }); |
1 | -import '../request/request.dart'; | ||
2 | -import '../response/response.dart'; | 1 | +import '../../request/request.dart'; |
2 | +import '../../response/response.dart'; | ||
3 | 3 | ||
4 | /// Abstract interface of [HttpRequestImpl]. | 4 | /// Abstract interface of [HttpRequestImpl]. |
5 | abstract class HttpRequestBase { | 5 | abstract class HttpRequestBase { |
1 | +import 'dart:io'; | ||
2 | + | ||
3 | +List<int> fileToBytes(dynamic data) { | ||
4 | + if (data is File) { | ||
5 | + return data.readAsBytesSync(); | ||
6 | + } else if (data is String) { | ||
7 | + if (File(data).existsSync()) { | ||
8 | + return File(data).readAsBytesSync(); | ||
9 | + } else { | ||
10 | + throw 'File [data] not exists'; | ||
11 | + } | ||
12 | + } else if (data is List<int>) { | ||
13 | + return data; | ||
14 | + } else { | ||
15 | + throw FormatException('File is not [File] or [String] or [List<int>]'); | ||
16 | + } | ||
17 | +} | ||
18 | + | ||
19 | +void writeOnFile(List<int> bytes) {} |
1 | +import 'dart:async'; | ||
1 | import 'dart:io' as io; | 2 | import 'dart:io' as io; |
2 | 3 | ||
3 | -import '../certificates/certificates.dart'; | ||
4 | -import '../exceptions/exceptions.dart'; | ||
5 | -import '../request/request.dart'; | ||
6 | -import '../response/response.dart'; | ||
7 | -import 'body_decoder.dart'; | ||
8 | -import 'request_base.dart'; | 4 | +import '../../certificates/certificates.dart'; |
5 | +import '../../exceptions/exceptions.dart'; | ||
6 | +import '../../request/request.dart'; | ||
7 | +import '../../response/response.dart'; | ||
8 | +import '../interface/request_base.dart'; | ||
9 | +import '../utils/body_decoder.dart'; | ||
9 | 10 | ||
10 | /// A `dart:io` implementation of `HttpRequestBase`. | 11 | /// A `dart:io` implementation of `HttpRequestBase`. |
11 | class HttpRequestImpl extends HttpRequestBase { | 12 | class HttpRequestImpl extends HttpRequestBase { |
@@ -32,7 +33,7 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -32,7 +33,7 @@ class HttpRequestImpl extends HttpRequestBase { | ||
32 | @override | 33 | @override |
33 | Future<Response<T>> send<T>(Request<T> request) async { | 34 | Future<Response<T>> send<T>(Request<T> request) async { |
34 | var requestBody = await request.bodyBytes.toBytes(); | 35 | var requestBody = await request.bodyBytes.toBytes(); |
35 | - var stream = BodyBytes.fromBytes(requestBody ?? const []); | 36 | + var stream = BodyBytesStream.fromBytes(requestBody ?? const []); |
36 | 37 | ||
37 | try { | 38 | try { |
38 | var ioRequest = (await _httpClient.openUrl(request.method, request.url)) | 39 | var ioRequest = (await _httpClient.openUrl(request.method, request.url)) |
@@ -42,6 +43,12 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -42,6 +43,12 @@ class HttpRequestImpl extends HttpRequestBase { | ||
42 | ..contentLength = requestBody.length ?? -1; | 43 | ..contentLength = requestBody.length ?? -1; |
43 | request.headers.forEach(ioRequest.headers.set); | 44 | request.headers.forEach(ioRequest.headers.set); |
44 | 45 | ||
46 | + // var response = await stream.map((s) { | ||
47 | + // received += s.length; | ||
48 | + // print("${(received / total) * 100} %"); | ||
49 | + // return s; | ||
50 | + // }).pipe(ioRequest) as io.HttpClientResponse; | ||
51 | + | ||
45 | var response = await stream.pipe(ioRequest) as io.HttpClientResponse; | 52 | var response = await stream.pipe(ioRequest) as io.HttpClientResponse; |
46 | 53 | ||
47 | var headers = <String, String>{}; | 54 | var headers = <String, String>{}; |
@@ -49,10 +56,16 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -49,10 +56,16 @@ class HttpRequestImpl extends HttpRequestBase { | ||
49 | headers[key] = values.join(','); | 56 | headers[key] = values.join(','); |
50 | }); | 57 | }); |
51 | 58 | ||
52 | - final bodyBytes = BodyBytes(response); | 59 | + final bodyBytes = BodyBytesStream(response); |
53 | final stringBody = await bodyBytesToString(bodyBytes, headers); | 60 | final stringBody = await bodyBytesToString(bodyBytes, headers); |
54 | 61 | ||
55 | - final body = bodyDecoded<T>(request, stringBody); | 62 | + // response.headers.contentType.mimeType == 'application/json' |
63 | + | ||
64 | + final body = bodyDecoded<T>( | ||
65 | + request, | ||
66 | + stringBody, | ||
67 | + response.headers.contentType.mimeType, | ||
68 | + ); | ||
56 | 69 | ||
57 | return Response( | 70 | return Response( |
58 | headers: headers, | 71 | headers: headers, |
@@ -61,6 +74,7 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -61,6 +74,7 @@ class HttpRequestImpl extends HttpRequestBase { | ||
61 | statusText: response.reasonPhrase, | 74 | statusText: response.reasonPhrase, |
62 | bodyBytes: bodyBytes, | 75 | bodyBytes: bodyBytes, |
63 | body: body, | 76 | body: body, |
77 | + bodyString: stringBody, | ||
64 | ); | 78 | ); |
65 | } on io.HttpException catch (error) { | 79 | } on io.HttpException catch (error) { |
66 | throw GetHttpException(error.message, error.uri); | 80 | throw GetHttpException(error.message, error.uri); |
@@ -77,8 +91,8 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -77,8 +91,8 @@ class HttpRequestImpl extends HttpRequestBase { | ||
77 | } | 91 | } |
78 | } | 92 | } |
79 | 93 | ||
80 | -extension FileExt on io.FileSystemEntity { | ||
81 | - String get fileName { | ||
82 | - return this?.path?.split(io.Platform.pathSeparator)?.last; | ||
83 | - } | ||
84 | -} | 94 | +// extension FileExt on io.FileSystemEntity { |
95 | +// String get fileName { | ||
96 | +// return this?.path?.split(io.Platform.pathSeparator)?.last; | ||
97 | +// } | ||
98 | +// } |
1 | -import '../certificates/certificates.dart'; | ||
2 | -import '../request/request.dart'; | ||
3 | -import '../response/response.dart'; | ||
4 | -import 'request_base.dart'; | 1 | +import '../../certificates/certificates.dart'; |
2 | +import '../../request/request.dart'; | ||
3 | +import '../../response/response.dart'; | ||
4 | +import '../interface/request_base.dart'; | ||
5 | 5 | ||
6 | class HttpRequestImpl extends HttpRequestBase { | 6 | class HttpRequestImpl extends HttpRequestBase { |
7 | HttpRequestImpl({ | 7 | HttpRequestImpl({ |
1 | import 'dart:convert'; | 1 | import 'dart:convert'; |
2 | 2 | ||
3 | -import '../../../../get_core/get_core.dart'; | 3 | +import '../../../../../get_core/get_core.dart'; |
4 | 4 | ||
5 | -import '../request/request.dart'; | 5 | +import '../../request/request.dart'; |
6 | 6 | ||
7 | -T bodyDecoded<T>(Request<T> request, String stringBody) { | 7 | +T bodyDecoded<T>(Request<T> request, String stringBody, String mimeType) { |
8 | T body; | 8 | T body; |
9 | var bodyToDecode; | 9 | var bodyToDecode; |
10 | + | ||
11 | + if (mimeType.contains('application/json')) { | ||
10 | try { | 12 | try { |
11 | bodyToDecode = jsonDecode(stringBody); | 13 | bodyToDecode = jsonDecode(stringBody); |
12 | } on FormatException catch (_) { | 14 | } on FormatException catch (_) { |
13 | - Get.log('Cannot decode body in json'); | 15 | + Get.log('Cannot decode server response to json'); |
14 | bodyToDecode = stringBody; | 16 | bodyToDecode = stringBody; |
15 | } | 17 | } |
18 | + } | ||
16 | 19 | ||
17 | try { | 20 | try { |
18 | if (request.decoder == null) { | 21 | if (request.decoder == null) { |
@@ -89,7 +89,7 @@ class FormData { | @@ -89,7 +89,7 @@ class FormData { | ||
89 | } | 89 | } |
90 | 90 | ||
91 | Future<List<int>> toBytes() { | 91 | Future<List<int>> toBytes() { |
92 | - return BodyBytes(_encode()).toBytes(); | 92 | + return BodyBytesStream(_encode()).toBytes(); |
93 | } | 93 | } |
94 | 94 | ||
95 | Stream<List<int>> _encode() async* { | 95 | Stream<List<int>> _encode() async* { |
1 | import 'package:flutter/foundation.dart'; | 1 | import 'package:flutter/foundation.dart'; |
2 | 2 | ||
3 | +import '../http/stub/file_decoder_stub.dart' | ||
4 | + if (dart.library.html) '../http/html/file_decoder_html.dart' | ||
5 | + if (dart.library.io) '../http/io/file_decoder_io.dart'; | ||
6 | + | ||
3 | import '../request/request.dart'; | 7 | import '../request/request.dart'; |
4 | 8 | ||
5 | class MultipartFile { | 9 | class MultipartFile { |
6 | MultipartFile( | 10 | MultipartFile( |
7 | - List<int> bytes, { | 11 | + dynamic data, { |
8 | @required this.filename, | 12 | @required this.filename, |
9 | this.contentType = 'application/octet-stream', | 13 | this.contentType = 'application/octet-stream', |
10 | - }) : length = bytes.length, | ||
11 | - stream = BodyBytes.fromBytes(bytes); | 14 | + }) : _bytes = fileToBytes(data) { |
15 | + _length = _bytes.length; | ||
16 | + _stream = BodyBytesStream.fromBytes(_bytes); | ||
17 | + } | ||
18 | + | ||
19 | + final List<int> _bytes; | ||
12 | 20 | ||
13 | final String contentType; | 21 | final String contentType; |
14 | 22 | ||
15 | /// This stream will emit the file content of File. | 23 | /// This stream will emit the file content of File. |
16 | - final BodyBytes stream; | 24 | + BodyBytesStream _stream; |
25 | + | ||
26 | + int _length; | ||
27 | + | ||
28 | + BodyBytesStream get stream => _stream; | ||
17 | 29 | ||
18 | - final int length; | 30 | + int get length => _length; |
19 | 31 | ||
20 | final String filename; | 32 | final String filename; |
21 | } | 33 | } |
@@ -20,8 +20,8 @@ class Request<T> { | @@ -20,8 +20,8 @@ class Request<T> { | ||
20 | /// ex: `GET`,`POST`,`PUT`,`DELETE` | 20 | /// ex: `GET`,`POST`,`PUT`,`DELETE` |
21 | final String method; | 21 | final String method; |
22 | 22 | ||
23 | - /// The BodyBytes of body from this [Request] | ||
24 | - final BodyBytes bodyBytes; | 23 | + /// The BodyBytesStream of body from this [Request] |
24 | + final BodyBytesStream bodyBytes; | ||
25 | 25 | ||
26 | /// When true, the client will follow redirects to resolves this [Request] | 26 | /// When true, the client will follow redirects to resolves this [Request] |
27 | final bool followRedirects; | 27 | final bool followRedirects; |
@@ -49,7 +49,7 @@ class Request<T> { | @@ -49,7 +49,7 @@ class Request<T> { | ||
49 | @required Uri url, | 49 | @required Uri url, |
50 | @required String method, | 50 | @required String method, |
51 | @required Map<String, String> headers, | 51 | @required Map<String, String> headers, |
52 | - BodyBytes bodyBytes, | 52 | + BodyBytesStream bodyBytes, |
53 | bool followRedirects = true, | 53 | bool followRedirects = true, |
54 | int maxRedirects = 4, | 54 | int maxRedirects = 4, |
55 | FormData files, | 55 | FormData files, |
@@ -66,7 +66,7 @@ class Request<T> { | @@ -66,7 +66,7 @@ class Request<T> { | ||
66 | return Request._( | 66 | return Request._( |
67 | url: url, | 67 | url: url, |
68 | method: method, | 68 | method: method, |
69 | - bodyBytes: bodyBytes ??= BodyBytes.fromBytes(const []), | 69 | + bodyBytes: bodyBytes ??= BodyBytesStream.fromBytes(const []), |
70 | headers: Map.from(headers ??= <String, String>{}), | 70 | headers: Map.from(headers ??= <String, String>{}), |
71 | followRedirects: followRedirects, | 71 | followRedirects: followRedirects, |
72 | maxRedirects: maxRedirects, | 72 | maxRedirects: maxRedirects, |
@@ -77,11 +77,11 @@ class Request<T> { | @@ -77,11 +77,11 @@ class Request<T> { | ||
77 | } | 77 | } |
78 | } | 78 | } |
79 | 79 | ||
80 | -class BodyBytes extends StreamView<List<int>> { | ||
81 | - BodyBytes(Stream<List<int>> stream) : super(stream); | 80 | +class BodyBytesStream extends StreamView<List<int>> { |
81 | + BodyBytesStream(Stream<List<int>> stream) : super(stream); | ||
82 | 82 | ||
83 | - factory BodyBytes.fromBytes(List<int> bytes) => | ||
84 | - BodyBytes(Stream.fromIterable([bytes])); | 83 | + factory BodyBytesStream.fromBytes(List<int> bytes) => |
84 | + BodyBytesStream(Stream.fromIterable([bytes])); | ||
85 | 85 | ||
86 | Future<Uint8List> toBytes() { | 86 | Future<Uint8List> toBytes() { |
87 | var completer = Completer<Uint8List>(); | 87 | var completer = Completer<Uint8List>(); |
1 | import 'dart:collection'; | 1 | import 'dart:collection'; |
2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
3 | - | ||
4 | -import 'package:flutter/foundation.dart'; | ||
5 | - | ||
6 | import '../request/request.dart'; | 3 | import '../request/request.dart'; |
7 | import '../status/http_status.dart'; | 4 | import '../status/http_status.dart'; |
8 | 5 | ||
9 | class Response<T> { | 6 | class Response<T> { |
10 | const Response({ | 7 | const Response({ |
11 | - @required this.request, | ||
12 | - @required this.statusCode, | ||
13 | - // ignore: always_require_non_null_named_parameters | 8 | + this.request, |
9 | + this.statusCode, | ||
14 | this.bodyBytes, | 10 | this.bodyBytes, |
11 | + this.bodyString, | ||
15 | this.statusText = '', | 12 | this.statusText = '', |
16 | this.headers = const {}, | 13 | this.headers = const {}, |
17 | - @required this.body, | 14 | + this.body, |
18 | }); | 15 | }); |
19 | 16 | ||
20 | /// The Http [Request] linked with this [Response]. | 17 | /// The Http [Request] linked with this [Response]. |
@@ -46,7 +43,10 @@ class Response<T> { | @@ -46,7 +43,10 @@ class Response<T> { | ||
46 | bool get unauthorized => status.isUnauthorized; | 43 | bool get unauthorized => status.isUnauthorized; |
47 | 44 | ||
48 | /// The response body as a Stream of Bytes. | 45 | /// The response body as a Stream of Bytes. |
49 | - final BodyBytes bodyBytes; | 46 | + final BodyBytesStream bodyBytes; |
47 | + | ||
48 | + /// The response body as a Stream of Bytes. | ||
49 | + final String bodyString; | ||
50 | 50 | ||
51 | /// The decoded body of this [Response]. You can access the | 51 | /// The decoded body of this [Response]. You can access the |
52 | /// body parameters as Map | 52 | /// body parameters as Map |
@@ -55,7 +55,7 @@ class Response<T> { | @@ -55,7 +55,7 @@ class Response<T> { | ||
55 | } | 55 | } |
56 | 56 | ||
57 | Future<String> bodyBytesToString( | 57 | Future<String> bodyBytesToString( |
58 | - BodyBytes bodyBytes, Map<String, String> headers) { | 58 | + BodyBytesStream bodyBytes, Map<String, String> headers) { |
59 | return bodyBytes.bytesToString(_encodingForHeaders(headers)); | 59 | return bodyBytes.bytesToString(_encodingForHeaders(headers)); |
60 | } | 60 | } |
61 | 61 |
@@ -52,9 +52,9 @@ String validateField(String field) { | @@ -52,9 +52,9 @@ String validateField(String field) { | ||
52 | return field.toLowerCase(); | 52 | return field.toLowerCase(); |
53 | } | 53 | } |
54 | 54 | ||
55 | -BodyBytes toBodyBytes(Stream<List<int>> stream) { | ||
56 | - if (stream is BodyBytes) return stream; | ||
57 | - return BodyBytes(stream); | 55 | +BodyBytesStream toBodyBytesStream(Stream<List<int>> stream) { |
56 | + if (stream is BodyBytesStream) return stream; | ||
57 | + return BodyBytesStream(stream); | ||
58 | } | 58 | } |
59 | 59 | ||
60 | final _asciiOnly = RegExp(r'^[\x00-\x7F]+$'); | 60 | final _asciiOnly = RegExp(r'^[\x00-\x7F]+$'); |
1 | name: get | 1 | name: get |
2 | description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX. | 2 | description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX. |
3 | -version: 3.21.3 | 3 | +version: 3.22.0 |
4 | homepage: https://github.com/jonataslaw/getx | 4 | homepage: https://github.com/jonataslaw/getx |
5 | 5 | ||
6 | environment: | 6 | environment: |
-
Please register or login to post a comment