如何在Flutter中使用表单数据进行HTTP POST请求?

28

我正试图进行一个HTTP POST请求,需要将请求体指定为form-data,因为服务器不会将请求作为原始数据接收。

这是我正在做的:

import 'dart:convert';

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';
    var requestBody = {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'example@mail.com.us',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    };

    http.Response response = await http.post(
        uri,
        body: json.encode(requestBody),
    );

    print(response.body);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Container(
        child: Center(
          child: RaisedButton(
            child: Text('Press Here'),
            onPressed: (){
              postTest();
            },
          ),
        ),
      ),
    );
  }
}

这是实际的回复:

{
    "error": "unsupported_grant_type",
    "error_description": "grant type not supported"
}

实际响应

{
    "access_token": "00D0b000000Bb08!AR8AQO.s8mAGXCbwV77FXNLQqc2vtl8g6_16miVbgWlQMsuNHsaf2IGLUwnMVXBOfAj19iznhqhwlPOi4tagvf7FFgiJJgoi",
    "instance_url": "https://na57.salesforce.com",
    "id": "https://login.salesforce.com/id/00D0b000000Bb08EAC/0050b000005nstiAAA",
    "token_type": "Bearer",
    "issued_at": "1567993324968",
    "signature": "1+Zd/dSh9i7Moh2U0nFJLdXkVHqPlPVU6emwdYzXDPk="
}

预期的响应

你可以在Postman中测试,在原始表单数据之间切换请求体,前者获得实际响应,后者获得预期响应。

PS:这些头部信息是客户端工具创建的临时头部信息。


你找到了相同的解决方案吗?请分享。 - Araju
请查看此链接 https://dev59.com/CFIG5IYBdhLWcg3w-W-R#67559979 - Malith Kuruwita
这个有什么解决方案吗?@MalithKuruwita提供的链接也没有被接受的答案。 - Eight Rice
@EightRice 如果你想要向接受 content-type: x-www-form-urlencoded 的服务器发送数据,那么我给出的上述 URL 是可行的。而且它适用于文本数据。但是如果你想要发送 multipart/form-data,例如文件或图像这样的二进制数据到服务器,那么请使用这个(https://www.developerlibs.com/2020/07/flutter-upload-multipart-images-server.html)。该博客展示了如何发送文本数据。请根据服务器接受的 content-type 使用相关的方法。 - Malith Kuruwita
12个回答

34
请使用Map而不是http包中的body,因为http包中的body只有三种类型:String、List或Map。尝试这样做:
final uri = 'https://na57.salesforce.com/services/oauth2/token';
var map = new Map<String, dynamic>();
map['grant_type'] = 'password';
map['client_id'] = '3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL';
map['client_secret'] = '42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42';
map['username'] = 'example@mail.com.us';
map['password'] = 'ABC1234563Af88jesKxPLVirJRW8wXvj3D';

http.Response response = await http.post(
    uri,
    body: map,
);

如果请求体是一个Map对象,它将使用指定的编码方式编码为表单字段。请求的Content-Type将被设置为"application/x-www-form-urlencoded"。 https://pub.dev/documentation/http/latest/http/Client/post.html - kaya
使用 Map 似乎比官方 Flutter 文档中使用的 jsonEncode 方法更自然地像一个 HTML 表单提交。谢谢。这解决了我的问题。 - Eaweb
1
地图对我不起作用。我必须使用Dio FormData,但我想知道为什么发送地图会收到400错误? - mohammad
1
@mohammad 你发了什么? - hoangquyy
错误状态:无法使用内容类型为“application/json”的请求设置正文字段。 - Akbar Pulatov

13

使用MultipartRequest类

multipart/form-data请求会自动将Content-Type头设置为multipart/form-data。

这个值会覆盖用户设置的任何值。

参考pub.dev文档这里

例如:

 Map<String, String> requestBody = <String,String>{
     'field1':value1
  };
 Map<String, String> headers= <String,String>{
     'Authorization':'Basic ${base64Encode(utf8.encode('user:password'))}'
  };

  var uri = Uri.parse('http://localhost.com');
  var request = http.MultipartRequest('POST', uri)
    ..headers.addAll(headers) //if u have headers, basic auth, token bearer... Else remove line
    ..fields.addAll(requestBody);
  var response = await request.send();
  final respStr = await response.stream.bytesToString();
  return jsonDecode(respStr);
希望这有所帮助。

1
如果有人像我一样想知道,你可以使用await http.Response.fromStream(streamedResponse)从streamedresponse获取通常的响应对象。 - lordvcs

12

有一个 Dart 包叫作 dio
它工作得非常好,我将其用作执行 HTTP 请求的标准。
请阅读文档 ,了解如何使用 dio 包发送表单数据。

import 'package:dio/dio.dart';    

postData(Map<String, dynamic> body)async{    
var dio = Dio();
try {
      FormData formData = new FormData.fromMap(body);
      var response = await dio.post(url, data: formData);
      return response.data;
    } catch (e) {
      print(e);
    }
}

当Map中没有参数时,您从哪里获取URL? - Mukta
1
这是一段代码片段。希望能够得到所需的答案。 - kishea

4

所以,你想将正文作为表单数据发送对吗?也许你可以试试这个方法?对我来说它起作用了。

postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';
    var requestBody = {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'example@mail.com.us',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    };

    http.Response response = await http.post(
        uri,
        body: requestBody,
    );

    print(response.body);
  }

