Flutter HTTP POST請求提交表單資料

最後更新於 2021 年 10 月 17 日

因為本篇以 POST請求 為主旨,GET及其他HTTP操作的部分不會提到太多。我之前發過一篇關於Flutter發出HTTP請求獲取數據的文章,有興趣可以看一下:[Flutter]發出HTTP請求獲取數據

在開始之前,先補充一些知識點。

HTTP

HyperText Transfer Protocol(HTTP)是一種用戶端瀏覽器和伺服端伺服器之間溝通的標準協定,他是屬於OSI七層模型中的應用層。

網路資料的傳輸是建構在 HTTP 協定之上,如下圖 Request、Response之間的交換機制都是基於HTTP的基本規範。

Chapter 2: Protocols - An Introduction to APIs | Zapier
  • Request:使用者透過瀏覽器或程式發送的 HTTP 請求 ,一般來說分成 GETPOST 兩種方法。
  • Response:網頁伺服器收到 Request 後,回傳給使用者的 HTTP Response,通常會有兩種形式。一種是僅有特定資料格式所組成的字串,稱為是 API;另一種是包含 HTML 的原始碼,稱為 HTML Response

如果查看使用POST請求方法的資源並在請求頭的地方點擊「View Source」可以查看HTTP的訊息格式。不管是 Request 或是 Response 的封包都由三種部份所組成:State、Headers、Body,以下有兩種例子作參考:

Requests and responses share a common structure in HTTP
圖取自 https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
image 40 Flutter HTTP POST請求提交表單資料
圖為自截+編輯

HTTP請求方法

常見如GET、POST,但其實不只這兩種,只不過其他很少會用到就不多提。

GET vs. POST

GET的資料是包含在網址當中,而POST的資料是包含在封包之內,看一個例子就能理解:

GET

你在 Twitter 搜索 COVID-19 後觀察網址可以得到:

https://twitter.com/search?q=COVID-19&src=typed_query

網址後面多了一串 search?q=COVID-19&src=typed_query 是為什麼呢?

原因是使用GET方式請求瀏覽器會將表單內容轉為Query String加在URL裡進行連線,網址 ? 後面的為參數,每個參數值以 & 隔開,因此可以得知總共有兩個參數:q(搜索關鍵字)、src

使用開發者工具觀察可以看到Request Method確實是GET。

image 37 Flutter HTTP POST請求提交表單資料

POST

但是如果是以POST方式請求的話,可以觀察送出前後網址是不會有變化的,得開啟開發者工具查看詳細的請求資訊以及輸入的表單資料:

image 38 Flutter HTTP POST請求提交表單資料

送出的表單資料可以在headers / Form Data得到

formdata Flutter HTTP POST請求提交表單資料

Flutter的網路請求

大致分為三種方式:

HttpClient

Dart原生網路請求方式

import 'dart:io';
var httpClient = new HttpClient();

該 client 支持常用的HTTP操作,如:GET, POST, PUT, DELETE. 但此方法對POST貌似比較不友好,所以建議使用http庫或dio庫

http庫

https://pub.dev/packages/http

POST請求格式

  • url:請求地址(必要)
  • headers:請求頭(可選)
  • body:參數(可選)
  • Encoding:編碼(編碼)
post(dynamic url, { Map<String, String> headers, dynamic body, Encoding encoding }) → Future<Response>

Example:

import 'package:http/http.dart' as http;

await http.post('https://www.coa.gov.tw/theme_list.php?theme=news&sub_theme=agri',
           headers: headersMap,body: formdata,encoding: Utf8Codec())
    .then((http.Response response) {
      	var responseBody = response.body;
       // 處理響應數據
    
    }).catchError((error) {
      print('$error');
    });

dio庫

https://pub.dev/packages/dio

Example:

import 'package:dio/dio.dart';
void getHttp() async {
  try {
    var response = await Dio().get('http://www.google.com');
    print(response);
  } catch (e) {
    print(e);
  }
}

至於選擇哪一種方式全憑個人喜好和習慣,我個人是比較常用http庫。

資源Headers

先叫出開發者工具 找到 Network,可以得到頁面的資源,至於開發者工具的相關說明這邊不多加篇幅贅述,請自行google。

Network:從發起網頁頁面請求Request後,分析HTTP請求後得到的各個資源請求資訊(包括狀態、資源型別、大小、所用時間等)

試著修改表單的一些選項,然後刷新取得最新的資源請求(與網址同名)

flutter http post formdata Flutter HTTP POST請求提交表單資料

點開後能查看該資源的詳細訊息,分為Headers、Preview、Response、Initiator、Timing、Cookies

Headers

