Подтвердить что ты не робот

Как я могу выполнить запрос 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, вся загрузка загружается (я смотрел это в wirehark).

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

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

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

4b9b3361

Ответ 1

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

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

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

Вероятно, вам придется написать метод, который пытается последовательно:

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

Ответ 2

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

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

Ответ 3

Не могли бы вы использовать 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 открывает поток внутри. Но, похоже, это позволяет частичное считывание данных.