You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Flutter中API调用时出现的308重定向错误?

解决Flutter中MultipartRequest遇到308重定向无法获取图片的问题

问题核心

你遇到的问题根源在于:308重定向后不需要再次上传图片文件,服务器返回的Location URL是直接获取生成后图片的地址,应该用GET请求访问,而不是重复发送带文件的POST请求。Postman会自动处理这种重定向逻辑,但Flutter的http.MultipartRequest不会自动跟随重定向,而你手动处理时错误地重复上传了文件,导致服务器返回重定向页面而非图片。

修复步骤

  1. 收到308/301/302状态码时,从响应头的location字段提取目标URL
  2. 对该URL发送GET请求,无需再添加任何文件或表单数据
  3. 处理相对路径的重定向URL:如果location返回的是相对路径,需要和原请求的Base URL拼接

修改后的代码

Future<void> tryOn() async {
  setState(() {
    _isLoading = true;
  });

  try {
    if (selectedImageFile == null || selectedSourceFile == null) {
      showDialog(
        context: context,
        builder: (context) => CustomDialog(
          headline: "未选择文件",
          subline: "请选择两张图片。",
          red: () {},
          green: () {},
          redButton: "",
          greenButton: "",
          animation: "assets/animations/inf.json",
        ),
      );
      return;
    }

    final uri = Uri.parse(urlText.text);
    var request = http.MultipartRequest('POST', uri);

    request.files.add(await http.MultipartFile.fromPath(
      'profile_image',
      selectedImageFile!.path,
    ));
    request.files.add(await http.MultipartFile.fromPath(
      'source_image',
      selectedSourceFile!.path,
    ));

    request.headers['Accept'] = 'application/json';

    final streamedResponse = await request.send();
    var response = await http.Response.fromStream(streamedResponse);

    log("响应状态码: ${response.statusCode}");
    log("响应体: ${response.body}");

    // 处理重定向
    if ([301, 302, 308].contains(response.statusCode)) {
      final redirectedUrl = response.headers['location'];
      if (redirectedUrl != null) {
        log("重定向到: $redirectedUrl");
        Uri redirectedUri;
        // 处理相对路径的重定向URL
        if (redirectedUrl.startsWith('http')) {
          redirectedUri = Uri.parse(redirectedUrl);
        } else {
          redirectedUri = uri.resolve(redirectedUrl);
        }

        // 发送GET请求获取图片,无需再传文件
        final imageResponse = await http.get(redirectedUri);

        log("重定向响应状态码: ${imageResponse.statusCode}");

        if (imageResponse.statusCode == 200) {
          Uint8List resultImage = imageResponse.bodyBytes;
          showDialog(
            context: context,
            builder: (context) => Dialog(
              child: Image.memory(resultImage),
            ),
          );
        } else {
          showDialog(
            context: context,
            builder: (context) => CustomDialog(
              headline: "失败",
              subline: "服务器返回: ${imageResponse.body}",
              red: () {},
              green: () {},
              redButton: "",
              greenButton: "",
              animation: "assets/animations/inf.json",
            ),
          );
        }
      }
    } else if (response.statusCode == 200) {
      Uint8List resultImage = response.bodyBytes;
      showDialog(
        context: context,
        builder: (context) => Dialog(
          child: Image.memory(resultImage),
        ),
      );
    } else {
      showDialog(
        context: context,
        builder: (context) => CustomDialog(
          headline: "失败",
          subline: "服务器返回: ${response.body}",
          red: () {},
          green: () {},
          redButton: "",
          greenButton: "",
          animation: "assets/animations/inf.json",
        ),
      );
    }
  } on SocketException catch (e) {
    log("Socket异常: $e");
    showDialog(
      context: context,
      builder: (context) => CustomDialog(
        headline: "网络错误",
        subline: "发生网络错误: $e",
        red: () {},
        green: () {},
        redButton: "",
        greenButton: "",
        animation: "assets/animations/inf.json",
      ),
    );
  } on http.ClientException catch (e) {
    log("客户端异常: $e");
    showDialog(
      context: context,
      builder: (context) => CustomDialog(
        headline: "客户端错误",
        subline: "发生客户端错误: $e",
        red: () {},
        green: () {},
        redButton: "",
        greenButton: "",
        animation: "assets/animations/inf.json",
      ),
    );
  } catch (e) {
    log("异常: $e");
    showDialog(
      context: context,
      builder: (context) => CustomDialog(
        headline: "失败",
        subline: "$e",
        red: () {},
        green: () {},
        redButton: "",
        greenButton: "",
        animation: "assets/animations/inf.json",
      ),
    );
  }

  setState(() {
    _isLoading = false;
  });
}

额外说明

  • 308状态码属于永久重定向,虽要求保留原请求方法,但在这个场景中,服务器的重定向实际指向生成后的图片资源,因此用GET请求即可
  • 使用uri.resolve(redirectedUrl)可以自动拼接原URL的Base路径,避免无效的相对路径请求

内容的提问来源于stack exchange,提问作者Haroon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:49:56