簡介

Http

網路請求

是一門開發語言裡比較常用和重要的功能,主要用於資源訪問、介面資料請求和提交、上傳下載檔案等等操作,Http請求方式主要有:GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT、OPTIONS。本文主要GET和POST這兩種常用請求在Flutter中的用法,其中對POST將進行著重講解。Flutter的Http網路請求的實現主要分為三種:io。dart裡的HttpClient、Dart原生http請求和第三方庫實現。

Http網路請求是網際網路開發的基礎協議,Http支援的請求方式有:GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT、OPTIONS這八種。

GET請求

GET請求主要是執行獲取資源操作的,例如透過URL從伺服器獲取返回的資源,其中GET可以把請求的一些引數資訊拼接在URL上,傳遞給伺服器,由伺服器端進行引數資訊解析,伺服器收到請求後返回相應的資源給請求者。注意:GET請求拼接的URL資料大小和長度是有最大限制的,傳輸的資料量一般限制在2KB。

POST請求

POST請求主要用於執行提交資訊、請求資訊等操作,相比GET請求,POST請求的可以攜帶更多的資料,而且格式不限,如JSON、XML、文字等等都支援。並且POST傳遞的一些資料和引數不是直接拼接在URL後的,而是放在Http請求Body裡,相對GET來說比較安全。並且傳遞的資料大小和格式是無限制的。 POST

請求方式

是一種比較常用網路請求方式,通常由請求頭(header)和請求體(body)兩部分組成。POST請求常見的請求體(body)有三種傳輸內容型別Content-type:application/x-www-form-urlencoded、application/json和multipart/form-data,當然還有其他的幾種,不過不常用,常用的就是這三種。

HEAD請求

HEAD請求主要用於給請求的客戶端返回頭資訊,而不返回Body主體內容。和GET方式類似,只不過GET方式有Body實體返回,而HEAD只返回頭資訊,無Body實體內容返回。主要是用於確認URL的有效性、資源更新的日期時間、檢視伺服器狀態等等,對於有這方面需求的請求來說,比較不佔用資源。

PUT請求

PUT請求主要用於執行傳輸檔案操作,類似於FTP的檔案上傳一樣,請求裡包含檔案內容,並將此檔案儲存到URI指定的伺服器位置。 和

POST方式

的主要區別是:PUT請求方式如果前後兩個請求相同,則後一個請求會把前一個請求覆蓋掉,實現了PUT方式的修改資源;而POST請求方式如果前後兩個請求相同,則後一個請求不會把前一個請求覆蓋掉,實現了POST的增加資源。

DELETE請求

DELETE請求主要用於執行刪除操作,告訴伺服器想要刪除的資源,不常用。

OPTIONS請求

OPTIONS請求主要用於執行查詢針對所要請求的URI資源伺服器所支援的請求方式,也就是獲取這個URI所支援客戶端提交給伺服器端的請求方式有哪些。

TRACE請求

TRACE請求主要用於執行追蹤傳輸路徑的操作,例如,我們發起了一個Http請求,在這個過程中這個請求可能會經過很多個路徑和過程,TRACE就是告訴伺服器在收到請求後,返回一條響應資訊,將它收到的原始Http請求資訊返回給客戶端,這樣就可以驗證在Http傳輸過程中請求是否被修改過。

CONNECT請求

CONNECT請求主要用於執行連線代理操作,例如“翻牆”。客戶端透過CONNECT方式與伺服器建立通訊隧道,進行TCP通訊。主要透過SSL和TLS安全傳輸資料。CONNECT的作用就是告訴伺服器讓它代替客戶端去請求訪問某個資源,然後再將資料返回給客戶端,相當於一個媒介中轉。

Dart的Http請求

Dart原生http請求庫是Dart提供的一種請求方式,常見的請求方式都支援,除此之外,還支援如上傳和下載檔案等操作。

Dart官方倉庫提供了大量的三方庫和官方庫,引用也非常的方便,Dart PUB官方地址為:

https://

pub。dartlang。org

