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的网络层封装框架。到时候你会发现,原来网络请求也可以写得这么优雅。