Я использую Abot таким образом, что у меня есть приложение WPF, которое отображает элемент управления браузера (CefSharp). Пользователь регистрируется и, в зависимости от того, какая возможная пользовательская аутентификация, которую использует сайт, будет работать во время сканирования таким же образом, как если бы пользователь действительно просматривал сайт.
Таким образом, когда я сканирую, я хочу использовать этот элемент управления браузера, чтобы выполнить запрос и просто вернуть данные страницы. Поэтому я внедрил свой собственный PageRequester, полный список ниже.
Проблема в том, что с CefSharp, как и с другими элементами управления браузера, невозможно получить HttpWebRequest/Response, связанный с CrawlPage. Не устанавливая эти два свойства, Абот не продолжит сканирование дальше.
Есть ли что-то, что я могу сделать, чтобы обойти эту проблему?
Список кодов:
using Abot.Core;
using Abot.Poco;
using CefSharp.Wpf;
using System;
using System.Net;
using System.Text;
using System.Threading;
public class CefPageRequester : IPageRequester
{
private MainWindowDataContext DataContext;
private ChromiumWebBrowser ChromiumWebBrowser;
private CrawlConfiguration CrawlConfig;
private volatile bool _navigationCompleted;
private string _pageSource;
public CefPageRequester(MainWindowDataContext dataContext, ChromiumWebBrowser chromiumWebBrowser, CrawlConfiguration crawlConfig)
{
this.DataContext = dataContext;
this.ChromiumWebBrowser = chromiumWebBrowser;
this.CrawlConfig = crawlConfig;
this.ChromiumWebBrowser.FrameLoadEnd += ChromiumWebBrowser_FrameLoadEnd;
}
public CrawledPage MakeRequest(Uri uri)
{
return this.MakeRequest(uri, cp => new CrawlDecision() { Allow = true });
}
public CrawledPage MakeRequest(Uri uri, Func<CrawledPage, CrawlDecision> shouldDownloadContent)
{
if (uri == null)
throw new ArgumentNullException("uri");
CrawledPage crawledPage = new CrawledPage(uri);
try
{
//the browser control is bound to the address of the data context,
//if we set the address directly it breaks for some reason, although it a two way binding.
this.DataContext.Address = uri.AbsolutePath;
crawledPage.RequestStarted = DateTime.Now;
crawledPage.DownloadContentStarted = crawledPage.RequestStarted;
while (!_navigationCompleted)
Thread.CurrentThread.Join(10);
}
catch (WebException e)
{
crawledPage.WebException = e;
}
catch
{
//bad luck, we should log this.
}
finally
{
//TODO must add these properties!!
//crawledPage.HttpWebRequest = request;
//crawledPage.HttpWebResponse = response;
crawledPage.RequestCompleted = DateTime.Now;
crawledPage.DownloadContentCompleted = crawledPage.RequestCompleted;
if (!String.IsNullOrWhiteSpace(_pageSource))
crawledPage.Content = this.GetContent("UTF-8", _pageSource);
_navigationCompleted = false;
_pageSource = null;
}
return crawledPage;
}
private void ChromiumWebBrowser_FrameLoadEnd(object sender, CefSharp.FrameLoadEndEventArgs e)
{
if (!e.IsMainFrame)
return;
this.ChromiumWebBrowser.Dispatcher.BeginInvoke(
(Action)(() =>
{
_pageSource = this.ChromiumWebBrowser.GetSourceAsync().Result;
_navigationCompleted = true;
}));
}
private PageContent GetContent(string charset, string html)
{
PageContent pageContent = new PageContent();
pageContent.Charset = charset;
pageContent.Encoding = this.GetEncoding(charset);
pageContent.Text = html;
pageContent.Bytes = pageContent.Encoding.GetBytes(html);
return pageContent;
}
private Encoding GetEncoding(string charset)
{
Encoding e = Encoding.UTF8;
if (charset != null)
{
try
{
e = Encoding.GetEncoding(charset);
}
catch { }
}
return e;
}
}
Вопрос также может быть сформулирован как: как избежать необходимости создавать HttpWebResponse из потока? Что кажется невозможным, учитывая, что MSDN говорит:
Вы никогда не должны напрямую создавать экземпляр класса HttpWebResponse. Вместо этого используйте экземпляр, возвращенный вызовом HttpWebRequest.GetResponse.
Я должен был бы опубликовать запрос, чтобы получить ответ, чего я хочу избежать, имея управление веб-браузером.
Как вам известно, многие функции зависят от установленного HttpWebRequest и HttpWebResponse. Я заказал несколько вариантов для вас с головы до головы...
1) Refactor Abot использовать некоторую абстракцию POCO вместо этих классов. Затем просто конвертер, который преобразует реальные HttpWebRequest и HttpWebResponse в эти типы POCO, а также конвертер, который преобразует ваш ответ объектов браузера в эти POCOs.
2) Создайте CustomHttpWebRequest и CustomHttpWebResponse, которые наследуются от классов.net, чтобы вы могли получить доступ/переопределить общедоступные/защищенные свойства, которые могут позволить вам вручную создать экземпляр, который моделирует запрос/ответ, возвращаемый вашим компонентом браузера. Я знаю, что это может быть сложно, но может работать (я никогда не делал этого, поэтому не могу сказать точно).
3) [НЕНАВИЖУ ЭТУ ИДЕЮ. Это ДОЛЖНО БЫТЬ ВАШИМ ПОСЛЕДНИМ КУРОРТОМ] Создайте реальный экземпляр этих классов и используйте отражение, чтобы установить, какие свойства/значения нужно установить, чтобы удовлетворить все обычаи Abot.
4) [Я НЕНАВИЖУ ЭТУ ИДЕЮ ДАЖЕ ВИДЕО] Используйте MS Fakes для создания прокладок/заглушек/подделок для свойств и методов HttpWebRequest и HttpWebResponse. Затем вы можете настроить его для возврата ваших значений. Этот инструмент обычно используется только для тестирования, но я считаю, что его можно использовать для производственного кода, если вы в отчаянии, не заботитесь о производительности и/или безумны.
Я также включил ужасные идеи, а на случай, если они помогут вам задуматься. Надеюсь, это поможет...