Flutter 报错403原因
======== Exception caught by image resource service ================================================
The following NetworkImageLoadException was thrown resolving an image codec:
HTTP request failed, statusCode: 403, https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg
When the exception was thrown, this was the stack:
#0 NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:150:9)
<asynchronous suspension>
Image provider: NetworkImage("https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg", scale: 1.0)
Image key: NetworkImage("https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg", scale: 1.0)
====================================================================================================
Flutter加载图片爆403错误,然而通过浏览器访问此url却能正常访问,原因是由于服务器设置了防伪链的措施,导致使用Image.network()加载图片的时候服务器拒绝客户端的请求
解决思路
Referer
Referer是 HTTP 请求头中的一个字段,它记录了当前请求页面来自的页面地址。通过这个字段,我们可以追踪访客的来源,了解他们是从哪个页面链接访问当前页面的。
防盗链
当用户浏览网页时,如果他们点击链接或图片跳转到新页面,那么前一个页面的URL就会作为Referer字段自动包含在HTTP请求头中。对于图片请求,Referer通常指的是图片所在网页的URL。浏览器会自动将Referer字段添加到发出的请求中,这样服务器就可以了解用户是如何到达当前页面的。
比如说,我把图片文件放到Gitee上,然后我写了一个客户端每次都去调用他,这样子相当于借用了Gitee的服务器资源,这些服务资源都是需要付费的,而我从中获得了利益。这种行为就相当于盗链。
通常服务器防盗链技术一般有以下几种方式
- 通过请求头的Referer来判定
- 通过检验Cookie、Session来进行判定
- 通过定期修改文件名以及文件链接
思路
既然我们已经知道服务器是根据客户端请求的Referer内容来判定是否有权限访问此图片资源,我们可以把请求头的Referer去掉,以模拟初始使用浏览器访问该图片资源的情况,避免被服务器检索出盗链。
解决方法
通过http请求自定义修改请求头,首先导入dio依赖
dependencies:
flutter:
sdk: flutter
#网络请求包
dio: ^5.3.3
在lib目录下创建一个自定义请求类封装dio,使其get请求接口暴露给使用者,来达到我们想要的自定义模拟请求头
import 'dart:typed_data';
import 'package:dio/dio.dart';
const String bytes_user_agent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76";
class SSJRequestManager {
final Dio _dio = Dio();
Dio getDio() {
return _dio;
}
SSJRequestManager();
// 获取二进制数据
Future<Uint8List> get_bytes(String path, Map<String, dynamic> params) async {
var options = Options(
method: "GET",
responseType: ResponseType.bytes,
headers: {"User-Agent": bytes_user_agent});
return await _dio
.get(path, queryParameters: params, options: options)
.then((value) => value.data);
}
}
final SSJRequestManager ssjRequestManager = SSJRequestManager();
然后在需要调用的地方使用我们定义好的get_bytes函数来获取二进制字节数据,用于构建图片,这里要注意的是 responseType:ResponseType.bytes 这一句,设置返回响应体为我们想要的 Uint8List 数据类型然后我们在需要构建组件的地方,使用 FutureBuilder 来异步构建组件模块
import 'package:flutter/material.dart';
import 'package:yqplaymusic/common/utils/request.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: Scaffold(
body: Center(
child: SizedBox(
height: 100,
width: 100,
child: FutureBuilder(
future: ssjRequestManager.get_bytes(
"http://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg",
{}),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.connectionState == ConnectionState.done)
{
return Image.memory(snapshot.data);
}
else
{
return const Icon(Icons.file_download);
//return Image.network("https://p2.music.126.net/GvYQoflE99eoeGi9jG4Bsw==/109951165375336156.jpg");
}
},
),
),
),
),
);
}
}