Mathieu
Mathieu

Reputation: 1703

How to post x-www-form-urlencoded in Flutter

I am trying to send a POST request to an API to create an account.
The request is working well, it should look like this :

Bulk Edit Mode :
Bulk Edit mode

Key-Value Edit mode :
Key-Value Edit mode

There are also 9 headers that are auto-generated, so I did not show them, but I can take another screen if you need to.

My request looks like this :

import 'dart:convert' as convert ;

import 'package:my_project/requests/utils.dart';
import 'package:http/http.dart' as http;


Future<String>      createUser(String firstName, String name, String mail,
    String password, String confirmPassword, String birthDate,
    String phone) async {
  String              url = BASE_URL + "createUser" ; // Don't worry about BASE_URL, the final url is correct

  Map<String, dynamic>    formMap = {
    "name": name,
    "surname": firstName,
    "mail": mail,
    "password": password,
    "birth": birthDate,
    "phone": phone,
    "confirmPassword": confirmPassword
  } ;

  http.Response    response = await http.post(
    url,
    body: convert.jsonEncode(formMap),
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    encoding: convert.Encoding.getByName("utf-8"),
  );
  print("RESPONSE ${response.statusCode} ; BODY = ${response.body}");

  return (response.body) ;

}

Here is my print result :

I/flutter ( 6942): RESPONSE 307 ; BODY =  

As you can see, I am getting a 307 error, and the problem does not come from the server, as it worked with Postman.

Am I sending this form-urlencoded POST request correctly ?

I also tried :

http.Response    response = await http.post(
    url,
    body: "name=$name&surname=$firstName&mail=$mail&password=$password&birth=$birthDate&phone=$phone&confirmPassword=$confirmPassword",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    encoding: convert.Encoding.getByName("utf-8"),
  );

but with the same results. I tried too :

http.Response    response = await http.post(
    url,
    body: formMap,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    encoding: convert.Encoding.getByName("utf-8"),
  );

with same result again.
What am I doing wrong ?

EDIT :

I tried FoggyDay answer, here is my request now :

final client = HttpClient() ;
final request = await client.postUrl(Uri.parse(url));
request.headers.set(HttpHeaders.contentTypeHeader, "application/x-www_form-urlencoded");
request.followRedirects = true ;
request.write(formMap);
final response = await request.close();
print("STATUS CODE = ${response.statusCode}");

However I still have a 307 error. Did I create the right request ?

EDIT 2 :

As asked, I printed location as follow :

final client = HttpClient() ;
final request = await client.postUrl(Uri.parse(url));
request.headers.set(HttpHeaders.contentTypeHeader, "application/x-www_form-urlencoded");
request.followRedirects = true ;
request.write(formMap);
final response = await request.close();
print("STATUS CODE = ${response.statusCode}");
print("Response headers = ${response.headers}");

And I get :

I/flutter ( 7671): STATUS CODE = 307
I/flutter ( 7671): Response headers = location: /app/createUser/
I/flutter ( 7671): date: Tue, 26 May 2020 09:00:29 GMT
I/flutter ( 7671): content-length: 0
I/flutter ( 7671): server: Apache/2.4.41 (Amazon) OpenSSL/1.0.2k-fips

The thing is I am already making a call on /app/createUser... ('/app/' is in BASE_URL)

Upvotes: 20

Views: 33386

Answers (6)

marcos E.
marcos E.

Reputation: 475

I had the same error and it was the Request parsing the content-type header. When setting the request body using request.bodyFields it sets the content-type and information correctly.

//Setup default values
String defaultBaseUrl = FlavorConfig.instance.values.fileRepositoryUrlLogin;
String tenantId = FlavorConfig.instance.values.fileRepositoryTenantId;
baseUrl ??= defaultBaseUrl;
//TODO this failed for me
headers ??= {
  //'content-type': 'application/x-www-form-urlencoded;'
};
String url = "$baseUrl/$tenantId/oauth2/v2.0/token";
debugPrint("OneDrive url ==> $url");

String clientId = FlavorConfig.instance.values.fileRepositoryClientId;
String clientSecret = FlavorConfig.instance.values.fileRepositoryClientSecret;
String scope = "https://graph.microsoft.com/.default";
String grantType = "client_credentials";
try {
  var request = http.Request('POST', Uri.parse(url));
  request.headers.clear();
  request.headers.addAll(headers);

  //This configures internally the header Content-type ==> "application/x-www-form-urlencoded".
  // TODO when I do it manually it crashes parsing the MediaType.
  request.bodyFields = {
    "client_id": clientId,
    "client_secret": clientSecret,
    "scope": scope,
    "grant_type": grantType,
  };
  request.encoding = Encoding.getByName('utf-8')!;

  final response = await request.send();
  if(response.statusCode == 200) {
    debugPrint("Success");
    return utf8.decoder.convert(await response.stream.single);
  } else {
    debugPrint("Failure");
    throw RuntimeException("Failed");
  }
} catch (e, s) {
  debugPrintStack(stackTrace: s);
}

