Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed: update in example/lib/queued_interceptor_csrftoken.dart #1262

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions dio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,28 +444,41 @@ Because of security reasons, we need all the requests to set up a csrfToken in t
// dio instance to request token
var tokenDio = Dio();
String? csrfToken;
dio.options.baseUrl = 'http://www.dtworkroom.com/doris/1/2.0.0/';
dio.options.baseUrl = 'https://seunghwanlytest.mocklab.io/';
tokenDio.options = dio.options;
dio.interceptors.add(QueuedInterceptorsWrapper(
onRequest: (options, handler) {
onRequest: (options, handler) async {
print('send request:path:${options.path},baseURL:${options.baseUrl}');

if (csrfToken == null) {
print('no token,request token firstly...');
tokenDio.get('/token').then((d) {
options.headers['csrfToken'] = csrfToken = d.data['data']['token'];
print('request token succeed, value: ' + d.data['data']['token']);
print(
'continue to perform request:path:${options.path},baseURL:${options.path}');
handler.next(options);
}).catchError((error, stackTrace) {
handler.reject(error, true);
});
} else {
options.headers['csrfToken'] = csrfToken;
return handler.next(options);

final result = await tokenDio.get('/token');

if (result.hasSucceed) {
/// assume `token` is in response body
final body = jsonDecode(result.data) as Map<String, dynamic>?;

if (body != null && body.containsKey('data')) {
options.headers['csrfToken'] = csrfToken = body['data']['token'];
print('request token succeed, value: $csrfToken');
print(
'continue to perform request:path:${options.path},baseURL:${options.path}',
);
return handler.next(options);
}
}

return handler.reject(
DioError(requestOptions: result.requestOptions),
true,
);
}

options.headers['csrfToken'] = csrfToken;
return handler.next(options);
},
);
);
```

You can clean the waiting queue by calling `clear()`;
Expand Down
10 changes: 10 additions & 0 deletions dio/lib/src/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ class Response<T> {
/// Http status code.
int? statusCode;

/// Returns if the response has an error
///
/// `hasError` is true when status code is not between 200 and 299
bool get hasError => statusCode != null && statusCode! ~/ 100 != 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC we have ValidateStatus as a prediction, it might be inappropriate to use such a getter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it would be nice to have the getter direct to the Responseobject, but if it is an appropriate way I'll remove the getter. Thanks :)


/// Returns if the response has succeed
///
/// `hasSucceed` is true when status code is between 200 and 299
bool get hasSucceed => !hasError;

/// Returns the reason phrase associated with the status code.
/// The reason phrase must be set before the body is written
/// to. Setting the reason phrase after writing to the body.
Expand Down
108 changes: 61 additions & 47 deletions example/lib/queued_interceptor_crsftoken.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';

import 'package:dio/dio.dart';

Expand All @@ -7,57 +8,70 @@ void main() async {
// dio instance to request token
var tokenDio = Dio();
String? csrfToken;
dio.options.baseUrl = 'http://www.dtworkroom.com/doris/1/2.0.0/';
dio.options.baseUrl = 'https://seunghwanlytest.mocklab.io/';
tokenDio.options = dio.options;
dio.interceptors.add(QueuedInterceptorsWrapper(
onRequest: (options, handler) {
onRequest: (options, handler) async {
print('send request:path:${options.path},baseURL:${options.baseUrl}');

if (csrfToken == null) {
print('no token,request token firstly...');
tokenDio.get('/token').then((d) {
options.headers['csrfToken'] = csrfToken = d.data['data']['token'];
print('request token succeed, value: ' + d.data['data']['token']);
print(
'continue to perform request:path:${options.path},baseURL:${options.path}');
handler.next(options);
}).catchError((error, stackTrace) {
handler.reject(error, true);
});
} else {
options.headers['csrfToken'] = csrfToken;
return handler.next(options);

final result = await tokenDio.get('/token');

if (result.hasSucceed) {
/// assume `token` is in response body
final body = jsonDecode(result.data) as Map<String, dynamic>?;

if (body != null && body.containsKey('data')) {
options.headers['csrfToken'] = csrfToken = body['data']['token'];
print('request token succeed, value: $csrfToken');
print(
'continue to perform request:path:${options.path},baseURL:${options.path}',
);
return handler.next(options);
}
}

return handler.reject(
DioError(requestOptions: result.requestOptions),
true,
);
}

options.headers['csrfToken'] = csrfToken;
return handler.next(options);
},
onError: (error, handler) {
//print(error);
// Assume 401 stands for token expired
onError: (error, handler) async {
/// Assume 401 stands for token expired
if (error.response?.statusCode == 401) {
var options = error.response!.requestOptions;
// If the token has been updated, repeat directly.
if (csrfToken != options.headers['csrfToken']) {
options.headers['csrfToken'] = csrfToken;
//repeat
dio.fetch(options).then(
(r) => handler.resolve(r),
onError: (e) {
handler.reject(e);
},
);
return;
print('the token has expired, need to receive new token');
final options = error.response!.requestOptions;

/// assume receiving the token has no errors
/// to check `null-safety` and error handling
/// please check inside the [onRequest] closure
final tokenResult = await tokenDio.get('/token');

/// update [csrfToken]
/// assume `token` is in response body
final body = jsonDecode(tokenResult.data) as Map<String, dynamic>?;
options.headers['csrfToken'] = csrfToken = body!['data']['token'];

if (options.headers['csrfToken'] != null) {
print('the token has been updated');

/// since the api has no state, force to pass the 401 error
/// by adding query parameter
final originResult = await dio.fetch(options..path += '&pass=true');
if (originResult.hasSucceed) {
return handler.resolve(originResult);
}
}
tokenDio.get('/token').then((d) {
//update csrfToken
options.headers['csrfToken'] = csrfToken = d.data['data']['token'];
}).then((e) {
//repeat
dio.fetch(options).then(
(r) => handler.resolve(r),
onError: (e) {
handler.reject(e);
},
);
});
return;
print('the token has not been updated');
return handler.reject(
DioError(requestOptions: options),
);
}
return handler.next(error);
},
Expand All @@ -67,9 +81,9 @@ void main() async {
print('request ok!');
}

await Future.wait([
dio.get('/test?tag=1').then(_onResult),
dio.get('/test?tag=2').then(_onResult),
dio.get('/test?tag=3').then(_onResult)
]);
/// assume `/test?tag=2` path occurs the authorization error (401)
/// and token to be updated
await dio.get('/test?tag=1').then(_onResult);
await dio.get('/test?tag=2').then(_onResult);
await dio.get('/test?tag=3').then(_onResult);
}