Skip to content
New issue

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

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

Already on GitHub? # to your account

Implement automatic redirects for requests regardless of the HttpClient #2214

Open
AlexV525 opened this issue May 15, 2024 · 1 comment
Open
Labels
s: feature This issue indicates a feature request

Comments

@AlexV525
Copy link
Member

AlexV525 commented May 15, 2024

Request Statement

As Dart doc stated:

  /// Automatic redirect will only happen for "GET" and "HEAD" requests
  /// and only for the status codes [HttpStatus.movedPermanently]
  /// (301), [HttpStatus.found] (302),
  /// [HttpStatus.movedTemporarily] (302, alias for
  /// [HttpStatus.found]), [HttpStatus.seeOther] (303),
  /// [HttpStatus.temporaryRedirect] (307) and
  /// [HttpStatus.permanentRedirect] (308). For
  /// [HttpStatus.seeOther] (303) automatic redirect will also happen
  /// for "POST" requests with the method changed to "GET" when
  /// following the redirect.

The redirections will only happen in these circumstances.

But it might be great if other requests could follow redirections too.

Solution Brainstorm

Pseudo implementation:

int redirectTimes = 0;
Response response = await request(redirectTimes);
while (reseponse.isRedirect && redirectTimes < options.maxRedirects) {
  redirectTimes++;
  response = await request(redirectTimes);
}
if (response.isRedirect) {
  throw StateError('Maximum redirects limit reached.');
}
return response;
@AlexV525 AlexV525 added the s: feature This issue indicates a feature request label May 15, 2024
@wilinz
Copy link

wilinz commented Jun 30, 2024

My solution:

import 'package:dio/dio.dart';

typedef RedirectCallback = bool Function(
    Response response, ResponseInterceptorHandler handler);

class RedirectInterceptor extends Interceptor {
  final Dio _dio;
  RedirectCallback? _redirectCallback;
  static const String followRedirects = "followRedirects";
  static const String rawUri = "rawUri";
  static const String rawRequestOption = "rawRequestOption";
  static const String redirectCount = "redirectCount";

  RedirectInterceptor(this._dio, {RedirectCallback? onRedirect})
      : _redirectCallback = onRedirect;

  @override
  Future<void> onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    final isFollowRedirects =
        response.requestOptions.extra[followRedirects] as bool? ?? true;
    if (!isFollowRedirects) {
      handler.next(response);
      return;
    }

    final rawUriValue = response.requestOptions.extra[rawUri] as Uri?;
    if (rawUriValue == null) {
      response.requestOptions.extra[rawUri] = response.requestOptions.uri;
    }
    final rawRequestOptionValue = response.requestOptions.extra[rawRequestOption];
    if (rawRequestOptionValue == null){
      response.requestOptions.extra[rawRequestOption] = response.requestOptions;
    }

    if (_isRedirect(response.statusCode ?? 0)) {
      try {
        final redirectCountValue =
            response.requestOptions.extra[redirectCount] ?? 0;
        if (redirectCountValue >= 5) {
          handler.next(response);
          return;
        }
        if (_redirectCallback != null &&
            !_redirectCallback!.call(response, handler)) {
          return;
        }
        final location = response.headers.value('location');
        if (location == null) throw Exception("Redirect location is null");
        final requestOptions = response.requestOptions;
        final rawUri = requestOptions.uri.toString();
        final newUri = Uri.parse(_parseHttpLocation(rawUri, location));
        response.requestOptions.extra[redirectCount] = redirectCountValue + 1;

        final option = Options(
          sendTimeout: requestOptions.sendTimeout,
          receiveTimeout: requestOptions.receiveTimeout,
          extra: requestOptions.extra,
          // headers: requestOptions.headers
          //   ..removeWhere((key, value) {
          //     return key.toLowerCase() == "cookie" || key.toLowerCase() == 'content-length';
          //   }),
          responseType: requestOptions.responseType,
          validateStatus: requestOptions.validateStatus,
          receiveDataWhenStatusError: requestOptions.receiveDataWhenStatusError,
          followRedirects: requestOptions.followRedirects,
          maxRedirects: requestOptions.maxRedirects,
          persistentConnection: requestOptions.persistentConnection,
          requestEncoder: requestOptions.requestEncoder,
          responseDecoder: requestOptions.responseDecoder,
          listFormat: requestOptions.listFormat,
        );

        final redirectResponse = await _dio.getUri(
          newUri,
          options: option,
        );
        return handler.next(redirectResponse);
      } on DioException catch (e) {
        return handler.reject(e);
      }
    }
    return handler.next(response);
  }

  bool _isRedirect(int statusCode) {
    return statusCode == 301 ||
        statusCode == 302 ||
        statusCode == 303 ||
        statusCode == 307 ||
        statusCode == 308;
  }

  String _parseHttpLocation(final String rawUri, final String location) {
    var location1 = location;
    String uri;
    if (!location1.contains("://")) {
      final schemaEndIndex = rawUri.indexOf("://") + 3;
      var index = location1.startsWith("/")
          ? rawUri.indexOf("/", schemaEndIndex)
          : rawUri.substring(schemaEndIndex).lastIndexOf("/") + schemaEndIndex;
      if (index == -1) index = rawUri.length - 1;
      var baseUrl = rawUri.substring(0, index + 1);
      if (baseUrl.endsWith("/")) {
        baseUrl = baseUrl.substring(0, baseUrl.length - 1);
      }
      if (location1.startsWith("/")) {
        location1 = location1.substring(1);
      }
      uri = baseUrl + "/" + location1;
    } else {
      uri = location1;
    }
    return uri;
  }
}

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
s: feature This issue indicates a feature request
Projects
None yet
Development

No branches or pull requests

2 participants