Skip to content

Commit

Permalink
1. fix #877 'dio.interceptors.errorLock.lock()'
Browse files Browse the repository at this point in the history
2. fix #851
3. fix #641
  • Loading branch information
wendux committed Aug 7, 2020
1 parent 36ba724 commit 516825f
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 50 deletions.
1 change: 1 addition & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion dio/lib/src/adapters/browser_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
Expand Down
4 changes: 3 additions & 1 deletion dio/lib/src/adapters/io_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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;
Expand Down
24 changes: 14 additions & 10 deletions dio/lib/src/dio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
};
}

Expand Down Expand Up @@ -1032,7 +1035,8 @@ abstract class DioMixin implements Dio {
options.headers[Headers.contentLengthHeader] = length.toString();
}
var complete = 0;
var byteStream = stream.transform<Uint8List>(StreamTransformer.fromHandlers(
var byteStream =
stream.transform<Uint8List>(StreamTransformer.fromHandlers(
handleData: (data, sink) {
if (options.cancelToken != null && options.cancelToken.isCancelled) {
sink
Expand Down
32 changes: 16 additions & 16 deletions dio/lib/src/interceptors/log.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
}
Expand All @@ -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);
}
}
6 changes: 2 additions & 4 deletions dio/lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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];
Expand Down
4 changes: 2 additions & 2 deletions dio/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

59 changes: 58 additions & 1 deletion dio/test/interceptor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
});
});
}
29 changes: 29 additions & 0 deletions dio/test/mock_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions example/dio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion example/interceptor_lock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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...");
Expand Down
26 changes: 12 additions & 14 deletions example/test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit 516825f

Please sign in to comment.