,如下圖所示:

Flutter開發之網路請求

1。1 安裝依賴

使用Dart的原生http庫進行網路請求時,需要先在Dart PUB或官方Github裡把相關的http庫引用下來,然後才能使用。新增包依賴前,我們可以使用

https://

pub。dev/packages/http

來檢視依賴包的版本和使用方法。

Flutter開發之網路請求

然後,在

pubspec。yaml

檔案的dependencies節點新增http庫依賴,如下所示:

http: ^0。12。0+2

然後,使用flutter packages get命令拉取庫依賴。使用http進行網路請求前,需要先匯入http包,如下:

import ‘package:http/http。dart’ as http;

1。2 常用方法

http庫支援常見的get、post、del等請求。其中,get請求的格式如下:

get(dynamic url, { Map headers }) → Future

(必須)url:請求地址

(可選)headers:請求頭

post請求的格式如下:

post(dynamic url, { Map headers, dynamic body, Encoding encoding }) → Future

(必須)url:請求地址

(可選)headers:請求頭

(可選)body:引數

(編碼)Encoding:編碼

例如,下面是post的示例:

http。post(‘https://flutter-cn。firebaseio。com/products。json’,

body: json。encode(param),encoding: Utf8Codec())

。then((http。Response response) {

final Map responseData = json。decode(response。body);

// 處理響應資料

})。catchError((error) {

print(‘$error錯誤’);

});

1。3 示例

例如,下面使用Dart的http庫實現get請求的示例,示例程式碼如下:

import ‘package:flutter/material。dart’;

import ‘package:http/http。dart’ as http;

void main() => runApp(MyApp());

var hotMovies =

‘https://api。douban。com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a’;

class MyApp extends StatelessWidget {

var movies = ‘’;

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘http請求示例’,

theme: new ThemeData(

primaryColor: Colors。white,

),

home: new Scaffold(

appBar: new AppBar(

title: new Text(‘http請求示例’),

),

body: new Column(children:

new RaisedButton(

child: new Text(‘獲取電影列表’), onPressed: getFilmList()),

new Expanded(

child: new Text(‘$movies’),

]),

));

}

getFilmList() {

http。get(hotMovies)。then((response) {

movies = response。body;

});

}

}

執行上面的程式碼,結果如下圖:

Flutter開發之網路請求

除了get請求,http的post請求示例如下:

import ‘dart:convert’;

import ‘dart:io’;

import ‘package:http/http。dart’ as http;

import ‘package:http_parser/http_parser。dart’;

