Как я могу выполнить запрос GET без загрузки контента?

Я работаю над проверкой ссылок, в общем могу выполнить HEAD запросы, однако некоторые сайты, кажется, отключить этот глагол, поэтому в случае неудачи мне нужно также выполнить GET запрос (перепроверить ссылку действительно мертв)

В качестве тестера ссылок я использую следующий код:

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

Это все работает отлично и денди. За исключением того, что когда я выполняю GET запрос, весь полезный груз загружен (я наблюдал это в wireshark).

Есть ли способ настроить базовый ServicePoint или HttpWebRequest не буферизировать или не загружать тело ответа вообще?

(Если бы я вручную кодировал это, я бы установил окно приема TCP на очень низкое значение, а затем собирал только достаточное количество пакетов, чтобы получить заголовки, прекрати блокировать TCP-пакеты, как только у меня будет достаточно информации.)

для тех, кто интересуется, для чего это нужно, я не хочу загружать 40k 404, когда получаю 404, делать это несколько сотен тысяч раз дороже в сети

3 ответа

Решение

Когда вы выполните GET, сервер начнет отправку данных от начала файла до конца. Если только вы не прервете это. Конечно, при скорости 10 Мбит / с это будет мегабайт в секунду, поэтому, если файл небольшой, вы получите все это. Вы можете минимизировать сумму, которую вы фактически загружаете несколькими способами.

Во-первых, вы можете позвонить request.Abort после получения ответа и перед звонком response.close, Это гарантирует, что базовый код не будет пытаться загрузить все перед закрытием ответа. Поможет ли это на небольших файлах, я не знаю. Я знаю, что это предотвратит зависание вашего приложения при попытке загрузить файл размером в несколько гигабайт.

Другая вещь, которую вы можете сделать, это запросить диапазон, а не весь файл. Смотрите метод AddRange и его перегрузки. Вы могли бы, например, написать request.AddRange(512), который будет загружать только первые 512 байт файла. Это зависит, конечно, от сервера, поддерживающего запросы диапазона. Большинство делают. Но тогда большинство запросов на поддержку HEAD тоже.

Вы, вероятно, в конечном итоге должны будете написать метод, который пробует вещи в последовательности:

  • попробуйте сделать запрос HEAD. Если это работает (то есть не возвращает 500), то все готово
  • попробуйте GET с запросом диапазона. Если это не вернет 500, то все готово.
  • сделать обычный GET, с request.Abort после GetResponse возвращается.

Если вы используете запрос GET, вы получите тело сообщения, хотите вы этого или нет. Данные по-прежнему будут передаваться на вашу конечную точку независимо от того, читаете вы их из сокета или нет. Данные будут просто оставаться в очереди в RecvQ, ожидая, когда их выберут.

Для этого вам действительно следует использовать запрос "HEAD", если это возможно, что избавит вас от тела сообщения.

Не могли бы вы использовать WebClient, чтобы открыть поток и прочитать только те байты, которые вам нужны?

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

Я не уверен, как WebClient открывает поток внутренне. Но это, кажется, позволяет частичное чтение данных.

Другие вопросы по тегам