I'm trying to implement a series of http requests in flutter that needs to keep the session active. Right now, I've implemented those requests through native code for both Android and iOS.
In iOS I use URLSession:
class Session {
private var session: URLSession = URLSession.shared
init() {
self.session.configuration.httpCookieAcceptPolicy = .always
}
func requestToken(result: @escaping (String)->()) {
// Makes a GET request and obtain a token
}
func get(url: String, result: @escaping (String)->()) {
let task = session.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
result("")
return
}
guard (200 ... 299) ~= response.statusCode else {
return
}
result(String(data: data, encoding: .utf8) ?? "")
})
task.resume()
}
func post(url: String, data: [String:String], result: @escaping (String)->()) {
requestToken { token in
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
var newData = data
newData[token] = "1"
request.httpBody = newData.percentEncoded()
let task = self.session.dataTask(with: request) { (data, response, error) in
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else {
result("")
return
}
guard (200 ... 299) ~= response.statusCode else {
result("")
return
}
result(String(data: data, encoding: .utf8) ?? "")
}
task.resume()
}
}
}
In Android I use Volley, with a modified StringRequest:
class Session(private val context: Context) {
private var session: RequestQueue = Volley.newRequestQueue(this.context)
init {
session = Volley.newRequestQueue(context)
val cookieManager = CookieManager()
CookieHandler.setDefault(cookieManager)
}
private fun requestToken(result: (String)->Unit) {
// Makes a GET request and obtain a token
}
fun get(url: String, result: (String)->Unit) {
val request = StringRequest(
Request.Method.GET,
url,
HashMap(),
{ result(it) },
{ result("") },
context
)
session.add(request)
}
fun post(url: String, data: HashMap<String, String>, result: (String)->Unit) {
requestToken { token ->
Log.d("OK TOKEN", token)
val newData = HashMap(data)
newData[token] = "1"
val request = StringRequest(
Request.Method.POST,
url,
newData,
{ result(it) },
{ result("") },
context
)
request.retryPolicy = DefaultRetryPolicy(
DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 10,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
request.setShouldCache(false)
session.add(request)
}
}
}
This is the StringRequest class in Android:
public class StringRequest extends com.android.volley.toolbox.StringRequest {
private final Map<String, String> _params;
private Context context;
public StringRequest(int method, String url, Map<String, String> params, Response.Listener<String> listener, Response.ErrorListener errorListener, Context context) {
super(method, url, listener, errorListener);
this.context = context;
_params = params;
}
@Override
protected Map<String, String> getParams() {
return _params;
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
checkSessionCookie(response.headers);
return super.parseNetworkResponse(response);
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
if (headers == null
|| headers.equals(Collections.emptyMap())) {
headers = new HashMap<String, String>();
}
addSessionCookie(headers);
return headers;
}
private static final String SET_COOKIE_KEY = "Set-Cookie";
private static final String COOKIE_KEY = "Cookie";
private static final String SESSION_COOKIE = "sessionid";
public final void checkSessionCookie(Map<String, String> headers) {
if (headers.containsKey(SET_COOKIE_KEY)
&& headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) {
String cookie = headers.get(SET_COOKIE_KEY);
if (cookie.length() > 0) {
String[] splitCookie = cookie.split(";");
String[] splitSessionId = splitCookie[0].split("=");
cookie = splitSessionId[1];
SharedPreferences preferences = context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor prefEditor = preferences.edit();
prefEditor.putString(SESSION_COOKIE, cookie);
prefEditor.apply();
}
}
}
public final void addSessionCookie(Map<String, String> headers) {
SharedPreferences preferences = context.getSharedPreferences("my_prefs",Context.MODE_PRIVATE);
String sessionId = preferences.getString(SESSION_COOKIE, "");
if (sessionId.length() > 0) {
StringBuilder builder = new StringBuilder();
builder.append(SESSION_COOKIE);
builder.append("=");
builder.append(sessionId);
if (headers.containsKey(COOKIE_KEY)) {
builder.append("; ");
builder.append(headers.get(COOKIE_KEY));
}
headers.put(COOKIE_KEY, builder.toString());
}
}
}
Finally, this is the code in Flutter I use to make get and post requests:
class Session {
static const platform = const MethodChannel('com.myapp/session');
Future<String?> get(String? url) async {
try {
return await platform.invokeMethod('get', {"url": url});
} on PlatformException catch (e) {
print(e.message);
return "";
}
}
Future<String?> post(String url, Map<String, String?> data) async {
try {
String? response = await platform.invokeMethod('post', {"url": url, "data": jsonEncode(data)});
return response;
} on PlatformException catch (e) {
print(e.message);
return "";
}
}
}
I would like to bring everything on the Flutter project without the need of a MethodChannel to run native code. I've tried with dio, requests and also with the code from this answer.
However, in none of the cases have I been able to replicate the behavior I get with native code.
This is the Dart code I used, no plugins. I don't get the same results as the native code. It is as if it does not keep the session as in the case of URLSession or Volley.
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);
}
}
}
I've also changed the last line, keeping all cookies:
headers['cookie'] = rawCookie;