0

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;

ecorradini
  • 55
  • 1
  • 8
  • Does this help? https://stackoverflow.com/questions/50299253/flutter-http-maintain-php-session/50299669#50299669 – Richard Heap Dec 23 '21 at 14:04
  • And/or this: https://stackoverflow.com/questions/52241089/how-do-i-make-an-http-request-using-cookies-on-flutter – Richard Heap Dec 23 '21 at 14:14
  • Yes, it's your answer I linked in the question. But I do not get the same result as the native code. – ecorradini Dec 23 '21 at 14:27
  • Ok, you don't show your Dart code, nor what the difference in result is. If using http have you checked the differences using a packet capture? Have you tested using Postman? – Richard Heap Dec 23 '21 at 14:38
  • The dart code is the last one, it calls the native code. Yes I’ve tried with Postman. I need to replicate the native code in dart. However, everything I tried didn’t work. – ecorradini Dec 23 '21 at 14:50
  • The difference is that with just dart code, two or more successive requests don't work, as I need to keep the session active, as if it were a browser. The only way it works right now is with the use of the native code I wrote. – ecorradini Dec 23 '21 at 14:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240387/discussion-between-richard-heap-and-ecorradini). – Richard Heap Dec 23 '21 at 15:01

0 Answers0