class DartHttpUtils {

//建立client例項

var _client = http。Client();

//傳送GET請求

getClient() async {

var url = “https://abc。com:8090/path1?name=abc&pwd=123”;

_client。get(url)。then((http。Response response) {

//處理響應資訊

if (response。statusCode == 200) {

print(

response。body

);

} else {

print(‘error’);

}

});

}

//傳送POST請求,application/x-www-form-urlencoded

postUrlencodedClient() async {

var url = “https://abc。com:8090/path2”;

//設定header

Map headersMap = new Map();

headersMap[“content-type”] = “application/x-www-form-urlencoded”;

//設定body引數

Map bodyParams = new Map();

bodyParams[“name”] = “value1”;

bodyParams[“pwd”] = “value2”;

_client

。post(url, headers: headersMap, body: bodyParams, encoding: Utf8Codec())

。then((http。Response response) {

if (response。statusCode == 200) {

print(response。body);

} else {

print(‘error’);

}

})。catchError((error) {

print(‘error’);

});

}

//傳送POST請求,application/json

postJsonClient() async {

var url = “https://abc。com:8090/path3”;

Map headersMap = new Map();

headersMap[“content-type”] = ContentType。json。toString();

Map bodyParams = new Map();

bodyParams[“name”] = “value1”;

bodyParams[“pwd”] = “value2”;

_client

。post(url,

headers: headersMap,

body: jsonEncode(bodyParams),

encoding: Utf8Codec())

。then((http。Response response) {

if (response。statusCode == 200) {

print(response。body);

} else {

print(‘error’);

}

})。catchError((error) {

print(‘error’);

});

}

// 傳送POST請求,multipart/form-data

postFormDataClient() async {

var url = “https://abc。com:8090/path4”;

var client = new http。MultipartRequest(“post”, Uri。parse(url));

client。fields[“name”] = “value1”;

client。fields[“pwd”] = “value2”;

client。send()。then((http。StreamedResponse response) {

if (response。statusCode == 200) {

response。stream。transform(utf8。decoder)。join()。then((String string) {

print(string);

});

} else {

print(‘error’);

}

})。catchError((error) {

print(‘error’);

});

}

// 傳送POST請求,multipart/form-data,上傳檔案

postFileClient() async {

var url = “https://abc。com:8090/path5”;

var client = new http。MultipartRequest(“post”, Uri。parse(url));

http。MultipartFile。fromPath(‘file’, ‘sdcard/img。png’,

filename: ‘

img。png

’, contentType: MediaType(‘image’, ‘png’))

。then((http。MultipartFile file) {

client。files。add(file);

client。fields[“description”] = “descriptiondescription”;

client。send()。then((http。StreamedResponse response) {

if (response。statusCode == 200) {

response。stream。transform(utf8。decoder)。join()。then((String string) {

print(string);

});

} else {

response。stream。transform(utf8。decoder)。join()。then((String string) {

print(string);

});

}

})。catchError((error) {

print(error);

});

});

}

///其餘的HEAD、PUT、DELETE請求用法類似,大同小異,大家可以自己試一下

///在Widget裡請求成功資料後,使用setState來更新內容和狀態即可

///setState(() {

/// 。。。

/// });

}

HttpClient請求

Dart IO庫中提供的HttpClient可以實現一些基本的Http請求。不過,HttpClient只能實現一些基本的網路請求,對應一些複雜的網路請求還無法完成,如POST裡的Body請求體傳輸內容型別部分還無法支援,multipart/form-data這個型別傳輸還不支援。

2。1 使用方法

使用HttpClient發起請求主要分為五步: 1,建立一個HttpClient。

HttpClient httpClient = new HttpClient();

2,開啟Http連線,設定請求頭。

HttpClientRequest request = await httpClient。getUrl(uri);

在這一步,我們可以使用任意Http method,如httpClient。post(。。。)、httpClient。delete(。。。)等。如果包含Query引數,可以在構建uri時新增,如:

Uri uri=Uri(scheme: “https”, host: “flutterchina。club”, queryParameters: {

“xx”:“xx”,

“yy”:“dd”

});

如果需要設定請求頭,可以透過HttpClientRequest設定請求header,如:

request。headers。add(“user-agent”, “test”);

如果是post或put等可以攜帶請求體的請求,還可以透過HttpClientRequest物件傳送request body,如:

String payload=“。。。”;

request。add(utf8。encode(payload));

//request。addStream(_inputStream); //可以直接新增輸入流

3,等待連線伺服器。

HttpClientResponse response = await request。close();

到這一步之後,請求資訊就已經發送給伺服器了,返回一個HttpClientResponse物件,它包含響應頭(header)和響應流(響應體的Stream),接下來就可以透過讀取響應流來獲取響應內容。

4,讀取響應內容

String responseBody = await response。transform(utf8。decoder)。join();

5,請求結束後,還需要關閉HttpClient。

httpClient。close();

2。2 請求示例

import ‘package:flutter/material。dart’;

import ‘dart:convert’;

import ‘dart:io’;

void main() => runApp(MyApp());

var hotMovies =

‘https://api。douban。com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a’;

class MyApp extends StatelessWidget {

var movies = ‘’;

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘HttpClient請求示例’,

theme: new ThemeData(

primaryColor: Colors。white,

),

home: new Scaffold(

appBar: new AppBar(

title: new Text(‘HttpClient請求示例’),

),

body: new Column(children:

new RaisedButton(

child: new Text(‘獲取電影列表’), onPressed: getFilmList),

new Expanded(

child: new Text(‘$movies’),

]),

));

}

