diff --git a/.github/stale.yml b/.github/stale.yml index 3b3b587f7..470dec312 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -5,6 +5,7 @@ daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - important + - pinned # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable diff --git a/dio/lib/src/adapters/browser_adapter.dart b/dio/lib/src/adapters/browser_adapter.dart index fdd50b538..6806ab305 100644 --- a/dio/lib/src/adapters/browser_adapter.dart +++ b/dio/lib/src/adapters/browser_adapter.dart @@ -47,7 +47,7 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { body, xhr.status, headers: xhr.responseHeaders - .map((k, v) => MapEntry(k.toLowerCase(), v.split(','))), + .map((k, v) => MapEntry(k, v.split(','))), statusMessage: xhr.statusText, isRedirect: xhr.status == 302 || xhr.status == 301, ), diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index f2b1c0cd0..6071aa6e0 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -86,7 +86,8 @@ class DefaultHttpClientAdapter implements HttpClientAdapter { stream, responseStream.statusCode, headers: headers, - isRedirect: responseStream.isRedirect||responseStream.redirects.isNotEmpty, + isRedirect: + responseStream.isRedirect || responseStream.redirects.isNotEmpty, redirects: responseStream.redirects .map((e) => RedirectRecord(e.statusCode, e.method, e.location)) .toList(), @@ -100,6 +101,7 @@ class DefaultHttpClientAdapter implements HttpClientAdapter { : null; if (cancelFuture != null) { var _httpClient = HttpClient(); + _httpClient.userAgent = null; if (onHttpClientCreate != null) { //user can return a HttpClient instance _httpClient = onHttpClientCreate(_httpClient) ?? _httpClient; diff --git a/dio/lib/src/dio.dart b/dio/lib/src/dio.dart index a64df98c2..6120dea2e 100644 --- a/dio/lib/src/dio.dart +++ b/dio/lib/src/dio.dart @@ -862,16 +862,19 @@ abstract class DioMixin implements Dio { // Convert the error interceptor to a functional callback in which // we can handle the return value of interceptor callback. Function _errorInterceptorWrapper(errInterceptor) { - return (err) async { - if (err is! Response) { - var _e = await errInterceptor(assureDioError(err, requestOptions)); - if (_e is! Response) { - throw assureDioError(_e ?? err, requestOptions); + return (err) { + return checkIfNeedEnqueue(interceptors.errorLock, (){ + if (err is! Response) { + return errInterceptor(assureDioError(err, requestOptions)).then((e){ + if (e is! Response) { + throw assureDioError(e ?? err, requestOptions); + } + return e; + }); } - err = _e; - } - // err is a Response instance - return err; + // err is a Response instance + return err; + }); }; } @@ -1032,7 +1035,8 @@ abstract class DioMixin implements Dio { options.headers[Headers.contentLengthHeader] = length.toString(); } var complete = 0; - var byteStream = stream.transform(StreamTransformer.fromHandlers( + var byteStream = + stream.transform(StreamTransformer.fromHandlers( handleData: (data, sink) { if (options.cancelToken != null && options.cancelToken.isCancelled) { sink diff --git a/dio/lib/src/interceptors/log.dart b/dio/lib/src/interceptors/log.dart index 458d366c5..82e3dce6a 100644 --- a/dio/lib/src/interceptors/log.dart +++ b/dio/lib/src/interceptors/log.dart @@ -53,23 +53,23 @@ class LogInterceptor extends Interceptor { @override Future onRequest(RequestOptions options) async { logPrint('*** Request ***'); - printKV('uri', options.uri); + _printKV('uri', options.uri); if (request) { - printKV('method', options.method); - printKV('responseType', options.responseType?.toString()); - printKV('followRedirects', options.followRedirects); - printKV('connectTimeout', options.connectTimeout); - printKV('receiveTimeout', options.receiveTimeout); - printKV('extra', options.extra); + _printKV('method', options.method); + _printKV('responseType', options.responseType?.toString()); + _printKV('followRedirects', options.followRedirects); + _printKV('connectTimeout', options.connectTimeout); + _printKV('receiveTimeout', options.receiveTimeout); + _printKV('extra', options.extra); } if (requestHeader) { logPrint('headers:'); - options.headers.forEach((key, v) => printKV(' $key', v)); + options.headers.forEach((key, v) => _printKV(' $key', v)); } if (requestBody) { logPrint('data:'); - printAll(options.data); + _printAll(options.data); } logPrint(''); } @@ -94,29 +94,29 @@ class LogInterceptor extends Interceptor { } void _printResponse(Response response) { - printKV('uri', response.request?.uri); + _printKV('uri', response.request?.uri); if (responseHeader) { - printKV('statusCode', response.statusCode); + _printKV('statusCode', response.statusCode); if (response.isRedirect == true) { - printKV('redirect', response.realUri); + _printKV('redirect', response.realUri); } if (response.headers != null) { logPrint('headers:'); - response.headers.forEach((key, v) => printKV(' $key', v.join(','))); + response.headers.forEach((key, v) => _printKV(' $key', v.join(','))); } } if (responseBody) { logPrint('Response Text:'); - printAll(response.toString()); + _printAll(response.toString()); } logPrint(''); } - void printKV(String key, Object v) { + void _printKV(String key, Object v) { logPrint('$key: $v'); } - void printAll(msg) { + void _printAll(msg) { msg.toString().split('\n').forEach(logPrint); } } diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index 9411ebd86..f4b25cea6 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -137,7 +137,7 @@ class BaseOptions extends _RequestConfig { /// Every request can pass an [Options] object which will be merged with [Dio.options] class Options extends _RequestConfig { - Options({ + Options({ String method, int sendTimeout, int receiveTimeout, @@ -350,8 +350,6 @@ class _RequestConfig { this.headers = headers ?? {}; this.extra = extra ?? {}; this.contentType = contentType; - this.headers = - this.headers.map((key, v) => MapEntry(key.toLowerCase().toString(), v)); } /// Http method. @@ -380,7 +378,7 @@ class _RequestConfig { /// you can set `ContentType.parse('application/x-www-form-urlencoded')`, and [Dio] /// will automatically encode the request body. set contentType(String contentType) { - headers[Headers.contentTypeHeader] = contentType?.toLowerCase()?.trim(); + headers[Headers.contentTypeHeader] = contentType?.trim(); } String get contentType => headers[Headers.contentTypeHeader]; diff --git a/dio/pubspec.yaml b/dio/pubspec.yaml index 4b2704ca0..61b8d8b3e 100644 --- a/dio/pubspec.yaml +++ b/dio/pubspec.yaml @@ -14,6 +14,6 @@ dev_dependencies: test: ^1.5.1 pedantic: ^1.8.0 test_coverage: ^0.4.1 - flutter_test: - sdk: flutter +# flutter_test: +# sdk: flutter diff --git a/dio/test/interceptor_test.dart b/dio/test/interceptor_test.dart index 40625940e..f2821fcd4 100644 --- a/dio/test/interceptor_test.dart +++ b/dio/test/interceptor_test.dart @@ -109,7 +109,7 @@ void main() { throwsA(equals('custom error info [404]'))); }); - group('Interceptor lock', () { + group('Interceptor request lock', () { test('test', () async { String csrfToken; Dio dio = Dio(); @@ -153,4 +153,61 @@ void main() { assert(dio.interceptors.isEmpty == true); }); }); + + group('Interceptor error lock', () { + test('test', () async { + String csrfToken; + Dio dio = Dio(); + int tokenRequestCounts = 0; + // dio instance to request token + Dio tokenDio = Dio(); + dio.options.baseUrl = tokenDio.options.baseUrl = MockAdapter.mockBase; + dio.httpClientAdapter = tokenDio.httpClientAdapter = MockAdapter(); + dio.interceptors.add(InterceptorsWrapper(onRequest: (opt) { + opt.headers["csrfToken"] = csrfToken; + }, onError: (DioError error) { + // Assume 401 stands for token expired + if (error.response?.statusCode == 401) { + RequestOptions options = error.response.request; + // If the token has been updated, repeat directly. + if (csrfToken != options.headers["csrfToken"]) { + options.headers["csrfToken"] = csrfToken; + //repeat + return dio.request(options.path, options: options); + } + // update token and repeat + // Lock to block the incoming request until the token updated + dio.lock(); + dio.interceptors.responseLock.lock(); + dio.interceptors.errorLock.lock(); + tokenRequestCounts++; + return tokenDio.get("/token").then((d) { + //update csrfToken + options.headers["csrfToken"] = csrfToken = d.data['data']['token']; + }).whenComplete(() { + dio.unlock(); + dio.interceptors.responseLock.unlock(); + dio.interceptors.errorLock.unlock(); + }).then((e) { + //repeat + return dio.request(options.path, options: options); + }); + } + return error; + })); + + int result = 0; + _onResult(d) { + if (tokenRequestCounts > 0) ++result; + } + + await Future.wait([ + dio.get('/test-auth?tag=1').then(_onResult), + dio.get('/test-auth?tag=2').then(_onResult), + dio.get('/test-auth?tag=3').then(_onResult) + ]); + expect(tokenRequestCounts, 1); + expect(result, 3); + }); + }); } diff --git a/dio/test/mock_adapter.dart b/dio/test/mock_adapter.dart index 11c92b41b..fa72bc49b 100644 --- a/dio/test/mock_adapter.dart +++ b/dio/test/mock_adapter.dart @@ -27,6 +27,35 @@ class MockAdapter extends HttpClientAdapter { Headers.contentTypeHeader: [Headers.jsonContentType], }, ); + break; + case "/test-auth": + { + return Future.delayed(Duration(milliseconds: 300), () { + if (options.headers['csrfToken'] == null) { + return ResponseBody.fromString( + jsonEncode({ + "errCode": -1, + "data": {"path": uri.path} + }), + 401, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ); + } + return ResponseBody.fromString( + jsonEncode({ + "errCode": 0, + "data": {"path": uri.path} + }), + 200, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ); + }); + } + break; case "/download": return Future.delayed(Duration(milliseconds: 300), () { return ResponseBody( diff --git a/example/dio.dart b/example/dio.dart index e435b3536..7e5145ceb 100644 --- a/example/dio.dart +++ b/example/dio.dart @@ -13,6 +13,7 @@ main() async { ..headers = { HttpHeaders.userAgentHeader: 'dio', 'common-header': 'xx', + 'Common-header': 'xx', }; // Or you can create dio instance and config it as follow: diff --git a/example/interceptor_lock.dart b/example/interceptor_lock.dart index 628bc92ec..88de37105 100644 --- a/example/interceptor_lock.dart +++ b/example/interceptor_lock.dart @@ -9,7 +9,8 @@ main() async { String csrfToken; dio.options.baseUrl = "http://www.dtworkroom.com/doris/1/2.0.0/"; tokenDio.options = dio.options; - dio.interceptors.add(InterceptorsWrapper(onRequest: (RequestOptions options) { + dio.interceptors.add(InterceptorsWrapper( + onRequest: (RequestOptions options) { print('send request:path:${options.path},baseURL:${options.baseUrl}'); if (csrfToken == null) { print("no token,request token firstly..."); diff --git a/example/test.dart b/example/test.dart index 74f52dd6c..f4c24e2ea 100644 --- a/example/test.dart +++ b/example/test.dart @@ -4,24 +4,22 @@ void getHttp() async { var dio = Dio(); dio.interceptors.add(LogInterceptor(responseBody: true)); dio.options.baseUrl = "http://httpbin.org"; + dio.options.headers= { + 'Authorization': 'Bearer ' + }; //dio.options.baseUrl = "http://localhost:3000"; - dio.options.receiveDataWhenStatusError=false; - dio.interceptors.add(InterceptorsWrapper(onResponse: (r){ - //throw DioError(...); or - return dio.reject("xxx"); - })); - try { - await Future.wait([ - dio.get("/get", queryParameters: {"id": 1}), - dio.get("/get", queryParameters: {"id": 2}) - ]); - } catch (e) { - print(e); - } + var response=await dio.post("/post",data:null, + options: Options(contentType: Headers.jsonContentType, headers: {"Content-Type": "application/json"}) + ); + print(response); } main() async { - await getHttp(); + //await getHttp(); + + var response = await Dio().get("http://flutterchina.club"); + print(response.isRedirect); + // var t = await MultipartFile.fromBytes([5]); // print(t); }