1、网络请求基础:HTTP协议回顾、RESTful API设计原则、Flutter中网络请求的几种方式

1.1 HTTP协议:你真的了解它吗?

说实话,很多做Flutter开发的同学,一上来就撸代码,结果遇到网络问题就抓瞎。我个人觉得,先把HTTP协议搞明白,后面能省掉80%的坑。

HTTP协议,说白了就是客户端和服务器之间沟通的「语言」。它规定了请求长什么样、响应长什么样。我刚开始做移动端时,以为网络请求就是调个接口拿数据,直到有一次线上环境频繁超时,排查了半天才发现是请求头里少了个关键的Content-Type。嗯,从那以后我再也不敢小看协议细节了。

咱们快速过一下核心要点:

  • 请求方法:GET(查)、POST(增)、PUT(全量改)、PATCH(局部改)、DELETE(删)。记住这五个就够了。
  • 状态码:2xx成功、3xx重定向、4xx客户端错误、5xx服务端错误。我最常遇到的是404(路径写错)和500(后端炸了)。
  • 请求头:Content-Type、Authorization、User-Agent 这三个你一定会用到。
  • 请求体:POST/PUT时携带的数据,常见格式有JSON、Form-data。

避坑指南:我曾经在项目里遇到一个诡异问题——Android端请求正常,iOS端却一直报400。查了半天,发现是iOS端默认发送的请求头里带了不兼容的字符编码。所以,请求头一定要显式设置,别依赖平台默认行为。

1.2 RESTful API设计原则:别把接口写成「一锅粥」

你想想看,如果后端把获取用户信息的接口写成 /getUserInfo,删除用户写成 /deleteUserById,更新用户写成 /updateUser……是不是看着就头疼?

RESTful API就是为了解决这个问题。它强调用资源(名词)来设计URL,用HTTP方法来表达操作。我个人习惯这样设计:

操作 HTTP方法 URL示例 说明
获取用户列表 GET /api/users 查询所有用户
获取单个用户 GET /api/users/123 查询ID为123的用户
创建用户 POST /api/users 请求体携带用户信息
更新用户 PUT /api/users/123 全量更新
删除用户 DELETE /api/users/123 删除指定用户

核心原则就三条:

  • URL用名词复数:比如 /api/products,别用动词。
  • 用HTTP方法表达动作:别在URL里写 /api/deleteProduct,直接用 DELETE /api/products/123
  • 状态码要语义化:创建成功返回201,参数错误返回400,没权限返回403。

我的经验:我在项目中遇到过后端把删除接口设计成 POST /api/deleteProduct 的情况,结果前端和后端为了「到底用POST还是DELETE」吵了半天。其实只要约定好,用POST也不是不行,但既然用了RESTful,就尽量遵循规范,不然团队协作成本会很高。

1.3 Flutter中网络请求的几种方式

Flutter里做网络请求,主流方式就三种。我按推荐程度排个序:

方式一:dio(强烈推荐)

这是目前Flutter社区最流行的网络库,没有之一。我个人几乎所有的项目都用它。为什么?因为它功能太全了:拦截器、请求取消、文件上传下载、超时设置、Cookie管理……基本上你能想到的它都有。

// 一个简单的dio请求示例
import 'package:dio/dio.dart';

void fetchUser() async {
  final dio = Dio();
  try {
    final response = await dio.get('https://api.example.com/users/123');
    print(response.data);
  } on DioException catch (e) {
    print('请求失败: ${e.message}');
  }
}

注意:dio的版本迭代比较快,建议锁定版本号,避免升级后出现不兼容问题。我一般会在 pubspec.yaml 里写成 dio: ^5.4.0 这样。

方式二:http(官方库,轻量级)

如果你只是做简单的GET/POST请求,不想引入第三方库,那用官方的 http 包就够了。它很轻,但功能也少——没有拦截器,没有请求取消,没有超时控制(需要自己写)。

// 使用http包
import 'package:http/http.dart' as http;
import 'dart:convert';

void fetchUser() async {
  final url = Uri.parse('https://api.example.com/users/123');
  final response = await http.get(url);
  
  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    print(data);
  } else {
    print('请求失败: ${response.statusCode}');
  }
}

说实话,我只有在写demo或者小工具时才会用 http 包。生产环境?还是dio更靠谱。

方式三:WebSocket(实时通信)

如果你需要做聊天、实时推送这类功能,那就得上WebSocket了。Flutter官方提供了 web_socket_channel 包,用起来也不复杂。

// WebSocket示例
import 'package:web_socket_channel/web_socket_channel.dart';

void connectWebSocket() {
  final channel = WebSocketChannel.connect(
    Uri.parse('wss://echo.websocket.org'),
  );

  // 发送消息
  channel.sink.add('Hello!');

  // 接收消息
  channel.stream.listen((message) {
    print('收到: $message');
  });
}

重要提醒:WebSocket连接是长连接,记得在页面销毁时关闭,否则会造成内存泄漏。我见过不少新手忘记调用 channel.sink.close(),结果App越来越卡。

1.4 小结:选哪个?

我建议你直接上dio。原因很简单:

  • 功能全面,后期扩展不用换库
  • 社区活跃,遇到问题容易找到解决方案
  • 拦截器机制非常强大,后面讲请求优化时会重点讲

当然,如果你只是做个简单的天气查询App,用 http 包也完全没问题。别为了用而用,合适才是最好的

下一章,我会带你手把手搭建一个基于dio的网络层封装框架。到时候你会发现,原来网络请求也可以写得这么优雅。