void getFilmList() async {

try {

HttpClient httpClient = new HttpClient();

HttpClientRequest request = await httpClient。getUrl(Uri。parse(hotMovies));

HttpClientResponse response = await request。close();

var result = await response。transform(utf8。decoder)。join();

movies = result;

print(‘movies’+result);

httpClient。close();

}catch(e){

print(‘請求失敗:$e’);

}

}

}

執行上面的程式碼,結果如下圖:

Flutter開發之網路請求

dio庫

除了上面兩種常見的請求方式外,Flutter開發中還可以使用dio等第三方庫來實現Http網路請求,如Dart社群提供的dio庫。

前面說過,HttpClient發起網路請求是比較麻煩的,很多事情都需要我們手動處理,如果再涉及到檔案上傳/下載、Cookie管理等就會非常繁瑣。而Dart社群有一些第三方http請求庫,就可以簡化這些操作。dio庫不僅支援常見的網路請求,還支援Restful API、FormData、攔截器、請求取消、Cookie管理、檔案上傳/下載、超時等操作。

3。1 安裝依賴

和使用其他的第三方庫一樣,使用dio庫之前需要先安裝依賴,安裝前可以在Dart PUB上搜索dio,確定其版本號,如下所示:

dependencies:

dio: 2。1。x #latest version

然後,執行flutter packages get命令或者點選【Packages get】選項拉取庫依賴。 使用dio之前需要先匯入dio庫,並建立dio例項,如下所示:

import ‘package:dio/dio。dart’;

Dio dio = new Dio();

接下來,就可以透過 dio例項來發起網路請求了,注意,一個dio例項可以發起多個http請求,一般來說,APP只有一個

http資料來源

時,dio應該使用單例模式。

3。2 使用方法

3。2。1 GET請求

import ‘package:dio/dio。dart’;

void getHttp() async {

try {

Response response;

response=await dio。get(“/test?id=12&name=wendu”)

print(response。data。toString());

} catch (e) {

print(e);

}

}

在上面的示例中,我們可以將

query引數

透過物件來傳遞,上面的程式碼等同於:

response=await dio。get(“/test”,queryParameters:{“id”:12,“name”:“wendu”})

print(response);

3。2。2 POST請求

response=await dio。post(“/test”,data:{“id”:12,“name”:“wendu”})

3。2。3 多個併發請求

如果要發起多個併發請求,可以使用下面的方式:

response= await Future。wait([dio。post(“/info”),dio。get(“/token”)]);

3。2。4 下載檔案

如果要下載檔案,可以使用dio的download函式,如下所示:

response=await dio。download(“https://www。google。com/”,_savePath);

3。2。5 FormData請求

如果要發起表單請求,可以使用下面的方式:

FormData formData = new FormData。from({

“name”: “wendux”,

“age”: 25,

});

response = await dio。post(“/info”, data: formData)

如果傳送的資料是FormData,則dio會將請求header的contentType設為“multipart/form-data”。 當然,FormData也支援上傳多個檔案操作,例如:

FormData formData = new FormData。from({

“name”: “wendux”,

“age”: 25,

“file1”: new UploadFileInfo(new File(“。/upload。txt”), “upload1。txt”),

“file2”: new UploadFileInfo(new File(“。/upload。txt”), “upload2。txt”),

// 支援檔案陣列上傳

“files”: [

new UploadFileInfo(new File(“。/example/

upload。txt

”), “upload。txt”),

new UploadFileInfo(new File(“。/example/upload。txt”), “upload。txt”)

});

response = await dio。post(“/info”, data: formData)

3。2。6 回撥設定

值得一提的是,dio內部仍然使用HttpClient發起的請求,所以代理、請求認證、證書校驗等和HttpClient是相同的,我們可以在onHttpClientCreate回撥中進行設定,例如:

(dio。httpClientAdapter as DefaultHttpClientAdapter)。onHttpClientCreate = (client) {

//設定代理

client。findProxy = (uri) {

return “PROXY 192。168。1。2:8888”;

};

//校驗證書

httpClient。badCertificateCallback=(X509Certificate cert, String host, int port){

if(cert。pem==PEM){

return true; //證書一致,則允許傳送資料

}

return false;

};

};

3。3 示例

import ‘package:flutter/

material。dart

’;

import ‘package:dio/

dio。dart

’;

void main() => runApp(MyApp());

var hotMovies = ‘http://api。douban。com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a’;

class MyApp extends StatelessWidget {

var movies = ‘’;

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘Dio請求示例’,

theme: new ThemeData(

primaryColor: Colors。white,

),

home: new Scaffold(

appBar: new AppBar(

title: new Text(‘Dio請求示例’),

),

body: new Column(children:

new RaisedButton(

child: new Text(‘獲取電影列表’), onPressed: getFilmList),

new Expanded(

child: new Text(‘$movies’),

]),

));

}

void getFilmList() async {

Dio dio = new Dio();

Response response=await dio。get(hotMovies);

movies=response。toString();

print(‘電影資料:’+movies);

}

}

