2012-05-25 16 views
26

estoy trabajando en un comprobador de enlaces, en general puedo realizar HEAD solicitudes, sin embargo, algunos sitios parecen desactivar este verbo, por lo que en caso de fallo que necesito para llevar a cabo también una solicitud GET (que vuelva a comprobar el enlace está realmente muerto)¿Cómo puedo realizar una solicitud GET sin descargar el contenido?

uso el siguiente código como mi probador enlace:

public class ValidateResult 
{ 
    public HttpStatusCode? StatusCode { get; set; } 
    public Uri RedirectResult { get; set; } 
    public WebExceptionStatus? WebExceptionStatus { get; set; } 
} 


public ValidateResult Validate(Uri uri, bool useHeadMethod = true, 
      bool enableKeepAlive = false, int timeoutSeconds = 30) 
{ 
    ValidateResult result = new ValidateResult(); 

    HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest; 
    if (useHeadMethod) 
    { 
    request.Method = "HEAD"; 
    } 
    else 
    { 
    request.Method = "GET"; 
    } 

    // always compress, if you get back a 404 from a HEAD it can be quite big. 
    request.AutomaticDecompression = DecompressionMethods.GZip; 
    request.AllowAutoRedirect = false; 
    request.UserAgent = UserAgentString; 
    request.Timeout = timeoutSeconds * 1000; 
    request.KeepAlive = enableKeepAlive; 

    HttpWebResponse response = null; 
    try 
    { 
    response = request.GetResponse() as HttpWebResponse; 

    result.StatusCode = response.StatusCode; 
    if (response.StatusCode == HttpStatusCode.Redirect || 
     response.StatusCode == HttpStatusCode.MovedPermanently || 
     response.StatusCode == HttpStatusCode.SeeOther) 
    { 
     try 
     { 
     Uri targetUri = new Uri(Uri, response.Headers["Location"]); 
     var scheme = targetUri.Scheme.ToLower(); 
     if (scheme == "http" || scheme == "https") 
     { 
      result.RedirectResult = targetUri; 
     } 
     else 
     { 
      // this little gem was born out of http://tinyurl.com/18r 
      // redirecting to about:blank 
      result.StatusCode = HttpStatusCode.SwitchingProtocols; 
      result.WebExceptionStatus = null; 
     } 
     } 
     catch (UriFormatException) 
     { 
     // another gem... people sometimes redirect to http://nonsense:port/yay 
     result.StatusCode = HttpStatusCode.SwitchingProtocols; 
     result.WebExceptionStatus = WebExceptionStatus.NameResolutionFailure; 
     } 

    } 
    } 
    catch (WebException ex) 
    { 
    result.WebExceptionStatus = ex.Status; 
    response = ex.Response as HttpWebResponse; 
    if (response != null) 
    { 
     result.StatusCode = response.StatusCode; 
    } 
    } 
    finally 
    { 
    if (response != null) 
    { 
     response.Close(); 
    } 
    } 

    return result; 
} 

todo esto funciona bien y dandy. Excepto que cuando realizo una solicitud GET, toda la carga útil se descarga (vi esto en wireshark).

¿Hay alguna manera de configurar el ServicePoint subyacente o la HttpWebRequest no para amortiguar o la carga ansiosa el cuerpo de la respuesta en absoluto?

(Si estuviera codificación manual esta fijaría ventana realmente bajo la recepción de TCP, y sólo agarrar suficientes paquetes para obtener los encabezados, dejar de acking paquetes TCP, tan pronto como tengo suficiente información.)

para aquellos que se preguntan qué se pretende lograr, no quiero descargar un 40k 404 cuando obtengo un 404, hacerlo unos cientos de miles es caro en la red

+0

Nota: aunque la codificación manual de la versión HTTP es bastante simple, la HTTPS me asusta un poco. (tal vez hay una biblioteca de sistema operativo que hace esto ya?) –

+0

Pruebe una descarga parcial. Es posible descargar solo un rango con el rango http de rango. – rekire

+1

@rekire 'Content-Range' puede estar bien para los servidores HTTP 1.1 que tienen el contenido, pero si obtiene un 404, todavía se lo devolverá por completo –

