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

如何解决Flutter Web项目中的XMLHttpRequest (CORS)异常问题

Flutter Web 请求返回Null,实际为CORS跨域异常问题解决

一开始误以为是null变量处理问题,已在Model中使用可空类型,但报错仍存在,后续排查发现核心是Flutter Web的XMLHttpRequest (CORS)跨域限制。


相关信息

服务器返回的JSON数据

{
    "success": true,
    "status_code": 200,
    "error": null,
    "data": {
        "message": "sussecc!",
        "has_paginate": 0,
        "results": {
            "user": {
                "id": 1,
                "full_name": "emin",
                "phone_number": "+1125585",
                "admin_role": "0",
                "national_id": "21",
                "address": null,
                "is_active": "1",
                "email": null,
                "email_verified_at": "2023-12-04 20:41:41",
                "created_at": "2023-12-04T17:11:42.000000Z",
                "updated_at": "2023-12-04T17:11:42.000000Z"
            },
            "token": "31|ssasdsdfsdgeedewfsdvfdbdfve3svxfb"
        }
    }
}

对应的Model类代码

class ModelLoginService {
  ModelLoginService({
    required this.success,
    required this.statusCode,
    required this.error,
    required this.data,
  });
  late final bool success;
  late final int statusCode;
  late final dynamic? error;
  late final Data? data;

  ModelLoginService.fromJson(Map<String, dynamic> json) {
    success = json['success'];
    statusCode = json['status_code'];
    error = Error.fromJson(json['error']);
    data = Data.fromJson(json['data']);
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['success'] = success;
    _data['status_code'] = statusCode;
    _data['error'] = error!.toJson() == null ? null : error.toJson();
    _data['data'] = data!.toJson() == null ? null : error.toJson();
    return _data;
  }
}

class Error {
  Error({
    required this.message,
    required this.errors,
  });
  late final String? message;
  late final Errors? errors;

  Error.fromJson(Map<String, dynamic> json) {
    message = json['message'];
    errors = Errors.fromJson(json['errors']);
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['message'] = message!;
    _data['errors'] = errors!.toJson().isEmpty;
    return _data;
  }
}

class Errors {
  Errors({
    required this.reason,
    required this.message,
  });
  late final String reason;
  late final String message;

  Errors.fromJson(Map<String, dynamic> json) {
    reason = json['reason'];
    message = json['message'];
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['reason'] = reason;
    _data['message'] = message;
    return _data;
  }
}

class Data {
  Data({
    required this.message,
    required this.hasPaginate,
    required this.results,
  });
  late final String message;
  late final int hasPaginate;
  late final Results results;

  Data.fromJson(Map<String, dynamic> json) {
    message = json['message'];
    hasPaginate = json['has_paginate'];
    results = Results.fromJson(json['results']);
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['message'] = message;
    _data['has_paginate'] = hasPaginate;
    _data['results'] = results.toJson();
    return _data;
  }
}

class Results {
  Results({
    required this.user,
    required this.token,
  });
  late final User user;
  late final String token;

  Results.fromJson(Map<String, dynamic> json) {
    user = User.fromJson(json['user']);
    token = json['token'];
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['user'] = user.toJson();
    _data['token'] = token;
    return _data;
  }
}

class User {
  User({
    required this.id,
    required this.fullName,
    required this.phoneNumber,
    required this.adminRole,
    required this.nationalId,
    required this.address,
    required this.isActive,
    required this.email,
    required this.emailVerifiedAt,
    required this.createdAt,
    required this.updatedAt,
  });
  late final int id;
  late final String fullName;
  late final String phoneNumber;
  late final String adminRole;
  late final String nationalId;
  late final String? address;
  late final String isActive;
  late final String? email;
  late final String emailVerifiedAt;
  late final String createdAt;
  late final String updatedAt;

  User.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    fullName = json['full_name'];
    phoneNumber = json['phone_number'];
    adminRole = json['admin_role'];
    nationalId = json['national_id'];
    address = json['address'] == null ? null : json['address'];
    isActive = json['is_active'];
    email = json['email'] == null ? null : json['email'];
    emailVerifiedAt = json['email_verified_at'];
    createdAt = json['created_at'];
    updatedAt = json['updated_at'];
  }

  Map<String, dynamic> toJson() {
    final _data = <String, dynamic>{};
    _data['id'] = id;
    _data['full_name'] = fullName;
    _data['phone_number'] = phoneNumber;
    _data['admin_role'] = adminRole;
    _data['national_id'] = nationalId;
    _data['address'] = address;
    _data['is_active'] = isActive;
    _data['email'] = email;
    _data['email_verified_at'] = emailVerifiedAt;
    _data['created_at'] = createdAt;
    _data['updated_at'] = updatedAt;
    return _data;
  }
}

