/ 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的主要作用是
对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全
对网站服务器进行真实身份认证
SSL/TLS 协议采用非对称加密方式,服务端会生成公钥和私钥,公钥用来加密信息,可以提供给所有需要进行通信的客户端,私钥保存在本地,不能泄露。客户端使用这份公钥对信息进行加密,将请求发送给服务器,服务器用私钥解密。反之,服务器对客户端的返回,则使用客户端提供的公钥进行加密,客户端使用本地对应的私钥来解密,保证了通信的安全。
基于 SSL/TLS 进行 一次的 HTTPS 会话的过程,简单地说可以分成3步
客户端向服务器端索要并验证公钥。
双方协商生成”对话密钥”。
双方采用”对话密钥”进行加密通信。
HTTP 协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,使用 HTTPS 则有以下几个方面的优势:
保护站点安全、保护用户隐私、未来的趋势所在
升级HTTPS :
获取证书、在服务器安装证书、重定向配置、修改资源链接
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');
}
请求结果 :
后续
接下来的日子,我会在博客里面演练MVC、MVP、MVVM与Dio结合进行网络请求