C# HttpBrowser 跨进程访问,解决内存泄露问题

时间:2023-03-09 01:04:14
C# HttpBrowser 跨进程访问,解决内存泄露问题
 #undef DEBUG
using Microsoft.Win32;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; namespace AnfleCrawler.Common
{
/// <summary>
/// Chromium / CasperJS + PhantomJS
/// http://pinvoke.net/index.aspx
/// </summary>
public sealed partial class HttpBrowser : IHttpClient
{
#region NestedTypes
[Serializable]
public class AjaxBlockEntity
{
internal const string AjaxBlock = "_AjaxBlock";
public string ID { get; set; }
public string Text { get; set; }
public bool IsEvent { get; set; }
}
public class AjaxEventEntity : MarshalByRefObject
{
public string ListenerSelector { get; set; }
public bool EntryCall { get; set; }
public Action<string> FinalCallback { get; set; }
} [ComVisible(true)]
public sealed class STAContext : Disposable
{
#region Fields
public volatile bool IsRedirect;
//internal MessageLoopApartment _Apartment;
private SynchronizedCollection<Tuple<HtmlElement, EventHandler>> _releaseSet;
private AutoResetEvent _sendReceiveWaiter;
private CountdownEvent _ajaxWaiter;
private System.Threading.Timer _lazyTimer; internal volatile bool DoInvokeHtml;
private volatile string _outerHtml;
#endregion #region Properties
public Uri RequestUrl { get; private set; }
public HttpRequestContent RequestContent { get; private set; }
internal AutoResetEvent WaitHandle { get; set; } internal AutoResetEvent SendReceiveWaiter
{
get
{
if (_sendReceiveWaiter == null)
{
_sendReceiveWaiter = new AutoResetEvent(false);
}
return _sendReceiveWaiter;
}
}
internal AjaxBlockEntity[] AjaxBlocks { get; private set; }
internal CountdownEvent AjaxWaiter
{
get
{
if (_ajaxWaiter == null)
{
_ajaxWaiter = new CountdownEvent();
}
return _ajaxWaiter;
}
}
internal volatile bool IsProcessEvent;
internal AjaxEventEntity AjaxEvent { get; set; } internal string OuterHtml
{
get
{
DoInvokeHtml = true;
return _outerHtml;
}
set
{
_outerHtml = value;
}
}
#endregion #region Constructor
internal STAContext(Uri url, HttpRequestContent content)
{
this.RequestUrl = url;
this.RequestContent = content;
string ablock;
if (this.RequestContent != null && this.RequestContent.Form != null)
{
if (!string.IsNullOrEmpty(ablock = this.RequestContent.Form.Get(AjaxBlockEntity.AjaxBlock)))
{
this.AjaxBlocks = JsonConvert.DeserializeObject<AjaxBlockEntity[]>(ablock);
this.RequestContent.Form.Remove(AjaxBlockEntity.AjaxBlock);
}
}
DoInvokeHtml = true;
} protected override void DisposeInternal(bool disposing)
{
if (disposing)
{
//if (_Apartment != null)
//{
// _Apartment.Dispose();
// _Apartment = null;
//}
if (_lazyTimer != null)
{
_lazyTimer.Dispose();
_lazyTimer = null;
}
if (this.WaitHandle != null)
{
this.WaitHandle.Dispose();
this.WaitHandle = null;
} DisposeObject(_sendReceiveWaiter);
DisposeObject(_ajaxWaiter);
}
}
#endregion #region Methods
public void SetHtml(string html)
{
_outerHtml = html;
DoInvokeHtml = false;
} internal void RegisterLazyLoad(Action<object> func, object state)
{
if (_lazyTimer != null)
{
return;
}
_lazyTimer = new System.Threading.Timer(x => STA_Run(func, x, this), state, , Timeout.Infinite);
}
/// <summary>
/// 另种思路,在每次加载完毕后delay
/// </summary>
internal void DelayLazyLoad()
{
if (_lazyTimer == null)
{
return;
}
_lazyTimer.Change(, Timeout.Infinite);
} /// <summary>
/// STA
/// </summary>
/// <param name="node"></param>
/// <param name="e"></param>
internal void AjaxMark(HtmlElement node, EventHandler e)
{
if (_releaseSet == null)
{
_releaseSet = new SynchronizedCollection<Tuple<HtmlElement, EventHandler>>();
}
var q = from t in _releaseSet
where t.Item1 == node
select t;
if (q.Any())
{
return;
}
_releaseSet.Add(Tuple.Create(node, e));
node.AttachEventHandler("onpropertychange", e);
} /// <summary>
/// STA
/// </summary>
internal void AjaxUnmarks()
{
if (_releaseSet.IsNullOrEmpty())
{
return;
}
foreach (var item in _releaseSet)
{
var node = item.Item1;
node.DetachEventHandler("onpropertychange", item.Item2);
}
_releaseSet = null;
} internal void _ReleaseMemory()
{
return;
#if !DEBUG
var proc = Process.GetCurrentProcess();
//128M
if (proc.PrivateMemorySize64 <= 134217728L)
{
return;
}
base.ReleaseMemory();
#endif
}
#endregion
}
#endregion #region Static
public const string Callback_Snapshot = "_xSnapshot"; static HttpBrowser()
{
SetBrowserFeatureControl();
//NativeMethods.SetErrorMode(NativeMethods.ErrorModes.SYSTEM_DEFAULT);
NativeMethods.SetErrorMode(NativeMethods.ErrorModes.SEM_FAILCRITICALERRORS | NativeMethods.ErrorModes.SEM_NOGPFAULTERRORBOX | NativeMethods.ErrorModes.SEM_NOOPENFILEERRORBOX);
} /// <summary>
/// http://msdn.microsoft.com/en-us/library/ee330720(v=vs.85).aspx
/// </summary>
private static void SetBrowserFeatureControl()
{
// FeatureControl settings are per-process
string fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
string[] skip = new string[] { "devenv.exe", "XDesProc.exe" };
if (skip.Any(p => p.Equals(fileName, StringComparison.OrdinalIgnoreCase)))
{
return;
} SetBrowserFeatureControlKey("FEATURE_BROWSER_EMULATION", fileName, GetBrowserEmulationMode());
SetBrowserFeatureControlKey("FEATURE_MANAGE_SCRIPT_CIRCULAR_REFS", fileName, );
//SetBrowserFeatureControlKey("FEATURE_GPU_RENDERING ", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_AJAX_CONNECTIONEVENTS", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_ENABLE_CLIPCHILDREN_OPTIMIZATION", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_DOMSTORAGE ", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_IVIEWOBJECTDRAW_DMLT9_WITH_GDI ", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_NINPUT_LEGACYMODE", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_DISABLE_LEGACY_COMPRESSION", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_LOCALMACHINE_LOCKDOWN", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_BLOCK_LMZ_OBJECT", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_BLOCK_LMZ_SCRIPT", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_DISABLE_NAVIGATION_SOUNDS", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_SCRIPTURL_MITIGATION", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_SPELLCHECKING", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_STATUS_BAR_THROTTLING", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_TABBED_BROWSING", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_VALIDATE_NAVIGATE_URL", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_WEBOC_DOCUMENT_ZOOM", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_WEBOC_POPUPMANAGEMENT", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_WEBOC_MOVESIZECHILD", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_ADDON_MANAGEMENT", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_WEBSOCKET", fileName, 1);
//SetBrowserFeatureControlKey("FEATURE_WINDOW_RESTRICTIONS ", fileName, 0);
//SetBrowserFeatureControlKey("FEATURE_XMLHTTP", fileName, 1);
}
/// <summary>
/// http://msdn.microsoft.com/en-us/library/ie/ee330730(v=vs.85).aspx
/// </summary>
/// <returns></returns>
private static uint GetBrowserEmulationMode()
{
int browserVersion;
using (var ieKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Internet Explorer",
RegistryKeyPermissionCheck.ReadSubTree, System.Security.AccessControl.RegistryRights.QueryValues))
{
var version = ieKey.GetValue("svcVersion") ?? ieKey.GetValue("Version");
if (version == null)
{
throw new ApplicationException("Microsoft Internet Explorer is required!");
}
int.TryParse(version.ToString().Split('.')[], out browserVersion);
}
if (browserVersion < )
{
throw new ApplicationException("Microsoft Internet Explorer 8 is required!");
}
switch (browserVersion)
{
case :
return ;
case :
return ;
case :
return ;
default:
return ;
}
}
private static void SetBrowserFeatureControlKey(string feature, string appName, uint value)
{
using (var key = Registry.CurrentUser.CreateSubKey(
String.Concat(@"Software\Microsoft\Internet Explorer\Main\FeatureControl\", feature),
RegistryKeyPermissionCheck.ReadWriteSubTree))
{
key.SetValue(appName, value, RegistryValueKind.DWord);
}
} private static void STA_Run(Action<object> func, object state, STAContext context)
{
var sta = new Thread(arg =>
{
var set = (object[])arg;
try
{
var func2 = (Action<object>)set[];
func2(set[]);
}
catch (Exception ex)
{
App.LogError(ex, "STA_Run");
}
}, * ); //1024 * 512, 默认1M
sta.IsBackground = true;
sta.SetApartmentState(ApartmentState.STA);
try
{
sta.Start(new object[] { func, state });
}
catch (OutOfMemoryException ex)
{
HandleException(ex);
} //context._Apartment.Invoke(func, state);
} public static void FillAjaxBlock(NameValueCollection form, AjaxBlockEntity[] set)
{
Contract.Requires(form != null); form[AjaxBlockEntity.AjaxBlock] = JsonConvert.SerializeObject(set, Formatting.None);
}
#endregion #region Fields
private EndPoint _proxyAddr;
private Lazy<IHttpClient> _lazyClient;
private CookieContainer _cookieContainer;
private Action<STAContext, HtmlDocument> _onLoad;
#endregion #region Properties
public int SendReceiveTimeout { get; set; }
public ushort? RetryCount { get; set; }
public TimeSpan? RetryWaitDuration { get; set; }
public bool UseCookies { get; set; }
public CookieContainer CookieContainer
{
get { return _cookieContainer; }
}
public string SaveFileDirectory { get; set; }
/// <summary>
/// 网页快照大小,Full Screenshot则设置Size.Empty
/// </summary>
public Size? Snapshot { get; set; }
/// <summary>
/// 供下载使用
/// </summary>
internal IHttpClient Client
{
get
{
var client = _lazyClient.Value;
client.SendReceiveTimeout = this.SendReceiveTimeout;
client.RetryCount = this.RetryCount;
client.RetryWaitDuration = this.RetryWaitDuration;
client.UseCookies = this.UseCookies;
client.SaveFileDirectory = this.SaveFileDirectory;
return client;
}
}
#endregion #region Constructors
public HttpBrowser()
{
this.SendReceiveTimeout = -;
_lazyClient = new Lazy<IHttpClient>(() => new HttpClient(), false);
_cookieContainer = new CookieContainer();
this.UseCookies = true;
}
/// <summary>
/// crossLoad中如有跨域交互,请继承扩展IsolateProxy
/// </summary>
/// <param name="crossLoad"></param>
public HttpBrowser(Action<STAContext, HtmlDocument> crossLoad)
: this()
{
_onLoad = crossLoad;
}
#endregion #region Methods
public void SetProxy(EndPoint address, NetworkCredential credential = null)
{
if (credential != null)
{
throw new NotSupportedException("credential");
} if (IsSpawned)
{
_proxyAddr = address;
}
else
{
#if DEBUG
App.LogInfo("SetProxy HttpBrowser {0}", address);
#endif
if (WinInetInterop.SetConnectionProxy(address.ToString()))
{
App.LogInfo("SetProxy HttpBrowser {0} succeed", address);
}
}
}
internal void RestoreSystemProxy()
{
if (IsSpawned)
{
_proxyAddr = null;
}
else
{
#if DEBUG
App.LogInfo("RestoreSystemProxy HttpBrowser");
#endif
if (WinInetInterop.RestoreSystemProxy())
{
App.LogInfo("RestoreSystemProxy HttpBrowser succeed");
}
}
} public string GetHtml(Uri requestUrl, HttpRequestContent content = null)
{
if (IsSpawned)
{
return SpawnedStart(_proxyAddr, requestUrl, content);
}
using (var arg = new STAContext(requestUrl, content))
{
arg.WaitHandle = new AutoResetEvent(false);
this.STA_Run(arg);
arg.WaitHandle.WaitOne();
return arg.OuterHtml;
}
} public string GetHtml(Uri requestUrl, AjaxEventEntity local, HttpRequestContent content = null)
{
Contract.Requires(requestUrl != null);
if (local == null)
{
return GetHtml(requestUrl, content);
} using (var arg = new STAContext(requestUrl, content))
{
arg.AjaxEvent = local;
arg.WaitHandle = new AutoResetEvent(false);
this.STA_Run(arg);
arg.WaitHandle.WaitOne();
return arg.OuterHtml;
}
} public Stream GetStream(Uri requestUrl, HttpRequestContent content = null)
{
return this.Client.GetStream(requestUrl, content);
} public void DownloadFile(Uri fileUrl, out string fileName)
{
this.Client.DownloadFile(fileUrl, out fileName);
}
#endregion #region Hepler
/// <summary>
/// 注入Script
/// </summary>
/// <param name="document"></param>
/// <param name="js"></param>
public void InjectScript(HtmlDocument document, string js)
{
Contract.Requires(document != null); if (!CheckDocument(document.Url))
{
App.LogInfo("HttpBrowser InjectScript Cancel");
return;
}
var head = document.GetElementsByTagName("head")[];
var script = document.CreateElement("script");
script.SetAttribute("type", "text/javascript");
script.SetAttribute("text", js);
head.AppendChild(script);
}
private bool CheckDocument(Uri documentUrl)
{
if (documentUrl != null && documentUrl.OriginalString.StartsWith("res://ieframe.dll", StringComparison.OrdinalIgnoreCase))
{
App.LogInfo("CheckDocument {0}", documentUrl);
return false;
}
return true;
} /// <summary>
/// 设置ajax参数
/// </summary>
/// <param name="browser"></param>
private void SetAjax(WebBrowser browser, bool isEvent)
{
var arg = (STAContext)browser.ObjectForScripting;
if (arg.AjaxBlocks.IsNullOrEmpty())
{
return;
}
foreach (var block in arg.AjaxBlocks.Where(p => p.IsEvent == isEvent))
{
var node = browser.Document.GetElementById(block.ID);
if (node == null)
{
continue;
}
arg.AjaxWaiter.AddCount();
arg.AjaxMark(node, (sender, e) =>
{
node = browser.Document.GetElementById(block.ID);
if (node == null || block.Text == null
|| (!block.Text.Equals(node.InnerText, StringComparison.OrdinalIgnoreCase)))
{
// bug 如果先Signal再AddCount就会出错
arg.AjaxWaiter.Signal();
}
});
}
arg.AjaxWaiter.Signal();
}
/// <summary>
/// 等待ajax执行
/// </summary>
/// <param name="arg"></param>
private bool WaitAjax(STAContext arg)
{
if (arg.AjaxBlocks.IsNullOrEmpty())
{
return false;
}
int aTimeout = this.SendReceiveTimeout;
if (aTimeout <= )
{
aTimeout = (int)TimeSpan.FromSeconds(60d).TotalMilliseconds;
}
if (!arg.AjaxWaiter.Wait(aTimeout))
{
App.LogInfo("HttpBrowser Ajax Timeout {0}", arg.RequestUrl);
return false;
}
return true;
} private void ProcessAjaxEvent(WebBrowser browser)
{
var arg = (STAContext)browser.ObjectForScripting;
if (arg.AjaxEvent == null || string.IsNullOrEmpty(arg.AjaxEvent.ListenerSelector))
{
return;
} arg.IsProcessEvent = true;
if (arg.AjaxEvent.EntryCall && arg.AjaxEvent.FinalCallback != null)
{
InvokeHtml(browser);
arg.AjaxEvent.FinalCallback(arg.OuterHtml);
}
object val = browser.Document.InvokeScript("Soubiscbot", new object[] { , arg.AjaxEvent.ListenerSelector });
var set = val.ToString().Split(',');
foreach (string id in set)
{
var node = browser.Document.GetElementById(id);
if (node == null)
{
continue;
}
arg.AjaxWaiter.Reset();
SetAjax(browser, true);
node.InvokeMember("click");
bool isSet = WaitAjax(arg);
Console.WriteLine("ProcessAjaxEvent isSet={0}", isSet);
if (arg.AjaxEvent.FinalCallback != null)
{
InvokeHtml(browser);
arg.AjaxEvent.FinalCallback(arg.OuterHtml);
}
}
arg.IsProcessEvent = false;
} /// <summary>
/// 读取页面OuterHtml
/// </summary>
/// <param name="browser"></param>
/// <returns></returns>
private void InvokeHtml(WebBrowser browser)
{
var scripting = (STAContext)browser.ObjectForScripting;
if (scripting == null)
{
throw new InvalidOperationException("InvokeHtml");
}
if (!scripting.DoInvokeHtml)
{
return;
}
scripting.OuterHtml = (string)browser.Document.InvokeScript("Soubiscbot");
}
#endregion #region STAThread
private void STA_Run(STAContext context)
{
context._ReleaseMemory();
//context._Apartment = new MessageLoopApartment();
STA_Run(state =>
{
var browser = new WebBrowser()
{
ScriptErrorsSuppressed = true,
IsWebBrowserContextMenuEnabled = false,
ObjectForScripting = state
};
browser.Navigating += browser_Navigating;
browser.DocumentCompleted += browser_DocumentCompleted;
browser.NewWindow += browser_NewWindow;
if (this.Snapshot.HasValue)
{
browser.ScrollBarsEnabled = false;
browser.Size = new Size(Screen.PrimaryScreen.WorkingArea.Width, );
browser.Show();
}
else
{
browser.Hide();
}
var arg = (STAContext)state;
byte[] postData = null;
string headers = null;
if (arg.RequestContent != null)
{
if (this.UseCookies)
{
if (arg.RequestContent.HasCookie)
{
_cookieContainer.Add(arg.RequestUrl, arg.RequestContent.Cookies);
}
string cookieHeader = arg.RequestContent.Headers[HttpRequestHeader.Cookie];
if (!string.IsNullOrEmpty(cookieHeader))
{
_cookieContainer.SetCookies(arg.RequestUrl, cookieHeader.Replace(';', ','));
arg.RequestContent.Headers.Remove(HttpRequestHeader.Cookie);
}
cookieHeader = _cookieContainer.GetCookieHeader(arg.RequestUrl);
if (cookieHeader.Length > )
{
arg.RequestContent.Headers[HttpRequestHeader.Cookie] = cookieHeader.Replace(',', ';');
}
//WinInetInterop.SaveCookies(_cookieContainer, absoluteUri);
}
else
{
arg.RequestContent.Headers[HttpRequestHeader.Cookie] = string.Empty;
//WinInetInterop.DeleteCache(WinInetInterop.CacheKind.Cookies);
}
if (arg.RequestContent.HasBody)
{
arg.RequestContent.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
postData = Encoding.UTF8.GetBytes(arg.RequestContent.GetFormString());
}
headers = arg.RequestContent.GetHeadersString();
}
browser.Navigate(arg.RequestUrl, "_self", postData, headers); STA_Run(STA_Wait, browser, arg);
//会阻塞当前线程
Application.Run();
}, context, context);
}
private void STA_Wait(object state)
{
var browser = (WebBrowser)state;
#if DEBUG
App.LogInfo("STA_Wait {0}", browser.Url);
#endif
var arg = (STAContext)browser.ObjectForScripting;
try
{
int srTimeout = this.SendReceiveTimeout;
if (srTimeout > - && !arg.SendReceiveWaiter.WaitOne(srTimeout))
{
//请求超时
browser.Invoke((Action)(() =>
{
if (browser.ReadyState != WebBrowserReadyState.Complete)
{
browser.Stop();
App.LogInfo("HttpBrowser SendReceive Timeout {0}", arg.RequestUrl);
}
}));
}
WaitAjax(arg);
}
catch (Exception ex)
{
App.LogError(ex, "HttpBrowser STA_Wait {0}", arg.RequestUrl);
HandleException(ex);
}
} private void browser_NewWindow(object sender, System.ComponentModel.CancelEventArgs e)
{
var browser = (WebBrowser)sender;
var node = browser.Document.ActiveElement;
string link;
if (node != null && !string.IsNullOrEmpty(link = node.GetAttribute("href")))
{
e.Cancel = true;
browser.Navigate(link);
}
}
private void browser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
var browser = (WebBrowser)sender;
#if DEBUG
App.LogInfo("browser_Navigating {0}", browser.Url);
#endif
var arg = (STAContext)browser.ObjectForScripting;
arg.DelayLazyLoad();
}
private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var browser = (WebBrowser)sender;
#if DEBUG
App.LogInfo("browser_DocumentCompleted {0}", browser.Url);
#endif
var arg = (STAContext)browser.ObjectForScripting;
try
{
//e.Url不会变res://
if (!CheckDocument(browser.Url))
{
App.LogInfo("HttpBrowser DocumentCompleted Cancel {0}", browser.Url);
return;
}
if (browser.ReadyState != WebBrowserReadyState.Complete)
{
return;
} //发生redirect或iframe load
if (browser.Url != e.Url)
{
App.LogInfo("HttpBrowser Redirect {0} to {1}", arg.RequestUrl, e.Url);
}
if (this.UseCookies)
{
WinInetInterop.LoadCookies(_cookieContainer, browser.Document.Url);
}
InjectScript(browser.Document, @"if (typeof ($) == 'undefined') {
var script = document.createElement('script');
script.src = 'http://libs.baidu.com/jquery/1.9.0/jquery.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
function Soubiscbot(kind) {
switch (kind) {
case 0:
var set = [];
$(arguments[1]).each(function (i, o) {
var me = $(o);
var id = me.attr('id');
if (!id) {
id = Math.random();
me.attr('id', id);
}
set[i] = id;
});
return set.toString();
break;
case 1:
try {
return arguments[1]();
}
catch (ex) {
return ex.toString();
}
break;
default:
return document.documentElement.outerHTML;
break;
}
}"); if (this.SendReceiveTimeout > -)
{
arg.SendReceiveWaiter.Set();
}
SetAjax(browser, false);
if (_onLoad != null)
{
_onLoad(arg, browser.Document);
}
if (arg.IsRedirect)
{
STA_Run(STA_Wait, browser, arg);
}
else
{
arg.RegisterLazyLoad(x =>
{
var b = (WebBrowser)x;
if (b.IsDisposed)
{
return;
}
b.Invoke((Action<WebBrowser>)ProcessAjaxEvent, b);
b.Invoke((Action<object>)Callback, b);
}, browser);
}
}
catch (Exception ex)
{
App.LogError(ex, "HttpBrowser DocumentCompleted RequestUrl={0} BrowserUrl={1}", arg.RequestUrl, browser.Url);
HandleException(ex);
}
} private static void HandleException(Exception ex)
{
if (ex is OutOfMemoryException || ex is AccessViolationException)
{
App.LogInfo("HttpBrowser auto exit {0}", ex.HResult);
Environment.Exit(ex.HResult);
}
}
#endregion #region Callback
private void Callback(object state)
{
var browser = (WebBrowser)state;
#if DEBUG
App.LogInfo("Callback {0}", browser.Url);
#endif
var arg = (STAContext)browser.ObjectForScripting;
if (!Monitor.TryEnter(arg))
{
return;
}
try
{
#warning HACK
if (this.Snapshot.HasValue)
{
Thread.Sleep();
}
browser.Invoke((Action)(() =>
{
if (this.Snapshot.HasValue)
{
//Guid fileID = CryptoManaged.MD5Hash(browser.Url.OriginalString);//browser.Url为ResponseUrl
Guid fileID = Guid.NewGuid();
var js = new StringBuilder();
js.AppendFormat("document.body.setAttribute('{0}', '{1}');", Callback_Snapshot, fileID);
js.Append(@" window.addEventListener('load', function () {
window.scrollTo(0, document.documentElement.offsetHeight);
});
");
browser.Document.InvokeScript("eval", new object[] { js.ToString() });
string savePath = Path.Combine(this.SaveFileDirectory, string.Format("{0}.png", fileID));
try
{
var shotSize = this.Snapshot.Value == Size.Empty ? browser.Document.Body.ScrollRectangle.Size : this.Snapshot.Value;
browser.Size = shotSize;
using (var img = new Bitmap(browser.Width, browser.Height))
{
//browser.DrawToBitmap(img, new Rectangle(Point.Empty, img.Size));
NativeMethods.DrawTo(browser.ActiveXInstance, img, Color.White);
img.Save(savePath, System.Drawing.Imaging.ImageFormat.Png);
App.LogInfo("xSnapshot {0} {1}", browser.Url, savePath);
}
}
catch (Exception ex)
{
App.LogError(ex, "xSnapshot {0} {1}", browser.Url, savePath);
}
}
InvokeHtml(browser);
}));
}
catch (Exception ex)
{
App.LogError(ex, "HttpBrowser Callback {0}", arg.RequestUrl);
HandleException(ex);
}
finally
{
Monitor.Exit(arg);
STA_Exit(browser);
}
} /// <summary>
/// !重要! 退出STAUI线程
/// </summary>
private void STA_Exit(WebBrowser browser)
{
#if DEBUG
App.LogInfo("STA_Exit {0}", browser.Url);
#endif
RestoreSystemProxy();
var arg = (STAContext)browser.ObjectForScripting;
if (arg.WaitHandle != null)
{
arg.WaitHandle.Set();
}
try
{
browser.Stop();
arg.AjaxUnmarks();
//arg._Apartment.Dispose();
browser.Invoke((Action)(() => Application.ExitThread()));
browser.Dispose();
}
catch (SystemException ex)
{
//AccessViolationException
//InvalidComObjectException
App.LogError(ex, "HttpBrowser STA_Exit {0}", arg.RequestUrl);
}
}
#endregion
}
}

