Дарт http: "Плохое состояние: не удается завершить завершенный запрос" при повторной попытке http.Request после получения нового токена доступа
В настоящее время я пытаюсь получить доступ к веб-API во Flutter, для которого требуется авторизационный токен JWT. Срок действия токена истекает через определенное время.
Новый токен доступа можно запросить с помощью отдельного токена обновления. Сейчас это обновление токена доступа выполняется, как только запрос возвращает ответ 401. После этого неудавшийся запрос следует повторить с новым токеном доступа.
У меня проблемы с этим последним шагом. Похоже на http.BaseRequest
можно отправить только один раз. Как мне повторить запрос http с новым токеном?
Как предложено в dart http readme, я создал подкласс http.BaseClient
добавить поведение авторизации. Вот упрощенная версия:
import 'dart:async';
import 'package:http/http.dart' as http;
class AuthorizedClient extends http.BaseClient {
AuthorizedClient(this._authService) : _inner = http.Client();
final http.Client _inner;
final AuthService _authService;
Future<http.StreamedResponse> send(http.BaseRequest request) async {
final token = await _authService.getAccessToken();
request.headers['Authorization'] = 'Bearer $token';
final response = await _inner.send(request);
if (response.statusCode == 401) {
final newToken = await _authService.refreshAccessToken();
request.headers['Authorization'] = 'Bearer $newToken';
// throws error: Bad state: Can't finalize a finalized Request
final retryResponse = await _inner.send(request);
return retryResponse;
}
return response;
}
}
abstract class AuthService {
Future<String> getAccessToken();
Future<String> refreshAccessToken();
}
3 ответа
Вот что я придумал, основываясь на ответе Ричарда Хипа: чтобы отправить запрос, мы должны его скопировать.
До сих пор я не смог найти решение для потоковых запросов!
http.BaseRequest _copyRequest(http.BaseRequest request) {
http.BaseRequest requestCopy;
if(request is http.Request) {
requestCopy = http.Request(request.method, request.url)
..encoding = request.encoding
..bodyBytes = request.bodyBytes;
}
else if(request is http.MultipartRequest) {
requestCopy = http.MultipartRequest(request.method, request.url)
..fields.addAll(request.fields)
..files.addAll(request.files);
}
else if(request is http.StreamedRequest) {
throw Exception('copying streamed requests is not supported');
}
else {
throw Exception('request type is unknown, cannot copy');
}
requestCopy
..persistentConnection = request.persistentConnection
..followRedirects = request.followRedirects
..maxRedirects = request.maxRedirects
..headers.addAll(request.headers);
return requestCopy;
}
Ты не можешь send
тот же самый BaseRequest
дважды. Сделать новый BaseRequest
от первого и отправьте эту копию.
Вот некоторый код (из io_client) для "клонирования" BaseRequest.
var copyRequest = await _inner.openUrl(request.method, request.url);
copyRequest
..followRedirects = request.followRedirects
..maxRedirects = request.maxRedirects
..contentLength = request.contentLength == null
? -1
: request.contentLength
..persistentConnection = request.persistentConnection;
request.headers.forEach((name, value) {
copyRequest.headers.set(name, value);
});
Кажется, проблема с отправкой одних и тех же данных формы без завершения последнего запроса, решена та же проблема следующим образом:
static final Dio dio2 = Dio();
static Future<dynamic> _retry(RequestOptions requestOptions) async {
final options = new Options(
method: requestOptions.method,
headers: (await getHeaders()),
);
options.responseType = (ResponseType.json);
Map<String, String>? data;
try {
data = Map.fromEntries(requestOptions.data?.fields);
} catch (e) {}
var res = dio2.request<dynamic>(
requestOptions.baseUrl + requestOptions.path,
data: data == null ? null : FormData.fromMap(data),
queryParameters: requestOptions.queryParameters,
options: options);
return res;
}
используйте его в блоке 401 следующим образом:
handler.resolve(await _retry(response.requestOptions));