resolveOnce function

Future<ResolveResult> resolveOnce(
  1. String method,
  2. String currentUrl,
  3. int timeoutMs
)

执行一次 HTTP 请求(HEAD 或 GET)

method 请求方法:'HEAD' 或 'GET' currentUrl 当前要请求的 URL timeoutMs 超时时间(毫秒)

返回解析结果,包含状态码、下一个 URL(如果有重定向)和最终 URL

Implementation

Future<ResolveResult> resolveOnce(String method, String currentUrl, int timeoutMs) async {
  if (!isHttpLike(currentUrl)) {
    return ResolveResult(statusCode: 0, finalUrl: currentUrl);
  }

  final uri = Uri.parse(currentUrl);
  final client = http.Client();

  try {
    final request = http.Request(method, uri)..followRedirects = false;
    request.headers['User-Agent'] = 'expand-urls/1.0';

    if (method == 'GET') {
      request.headers['Range'] = 'bytes=0-0';
    }

    final response = await client
        .send(request)
        .timeout(
          Duration(milliseconds: timeoutMs),
          onTimeout: () {
            throw TimeoutException('请求超时', Duration(milliseconds: timeoutMs));
          },
        );

    final statusCode = response.statusCode;

    // 获取所有响应头,检查 location 头(HTTP 头名称不区分大小写)
    // Dart 的 Headers 类在访问时会自动进行大小写不敏感的匹配
    String? location;
    // 尝试多种可能的写法(虽然理论上不区分大小写,但为了确保兼容性)
    location = response.headers['location'] ?? response.headers['Location'] ?? response.headers['LOCATION'];

    // 如果还是找不到,遍历所有头查找(某些边缘情况)
    if (location == null) {
      for (final key in response.headers.keys) {
        if (key.toLowerCase() == 'location') {
          location = response.headers[key];
          break;
        }
      }
    }

    // 获取实际请求的 URL(相当于 TypeScript 的 res.url)
    final requestUrl = response.request?.url.toString() ?? currentUrl;
    final finalUrl = requestUrl;

    // 如果响应中有 location 头,处理它(与 TypeScript 代码保持一致:if (location))
    if (location != null && location.isNotEmpty) {
      try {
        // 使用 Uri.resolve 来解析相对 URL,类似于 TypeScript 的 new URL(location, res.url || currentUrl)
        // TypeScript: new URL(location, res.url || currentUrl)
        // 这里 res.url 是 got 库返回的实际请求 URL,如果不存在则使用 currentUrl
        final baseUri = Uri.parse(finalUrl);
        final absoluteUrl = baseUri.resolve(location).toString();
        if (isHttpLike(absoluteUrl)) {
          return ResolveResult(statusCode: statusCode, nextUrl: absoluteUrl, finalUrl: finalUrl);
        } else {
          // 遇到非 HTTP(S) 协议(如 app://, intent:, shopee://),停止跟随
          return ResolveResult(statusCode: statusCode, finalUrl: finalUrl);
        }
      } catch (e) {
        // 位置头格式错误,视为最终 URL
        return ResolveResult(statusCode: statusCode, finalUrl: finalUrl);
      }
    }

    return ResolveResult(statusCode: statusCode, finalUrl: finalUrl);
  } finally {
    client.close();
  }
}