綜合示例

為了對前面的知識做一個簡單的總結,下面透過一個見得的示例來講解Flutter的基本使用,最終效果如圖:

Flutter開發之網路請求

需要說的是,最新版本豆瓣api需要傳遞apikey才能獲取值,下面是電影列表的原始碼:

import ‘package:flutter/material。dart’;

import ‘dart:convert’ as Convert;

import ‘dart:io’;

import ‘package:flutter/cupertino。dart’;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

@override

Widget build(BuildContext context) {

return MaterialApp(

title: ‘豆瓣電影’,

home: Scaffold(

appBar: new AppBar(

title: new Text(‘豆瓣電影列表’),

),

body: DouBanListView(),),

);

}

}

class DouBanListView extends StatefulWidget {

@override

State createState() {

return DouBanState();

}

}

class DouBanState extends State with AutomaticKeepAliveClientMixin{

var url=‘http://api。douban。com/v2/movie/top250?start=25&count=10&apikey=0df993c66c0c636e29ecbb5344252a4a’;

var subjects = [];

var itemHeight = 150。0;

requestMovieTop() async {

var httpClient = new HttpClient();

var request = await httpClient。getUrl(Uri。parse(url));

var response = await request。close();

var responseBody = await response。transform(Convert。utf8。decoder)。join();

Map data = Convert。jsonDecode(responseBody);

setState(() {

subjects = data[‘subjects’];

});

}

@override

void initState() {

super。initState();

requestMovieTop();

}

@override

Widget build(BuildContext context) {

return Container(

child: getListViewContainer(),

);

}

getListViewContainer() {

if (

subjects。length

== 0) {

//loading

return CupertinoActivityIndicator();

}

return ListView。builder(

//item 的數量

itemCount: subjects。length,

itemBuilder: (BuildContext context, int index) {

return GestureDetector(

//Flutter 手勢處理

child: Container(

color: Colors。transparent,

child: Column(

crossAxisAlignment: CrossAxisAlignment。start,

children:

numberWidget(index + 1),

getItemContainerView(subjects[index]),

//下面的灰色分割線

Container(

height: 10,

color: Color。fromARGB(255, 234, 233, 234),

],

),

),

onTap: () {

//監聽點選事件

print(“click item index=$index”);

},

);

});

}

//肖申克的救贖(1993) View

getTitleView(subject) {

var title = subject[‘title’];

var year = subject[‘year’];

return Container(

child: Row(

children:

Icon(

Icons。play_circle_outline,

color: Colors。redAccent,

),

Text(

title,

style: TextStyle(

fontSize: 18, fontWeight: FontWeight。bold, color: Colors。black),

),

Text(‘($year)’,

style: TextStyle(

fontSize: 16,

fontWeight: FontWeight。bold,

color: Colors。grey))

],

),

);

}

