Cómo evitar la excepción de Stackoverflow en HtmlAgilityPack para html muy malo

c# html-agility-pack stack-overflow

Pregunta

Estoy usando HtmlAgilityPack en una MVC 5 Web Api. 99.99% de las veces, no hay problemas ... los sitios se cargan y los analizo para extraer el texto que quiero. Mi API podría alcanzarse varios cientos de miles de veces al día sin problemas. Ha manejado felizmente más de 2 millones de visitas en 24 horas en el pasado ...

Ocasionalmente, sin embargo, los sitios web terriblemente formados causan una respuesta de error 500. Luego, todas las solicitudes posteriores obtienen 500 errores y el sitio queda completamente inutilizable. La única solución en este escenario es reiniciar la aplicación web. El sitio está alojado en Windows Azure. He utilizado grandes instancias de carga equilibrada y una vez que la CPU aumenta, se mantiene alta. En el pasado, esto se ha ejecutado bien en una sola instancia de Medium Azure (2 core / 3.5 GB RAM)

El error es un Stackoverflow ... que sé que no puedo atrapar.

Tenga en cuenta que este código NO bloquea una aplicación de consola

HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load("http://nursingandmidwiferycareersni.com/");            
Console.Write(doc.DocumentNode.InnerText);

... pero definitivamente se bloqueará una aplicación web MVC.

Sin embargo, en una aplicación web MVC simple, puedo reproducir el error de stackoverflow con un sitio como http://nursingandmidwiferycareersni.com/ . Si coloca http://nursingandmidwiferycareersni.com/ en https://validator.w3.org, ¡logrará obtener un error interno del servidor en validator.w3.org!

Haré un hack al código fuente HAP si es necesario para solucionar esto ... actualmente solo uso el paquete Nuget.

¿Es posible evitar que el stackoverflow suceda en HAP?
¿O hay una forma de comprobar si hay un html horrible y evitar que el bloqueo se produzca en primer lugar?

Respuesta popular

Prueba algo como esto, donde el método ParseHtml y el tipo ParsedHtml son solo marcadores de posición para que los completes:

public async Task<ParsedHtml> TryParseHtml(
    string untrustedHtml,
    CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<ParsedHtml>();

    var thread = new Thread(() =>
    {
        ParsedHtml result = ParseHtml(untrustedHtml);
        tcs.TrySetResult(result);
    });
    thread.Start();

    using (cancellationToken.Register(() => tcs.TrySetCanceled()))
    {
        try
        {
            return await tcs.Task;
        }
        catch (OperationCanceledException)
        {
            thread.Abort();
            throw;
        }
    }
}

La idea podría extenderse a ser más eficiente al reutilizar los hilos en el caso exitoso, en lugar de encender y cortar un hilo por cada página HTML.



Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow