Flutter 项目实战 Dio网络请求 四

/  HTTP  |   HTTPS  /

       HTTP是一个客户端(用户)和 服务端(网站)之间请求和应答的标准,通常使用TCP协议。客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。客户端 (用户代理程序) 向应答服务器 (源服务器) 发起请求 , 从服务器获取需要的资源 (包括 : 文件、图像 、文本、视频 等等) 。客户端和服务端之间 可能存在多个中间层 (例如 : 代理服务器、网关) 。HTTP可以在任何互联网协议或其他网络上实现 , 使用TCP作为其传输层 。

请求方式

GET 请求

向服务端发起请求用于从服务端读取数据 。浏览器发出的GET只能由一个url触发。GET上要在url之外带一些参数就只能依靠url上附带querystring。

使用场景

例如: https://host:port/path?querystring=value1&queryString=value2

https://tieba.baidu.com/f?ie=utf-8&kw=%E5%A4%A7%E4%BD%AC&fr=search

https://tieba.baidu.com/f?ie=utf-8&kw=%E5%A4%A7%E7%A5%9E&fr=search

https://tieba.baidu.com/f?ie=utf-8&kw=%E7%BE%8E%E5%A5%B3&fr=search

https://tieba.baidu.com/f?ie=utf-8&kw=%E5%B8%85%E5%93%A5&fr=search
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _testDioHeadReq() async {
    Response _headReq = await Dio().get('https://www.baidu.com/');
    print('HEAD请求获取到到 数据:${_headReq.data}\n');
    print('HEAD请求获取到到 extra:${_headReq.extra}\n');
    print('HEAD请求获取到到 headers:${_headReq.headers}\n');
    print('HEAD请求获取到到 isRedirect:${_headReq.isRedirect}\n');
    print('HEAD请求获取到到 statusCode:${_headReq.statusCode}\n');
    print('HEAD请求获取到到 statusMessage:${_headReq.statusMessage}\n');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: _testDioHeadReq,
        tooltip: 'TestDioHeadReq',
        child: Icon(Icons.add),
      ),
    );
  }
}

HEAD 

向服务端发起请求用于从服务端读取数据 。与GET的区别就是不会获取到数据 (响应体) , 只会获取到请求头 (header) 、状态码 (statusCode)、提示信息 (statusMessage) 等 响应头

HEAD请求常常被忽略,但是能提供很多有用的信息,特别是在有限的速度和带宽下。主要有以下特点:只请求资源的首部 、检查超链接的有效性 、检查网页是否被修改

多用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等 

Response _headReq = await Dio().head('https://www.baidu.com/');
print('HEAD请求获取到到 数据:${_headReq.data}\n');
print('HEAD请求获取到到 extra:${_headReq.extra}\n');
print('HEAD请求获取到到 headers:${_headReq.headers}\n');
print('HEAD请求获取到到 isRedirect:${_headReq.isRedirect}\n');
print('HEAD请求获取到到 statusCode:${_headReq.statusCode}\n');
print('HEAD请求获取到到 statusMessage:${_headReq.statusMessage}\n');

 POST 请求

        向指定资源提交数据 , 请求服务器处理 (例如提交表单或者上传文件) 。数据被包含在请求体中 , 数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源 。表单数据被浏览器编码到body里然后发送请求 。

body主要有四种格式 : 

application/x-www-form-urlencode (传递简单数据 / 格式 : 是"key1=value1&key2=value2") ,对于二进制文件这种数据的传输效率很低  。默认传递数据的格式 。消息包大,耗流量  。

 try {
      Response _headReq = await Dio().post(
        ///请求地址
        'https://tieba.baidu.com/f',
        ///请求参数
        data: {'ie': 'utf-8', 'kw': '大佬', 'fr': 'search'},
        ///请求头
        options: new Options(
          contentType: Headers.formUrlEncodedContentType,
        ),
      );
      print('HEAD请求获取到到 数据:${_headReq.data}\n');
      print('HEAD请求获取到到 extra:${_headReq.extra}\n');
      print('HEAD请求获取到到 headers:${_headReq.headers}\n');
      print('HEAD请求获取到到 isRedirect:${_headReq.isRedirect}\n');
      print('HEAD请求获取到到 statusCode:${_headReq.statusCode}\n');
      print('HEAD请求获取到到 statusMessage:${_headReq.statusMessage}\n');
    } catch (e) {
      print('请求异常:' + e.toString());
    }

multipart/form-data 

        multipart/form-data 定义在 rfc2388 中 , 用以支持向服务器发送二进制数据。这种编码方式,通常是用在客户端向服务端传送大文件数据,如:图片或者文件。

boundary 是一个占位符,代表我们规定的具体分割符;可以自己任意规定,但为了避免和正常文本重复了。

Boundary 参数设置注意事项:

必须以英文中间双横杠--开头,这个--称为前导连字符

除了前导连字符以外的部分不能超过70个字符

不能包含HTTP协议或者URL禁用的特殊意义的字符,例如英文冒号(:)等

 FormData _formData = new FormData.fromMap({
      'file': await MultipartFile.fromFile(
        ///手机存储卡上图片路径
        'filePath',
        ///图片名称
        filename: 'fileName',
      ),
    });
    Response _headReq = await Dio().post('url',
        
        data: _formData,
        options: Options(method: 'POST', contentType: 'multipart/form-data;boundary=xxxx'));
    print('HEAD请求获取到到 数据:${_headReq.data}\n');

 application/json 

        客户端向服务端传递序列化的JSON字符串。方便的提交复杂的结构化数据,特别适合 RESTful 的接口。各大抓包工具如 Chrome 自带的开发者工具、Firebug、Fiddler,都会以树形结构展示 JSON 数据,非常友好。

 Response _headReq = await Dio().post('https://www.wanandroid.com',
        data: {'username': '', 'password': '',},
    options: Options(contentType: 'application/json',),);
    print('HEAD请求获取到到 数据:${_headReq.data}\n');

text/xml

        传输和存储数据,它非常适合万维网传输。以纯文本形式进行编码,其中不包含任何控件或格式字符, 大部分情况不会使用 。

PUT 请求

        创建或者替换目标资源 。put调用一次和多次是等价的。而连续调用多次POST方法可能会有副作用,比如将一个订单重复提交多次。

DELETE 请求

        向服务端请求删除某个已存在的资源 。

CONNECT

        HTTP 协议中,CONNECT 方法可以开启一个客户端与所请求资源之间的双向沟通的通道 。

connect是为了建立http tunnel , 只有在受限制的网络环境中(防火墙、NAT、代理器)并且是https通信时,客户端使用http connect请求代理服务器,代理服务器使用connect方法与目标服务器建立http tunnel,通道建立后,客户端与服务器进行通信,代理服务器就像透明一样,只是接收、转发tcp stream。


建立http tunnel 的理由 ?
这是因为,网络环境受限,客户端无法直接访问某些网络,所以只能通过代理服务器访问网络,然后,将内容转发给客户端,从宏观上看,客户端与服务器端就像建立了一条隧道一样。
但是由于http tunnnel可控性不强,所以,服务器通常会限制"可connect的端口"(一般只开放SSL的443端口)
 

        HTTPS (HTTP over SSL/TLS) 是 HTTP 的安全版本,在 HTTP 上加了一层处理加密信息的模块。SSL/TLS 全称安全传输层协议 Transport Layer Security, 是介于 TCP 和 HTTP 之间的一层安全协议,不影响原有的 TCP 协议和 HTTP 协议,所以使用HTTPS基本上不需要对 HTTP 页面进

行太多的改造。浏览器访问支持 HTTPS 的站点时,在地址栏的前面会有一把绿色的锁一样的标

识,表明 HTTPS 生效了。

HTTPS-browser

HTTPS的主要作用是 

        对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全

        对网站服务器进行真实身份认证

SSL/TLS 协议采用非对称加密方式,服务端会生成公钥和私钥,公钥用来加密信息,可以提供给所有需要进行通信的客户端,私钥保存在本地,不能泄露。客户端使用这份公钥对信息进行加密,将请求发送给服务器,服务器用私钥解密。反之,服务器对客户端的返回,则使用客户端提供的公钥进行加密,客户端使用本地对应的私钥来解密,保证了通信的安全。

基于 SSL/TLS 进行 一次的 HTTPS 会话的过程,简单地说可以分成3步

        客户端向服务器端索要并验证公钥。

        双方协商生成”对话密钥”。

        双方采用”对话密钥”进行加密通信。

HTTP 协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,使用 HTTPS 则有以下几个方面的优势:

保护站点安全、保护用户隐私、未来的趋势所在

升级HTTPS :

获取证书、在服务器安装证书、重定向配置、修改资源链接

/ 网络请求神器DIO /

pubspec.yaml 文件配置dio插件依赖

        dio版本号建议配置成any , 以后即便更新了flutter sdk 也不用手动去配置版本号

查看flutter sdk、dart sdk 版本号

网络请求创建 单利模式

        确保某一个类只有一个实例,而且自行实例化并向整个应用提供这个实例。

适用于

全局日志的 Logger 类、应用全局的配置数据对象类,单业务管理类。
创建实例时占用资源较多,或实例化耗时较长的类。
等等…

class HttpManager {
  ///存储网络请求的token,方便网络请求完成后取消网络请求
  Map<String, CancelToken> _cancelTokens = Map<String, CancelToken>();

  ///超时时间
  static const int CONNECT_TIMEOUT = 30000;
  static const int RECEIVE_TIMEOUT = 30000;
  Dio? _client;
  static final HttpManager _instance = HttpManager._internal();

 
  factory HttpManager() => _instance;
 
  Dio get client => _client!;
 
  /// 创建 dio 实例对象
  HttpManager._internal() {
    if (_client == null) {
      /// 全局属性:请求前缀、连接超时时间、响应超时时间
      BaseOptions options = BaseOptions(
        connectTimeout: CONNECT_TIMEOUT,
        receiveTimeout: RECEIVE_TIMEOUT,
      );
      _client = Dio(options);
    }
  }
}

 创建网络请求HttpError

        通过网络请求状态码来提示用户 (网络错误、解析错误、证书错误、协议错误、响应超时、发送超时、网络请求错误 等...)

/// 网络请求错误
class HttpError {
  ///HTTP 状态码
  static const int UNAUTHORIZED = 401;
  static const int FORBIDDEN = 403;
  static const int NOT_FOUND = 404;
  static const int REQUEST_TIMEOUT = 408;
  static const int INTERNAL_SERVER_ERROR = 500;
  static const int BAD_GATEWAY = 502;
  static const int SERVICE_UNAVAILABLE = 503;
  static const int GATEWAY_TIMEOUT = 504;

  ///未知错误
  static const String UNKNOWN = "UNKNOWN";

  ///解析错误
  static const String PARSE_ERROR = "PARSE_ERROR";

  ///网络错误
  static const String NETWORK_ERROR = "NETWORK_ERROR";

  ///协议错误
  static const String HTTP_ERROR = "HTTP_ERROR";

  ///证书错误
  static const String SSL_ERROR = "SSL_ERROR";

  ///连接超时
  static const String CONNECT_TIMEOUT = "CONNECT_TIMEOUT";

  ///响应超时
  static const String RECEIVE_TIMEOUT = "RECEIVE_TIMEOUT";

  ///发送超时
  static const String SEND_TIMEOUT = "SEND_TIMEOUT";

  ///网络请求取消
  static const String CANCEL = "CANCEL";

  String? code;

  String? message;

  HttpError(this.code, this.message);

  HttpError.dioError(DioError error) {
    message = error.message;
    switch (error.type) {
      case DioErrorType.connectTimeout:
        code = CONNECT_TIMEOUT;
        message = "网络连接超时,请检查网络设置";
        break;
      case DioErrorType.receiveTimeout:
        code = RECEIVE_TIMEOUT;
        message = "服务器异常,请稍后重试!";
        break;
      case DioErrorType.sendTimeout:
        code = SEND_TIMEOUT;
        message = "网络连接超时,请检查网络设置";
        break;
      case DioErrorType.response:
        code = HTTP_ERROR;
        message = "服务器异常,请稍后重试!";
        break;
      case DioErrorType.cancel:
        code = CANCEL;
        message = "请求已被取消,请重新请求";
        break;
      case DioErrorType.other:
        code = UNKNOWN;
        message = "未知错误,请稍后重试!";
        break;
    }
  }

  @override
  String toString() {
    return 'HttpError{code: $code, message: $message}';
  }
}

main()函数初始化 网络请求公共参数

 ///初始化公共属性
  ///
  /// [baseUrl] 地址前缀
  /// [connectTimeout] 连接超时赶时间
  /// [receiveTimeout] 接收超时赶时间
  /// [interceptors] 基础拦截器
  void init(
      {String? baseUrl,
      int? connectTimeout,
      int? receiveTimeout,
      List<Interceptor>? interceptors}) {
    _client?.options = _client!.options.copyWith(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
    );
    if (interceptors != null && interceptors.isNotEmpty) {
      _client!.interceptors..addAll(interceptors);
    }
  }
void main() async{
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  await SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);


  ///网络请求管理初始化
  HttpManager().init(baseUrl: '网络请求域名前缀');
  runApp(MyApp());
}

创建POST和GET统一网络请求

