J'essaie une application simple à lire dans le code HTML d'un site Web et à la transformer pour créer une interface utilisateur facilement lisible sous Android (il s'agit d'un exercice d'apprentissage d'Android et non d'une application utilisable). Le problème que je rencontre est la persistance d'une session utilisateur sur Activités, puis l'utilisation de la session dans une variable HttpClient
une fois rappelée.
Je voudrais faire ceci "correctement", l'approche recommandée semble être d'utiliser CookieManager
. J'ai eu des problèmes avec ceci cependant - je n'arrive pas à trouver le moyen "correct" de prendre un Cookie
dans le CookieManager
et de l'utiliser dans une instanciation ultérieure de HttpClient
dans une activité séparée.
Lorsque vous utilisez un CookieManager
, je peux enregistrer le Cookie
et le Cookie
est alors concerné par d'autres activités (voir l'extrait de code 2). Je n'ai pas trouvé comment l'utiliser plus tard (voir l'extrait de code 3) lorsque vous demandez une page.
Assez parlé, voici du code. D'abord mon action de connexion et le stockage des cookies:
private OnClickListener loginActionListener = new OnClickListener()
{
public void onClick(View v)
{
EditText usernameTextView = (EditText) findViewById(R.id.Username);
EditText passwordTextView = (EditText) findViewById(R.id.Password);
String username = usernameTextView.getText().toString();
String password = passwordTextView.getText().toString();
try {
HttpPost postMethod = new HttpPost(URI);
HttpParams params = new BasicHttpParams();
params.setParameter("mode", "login");
params.setParameter("autologin", true);
params.setParameter("username", username);
params.setParameter("password", password);
postMethod.setParams(params);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(postMethod);
List<Cookie> cookies = httpClient.getCookieStore().getCookies();
if(cookies != null)
{
for(Cookie cookie : cookies)
{
String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();
CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);
}
}
CookieSyncManager.getInstance().sync();
Intent intent = new Intent(v.getContext(), IndexAction.class);
startActivity(intent);
} catch (Exception e) {...}
}
L’activité de démarrage qui décide si l’utilisateur doit se connecter ou se connecter à l’index est indiquée ci-dessous. Vous pouvez voir à partir de ce code que le cookie est dans la portée et peut être lu:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CookieSyncManager.createInstance(this);
if(CookieManager.getInstance().getCookie(URI) == null)
{
Intent intent = new Intent(this, LoginAction.class);
startActivity(intent);
}
else
{
Intent intent = new Intent(this, IndexAction.class);
startActivity(intent);
}
}
Mais à partir de mon code pour lire la page d'index, j'espère que vous pourrez suggérer ce qui me manque:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CookieSyncManager.createInstance(this);
try
{
HttpGet getMethod = new HttpGet(URI_INDEX);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 30000);
HttpConnectionParams.setSoTimeout(params, 30000);
// This code results in a ClassCastException, I'm assuming i've found a red herring with this solution.
// HttpContext localContext = new BasicHttpContext();
// localContext.setAttribute(ClientContext.COOKIE_STORE, CookieManager.getInstance().getCookie(URI));
DefaultHttpClient httpClient = new DefaultHttpClient(params);
HttpResponse response = httpClient.execute(getMethod);
if(response.getStatusLine().getStatusCode() > 299 && response.getStatusLine().getStatusCode() < 400)
{
// Not logged in doesn't give a redirect response. Very annoying.
}
final char[] buffer = new char[0x10000];
StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(response.getEntity().getContent(), "UTF-8");
int read = 0;
while (read>=0)
{
read = in.read(buffer, 0, buffer.length);
if (read>0) {
out.append(buffer, 0, read);
}
}
String returnString = out.toString();
} catch (ClientProtocolException e) {...}
}
La fonction HttpClient
sur execute(getMethod)
n'utilise pas le cookie (vérifié lors du débogage) pour extraire la page. Ce serait formidable si quelqu'un pouvait combler ce manque de connaissances.
Merci d'avance.
MODIFIER
Lorsque le code commenté est ajouté à nouveau (avec la méthode httpClient.execute(getMethod)
modifiée en httpClient.execute(getMethod, localContext)
), cette trace est générée - en supposant que je remplis l'attribut ClientContext.COOKIE_STORE
avec un Cookie
String
plutôt qu'un CookieStore
:
*org.Apache.http.client.protocol.RequestAddCookies.process(RequestAddCookies.Java:88), org.Apache.http.protocol.BasicHttpProcessor.process(BasicHttpProcessor.Java:290), org.Apache.http.protocol.HttpRequestExecutor.preProcess(HttpRequestExecutor.Java:160), org.Apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.Java:401)
org.Apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.Java:555), org.Apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.Java:487),
com.testapp.site.name.IndexAction.onCreate(IndexAction.Java:47),
Android.app.Instrumentation.callActivityOnCreate(Instrumentation.Java:1047),
Android.app.ActivityThread.performLaunchActivity(ActivityThread.Java:1611),
Android.app.ActivityThread.handleLaunchActivity(ActivityThread.Java:1663),
Android.app.ActivityThread.access$1500(ActivityThread.Java:117),
Android.app.ActivityThread$H.handleMessage(ActivityThread.Java:931),
Android.os.Handler.dispatchMessage(Handler.Java:99),
Android.os.Looper.loop(Looper.Java:123),
Android.app.ActivityThread.main(ActivityThread.Java:3683),
Java.lang.reflect.Method.invokeNative(Native Method),
Java.lang.reflect.Method.invoke(Method.Java:507),
com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:839),
com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:597),
dalvik.system.NativeStart.main(Native Method)*
(Comme promis, une solution à cela. Je ne l'aime toujours pas et j'ai l'impression de passer à côté de la méthode "correcte", mais ça marche.)
Vous pouvez utiliser la CookieManager
pour enregistrer vos cookies (et donc les rendre disponibles entre applications) avec le code suivant:
Enregistrer des cookies dans la CookieManager
:
List<Cookie> cookies = httpClient.getCookieStore().getCookies();
if(cookies != null)
{
for(Cookie cookie : cookies)
{
String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();
CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);
}
}
CookieSyncManager.getInstance().sync();
Vérification des cookies sur le domaine spécifié: if(CookieManager.getInstance().getCookie(URI_FOR_DOMAIN)
Pour reconstruire les valeurs pour HttpClient:
DefaultHttpClient httpClient = new DefaultHttpClient(params);
String[] keyValueSets = CookieManager.getInstance().getCookie(URI_FOR_DOMAIN).split(";");
for(String cookie : keyValueSets)
{
String[] keyValue = cookie.split("=");
String key = keyValue[0];
String value = "";
if(keyValue.length>1) value = keyValue[1];
httpClient.getCookieStore().addCookie(new BasicClientCookie(key, value));
}
CookieManager
est utilisé par le client HTTP interne de Java. Cela n'a rien à voir avec Apache HttpClient.
Dans votre code, vous créez toujours pour chaque requête une nouvelle instance de HttpClient
et donc une nouvelle instance CookieStore
, qui est évidemment récupérée avec tous les cookies stockés dès que cette instance HttpClient
est hors de portée.
Vous devriez soit
(1) Réutilisez la même instance de HttpClient
pour toutes les demandes HTTP liées de manière logique et partagez-la entre tous les threads liés de manière logique (méthode recommandée pour utiliser Apache HttpClient).
(2) ou, à tout le moins, partage la même instance de CookieStore
entre des threads liés logiquement
(3) ou, si vous insistez pour utiliser CookieManager
pour stocker tous vos cookies, créez une implémentation CookieStore
personnalisée soutenue par CookieManager
Dans mon serveur d'applications qui veut utiliser la même session avec les mêmes cookies ... Après plusieurs heures de "googler" et de maux de tête douloureux, je viens d'enregistrer les cookies dans SharedPreference ou tout simplement de placer un objet et de configurer DefaultHttpClient avec les mêmes cookies ... onDestroy suffit de supprimer SharedPreferences ... c'est tout:
Regardez l'exemple suivant:
public class NodeServerTask extends AsyncTask<Void, Void, String> {
private DefaultHttpClient client;
protected String doInBackground(Void... params) {
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
urlParameters.add(nameValuePair);
try {
httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
List<Cookie> cookies = loadSharedPreferencesCookie();
if (cookies!=null){
CookieStore cookieStore = new BasicCookieStore();
for (int i=0; i<cookies.size(); i++)
cookieStore.addCookie(cookies.get(i));
client.setCookieStore(cookieStore);
}
HttpResponse response = client.execute(httpPost);
cookies = client.getCookieStore().getCookies();
saveSharedPreferencesCookies(cookies);
// deux méthodes pour enregistrer et charger les cookies ...
private void saveSharedPreferencesCookies(List<Cookie> cookies) {
SerializableCookie[] serializableCookies = new SerializableCookie[cookies.size()];
for (int i=0;i<cookies.size();i++){
SerializableCookie serializableCookie = new SerializableCookie(cookies.get(i));
serializableCookies[i] = serializableCookie;
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
ObjectOutputStream objectOutput;
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
try {
objectOutput = new ObjectOutputStream(arrayOutputStream);
objectOutput.writeObject(serializableCookies);
byte[] data = arrayOutputStream.toByteArray();
objectOutput.close();
arrayOutputStream.close();
ByteArrayOutputStream out = new ByteArrayOutputStream();
Base64OutputStream b64 = new Base64OutputStream(out, Base64.DEFAULT);
b64.write(data);
b64.close();
out.close();
editor.putString("cookies", new String(out.toByteArray()));
editor.apply();
} catch (IOException e) {
e.printStackTrace();
}
}
private List<Cookie> loadSharedPreferencesCookie() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
byte[] bytes = preferences.getString("cookies", "{}").getBytes();
if (bytes.length == 0 || bytes.length==2)
return null;
ByteArrayInputStream byteArray = new ByteArrayInputStream(bytes);
Base64InputStream base64InputStream = new Base64InputStream(byteArray, Base64.DEFAULT);
ObjectInputStream in;
List<Cookie> cookies = new ArrayList<Cookie>();
SerializableCookie[] serializableCookies;
try {
in = new ObjectInputStream(base64InputStream);
serializableCookies = (SerializableCookie[]) in.readObject();
for (int i=0;i<serializableCookies.length; i++){
Cookie cookie = serializableCookies[i].getCookie();
cookies.add(cookie);
}
return cookies;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
Google chance
J'ai eu le même problème. Depuis que j'utilise Volley et non pas HTTPClient directement, le Singleton pour HTTPClient ne semblait pas être la bonne solution. Mais en utilisant la même idée, j'ai simplement enregistré CookieManager dans mon singleton d'application . Donc, si app est mon singleton d'application, alors dans la fonction onCreate () de chaque activité, j'ajoute ceci:
if (app.cookieManager == null) {
app.cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
}
CookieHandler.setDefault(app.cookieManager);
Pour que cela soit encore mieux, toutes mes activités sont des sous-classes de MyAppActivity, il suffit donc de les placer dans onCreate pour MyAppActivity. Toutes mes activités héritent de cette fonctionnalité. Désormais, toutes mes activités utilisent le même gestionnaire de cookies et ma session de service Web est partagée par toutes les activités. Simple et ça marche très bien.
Je suppose que l’une des meilleures choses est d’appliquer Singleton Pattern à votre HTTPClient afin que vous n’ayez qu’une seule instance!
import org.Apache.http.client.HttpClient;
import org.Apache.http.impl.client.DefaultHttpClient;
public class myHttpClient {
private static HttpClient mClient;
private myHttpClient() {};
public static HttpClient getInstance() {
if(mClient==null)
mClient = new DefaultHttpClient();
return mClient;
}
}