如何解决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:
在这个Chrome窗口中运行Flutter Web项目即可。# 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"
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
相关产品推荐
相关产品推荐