Respuesta

8

Cuando realice un GET, el servidor comenzará a enviar datos desde el inicio del archivo hasta el final. A menos que lo interrumpas. De acuerdo, a 10 Mb/seg, va a ser un megabyte por segundo, de modo que si el archivo es pequeño lo obtendrás todo. Puede minimizar la cantidad que realmente descarga de varias maneras.

Primero, puede llamar al request.Abort después de obtener la respuesta y antes de llamar al response.close. Eso asegurará que el código subyacente no intente descargar todo antes de cerrar la respuesta. Si esto ayuda en archivos pequeños, no lo sé. Sé que evitará que su aplicación se cuelgue cuando intente descargar un archivo de varios gigabytes.

La otra cosa que puede hacer es solicitar un rango, en lugar de todo el archivo. Consulte el método AddRange y sus sobrecargas. Podría, por ejemplo, escribir request.AddRange(512), que descargaría solo los primeros 512 bytes del archivo. Esto depende, por supuesto, de las consultas de rango de soporte del servidor. Debe hacerse. Pero entonces, la mayoría también admite solicitudes HEAD.

probablemente va a terminar encima de tener que escribir un método que trata de cosas en secuencia:

  • tratar de hacer una petición HEAD. Si eso funciona (es decirno devuelve un 500), entonces ha terminado
  • intente GET con una consulta de rango. Si eso no arroja un 500, entonces ya terminaste.
  • haga un GET regular, con un request.Abort después de GetResponse regresa.
+0

Una llamada para solicitarla.Abortar, lo suficientemente temprano hará que el ACK regrese con un conjunto de indicadores "FIN" , esto cerrará la conexión correctamente sin que el cliente reciba una gran cantidad de datos. El único signo de interrogación que tengo es sobre la capacidad de establecer el tamaño de ventana de recepción del cliente ... –

+0

hay algunas correcciones críticas ... HEAD puede devolver 404 pero obtener puede devolver un 200. La consulta del rango GET realmente hace poca diferencia al despertar de un aborto en funcionamiento (debería ser, por ejemplo, un código de estado de devolución menor que 400) –

+0

"Podría, por ejemplo, escribir' request.AddRange (512) ', que descargaría solo los primeros 512 bytes del archivo." ¿No debería ser '-512'? MSDN establece: "Si el rango es negativo, el parámetro range especifica el punto final del rango. El servidor debe comenzar a enviar datos desde el inicio de los datos en la entidad HTTP al parámetro de rango especificado". (http://msdn.microsoft.com/en-us/library/4ds43y3w) –

0

¿No podría usar un WebClient para abrir una secuencia y leer solo los pocos bytes que necesita?

using (var client = new WebClient()) 
     { 
      using (var stream = client.OpenRead(uri)) 
      { 
       const int chunkSize = 100; 
       var buffer = new byte[chunkSize]; 
       int bytesRead; 
       while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) 
       { 
        //check response here 
       } 
      } 
     } 

No estoy seguro de cómo WebClient abre la corriente internamente. Pero parece permitir la lectura parcial de datos.

+2

WebClient.OpenRead (...) usa el método GetResponse() internamente, por lo que este método no funcionará. Lo descargará todo. – Mikhail

+0

confirmado ... no funciona ... http://i.stack.imgur.com/wGRrv.png –

+0

Sí, lo intenté también. Parece que no puede encontrar ninguna clase incorporada que permita procesar respuestas web parciales. Debería haber sido posible al menos cuando se usan operaciones asíncronas. – nunespascal

1

Si está utilizando una solicitud GET, recibirá el mensaje-cuerpo si quiere o no. Los datos se seguirán transmitiendo a su punto final independientemente de si lo ha leído o no desde el socket. Los datos se mantendrán en cola en el RecvQ esperando ser seleccionados.

Para esto, realmente debería usar una solicitud "HEAD" si es posible, lo que le ahorrará el cuerpo del mensaje.

+1

Consulte la respuesta de Jim, el método .Abort funciona, establece el indicador FIN con el ACK, que cierra la conexión correctamente –

Cuestiones relacionadas