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