或者

postTest() async {
    final uri = 'https://na57.salesforce.com/services/oauth2/token';

    http.Response response = await http.post(
        uri, body: {
      'grant_type':'password',
      'client_id':'3MVG9dZJodJWITSviqdj3EnW.LrZ81MbuGBqgIxxxdD6u7Mru2NOEs8bHFoFyNw_nVKPhlF2EzDbNYI0rphQL',
      'client_secret':'42E131F37E4E05313646E1ED1D3788D76192EBECA7486D15BDDB8408B9726B42',
      'username':'example@mail.com.us',
      'password':'ABC1234563Af88jesKxPLVirJRW8wXvj3D'
    });

    print(response.body);
  }

3

编辑1(这对于代码登录流程有效):

String url = "https://login.salesforce.com/services/oauth2/token";
http.post(url, body: {
  "grant_type": "authorization_code",
  "client_id": "some_client_id",
  "redirect_uri": "some_redirect_uri",
  "code": "some_code_generated_by_salesforce_login",
  "client_secret": "some_client_secret",
}).then((response) {
  //--handle response
});

试试使用 'FormData',从以下地址获取:

import 'package:dio/dio.dart';

FormData formData = new FormData.fromMap(dataMap);

retrofitClient.getToken(formData).then((response){//--handle respnse--});

"retrofitClient"来自于包 "retrofit: ^1.0.1+1"。

无效的请求体“FormData的实例”。 - evals

2
这是一个使用表单数据函数的示例:

 Future<ResponseModel> postWithFormData(String url, List<File> files,
      {Map<String, String> body = const {}, bool throwAlert = false}) async {
    var request = http.MultipartRequest("POST", Uri.parse(localApiHost + url));

    request.headers
        .addAll({"Authorization": "Bearer ${Storage.getString(token)}"});
    request.fields.addAll(body);
    for (var file in files) {
      request.files.add(await http.MultipartFile.fromPath("files", file.path));
    }
    var sendRequest = await request.send();
    var response = await http.Response.fromStream(sendRequest);
    final responseData = json.decode(response.body);
    if (response.statusCode >= 400 && throwAlert) {
      showErrorDialog(responseData["message"]);
    }
    return ResponseModel(body: responseData, statusCode: response.statusCode);
  }

2

你可以尝试这个:

    String url = 'https://myendpoint.com';
      Map<String, String> headers = {
"Content-Type": "application/x-www-form-urlencoded"    
"Content-type": "application/json"};
      String json = '{"grant_type":"password",
        "username":"myuser@mail.com",
        "password":"123456"}';
      // make POST request
      Response response = await post(url, headers: headers, body: json);
      // check the status code for the result
      int statusCode = response.statusCode;
      // this API passes back the id of the new item added to the body
      String body = response.body;

1
使用或不使用头文件都没有关系,我的重点是如何将请求体发送为表单数据。 - M. Massula
我已经更新了我的问题,现在你可以测试更接近我的真实情况的东西。 - M. Massula
@M.Massula 我认为在Flutter中,没有使用表单数据请求的选项。唯一可能的方式是使用JSON传递数据。 - Hitesh Tarbundiya
这里在标题字符串之间缺少逗号。 - markhorrocks

1
这段代码片段成功执行了一个需要授权令牌和表单数据的POST api调用。
final headers = {'Authorization': 'Bearer $authToken'};
  var requestBody = {
    'shopId': '5',
    'fromDate': '01/01/2021',
    'toDate': '01/10/2022',
  };

  final response = await http.post(
    Uri.parse(
        'https://api.sample.com/mobile/dashboard/getdetails'),
    headers: headers,
    body: requestBody,
  );

  print("RESPONSE ${response.body}");

1

HTTP目前还不支持form-data

使用DIO代替。它会自行处理一切!


抱歉,但您是错误的。HTTP确实支持表单数据。 - Akif Kara

0

你也可以使用MultiPartRequest,它一定会起作用

  var request = new 
  http.MultipartRequest("POST",Uri.parse("$baseUrl/example/"));

  request.headers.addAll(baseHeader);
  request.fields['id'] = params.id.toString();
  request.fields['regionId'] = params.regionId.toString();
  request.fields['districtId'] = params.districtId.toString(); 
  http.Response response = await http.Response.fromStream(await 
  request.send());

  print('Uploaded! ${response.body} ++ ${response.statusCode}');
 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接