Upvotes: 0

kevintannguyen
kevintannguyen

Reputation: 67

let try with this code, it works well for me.

var headers = {
  'Content-Type': 'application/x-www-form-urlencoded'
};
var request = http.Request('POST', Uri.parse('https://oauth2.googleapis.com/token'));
request.bodyFields = {
  'client_id': '',
  'client_secret': '',
  'code': '',
  'grant_type': 'authorization_code',
  'redirect_uri': ''
};
request.headers.addAll(headers);

http.StreamedResponse response = await request.send();

if (response.statusCode == 200) {
  print(await response.stream.bytesToString());
}
else {
  print(response.reasonPhrase);
}

Upvotes: 2

Ali80
Ali80

Reputation: 8656

official http package from flutter is buggy with urlencoded type, you can use Dio package instead.

final dio = Dio();
final res = dio.post(
  '/info',
  data: {'id': 5},
  options: Options(contentType: Headers.formUrlEncodedContentType),
);

Upvotes: 8

Parisa Baastani
Parisa Baastani

Reputation: 1911

For x-www-form-urlencoded parameters, just use this:

  Future<String> login(user, pass) async {
   final response = await http.post(
    Uri.parse('https:youraddress.com/api'),
    headers: {
     "Content-Type": "application/x-www-form-urlencoded",
    },
    encoding: Encoding.getByName('utf-8'),
    body: {"title": "title"},
   );

 if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
   // then parse the JSON.
 } else {
    // If the server did not return a 200 OK response,
   // then throw an exception.
 }
}

Upvotes: 21

Malith Kuruwita
Malith Kuruwita

Reputation: 642

If you are using http, you should add the below lines

Android -

android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<application
        android:label="first_app"
        android:usesCleartextTraffic="true" //this line
        android:icon="@mipmap/ic_launcher">

iOS -

ios/Runner/info.plist

<key>NSAppTransportSecurity</key>
<dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
</dict>

Be warned that you will need to have an explanation for Apple's review team when enabling this, otherwise, your app will get rejected on submission.

Uninstall the app and Reinstall again if you have the app already in the emulator when you add those lines to avoid confusions.

If you send HTTP GET request, you can use query parameters as follows:

QueryParameters

http://example.com/path/to/page?name=ferret&color=purple

  • The contents are encoded as query parameters in the URL

application/x-www-form- urlencoded

  • The contents are encoded as query parameters in the body of the request instead of the URL.

  • The data is sent as a long query string. The query string contains name-value pairs separated by & character

POST example

flutter http package version - http: ^0.13.1

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


Future<dynamic> postData() async {
    //Uri.https("192.168.1.30:5000", "/api/data")
    //Uri.parse("your url");
    final Uri uri = Uri.http("192.168.1.30:5000", "/api/data");
    final response = await httpClient.post(
      uri,
      body: {
        "name": "yourName",
        "age": "yourAge"
      },
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      encoding: Encoding.getByName('utf-8'),
    );
    return response.body;
  }

Upvotes: 1

paulsm4
paulsm4

Reputation: 121669

As you can see, I am getting a 307 error, and the problem does not come from the server, as it worked with Postman.

No, that's NOT necessarily the case. Look here:

MDN: 307 Temporary Redirect

In other words, Postman is following the redirect ... and your Flutter app isn't.

SUGGESTION: Try setting followRedirects to true:

https://api.flutter.dev/flutter/dart-io/HttpClientRequest/followRedirects.html


ADDITIONAL INFO:

  • The default value for request.followRedirects happens to be "true" anyway. It doesn't hurt to explicitly set it ... but it explains why the behavior didn't change.

  • Per this post:

The Dart HTTP client won't follow redirects for POSTs unless the response code is 303. It follows 302 redirects for GET or HEAD.

The correct way to handle redirects on POST requests is to manually implement an appropriate strategy for your use case:

  var response = await client.post(...);
  if (response.statusCode == 301 || response.statusCode == 302) {
    // send post request again if appropriate
  }

Upvotes: 1

Related Questions