/ 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 :
獲取證書、在服務器安裝證書、重定向配置、修改資源鏈接
/ 網路請求神器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
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/399589.html
標籤:其他
