0

Есть следующий код:

private static readonly HttpClientHandler handler = new HttpClientHandler
{
    CookieContainer = new CookieContainer(),
    UseCookies = true
};
private static readonly HttpClient httpClient = new HttpClient(handler);

public Class1() { httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 UBrowser/6.0.1308.1016 Safari/537.36");

httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", "ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4");

}

Сохранение кук в виде json:

string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filename = Path.Combine(path, "test/cookies.bin");

if (File.Exists(filename)) File.Delete(filename);

var cookies = handler.CookieContainer.GetCookies(new Uri("https://site.ru/"));

using var fs = new FileStream(filename, FileMode.OpenOrCreate); await JsonSerializer.SerializeAsync(fs, cookies);

Загрузка кук:

string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filename = Path.Combine(path, "test/cookies.bin");

using var fs = new FileStream(filename, FileMode.Open); var cookies = await JsonSerializer.DeserializeAsync<List<Cookie>>(fs); foreach (var cookie in cookies) { handler.CookieContainer.Add(cookie); }

Куки в файле:

[{"Comment":"","CommentUri":null,"HttpOnly":true,"Discard":false,"Domain":"site.ru","Expired":false,"Expires":"0001-01-01T00:00:00","Name":"__RequestVerificationToken","Path":"/","Port":"","Secure":true,"TimeStamp":"2020-10-09T13:33:07.1633063+03:00","Value":"D4cK7wXeq6KThXJG0uZJ8gxUZQ_jGr1pNUwrCuJEhlKVeQHiLQzbvdkZXxmcWT8oYRt4ipPUes4D0dMTxbgY_2yZnJUZxM03F7n0AVtFShU1","Version":0},{"Comment":"","CommentUri":null,"HttpOnly":true,"Discard":false,"Domain":"site.ru","Expired":false,"Expires":"2020-10-11T13:33:08+03:00","Name":".ASPXAUTH","Path":"/","Port":"","Secure":false,"TimeStamp":"2020-10-09T13:33:07.6753356+03:00","Value":"824040565E5BCCD7A57ECDFC3BF4A3D5BEFF477161636356924B0340E53F1527470E6513BE272EDB9392A05C652542ED27F5A001D92DFDE5F395BD1D75708F60CCF2DE2D40ACDF1B8D82743D8CED67713FE85653BB04716B534FA3CEAD261FC9BED4BA50A21C9D2C989FDB2EF1D9ADE4","Version":0}]

GET запрос через браузер:

введите сюда описание изображения

GET запрос через программу:

введите сюда описание изображения

Дело в том, что после загрузки кук я не получаю ту страницу, которую должен, а получаю страницу авторизации. Судя по всему, проблема в том, что у меня как бы "не загружается" кука .ASPXAUTH.. и на изображение из браузера видно другие куки (_ym_uid и другие..), я не могу понять, они обязательны или нет?

Ранее с куками дел не имел, подскажите, пожалуйста, в чем дело?

UPD:

private static readonly HttpClientHandler handler = new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
    AllowAutoRedirect = true
};
private static readonly HttpClient httpClient = new HttpClient(handler);

public HttpManager() { httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 UBrowser/6.0.1308.1016 Safari/537.36"); httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br"); }

Получаю куки теперь так:

var cookies = handler.CookieContainer.GetAllCookies();

Загружаю так:

List<Cookie> cookies = await JsonSerializer.DeserializeAsync<List<Cookie>>(fs);
foreach (var cookie in cookies)
{
    // не загружать, если кука заэкспайрилась
    if (!cookie.Expired && (cookie.Expires == DateTime.MinValue || cookie.Expires > DateTime.Now))
        handler.CookieContainer.Add(cookie);
}

Этот класс позаимствовал по ссылке которую написали в комментариях:

