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

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;
  }
}

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

No branches or pull requests

2 participants