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

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)
====================================================================================================

问题产生原因分析

浏览器请求发现是正常的

image-2.png

Postman也能正常获取图片二进制数据

image-3.png

而我测试的时候通过第三方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()中添加请求头却发现还是报错,所以我初步怀疑FlutterImage.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.NetworkImageNetworkImage

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


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),是问题产生的原因

解决方案

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

image.png


添加如下代码

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

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


image-1.png

猜你喜欢

转载自blog.csdn.net/qianxiamuxin/article/details/134268190
今日推荐