HttpBrowser

#region Spawned Process
public bool IsSpawned { get; set; } internal string SpawnedStart(EndPoint proxy, Uri requestUrl, HttpRequestContent content)
{
#if DEBUG
App.LogInfo("SpawnedStart: Proxy={0}\tUrl={1}", proxy, requestUrl);
#endif
bool hasValue = content != null;
var stream = Serializer.Serialize(Tuple.Create(proxy, requestUrl,
hasValue ? content.Headers : null,
hasValue ? content.Form : null));
RestoreSystemProxy();
string[] args = Environment.GetCommandLineArgs();
string arg = string.Format("x#{0}", Convert.ToBase64String(stream.ToArray()));
var proc = Process.Start(new ProcessStartInfo(args[], arg)
{
RedirectStandardOutput = true,
UseShellExecute = false,
});
string html = proc.StandardOutput.ReadToEnd();
if (!proc.WaitForExit( * ))
{
proc.Kill();
}
proc.Close();
return html;
} public static bool SpawnedMain()
{
string[] args = Environment.GetCommandLineArgs();
if (!(args.Length > && args[].StartsWith("x#")))
{
return false;
}
var stream = new MemoryStream(Convert.FromBase64String(args[].Substring()));
var arg = (Tuple<EndPoint, Uri, WebHeaderCollection, NameValueCollection>)Serializer.Deserialize(stream);
var client = (IHttpClient)new HttpBrowser();
if (arg.Item1 != null)
{
client.SetProxy(arg.Item1);
}
string html = client.GetHtml(arg.Item2, new HttpRequestContent()
{
Headers = arg.Item3,
Form = arg.Item4
});
Console.WriteLine(html);
return true;
}
#endregion