Jak mogę wykonać żądanie GET bez pobierania zawartości?

Pracuję nad sprawdzaniem linków, ogólnie mogę wykonywać HEAD żądania, jednak niektóre strony wydają się wyłączać ten czasownik, więc przy niepowodzeniu muszę również wykonać GET żądanie (aby dwukrotnie sprawdzić link jest naprawdę martwy)

Używam poniższego kodu jako testera linków:

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;
}
To wszystko działa dobrze i elegancko. Tyle, że kiedy wykonuję GET żądanie, cały ładunek zostanie pobrany(oglądałem to w wireshark).

Czy Jest jakiś sposób, aby skonfigurować podstawowe ServicePoint lub HttpWebRequest aby w ogóle nie buforować lub nie ładować ciała odpowiedzi?

(gdybym ręcznie To kodował, ustawiłbym okno odbioru TCP na bardzo niskim poziomie, a następnie pobierał tylko tyle pakietów, aby uzyskać nagłówki, przestał ackingować pakiety TCP, jak tylko będę miał wystarczająco dużo informacji.)

dla tych, którzy zastanawiają się, co to ma osiągnąć, nie chcę pobierać 40K 404, gdy dostaję 404, robienie tego kilkaset tysięcy razy jest drogie w sieci

Author: Community, 2012-05-25

3 answers

Gdy wykonasz polecenie GET, serwer zacznie wysyłać dane Od początku pliku do końca. Chyba, że mu przeszkodzisz. Oczywiście, przy 10 Mb / s, to będzie megabajt na sekundę, więc jeśli plik jest mały dostaniesz całość. Możesz zminimalizować kwotę, którą faktycznie pobierasz na kilka sposobów.

Najpierw możesz wywołać request.Abort po otrzymaniu odpowiedzi i przed wywołaniem response.close. Zapewni to, że kod źródłowy nie będzie próbował pobrać całości przed zamknięciem odpowiedź. Czy to pomaga na małych plikach, Nie wiem. Wiem, że zapobiegnie to zawieszeniu Twojej aplikacji podczas próby pobrania pliku wielogigabajtowego.

Inną rzeczą, którą możesz zrobić, to zażądać zakresu, a nie całego pliku. Zobacz metodę AddRange i jej przeciążenia. Można na przykład napisać request.AddRange(512), który pobierze tylko pierwsze 512 bajtów pliku. Zależy to oczywiście od serwera obsługującego zapytania range. Jak większość. Ale wtedy, większość próśb o wsparcie.

Prawdopodobnie będziesz musiał napisać metodę, która próbuje rzeczy w kolejności:

  • spróbuj zrobić prośbę o głowę. Jeśli to działa (tzn. nie zwraca 500), to gotowe
  • spróbuj uzyskać z zapytaniem o zakres. Jeśli to nie zwróci 500, to jesteś skończony.
  • wykonaj zwykły GET, z request.Abort po GetResponse zwraca.
 8
Author: Jim Mischel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-25 14:17:05

Jeśli używasz żądania GET, otrzymasz treść wiadomości niezależnie od tego, czy chcesz, czy nie. Dane będą nadal przesyłane do punktu końcowego niezależnie od tego, czy odczytano je z gniazda, czy też nie. Dane pozostaną w kolejce w RecvQ czekając na wybranie.

W tym celu powinieneś użyć prośby "HEAD", jeśli to możliwe, która oszczędzi Ci treść wiadomości.

 1
Author: Peter Grace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-25 12:14:22

Nie można użyć WebClient, aby otworzyć strumień i odczytać tylko kilka bajtów, których potrzebujesz?

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
                }
            }
        }

Nie jestem pewien, jak WebClient otwiera strumień wewnętrznie. Ale wydaje się, że umożliwia częściowy odczyt danych.

 0
Author: nunespascal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-25 04:23:53