请求服务代码

class ServiceLogin extends GetConnect implements GetxService {
  Map<String, String> authorizationh = {};
  Map<String, dynamic> bodyMap = {};
  String username = "", password = "";

  Future<ModelLoginService> postData() async {
    bodyMap = {
      'full_name': 'admin',
      'password': 'c1992885',
    };

    var response = await post('http://test.com/admin/api/login', bodyMap);

    print('response.body ${response.body}');
    return ModelLoginService.fromJson(response.body);
  }
}

报错日志

Performing hot restart...
Waiting for connection from debug service on Edge...
Restarted application in 743ms.
[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized
[GETX] Instance "ApiController" has been created
[GETX] Instance "ApiController" has been initialized
response.body null
Error: Expected a value of type 'Map<String, dynamic>', but got one of type 'Null'
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 294:3       throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 127:3       castError
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 818:12  cast
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart 652:14     as_C
packages/shndz_panel/services/service_login.dart 21:30                            postData
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50                <fn>
dart-sdk/lib/async/zone.dart 1661:54                                              runUnary
dart-sdk/lib/async/future_impl.dart 162:18                                        handleValue
dart-sdk/lib/async/future_impl.dart 846:44                                        handleValueCallback
dart-sdk/lib/async/future_impl.dart 875:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 655:5                                         [_completeError]
dart-sdk/lib/async/future_impl.dart 745:7                                         callback
dart-sdk/lib/async/schedule_microtask.dart 40:11                                  _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5                                   _startMicrotaskLoop
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 181:7                <fn>

核心原因

报错中response.body null的本质是浏览器的CORS跨域拦截机制:Flutter Web运行在浏览器中,当请求的后端域名与前端域名不一致时,浏览器会先发送OPTIONS预检请求,若后端未配置允许跨域的响应头,浏览器会直接拦截请求,导致无法获取服务器返回的JSON数据,最终response.body为null,解析时抛出类型错误。


解决方案

1. 后端配置CORS(生产环境必用)

在后端服务中添加跨域允许配置,以Laravel为例:

  • 修改config/cors.php:
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['http://localhost:5000', '你的线上前端域名'], // 允许的前端地址
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
    
  • 确保HandleCors中间件在路由中启用。

其他后端框架(如Spring Boot、Express)配置逻辑一致,核心是返回Access-Control-Allow-Origin、Access-Control-Allow-Methods等响应头,允许前端域名的请求。

2. 本地开发临时绕过CORS

开发阶段可以用以下方式快速绕过:

  • 禁用Chrome的CORS检查:
    终端执行以下命令启动Chrome:
    # Windows
    chrome.exe --disable-web-security --user-data-dir="C:/ChromeDevSession"
    
    # macOS
    open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --disable-web-security --user-data-dir="/tmp/ChromeDevSession"
    
    # Linux
    google-chrome --disable-web-security --user-data-dir="/tmp/ChromeDevSession"
    
    在这个Chrome窗口中运行Flutter Web项目即可。

3. 修复Model类的潜在问题

虽然核心是CORS,但Model类存在几个解析null的问题,需要修正避免后续报错:

  • ModelLoginService.fromJson中处理null的error字段:
    error = json['error'] != null ? Error.fromJson(json['error']) : null;
    
  • ModelLoginService.toJson中修正data字段的错误:
    _data['data'] = data?.toJson();
    
  • Error.fromJson添加null判断:
    Error.fromJson(Map<String, dynamic>? json) {
      if (json == null) {
        message = null;
        errors = null;
        return;
      }
      message = json['message'];
      errors = json['errors'] != null ? Errors.fromJson(json['errors']) : null;
    }
    
  • Errors.fromJson处理null情况:
    Errors.fromJson(Map<String, dynamic>? json) {
      reason = json?['reason'] ?? '';
      message = json?['message'] ?? '';
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 16:27:07