Использование файлов cookie в действии при использовании HttpClient
Я пытаюсь простое приложение читать в HTML-сайте и транстротировать его для создания легко читаемого пользовательского интерфейса в Android (это проявляется в обучении Android, а не в полезном приложении). Проблема, с которой я сталкиваюсь, заключается в сохранении сеанса пользователей через действия и затем использовании сеанса в HttpClient
после вызова.
Я хотел бы сделать это "Правильно", рекомендуемый подход - использовать CookieManager
. Однако у меня были проблемы с этим - я не могу найти "правильный" способ взять Cookie
из CookieManager
и использовать его в более позднем экземпляре HttpClient
в отдельных действиях.
При использовании CookieManager
я могу сохранить Cookie
, а Cookie
- в области в других действиях (см. фрагмент кода 2). Я не нашел, как использовать это позже (см. Фрагмент кода 3) при запросе страницы.
Достаточно говорить, вот какой-то код. Сначала мое действие входа и хранилище файлов cookie:
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) {...}
}
Активность запуска, которая решает, чтобы войти в систему пользователя или перейти к индексу, приведен ниже. Вы можете видеть из этого кода, что файл cookie находится в области видимости и может быть прочитан:
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);
}
}
Но из моего кода, чтобы прочитать страницу Индекса, я надеюсь, что вы можете предположить, что мне не хватает:
@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) {...}
}
HttpClient
on execute(getMethod)
не использует Cookie (дважды проверял это при отладке), чтобы оттянуть страницу. Было бы здорово, если бы кто-то мог заполнить эту дыру, насколько мне известно.
Спасибо заранее.
ИЗМЕНИТЬ
Когда код комментария добавлен обратно (с изменением метода httpClient.execute(getMethod)
на httpClient.execute(getMethod, localContext)
), создается эта трассировка strack - предположительно, потому что я заполняю атрибут ClientContext.COOKIE_STORE
Cookie
String
, а не a 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)*
Ответы
Ответ 1
(Как обещал это решение. Мне все еще не нравится и чувствую, что я упускаю "правильный" способ сделать это, но это работает.)
Вы можете использовать CookieManager
для регистрации ваших файлов cookie (и, следовательно, сделать эти куки файлы доступными между приложениями) со следующим кодом:
Сохранение файлов cookie в 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();
Проверка файлов cookie в указанном домене:
if(CookieManager.getInstance().getCookie(URI_FOR_DOMAIN)
Чтобы восстановить значения для 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));
}
Ответ 2
CookieManager
используется внутренним HTTP-клиентом Java. Он не имеет ничего общего с Apache HttpClient.
В вашем коде вы всегда создаете для каждого запроса новый экземпляр HttpClient
и, следовательно, новый экземпляр CookieStore
, который, очевидно, получает мусор, собранный вместе со всеми куками, хранящимися, как только этот экземпляр HttpClient
выходит из объем.
Вы должны либо
(1) Повторно использовать один и тот же экземпляр HttpClient
для всех логически связанных HTTP-запросов и поделиться им между всеми логически связанными потоками (который является рекомендуемым способом использования Apache HttpClient)
(2) или, по крайней мере, совместно использовать один и тот же экземпляр CookieStore
между логически связанными потоками
(3) или, если вы настаиваете на использовании CookieManager
для хранения всех ваших файлов cookie, создать пользовательскую реализацию CookieStore
, поддерживаемую CookieManager
Ответ 3
В моем приложении сервер хочет использовать тот же сеанс с одинаковыми кукисами...
После нескольких часов "гуглинга" и болезненной головной боли я только что сохранил файлы cookie в SharedPreference или просто поместил в какой-то объект и установил DefaultHttpClient с тем же самым куки снова... onDestroy просто удалит SharedPreferences..., что все:
Посмотрите следующий пример:
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);
//два метода сохранения и загрузки файлов cookie...
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
Ответ 4
У меня была та же проблема. Поскольку я использую Volley, а не HTTPClient напрямую, Singleton для HTTPClient не похоже на правильное решение. Но используя ту же самую идею, я просто сохранил CookieManager в своем приложении singleton.
Поэтому, если приложение является моим приложением singleton, то в onCreate() каждого действия я добавляю следующее:
if (app.cookieManager == null) {
app.cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
}
CookieHandler.setDefault(app.cookieManager);
Чтобы сделать это еще лучше, все мои действия являются подклассами MyAppActivity, поэтому все, что мне нужно сделать, это поместить его в onCreate для MyAppActivity, а затем все мои действия наследуют эту функциональность. Теперь все мои действия используют один и тот же менеджер файлов cookie, и мой сеанс веб-службы разделяет все действия. Просто и отлично работает.
Ответ 5
Я полагаю, что одним из лучших является применение Singleton Pattern для вашего HTTPClient, поэтому у вас есть только один экземпляр!
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;
}
}
Ответ 6
Хорошим решением для хранилища файлов cookie должно следовать шаблон ThreadLocal, чтобы избежать случайного хранения файлов cookie.
Это поможет сохранить один и только CookieStore. Для более подробного объяснения этого шаблона вы можете следовать этой полезной ссылке.
http://veerasundar.com/blog/2010/11/java-thread-local-how-to-use-and-code-sample/
Приветствия