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

文章讲述了在Flutter中使用Image.network()加载图片链接时遇到403错误的问题,通过分析发现可能是user-agent头包含Dart/3.1(dart:io)导致。解决方案是移除该用户标识体,最终成功加载图片。
摘要由CSDN通过智能技术生成

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

Flutter,使用`webview_flutter`插件来模拟Android的postUrl请求,你需要遵循以下步骤: 1. 首先,确保你的Flutter项目已经添加了`webview_flutter`插件。如果没有添加,可以在`pubspec.yaml`文件添加依赖项: ```yaml dependencies: flutter: sdk: flutter webview_flutter: ^0.3.24 # 确保使用最新版本 ``` 2. 运行`flutter pub get`命令来安装插件。 3. 在你的Flutter代码,导入`webview_flutter`包: ```dart import 'package:webview_flutter/webview_flutter.dart'; ``` 4. 使用`WebView`组件,并设置JavaScript为true,以便可以执行JavaScript代码: ```dart WebView( javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _webViewController = webViewController; _postUrlRequest(); }, ) ``` 5. 创建一个方法来执行postUrl请求。你可以使用`WebViewController`的`runJavascript`方法来调用JavaScript代码,或者使用`evaluateJavascript`方法,这取决于你的具体需求。 ```dart void _postUrlRequest() async { const String url = "http://your-post-url.com"; // 替换为你的POST URL const String postData = '{"key1": "value1", "key2": "value2"}'; // 替换为你的POST数据 // 调用JavaScript代码来执行postUrl请求,这里仅为示例 await _webViewController?.evaluateJavascript(''' (function() { var xhr = new XMLHttpRequest(); xhr.open('POST', '$url', true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 300) { // 请求成功处理 } else { // 请求失败处理 } }; xhr.send($postData); })(); '''); } ``` 6. 根据你的具体需求,可能需要监听WebView加载完成的状态,并根据加载的网页内容进行相应的处理。 请注意,上述代码仅为示例,实际的实现可能需要根据你要POST的URL和数据进行调整。此外,确保你的应用有权限访问网络,并且你已经正确处理了任何可能的异常和错误。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值