public static class CookieContainerExtensions
{
    // Забирает все куки из контейнера
    public static CookieCollection GetAllCookies(this CookieContainer container)
    {
        CookieCollection allCookies = new CookieCollection();
        IDictionary domains = (IDictionary)container.GetType()
            .GetRuntimeFields()
            .FirstOrDefault(x => x.Name == "m_domainTable")
            .GetValue(container);
    foreach (object field in domains.Values)
    {
        IDictionary values = (IDictionary)field.GetType()
            .GetRuntimeFields()
            .FirstOrDefault(x =&gt; x.Name == &quot;m_list&quot;)
            .GetValue(field);

        foreach (CookieCollection cookies in values.Values)
        {
            allCookies.Add(cookies);
        }
    }
    return allCookies;
}

}

К сожалению, на деле получаю то, что получал ранее. Видать, я что-то упускаю из виду..

  • 1
    https://ru.stackoverflow.com/a/958900/206435 может поможет –  Oct 09 '20 at 10:50
  • 1
    Еще пример, точнее не просто пример, а берите и используйте. :) – aepot Oct 09 '20 at 10:54
  • 1
    _ym - это яндекс.метрика. _ga - гуглоаналитика. – aepot Oct 09 '20 at 10:59
  • 2
    Тут может быть несколько причин: 1. Печенька "протухла" - у вас, например, она до 13:33:07. 2. Не совпадают ключи - иногда делают так, что сайт требует 2+ ключа доступа и они связаны, и вот если один из них битый, то сайт не считает это успешной авторизацией. 3. - Неверно указан URI - Cookie очень сильно завязаны на адресе, особенно в C# и, если печенька для .site.ru (точка в начале), то это не равно просто site.ru (без точки), проверьте, правильный-ли вы адрес получаете и задаете при сохранении/восстановлении. По поводу "лишнее" - скорей всего да, как и все, что у вас в Class1. – EvgeniyZ Oct 09 '20 at 11:04
  • @EvgeniyZ, домен проверил - у меня он везде начинается так e.site.ru (правильно), в коде именно так везде и указано. Единственное, что я заметил сейчас, это то, что когда в браузере перед авторизацией ставлю чекбокс Запомнить, то в POST запрос отправляется так: Remember=true, Remember=false, т.е, для первого ключа указано true, для второго - false. Но у меня два раза добавить ключ в коллекцию Dictionary, естественно, не получается (ошибка). – Максим Oct 09 '20 at 12:49
  • 1
    Начнем с того, что у вас в JSON, что вы нам дали, указано "Domain":"site.ru", а вы пишете сейчас e.site.ru, уж определитесь. Далее, вам достаточно всего 1-2-х Cookie для авторизации, зачем десериализировать весь словарь? Найдите нужную, зайдите на сайт через Postman и уже затем пишите код. – EvgeniyZ Oct 09 '20 at 12:52
  • @EvgeniyZ, в JSON e.site.ru, это я когда сюда выкладывал намудрил, извиняюсь. На счет остального - сейчас буду пробовать, спасибо. – Максим Oct 09 '20 at 12:54
  • 1
    Это проделайте и выясните, что именно требует ваш сайт. Не пишите код сразу, сначала анализируйте, ибо я на 90% уверен, что большинство того, что вы понаписали сейчас - лишнее (UserAgent, AcceptEncoding, AutomaticDecompression, AllowAutoRedirect). – EvgeniyZ Oct 09 '20 at 12:59
  • @EvgeniyZ, в общем, попробовал я всё это проделать в Postman, штука удобная, но в принципе, ничего не изменилось. 1) Отправляю get запрос на страницу авторизации; 2) Беру в исходнике этой страницы токен; 3) Отправляю все данные (токен, логин, пароль) post запросом, приходит правильный ответ с куками (вижу свои данные); 4) Отправляю get запрос на главную страницу с одной кукой (токен) - авторизация НЕ УДАЛАСЬ, добавляю к этой куке вторую куку .ASPXAUTH - авторизация УДАЛАСЬ. Позже выяснил, что можно только одну куку .ASPXAUTH добавлять и она будет "подтягивать" за собой вторую. – Максим Oct 09 '20 at 14:53
  • А у меня по коду, загружается только кука с токеном.. сейчас попробую как-нибудь принудительно загружать .ASPXAUTH – Максим Oct 09 '20 at 14:53
  • 1
    ничего не изменилось - а должно было? Она вам помогла выяснить именно то, что необходимо отправить этому сайту для успешной авторизации, все лишнее теперь можете убрать, включая сериализацию/десериализацию всей коллекции печенек, достаточно лишь значение этого .ASPXAUTH в виде простой строки). Также мне не нравиться эта строка - handler.CookieContainer.Add(cookie); (вангую, что там теряется адрес), а также мне не нравиться то, что вы не устанавливаете BaseAdress (с которым связываются Cookie). Попробуйте нечто такое. – EvgeniyZ Oct 09 '20 at 15:13
  • @EvgeniyZ, спасибо большое, всё сделал как вы сказали, записываю в файл вообще теперь только сам ключ и всё. Ещё раз спасибо! – Максим Oct 09 '20 at 16:05
  • Закрывайте тогда вопрос своим ответом, напишите там то, что получили в итоге. – EvgeniyZ Oct 09 '20 at 16:06