///统一网络请求
  ///[url] 网络请求地址不包含域名
  ///[data] post 请求参数
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  void _request({
    String? url,
    String? method,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  }) async {
    //检查网络是否连接
    ConnectivityResult connectivityResult =
        await (Connectivity().checkConnectivity());
    if (connectivityResult == ConnectivityResult.none) {
      if (errorCallback != null) {
        errorCallback(HttpError(HttpError.NETWORK_ERROR, "网络异常,请稍后重试!"));
      }
      return;
    }
    //设置默认值
    params = params ?? {};
    method = method ?? 'GET';
    options?.method = method;
    options = options ?? Options(method: method,);
    ///请求头
    options.headers = await _headers();
    try {
      CancelToken cancelToken;
      cancelToken =
          (_cancelTokens[tag] == null ? CancelToken() : _cancelTokens[tag])!;
      _cancelTokens[tag!] = cancelToken;
      Response response = await _client!.request(url!,
          data: data,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken);
      var _responseData = response.data;
      print('响应的数据:$_responseData');
      int statusCode = _responseData["code"];
      if (statusCode == 200) {
        //成功
        successCallback!(_responseData["data"]);
      } else {
        //失败
        String message = _responseData["msg"].toString();
        errorCallback!(HttpError('$statusCode', message));
      }
    } on DioError catch (e, s) {
      if (e.type != DioErrorType.cancel) {
        errorCallback!(HttpError.dioError(e));
      }
    } catch (e, s) {
      errorCallback!(HttpError(HttpError.UNKNOWN, "未知错误,请稍后重试!"));
    }
  }

取消网络请求、自定义header

 ///取消网络请求
   ///取消网络请求
  void cancel(String tag) {

    print('取消网络请求前 cancelToken集合$_cancelTokens');

    if (_cancelTokens.containsKey(tag)) {
      if (!_cancelTokens[tag]!.isCancelled) {
        _cancelTokens[tag]!.cancel();
      }
      _cancelTokens.remove(tag);

      print('取消网络请求后 cancelToken集合$_cancelTokens');
    }


  }

  ///请求头
  Future<Map<String, String>> _headers() async {
    Map<String, String> _headers = new HashMap();
    String _token = '';
    _headers.addAll({"token": _token});
    return _headers;
  }

GET 请求 

 ///Get网络请求
  ///
  ///[url] 网络请求地址不包含域名
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  get({
    required String url,
    required Map<String, dynamic> params,
    Options? options,
    required HttpSuccessCallback successCallback,
    required HttpFailureCallback errorCallback,
    required String tag,
  }) async {
    return _request(
      url: url,
      params: params,
      method: 'get',
      options: options,
      successCallback: successCallback,
      errorCallback: errorCallback,
      tag: tag,
    );
  }

发起GET请求

class _MyHomePageState extends State<MyHomePage> {
  var _tag;
  void _testDioHeadReq() async {
    _tag = '${
   
   {DateTime.now().millisecondsSinceEpoch}}';
    HttpManager().get(
        url: '',
        params: {},
        successCallback: (_data) {
          print('响应数据:$_data');
          ///取消请求
          HttpManager().cancel(_tag);
        },
        errorCallback: (_data) {
          print('响应数据错误:$_data');
        },

        ///请求tag可以用时间戳进行定义
        tag: '$_tag');

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _testDioHeadReq,
        tooltip: 'TestDioHeadReq',
        child: Icon(Icons.add),
      ),
    );
  }
}

请求结果 : 

POST 请求

///post网络请求
  ///[url] 网络请求地址不包含域名
  ///[data] post 请求参数
  ///[params] url请求参数支持restful
  ///[options] 请求配置
  ///[successCallback] 请求成功回调
  ///[errorCallback] 请求失败回调
  ///[tag] 请求统一标识,用于取消网络请求
  void post({
    String? url,
    data,
    Map<String, dynamic>? params,
    Options? options,
    HttpSuccessCallback? successCallback,
    HttpFailureCallback? errorCallback,
    @required String? tag,
  }) async {
    _request(
      url: url!,
      data: data,
      method: POST,
      params: params!,
      options: options!,
      successCallback: successCallback!,
      errorCallback: errorCallback!,
      tag: tag!,
    );
  }

修改main()函数初始化 网络请求域名前缀 (BaseUrl)

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  await SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);

  ///网络请求管理初始化
  HttpManager().init(baseUrl: 'https://tieba.baidu.com/f');
  runApp(MyApp());
}
var _tag;
  void _testDioHeadReq() async {
    _tag = '${
   
   {DateTime.now().millisecondsSinceEpoch}}';
    HttpManager().post(
        url: '',
        params: {'ie':'utf-8','kw':'大佬','fr':'search'},
        successCallback: (_data) {
          print('响应数据:$_data');
          ///取消请求
          HttpManager().cancel(_tag);
        },
        errorCallback: (_data) {
          print('响应数据错误:$_data');
        },

        ///请求tag可以用时间戳进行定义
        tag: '$_tag');

  }

请求结果 :  

flutter_test_dio案例下载

 后续

        接下来的日子,我会在博客里面演练MVC、MVP、MVVM与Dio结合进行网络请求 

参考HTTP状态码

Flutter实战MVC、MVP、MVVM

Guess you like

Origin blog.csdn.net/u013491829/article/details/122220773