Flutter中Image.network()请求图片链接时返回403解决方案
问题复现
我有一个链接为 https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg图片,现在我通过调用 Image.network("https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg")
这个方法,控制台报错输出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/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg
When the exception was thrown, this was the stack:
#0 NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:152:9)
<asynchronous suspension>
Image provider: NetworkImage("https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg", scale: 1.0)
Image key: NetworkImage("https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg", scale: 1.0)
====================================================================================================
问题产生原因分析
浏览器请求发现是正常的
Postman也能正常获取图片二进制数据
而我测试的时候通过第三方dio库加上请求头也能获取到正常二进制数据
// 获取二进制数据
Future<ResultData> getBytes(
String path, {
Map<String, dynamic>? queryParameters,
MyOptions? myOptions,
}) async {
myOptions ??= MyOptions(); // 为空则创建
myOptions.method = "GET";
myOptions.responseType = ResponseType.bytes; // 设置返回类型为二进制格式
myOptions.headers = {"User-Agent": bytesUserAgent};
Response response = await _dio.get(path,
queryParameters: queryParameters, options: myOptions);
return response.data as ResultData;
}
但是通过Image.network()
中添加请求头却发现还是报错,所以我初步怀疑Flutter
的Image.network()
网络请求部分出了问题
我们通过查看Image.network()
源码
Image.network(
String src, {
super.key,
double scale = 1.0,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
this.opacity,
this.colorBlendMode,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
Map<String, String>? headers,
int? cacheWidth,
int? cacheHeight,
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0);
可知Image.network()
方法中创建了一个NetworkImage
的对象,我们跟进查看其中源码
abstract class NetworkImage extends ImageProvider<NetworkImage> {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
const factory NetworkImage(String url, { double scale, Map<String, String>? headers }) = network_image.NetworkImage;
/// The URL from which the image will be fetched.
String get url;
/// The scale to place in the [ImageInfo] object of the image.
double get scale;
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
///
/// When running flutter on the web, headers are not used.
Map<String, String>? get headers;
ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);
ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode);
ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode);
}
我们可以发现这段代码定义了一个名为NetworkImage
的工厂函数,它接受一个字符串类型的url
和一个可选的scale
参数(双精度浮点型),还有一个可选的headers
参数(字符串映射),因这里是一个抽象类,所以我们还得继续跟进查看,点击network_image.NetworkImage
的NetworkImage
通过阅读源代码,我们可以发现
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents, {
image_provider.ImageDecoderCallback? decode,
image_provider.DecoderBufferCallback? decodeBufferDeprecated,
image_provider.DecoderCallback? decodeDeprecated,
}) async {
try {
assert(key == this);
final Uri resolved = Uri.base.resolve(key.url);
final HttpClientRequest request = await _httpClient.getUrl(resolved);
headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
await response.drain<List<int>>(<int>[]);
throw image_provider.NetworkImageLoadException(
statusCode: response.statusCode, uri: resolved);
}
final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int? total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
},
);
if (bytes.lengthInBytes == 0) {
throw Exception('NetworkImage is an empty file: $resolved');
}
if (decode != null) {
final ui.ImmutableBuffer buffer =
await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} else if (decodeBufferDeprecated != null) {
final ui.ImmutableBuffer buffer =
await ui.ImmutableBuffer.fromUint8List(bytes);
return decodeBufferDeprecated(buffer);
} else {
assert(decodeDeprecated != null);
return decodeDeprecated!(bytes);
}
} catch (e) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance.imageCache.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
Future<ui.Codec> _loadAsync()
这个函数是Image.network()
请求网络数据的地方,根据前面分析,此报错大概率是由于请求的某个参数出现问题导致的
通过阅读
if (response.statusCode != HttpStatus.ok) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
await response.drain<List<int>>(<int>[]);
throw image_provider.NetworkImageLoadException(
statusCode: response.statusCode, uri: resolved);
}
我们可以知道,控制栏报错403是因为在这个抛出了一个异常,我们可以断定 final HttpClientResponse response = await request.close();
这段代码是发起网络请求,我们试着在前面打印一下请求头
输出
I/flutter (31164): user-agent: Dart/3.1 (dart:io)
I/flutter (31164): accept-encoding: gzip
I/flutter (31164): content-length: 0
I/flutter (31164): host: p2.music.126.net
这跟我们分析的一样,user-agent
里面多了一个Dart/3.1 (dart:io)
,是问题产生的原因
解决方案
我们尝试着除去这个用户标识体
添加如下代码
// 去除user-agent中的 dart/3.1
request.headers.remove("user-agent", "Dart/3.1 (dart:io)");
此时再次运行发现图片可以正常显示了