getItemContainerView(var subject) {

var imgUrl = subject[‘images’][‘medium’];

return Container(

width: double。infinity,

padding: EdgeInsets。all(5。0),

child: Row(

children:

getImage(imgUrl),

Expanded(

child: getMovieInfoView(subject),

flex: 1,

],

),

);

}

//圓角

圖片

getImage(var imgUrl) {

return Container(

decoration: BoxDecoration(

image:

DecorationImage(image: NetworkImage(imgUrl), fit: BoxFit。cover),

borderRadius: BorderRadius。all(Radius。circular(5。0))),

margin: EdgeInsets。only(left: 8, top: 3, right: 8, bottom: 3),

height: itemHeight,

width: 100。0,

);

}

getStaring(var stars) {

return Row(

children: [RatingBar(stars), Text(‘$stars’)],

);

}

//電影標題,星標評分,演員簡介Container

getMovieInfoView(var subject) {

var start = subject[‘rating’][‘average’];

return Container(

height: itemHeight,

alignment: Alignment。topLeft,

child: Column(

children:

getTitleView(subject),

RatingBar(start),

DescWidget(subject)

],

),

);

}

//NO。1 圖示

numberWidget(var no) {

return Container(

child: Text(

‘No。$no’,

style: TextStyle(color: Color。fromARGB(255, 133, 66, 0)),

),

decoration: BoxDecoration(

color: Color。fromARGB(255, 255, 201, 129),

borderRadius: BorderRadius。all(Radius。circular(5。0))),

padding: EdgeInsets。fromLTRB(8, 4, 8, 4),

margin: EdgeInsets。only(left: 12, top: 10),

);

}

@override

bool get wantKeepAlive => true;

}

//類別、演員介紹

class DescWidget extends StatelessWidget {

var subject;

DescWidget(this。subject);

@override

Widget build(BuildContext context) {

var casts = subject[‘casts’];

var sb = StringBuffer();

var genres = subject[‘genres’];

for (var i = 0; i < genres。length; i++) {

sb。write(‘${genres[i]} ’);

}

sb。write(“/ ”);

List list = List。generate(

casts。length, (int index) => casts[index][‘name’]。toString());

for (var i = 0; i < list。length; i++) {

sb。write(‘${list[i]} ’);

}

return Container(

alignment: Alignment。topLeft,

child: Text(

sb。toString(),

softWrap: true,

textDirection: TextDirection。ltr,

style:

TextStyle(fontSize: 16, color: Color。fromARGB(255, 118, 117, 118)),

),

);

}

}

class RatingBar extends StatelessWidget {

double stars;

RatingBar(this。stars);

@override

Widget build(BuildContext context) {

List startList = [];

//實心星星

var startNumber = stars ~/ 2;

//半實心星星

var startHalf = 0;

if (stars。toString()。contains(‘。’)) {

int tmp = int。parse((stars。toString()。split(‘。’)[1]));

if (tmp >= 5) {

startHalf = 1;

}

}

//

空心星星

var startEmpty = 5 - startNumber - startHalf;

for (var i = 0; i < startNumber; i++) {

startList。add(Icon(

Icons。star,

color: Colors。amberAccent,

size: 18,

));

}

if (startHalf > 0) {

startList。add(Icon(

Icons。star_half,

color: Colors。amberAccent,

size: 18,

));

}

for (var i = 0; i < startEmpty; i++) {

startList。add(Icon(

Icons。star_border,

color: Colors。grey,

size: 18,

));

}

startList。add(Text(

‘$stars’,

style: TextStyle(

color: Colors。grey,

),

));

return Container(

alignment: Alignment。topLeft,

padding: const EdgeInsets。only(left: 0, top: 8, right: 0, bottom: 5),

child: Row(

children: startList,

),

);

}

}

附: 1,Flutter系列教程之環境搭建 2,Flutter系列教程之學習線路 3,Flutter系列教程之Dart語法 4,Flutter系列教程之快速入門 5,Flutter系列教程之Flutter 1。7新特性 6,透過HttpClient發起HTTP請求