Flutter中Image.network()请求图片链接时返回403解决方案

Flutter中Image.network()请求图片链接时返回403解决方案

问题复现

我有一个链接为 p2.music.126.net/5CJeYN35Lnz...图片,现在我通过调用 Image.network("https://p2.music.126.net/5CJeYN35LnzRDsv5Lcs0-Q==/109951165374966765.jpg")这个方法,控制台报错输出403

dart 复制代码
======== 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库加上请求头也能获取到正常二进制数据

dart 复制代码
// 获取二进制数据
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()中添加请求头却发现还是报错,所以我初步怀疑FlutterImage.network()网络请求部分出了问题

我们通过查看Image.network()源码

dart 复制代码
  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的对象,我们跟进查看其中源码

dart 复制代码
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;

  @override
  ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);

  @override
  ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode);

  @override
  ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode);
}

我们可以发现这段代码定义了一个名为NetworkImage的工厂函数,它接受一个字符串类型的url和一个可选的scale参数(双精度浮点型),还有一个可选的headers参数(字符串映射),因这里是一个抽象类,所以我们还得继续跟进查看,点击network_image.NetworkImageNetworkImage

通过阅读源代码,我们可以发现

dart 复制代码
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()请求网络数据的地方,根据前面分析,此报错大概率是由于请求的某个参数出现问题导致的

通过阅读

dart 复制代码
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(); 这段代码是发起网络请求,我们试着在前面打印一下请求头

输出

dart 复制代码
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),是问题产生的原因

解决方案

我们尝试着除去这个用户标识体

添加如下代码

dart 复制代码
// 去除user-agent中的 dart/3.1
request.headers.remove("user-agent", "Dart/3.1 (dart:io)");

此时再次运行发现图片可以正常显示了

相关推荐
寒雒1 小时前
【Python】实战:实现GUI登录界面
开发语言·前端·python
独上归州1 小时前
Vue与React的Suspense组件对比
前端·vue.js·react.js·suspense
Komorebi⁼1 小时前
Vue核心特性解析(内含实践项目:设置购物车)
前端·javascript·vue.js·html·html5
明月清风徐徐1 小时前
Vue实训---0-完成Vue开发环境的搭建
前端·javascript·vue.js
SameX1 小时前
HarmonyOS Next 企业数据备份与恢复策略
前端·harmonyos
SameX1 小时前
HarmonyOS Next 企业数据传输安全策略
前端·harmonyos
daopuyun1 小时前
LoadRunner小贴士|开发Web-HTTP/HTML协议HTML5相关视频应用测试脚本的方法
前端·http·html
李先静1 小时前
AWTK-WEB 快速入门(1) - C 语言应用程序
c语言·开发语言·前端
MR·Feng1 小时前
使用Electron将vue2项目打包为桌面exe安装包
前端·javascript·electron
萧大侠jdeps2 小时前
图片生成视频-右进
前端·javascript·音视频