1 Answers1

0

Для правильной загрузки кук (конкретно для моего сайта), нужно было сохранять лишь одну куку — .ASPXAUTH, а потом её подгружать.

Итоговый код получился такой:

private static readonly HttpClientHandler handler = new HttpClientHandler
{
    CookieContainer = new CookieContainer(),
    UseCookies = true
};
private static readonly Uri baseUri = new Uri("https://e.site.ru");
private static readonly HttpClient httpClient = new HttpClient(handler) { BaseAddress = baseUri };

public static async void SaveCookies() { string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); string filename = Path.Combine(path, "test/cookies.bin");

var cookies = handler.CookieContainer.GetCookies(baseUri);

using var sw = new StreamWriter(filename, false);
// сохраняется лишь сам ключ, без имени
await sw.WriteAsync(cookies[1].Value);

}

public static async void LoadCookies() { string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); string filename = Path.Combine(path, "test/cookies.bin");

using var sr = new StreamReader(filename);
var cookie_value = await sr.ReadToEndAsync();

handler.CookieContainer.Add(baseUri, new Cookie(&quot;.ASPXAUTH&quot;, cookie_value));

}

  • 1
    Статика то тут зачем?) Старайтесь ее использовать как можно меньше. Также помните про такую вещь, как DRY - не повторяйся, к примеру, у вас path и filename повторяются, почему бы не вынести их и не сделать общими? Также не понимаю людей, которые использую Stream, ок, оптимизация, но чего вы оптимизировать собрались, запись одного простого текстового значения в файл? Не экономте на спичках, используйте то, что удобней (File.WriteAllText(filename, "ключ"), все, одна строка сделала то, что вы делаете через Stream). – EvgeniyZ Oct 09 '20 at 17:25
  • @EvgeniyZ, я как-то и не подумал про System.IO.File, действительно, его будет разумнее здесь использовать. А про путь к файлам, скажу, что в данном проекте мне было без разницы (просто тестовый проект), сам алгоритм буду использовать совсем в другом проекте. Про статику вы где имеете ввиду? В методах? Или в объявлении переменных? – Максим Oct 09 '20 at 19:28
  • 1
    Из всего, что в вашем коде, статичным должен быть только объект HttpClient, да и все. – EvgeniyZ Oct 09 '20 at 19:29
  • 1
    Также знаете какая у вас уязвимость в коде? cookies[1].Value - с чего вы взяли, что нужная Cookie будет всегда по индексу 1? Советую искать явно, по названию, иначе малейшее обновление на сайте, новая кука у них и все, ваш алгоритс упал. – EvgeniyZ Oct 09 '20 at 19:42
  • Спасибо, поправлю. А чем плох статичный метод? Ну, так проще его использовать, не нужно создавать экземпляр. – Максим Oct 09 '20 at 20:34
  • Есть такая вещь, как ООП, также есть такая вещь, как SOLID. Первый говорит, что вы должны строить приложение на основе отдельных объектов, то есть, есть у вас человек - это объект, к примеру его сердце - объект, мозг - объект и т.д. Каждый объект имеет свой функционал, сердце допустим качать кровь. SOLID - это набор правил, где S - это единственная ответственность (каждый метод должен делать что-то одно), а O - это принципы открытости и закрытости (ваш класс должен отдавать наружу только необходимые органы управления). – EvgeniyZ Oct 09 '20 at 20:50
  • И вот теперь давайте подумаем, ваш класс по отправке запросов, это объект, или простой набор методов (я их называю утилитами)? Я считаю его самостоятельным объектом, который должен хранить в себе например коллекцию Cookie, должен хранить в себе базовый адрес, который будет задан при его инициализации, у него есть органы управления, которые должны идти наружу (загрузка и сохранение). Сейчас у вас просто набор методов, которые доступны из всего приложения, любой имеет к ним доступ и любой может загрузить/сохранить. Это не есть хорошо, этот класс нарушает ооп, его трудно тестировать и так далее. – EvgeniyZ Oct 09 '20 at 20:54
  • Ну, а как быть тогда, если мне этот класс нужно вызывать в разных формах, например, но при этом мне не нужен новый экземпляр класса. Если я буду использовать паттерн singleton, то получается поменял шыло на мыло? Или какой-то совсем иной подход? – Максим Oct 09 '20 at 21:11
  • Синглтон != static. А так, ну давайте подумаем. К примеру, у вас есть класс A, ему необходим класс B для работы, так почему класс A может создаваться без класса B? Человека не будет без сердца) Сейчас вы помните, что человеку требуется это сердце, сделаете перерыв от проекта, забудете, что там и как и потом будете делать new Person(), а он требует сердце, которое вы ему не инициализировали. Согласитесь, это не есть хорошо. Поэтому, если классу нужен другой класс, то требуйте его через конструктор - это самый лучший и безопасный способ. – EvgeniyZ Oct 09 '20 at 21:18
  • То есть, сделали класс, отвечающий за всякие эти POST/GET запросы, сделали однажды его объект private HttpService httpService = new HttpService();, все, дальше передавайте его в нужные вам классы, где вы его используете, например, var login = new Login(httpService);, где сам класс Login будет в себе иметь конструктор, принимающий ссылку private HttpService httpService; public Login(HttpService httpService) => this.httpService = httpService;. Без каких-либо синглтонов и статики. Лаконично и понятно. А если вам не нравиться вечно писать new ..., то IoC в помощь, используйте контейнер. – EvgeniyZ Oct 09 '20 at 21:25
  • Сейчас не понял что-то. Есть класс А, ему необходим класс В для работы.. чет я запутался. Класс А создавался когда класса В ещё в помине не было :) можете, если не затруднит, привести пример? upd: вижу пример, спасибо. Попробую завтра всё переделать, с учётом ваших замечаний – Максим Oct 09 '20 at 21:27
  • Я имею ввиду это. – EvgeniyZ Oct 09 '20 at 21:50
  • @EvgeniyZ, я вот сделал как вы сказали, но у меня выходит что: класс А зависит от логики класса В, а класс В зависит от логики класса А. И как в таком случае инициализировать класс? Думаю, вы поняли. – Максим Oct 10 '20 at 18:21
  • 1
    Ну это значит, что у вас проблемы в вашей архитектуре. Вон пример выше, человек должен знать про сердце и его состояние, а вот сердце - это самостоятельный компонент. Или ваш класс из вопроса по управлению запросами, он должен знать о том, что находится, например в форме, которая по клику вызывает его метод? Нет, он должен лишь выполнить логику и отдать результат. В программировании все должно быть простым и не замысловатым. А если у вас А зависит от B, который зависит от А, то это уже явно не простая логика и вы явно делаете в одном из этих классов что-то, что не относиться к его обязанностям – EvgeniyZ Oct 10 '20 at 18:28
  • @EvgeniyZ, всё, я понял, спасибо. Я действительно в одном из классов делал то, что не относится к его обязанностям. – Максим Oct 10 '20 at 18:50