Flutter http Maintain PHP session - flutter

I'm new to flutter. Basically I'm using code Igniter framework for my web application. I created REST API for my web app, after user login using API all the methods check for the session_id if it exists then it proceeds, and if it doesn't then it gives
{ ['status'] = false, ['message'] = 'unauthorized access' }
I'm creating app with flutter, when i use the http method of flutter it changes session on each request. I mean, it doesn't maintain the session. I think it destroys and creates new connection each time. Here is thr class method which i use for api calls get and post request.
class ApiCall {
static Map data;
static List keys;
static Future<Map> getData(url) async {
http.Response response = await http.get(url);
Map body = JSON.decode(response.body);
data = body;
return body;
}
static Future postData(url, data) async {
Map result;
http.Response response = await http.post(url, body: data).then((response) {
result = JSON.decode(response.body);
}).catchError((error) => print(error.toString()));
data = result;
keys = result.keys.toList();
return result;
}
I want to make API request and then store session_id,
And is it possible to maintain session on the server so i can manage authentication on the web app it self.?

HTTP is a stateless protocol, so servers need some way to identify clients on the second, third and subsequent requests they make to the server. In your case you might authenticate using the first request, so you want the server to remember you on subsequent requests, so that it knows you are already authenticated. A common way to do this is with cookies.
Igniter sends a cookie with the session id. You need to gather this from each response and send it back in the next request. (Servers sometimes change the session id (to reduce things like clickjacking that we don't need to consider yet), so you need to keep extracting the cookie from every response.)
The cookie arrives as an HTTP response header called set-cookie (there may be more than one, though hopefully not for simplicity). To send the cookie back you add a HTTP request header to your subsequent requests called cookie, copying across some of the information you extracted from the set-cookie header.
Hopefully, Igniter only sends one set-cookie header, but for debugging purposes you may find it useful to print them all by using response.headers.forEach((a, b) => print('$a: $b'));. You should find Set-Cookie: somename=abcdef; optional stuff. We need to extract the string up to, but excluding the ;, i.e. somename=abcdef
On the next, and subsequent requests, add a request header to your next request of {'Cookie': 'somename=abcdef'}, by changing your post command to:
http.post(url, body: data, headers:{'Cookie': cookie})
Incidentally, I think you have a mismatch of awaits and thens in your code above. Generally, you don't want statics in classes, if they should be top level functions instead. Instead you could create a cookie aware class like:
class Session {
Map<String, String> headers = {};
Future<Map> get(String url) async {
http.Response response = await http.get(url, headers: headers);
updateCookie(response);
return json.decode(response.body);
}
Future<Map> post(String url, dynamic data) async {
http.Response response = await http.post(url, body: data, headers: headers);
updateCookie(response);
return json.decode(response.body);
}
void updateCookie(http.Response response) {
String rawCookie = response.headers['set-cookie'];
if (rawCookie != null) {
int index = rawCookie.indexOf(';');
headers['cookie'] =
(index == -1) ? rawCookie : rawCookie.substring(0, index);
}
}
}

Related

Create http request from cloud functions with header and response

I'm currently developing a flutter app that sends some http requests to an external service, but actually, I keep some API keys in the app, and I want to secure them using cloud functions. How can I create a function that executes something like this? This is a Stripe request that I actually make from the app.
Future<Customer?> createCustomer(String userId) async {
final String url = 'https://api.stripe.com/v1/customers';
Map<String,String> headers = {
'Authorization': 'Bearer <API_KEY_HERE>',
'Content-Type': 'application/x-www-form-urlencoded'
};
var response = await _client.post(
Uri.parse(url),
headers: headers,
body: {'name': "test", "metadata[userId]": userId},
);
final decodedResult = json.decode(response.body);
log.info(decodedResult);
if (response.statusCode == 200) {
try {
final customer = Customer.fromMap(decodedResult);
currentCustomer = customer;
return customer;
} catch (e) {
log.error(e.toString());
return null;
}
} else {
return null;
}
}
You cloud very well, from a Cloud Function, call the Stripe REST API with, for example, the axios library.
But since we use the Admin SDK for Node.js to write Cloud Functions for Firebase, I would suggest to use the Stripe Node.js API (This link points to the customers.create method).
You can do that from any type of Cloud Function, e.g. a Callable one that you call from your app or a background triggered one, e.g. when a new customer doc is created in a Firestore collection.
The following search on SO will return many code examples: https://stackoverflow.com/search?q=Firebase+Cloud+Functions+stripe

flutter http request/response details including headers?

while http request details can easily be inspected in browser dev tools(for web app), I tried to explore, where I can find the same for requests sent in flutter App, but couldn't locate it.
like for example - I can see the actual response from api by print(response), but I am talking about complete request response including headers.
I am using VScode IDE for Flutter.
update:
I want to view the headers sent like response.header. reason for the same is like I am using flutter cahe manager and the issue I am facing is like I have set the -cache control max-age=1.
so the flutter should try to fetch the same every time I access the page, which it is doing, but it is serving the page from the cache and then fetching the request. so if there is any change is on server side, it doesn't reflect when first open the page, but shows the change on every second visit.
so what I want is like if the flutter gets 304 response from server, it will serve from the cache else it should serve from the fetched data. but it is not happening.
also the response.header is not showing response code like it is 200 or 304, so that flutter can fetch or serve from cache.
Actual code being used is like this:
Future<MyUserProfile> fetchMyUserProfile() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final userid = prefs.getString('user_id');
var userProfile = await DefaultCacheManager().getSingleFile("url");
final response = await userProfile.readAsString();
if (response != '') {
print(userProfile);
// If the server did return a 200 OK response,
// then parse the JSON.
return MyUserProfile.fromJson(json.decode(response));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load Profile');
}
}
Don't wanna be rude, but the information you are looking for is quite easy to find... That is if you look in the right place, like official documentation.
https://api.flutter.dev/flutter/dart-io/HttpResponse-class.html
HttpResponse class ... headers → HttpHeaders Returns the response
headers. [...] read-only
http.Response response = await http.get(url...
print(response.headers);
EDIT: Answering the sub-question that was added to the original question.
To check what the status code is you simply access it via response.statusCode
Example:
http.Response response = await http.get(url...
if (response.statusCode == 200) {
// do something
} else if (response.statusCode == 304) {
// do something else
} else {
// handle this
}

How do I use Futures in Futures in Flutter?

Soo, the title might be a bit confusing but let me clear that up right now.
I have a class called UserController which has a method called updateUserData. It gets a Map<String, dynamic> and updates the given attributes of a user with whatever is given in the value of the map.
What I wanted to do is: Send a patch request to the server, wait for the server to return a changed user object, write that to some local variable and return either the value or the error to whoever called that method (which in my case is a GUI class).
The current method as it is:
Future<User> updateUserData(Map<String, dynamic> changes) async {
return await http.patch(
"url",
headers: {HttpHeaders.authorizationHeader: "token"},
body: changesMap
).then((newUserObject) => {
currentUser = newUserObject;
//return new user object for display
}); //error from server gets forwarded to GUI.
}
Sadly this doesn't work at all. Seems like Flutter/dart doesn't know what to return there (it gives me a return_of_invalid_type_from_closure error).
I hope it's clear what my goal was. I want to use a "then" clause in this method but then still return a future which either contains the user I get from the server or the error I get.
How do I do that? I looked up so many Future tutorials so far and none used something similar.
You never need to use async/await with then. In your case, the simplest thing to do is await the response of your request, and then, put it to your local variable.
Then you just need to return the value.
Future<User> updateUserData(Map<String, dynamic> changes) async {
final response = await http.patch(
"url",
headers: {HttpHeaders.authorizationHeader: "token"},
body: changesMap,
);
// You need to parse the response to get your User object.
final responseJson = json.decode(response.body);
newUserObject = User.fromJson(responseJson);
currentUser = newUserObject;
return newUserObject;
}
If you need to decode your Map to a Class, you can see the answer given here

Dart HttpClient not reading full response

I have a weird issue using the dart HttpClient where it seems like it's not reading the full response before "finishing". This is the code I have:
HttpClient client = new HttpClient();
client.connectionTimeout = Duration(seconds: 60);
Completer<MealPlan> completer = new Completer();
RecipeSearchFilter filter = new RecipeSearchFilter();
filter.restrictions = intolerances.map((intolerance) => intolerance.name).toList();
MealPlan mealPlan;
client
.getUrl(Uri.parse(
"https://myUrl.com/api/meal-plans?filter=${jsonEncode(filter)}"))
.then((HttpClientRequest request) {
request.headers.set("Authorization", "Bearer $idToken");
return request.close();
}).then((HttpClientResponse response) {
response.transform(utf8.decoder).listen((contents) {
Map<String, dynamic> contentMap = jsonDecode(contents);
mealPlan = MealPlan.fromJson(contentMap);
}, onDone: () => completer.complete(mealPlan));
});
return completer.future;
This is the most intensive function that my app contains, so this particular API itself typically takes 6-8 seconds to fully complete since there's a lot going on behind the scenes. The response isn't big (~60KB). Making the exact same call using Postman I can see that it does indeed return everything as expected, but if I look at the response inside of the }).then((HttpClientResponse response) { block, it only contains a very small, partial bit of the response. I'm not sure what I'm doing wrong here, but I'm guessing that I've configured the HttpClient incorrectly.
The problem is that your response will be delivered by the stream in pieces. After passing those chunks through the utf8 decoder, you should then form them into a single string before trying to json decode it. Currently you try to json decode the first chunk without waiting for the rest.
It's much easier to use the package:http which is a wrapper around HttpClient, and does a lot of the grunt work for you. Also, it's more readable to use the async/await syntax rather than .then.
Using an async method you could write, for example:
void getPlan(Map filter, String idToken) async {
var response = await http.get(
Uri.parse('https://myUrl.com/api/meal-plans?filter=${jsonEncode(filter)}'),
headers: <String, String>{
'Authorization': 'Bearer $idToken',
},
);
return MealPlan.fromJson(json.decode(response.body));
}
If you really want to control the connection timeout, you need to pass in your own HttpClient, as follows:
var client =
http.IOClient(HttpClient()..connectionTimeout = Duration(seconds: 60));
var response = await client.get(
//etc

http.post is being fired twice

I am a bit new to Flutter, and I am building a screen that posts data to an API built in PHP mon my hosting server. The API is built by me which receives a JSON object and then saves the data.
The app is working fine, and API is receiving the data, but the http.post seems is firing twice ( calling the API twice)
Which makes my API saves the record twice. there is no possible way for me to check before adding the send record. as My API simply saves a new record so whenever it receives a call it simply saves it and returns back a value for the mobile App ( built in Flutter).
If I use a condition to check, this way the first call will return correct data to the mobile app, but the second one will return an error for the mobile app since the record already exists.
I have read about the Access-Control-Allow-Origin and how it might be the issue and put it my my .httaccess file
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
but no luck.
any idea.
Note I am using a shared hosting.
Code I use in Flutter:
class _PostADToServerState extends State<PostADToServer> {
Future<List<JSBResponse>> _postRequest() async {
// print('Call API function is called');
Map<String, dynamic> myAd = {
"isbn10": widget.title.isbn10,
"isbn13": widget.title.isbn13,
... [Some fields]
"titleCondition": widget.title.titleCondition,
"priceIs": widget.title.priceIs,
"school_name": widget.title.schoolName
};
String json = jsonEncode(myAd);
var url = 'https://www.example.com/xapis/save_new_ad.php';
var body = json;
var data = await http.post(url,
headers: {
"Content-Type": "application/json",
"accept": "application/json",
"Access-Control-Allow-Origin": "*",
},
body: body);
var jsonData = jsonDecode(data.body);
Code in my PHP API starts with the following:
$data = file_get_contents('php://input');
$theTitle = json_decode($data);
Then I use the content I find in the $theTitle object as the following:
$title = $theTitle->title;