會列出資源的請求url、HTTP方法、狀態碼、請求頭和響應頭它們各自的值、請求參數等等。如圖所示

flutter http post formdata1 Flutter HTTP POST請求提交表單資料

General

Request URL代表請求路徑,也是API路徑。

Status CodeHTTP狀態碼

  • 資訊回應(Informational responses) 100-199
  • 成功回應(Successful responses) 200-299
  • 重定向(Redirects) 300-399
  • 用戶端錯誤 (Client errors) 400-499
  • 伺服器端錯誤 (Server errors) 500-599

這邊可以看到狀態碼是200 OK 代表請求成功,而Request方法為 POST。

general Flutter HTTP POST請求提交表單資料

Request Headers

請求頭資訊

  • accept:提供給後端了解前端所能接受的資料類型,就像是 txt 副檔名一樣,如果格式不正確會難以解析。
  • user-agent:發出請求的瀏覽器資訊,後端也常會透過此資訊來判斷瀏覽器的裝置為何(行動版、桌面版),藉此給予不同的回應。
  • cookie:瀏覽器紀錄的資訊,大多用來儲存具有時限的個人資訊,如驗證資料、網頁瀏覽紀錄。
  • authorization:驗證資訊,當發出的請求需要另外進行驗證(如後端資料)時,則可透過此參數夾帶驗證資料。
  • Content-Type:用於通知客戶端實際返回的內容的類型;如果表單method屬性的值為 POST,則Content-Type可能的值有以下三種:
    • application/x-www-form-urlencoded:未指定屬性時的默認值。
    • multipart/form-data:當表單包含 type=file<input>元素時使用此值。
    • text/plain:出現於 HTML5,用於調試。
request Flutter HTTP POST請求提交表單資料

詳細文檔:https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods/POST

Form Data

送出的表單資料。

formdata Flutter HTTP POST請求提交表單資料

Flutter HTTP POST Form Data實作

最近在學習抓取農委會發布的農業新聞資料,順便把我自己的作法分享出來。

Form Data各個參數的涵義:

  • keyword 關鍵字搜尋
  • division_lv1 發布機關
  • year 起始發布年
  • month 起始發布月
  • end_year 結束發布年
  • end_month 結束發布月
  • search_Submit 查詢
  • is_search 是否送出查詢
image 42 Flutter HTTP POST請求提交表單資料

division_lv1 這個參數比較特別,他的發布機關 value 並不是純數字代號、中文名稱,而是使用英文縮寫。

開啟開發工具觀察它的下拉選單 html 就能獲取所有機關的 value 。

division lv1 Flutter HTTP POST請求提交表單資料

發起請求

建立Map來存 HeadersForm Data,由於我最後想實現的效果是透過下拉選單切換發布機關來獲取不同機關所發布的新聞內容,因此我的 division_lv1 必須使用變數(selectDivision)。

String selectDivision = '*';
var response;

Future<http.Response> requestData(String division) async{
	selectDivision = division;
    var url_post = 'https://www.coa.gov.tw/theme_list.php?theme=news&sub_theme=agri';
    Map<String, String> headersMap = {
      "content-type":"application/x-www-form-urlencoded",
      "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
    };

    Map<String, String> formdata = {
      "division_lv1":selectDivision,
      "year":"110",
      "month":"1",
      "end_year":"110",
      "end_month":"5",
      "search_Submit":"查詢",
      "is_search":"y",
    };

    return response = await http.post(
      url_post,
      headers: headersMap,
      body: formdata,
      encoding: Utf8Codec() //注:Utf8Codec()需要 import 'dart:convert';
    );

  }

獲取新聞內容

HTML Selector 和 Matches 的部分就不多提了

image 41 Flutter HTTP POST請求提交表單資料
var title = new List();
var href = new List();
var date = new List();
var division = new List();
var date_reg = new RegExp(r'..-..-..'); //日期正則
var division_reg = new RegExp(r'(<td align="center">)(.+)(</td>)'); //發布機關正則

void getNews() async {
    var data = await requestData(selectDivision);

    setState(() {
      response = data;
    });
    
    if (response.statusCode == 200) {
      title.clear();
      href.clear();
      date.clear();
      division.clear();

      var document = parse(response.body);
      
      //標題和連結
      var titleElement = document.querySelectorAll('.main-c9-index');
      for (final word in titleElement) {
        title.add(word.attributes['title']);
        href.add('https://www.coa.gov.tw/' + word.attributes['href']);
      }
      
      //日期和發布機關
      var tableElement = document.querySelectorAll('.table > tbody > tr');

      for (final word in tableElement) {
        Iterable date_allMatches = date_reg.allMatches(word.innerHtml); //抓取符合日期正則的資料
        Iterable division_allMatches = division_reg.allMatches(word.innerHtml); //抓取符合發布機關正則的資料
        
        //日期
        date_allMatches.forEach((match) {
          date.add(word.innerHtml.substring(match.start, match.end));
        });
        
        //發布機關
        division_allMatches.forEach((match) {
          division.add(match.group(2));
        });
        
      }
      setState(() {
          title = title;
          href = href;
          date = date;
          division = division;
        });
    }
  }

初始化呼叫 getNews()

@override
  void initState() {
    getNews();
    super.initState();
  }

新聞列表

關於listview.separated 可以查看官方文檔:https://api.flutter.dev/flutter/widgets/ListView/ListView.separated.html

Widget newsList(){
    return ListView.separated(
      itemCount: title.length,
      itemBuilder: (context, index) {
        return new ListTile(
          leading: new Icon(Icons.new_releases),
          title: new Text("${title[index]}"),
          subtitle: new Text("${device[index]} ${day[index]}"),
          contentPadding: EdgeInsets.symmetric(horizontal: 20.0),
          enabled: true,
          onTap: () => {
            //頁面傳值
            Navigator.of(context) 
                .push(new MaterialPageRoute(builder: (_) {
                  return new newsContent(
                    title: "${title[index]}",
                    url: "${href[index]}",
                    day: "${date[index]}",
                    division: "${division[index]}",
                  );
            })),
          },
        );
      }, separatorBuilder: (BuildContext context, int index) {
          return Divider();
        },
    );
  }

選取發布機關

既然要能夠選取,那就必須要建立一個下拉選單。

建立 ListItem 物件

class ListItem {
  String id;
  String name;
  ListItem(this.id, this.name);
}

使用 ListItem物件 創建 _dropdownItems ItemList 將所有機關及代號存入

List<ListItem> _dropdownItems = [
    ListItem('*', "所有機關"),
    ListItem('coa', "農委會"),
    ListItem('afa', "農糧署"),
    ListItem('boaf', "農業金融局"),
    ListItem('forest', "林務局"),
    ListItem('swcb', "水土保持局"),
    ListItem('irrigation2', "農田水利署"),
    ListItem('tari', "農業試驗所"),
    ListItem('tfri', "林業試驗所"),
    ListItem('tactri', "農業藥物毒物試驗所"),
    ListItem('tydares', "桃園區農業改良場"),
    ListItem('mdares', "苗栗區農業改良場"),
    ListItem('tdares', "臺中區農業改良場"),
    ListItem('tndais', "臺南區農業改良場"),
    ListItem('kdais', "高雄區農業改良場"),
    ListItem('hdais', "花蓮區農業改良場"),
    ListItem('ttdares', "臺東區農業改良場"),
    ListItem('teais', "茶葉改良場"),
    ListItem('tss', "種苗改良繁殖場"),
    ListItem('pabp', "屏東農業生物技術園區"),
  ];

然後創建 DropdownMenuItem List 的 buildDropDownMenuItems 函數

 List<DropdownMenuItem<ListItem>> _dropdownMenuItems;
 ListItem _selectedItem;

 List<DropdownMenuItem<ListItem>> buildDropDownMenuItems(List listItems) {
   List<DropdownMenuItem<ListItem>> items = List();
   
    for (ListItem listItem in listItems) {
      items.add(
        DropdownMenuItem(
          child: SizedBox(
              width: 200,
              child: Text(
                listItem.name,
                textAlign: TextAlign.center,
              ),
            ),
          value: listItem,
          
        ),
      );
    }
   
    return items;
 }

初始化宣告 _dropdownMenuItems_selectedItem

  • _dropdownMenuItems:使用 buildDropDownMenuItems 函數建立下拉列表的所有選項。
  • _selectedItem:當使用者使用 DropdownButton 改變選擇的值時,會將改變的值傳給 _selectedItem,預設為第一個選選項的value。
@override
  void initState() {
    getNews();
    _dropdownMenuItems = buildDropDownMenuItems(_dropdownItems);
    _selectedItem = _dropdownMenuItems[0].value;
    super.initState();
  }

建立 DropdownButton

 DropdownButton<ListItem>(
    style: TextStyle(fontSize: 17),
    value: _selectedItem,
    items: _dropdownMenuItems,
    onChanged: (value) {
        setState((){
            _selectedItem = value;
            selectDivision = value.id;
          	getNews();
        });
    }
 ),

大功告成!

4e52d54f6bc42abb41d26eb5b0df6517?s=250&d=wavatar&r=g Flutter HTTP POST請求提交表單資料
0 0 評分數
Article Rating
訂閱
通知
guest
0 Comments
在線反饋
查看所有評論