C # Как я могу проверить, существует ли / действителен ли URL?

117

Я делаю простую программу на Visual C # 2005, которая ищет символ акций на Yahoo! Finance загружает исторические данные, а затем строит историю цен для указанного тикерного символа.

Я знаю точный URL-адрес, который мне нужен для получения данных, и если пользователь вводит существующий символ тикера (или хотя бы один с данными из Yahoo! Finance), он работает отлично. Однако у меня возникает ошибка времени выполнения, если пользователь создает символ тикера, поскольку программа пытается извлечь данные с несуществующей веб-страницы.

Я использую класс WebClient и функцию DownloadString. Я просмотрел все другие функции-члены класса WebClient, но не нашел ничего, что можно было бы использовать для проверки URL-адреса.

Как я могу это сделать?

Дэниел Уолтрип
источник
1
обновлено, чтобы показать использование C # 2.0 (VS2005)
Марк Грейвелл

Ответы:

110

Вы могли бы отправить запрос «HEAD», а не «GET»?

(править) - лол! Похоже, я делал это раньше !; изменен на вики, чтобы избежать обвинений в репутации. Итак, чтобы протестировать URL без затрат на загрузку контента:

// using MyClient from linked post
using(var client = new MyClient()) {
    client.HeadOnly = true;
    // fine, no content downloaded
    string s1 = client.DownloadString("http://google.com");
    // throws 404
    string s2 = client.DownloadString("http://google.com/silly");
}

Вы бы try/ catchвокруг, DownloadStringчтобы проверить наличие ошибок; Нет ошибки? Это существует...


С C # 2.0 (VS2005):

private bool headOnly;
public bool HeadOnly {
    get {return headOnly;}
    set {headOnly = value;}
}

и

using(WebClient client = new MyClient())
{
    // code as before
}
Марк Гравелл
источник
FWIW - Не уверен, действительно ли это решает проблему (кроме, возможно, другого поведения на стороне клиента), поскольку вы просто меняете метод HTTP. Ответ сервера будет сильно зависеть от того, как закодирована логика, и может не работать для динамических сервисов, таких как цена акций. Для статических ресурсов (например, изображений, файлов и т. Д.) HEAD обычно работает так, как рекламируется, поскольку он встроен в сервер. Многие программисты явно не выполняют запросы HEAD, поскольку обычно основное внимание уделяется POST и GET. YMMV
Дэвид Тейлор
Извините, что так долго выбирал ответ ... Я отвлекся от учебы и работы и как бы забыл об этом посте. Кстати, я не мог заставить ваше решение работать, потому что я использую Visual Studio 2005, в котором нет типа var. Я не работал над этим проектом несколько месяцев, но есть ли простое решение для этого факта? Также, когда я пытался реализовать ваше решение, я вспомнил, что он рассердился на меня за попытку определить свойство HeadOnly без кода в определениях get и set. А может я просто что-то не так делала. Спасибо за помощь!
Дэниел Уолтрип,
Что такое MyClient ?
Kiquenet
@Kiquenet в теле есть ссылка: stackoverflow.com/questions/153451/…
Марк Гравелл
136

Вот еще одна реализация этого решения:

using System.Net;

///
/// Checks the file exists or not.
///
/// The URL of the remote file.
/// True : If the file exits, False if file not exists
private bool RemoteFileExists(string url)
{
    try
    {
        //Creating the HttpWebRequest
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        //Setting the Request method HEAD, you can also use GET too.
        request.Method = "HEAD";
        //Getting the Web Response.
        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
        //Returns TRUE if the Status code == 200
        response.Close();
        return (response.StatusCode == HttpStatusCode.OK);
    }
    catch
    {
        //Any exception will returns false.
        return false;
    }
}

От: http://www.dotnetoughtts.net/2009/10/14/how-to-check-remote-file-exists-using-c/

BigJoe714
источник
2
Я использую этот код, чтобы проверить, существует ли куча изображений, и он довольно медленный (пара секунд на URL). Кто-нибудь знает, является ли это проблемой с этим кодом или просто фактом жизни при таких звонках?
ssmith
@ssmith Один из способов ускорить свой код - это выполнить проверку в цикле Parallel.Foreach, если вы еще этого не пробовали. Это сделало мое приложение для тестирования URL НАМНОГО быстрее.
Джек Фэрфилд
3
Этот материал возвращает DisposedObject (response.StatusCode == HttpStatusCode.OK); обертка в использовании
Лапенков Владимир
1
Возникла проблема с приведенным выше кодом. если вы сделаете response.Close (); то вы не можете проверить response.StatusCode, поскольку он закрыт, он выдаст исключение.
Renascent
@ssmith любой метод намного быстрее?
Kiquenet
36

Эти решения довольно хороши, но они забывают, что могут быть другие коды состояния, кроме 200 OK. Это решение, которое я использовал в производственных средах для мониторинга состояния и т.п.

Если на целевой странице есть перенаправление URL-адреса или какое-либо другое условие, возврат будет истинным с использованием этого метода. Кроме того, GetResponse () вызовет исключение, и, следовательно, вы не получите для него StatusCode. Вам нужно перехватить исключение и проверить наличие ProtocolError.

Любой код статуса 400 или 500 вернет false. Все остальные вернут истину. Этот код легко изменить в соответствии с вашими потребностями для конкретных кодов состояния.

/// <summary>
/// This method will check a url to see that it does not return server or protocol errors
/// </summary>
/// <param name="url">The path to check</param>
/// <returns></returns>
public bool UrlIsValid(string url)
{
    try
    {
        HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
        request.Timeout = 5000; //set the timeout to 5 seconds to keep the user from waiting too long for the page to load
        request.Method = "HEAD"; //Get only the header information -- no need to download any content

        using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
        {
            int statusCode = (int)response.StatusCode;
            if (statusCode >= 100 && statusCode < 400) //Good requests
            {
                return true;
            }
            else if (statusCode >= 500 && statusCode <= 510) //Server Errors
            {
                //log.Warn(String.Format("The remote server has thrown an internal error. Url is not valid: {0}", url));
                Debug.WriteLine(String.Format("The remote server has thrown an internal error. Url is not valid: {0}", url));
                return false;
            }
        }
    }
    catch (WebException ex)
    {
        if (ex.Status == WebExceptionStatus.ProtocolError) //400 errors
        {
            return false;
        }
        else
        {
            log.Warn(String.Format("Unhandled status [{0}] returned for url: {1}", ex.Status, url), ex);
        }
    }
    catch (Exception ex)
    {
        log.Error(String.Format("Could not test url {0}.", url), ex);
    }
    return false;
}
JSmith
источник
1
Я бы добавил, что некоторые коды состояния в диапазоне 3xx фактически вызовут выдачу ошибки, например 304 Not Modified, и в этом случае вы должны обрабатывать это в своем блоке catch
RobV
3
Только что столкнулся с проблемой выдергивания волос с этим подходом: HttpWebRequestне нравится, если вы не .Close()используете responseобъект до того, как попытаетесь загрузить что-нибудь еще. Потребовались часы, чтобы найти его!
jbeldock
4
HttpWebResponseобъект должен быть заключен в usingблок, поскольку он реализует, IDisposableчто также обеспечит закрытие соединения. Это может вызвать проблемы, с которыми столкнулся @jbeldock.
Хабиб
2
Он бросает 404 Not Founds на URL-адреса, которые нормально работают в браузере ...?
Майкл Транчида
Веб-серверы @MichaelTranchida, как известно, известны 404, когда вы запускаете метод, который не поддерживается. В вашем случае Headможет не поддерживаться на этом ресурсе, хотя Getможет быть. Вместо этого он должен был выбросить 405.
Шрирам Сакхивел
9

Если я правильно понял ваш вопрос, вы можете использовать небольшой метод вроде этого, чтобы получить результаты вашего URL-теста:

WebRequest webRequest = WebRequest.Create(url);  
WebResponse webResponse;
try 
{
  webResponse = webRequest.GetResponse();
}
catch //If exception thrown then couldn't get response from address
{
  return 0;
} 
return 1;

Вы можете обернуть приведенный выше код в метод и использовать его для проверки. Я надеюсь, что это ответ на ваш вопрос.

Программное обеспечение календаря
источник
1
Да, возможно, вы можете уточнить решение, различая разные случаи (сбой TCP-соединения - хост отказывается подключаться, 5xx - что-то фатальное, 404 - ресурс не найден и т. Д.). Взгляните на свойство Status WebException;)
Дэвид Тейлор,
Очень хорошее замечание, Дэвид! Это даст нам более подробную обратную связь, чтобы мы могли более проницательно справиться с ошибкой.
Calendar Software,
1
Спасибо. Я хочу сказать, что у этого лука есть несколько уровней, каждый из которых может бросить вызов работе (.Net Framework, разрешение DNS, TCP-соединение, целевой веб-сервер, целевое приложение и т. Д.). ИМХО, хороший дизайн должен уметь различать различные условия отказа, чтобы обеспечивать информативную обратную связь и полезную диагностику. Давайте также не будем забывать, что HTTP имеет коды состояния по какой-то причине;)
Дэвид Тейлор
6

Попробуйте это (убедитесь, что вы используете System.Net):

public bool checkWebsite(string URL) {
   try {
      WebClient wc = new WebClient();
      string HTMLSource = wc.DownloadString(URL);
      return true;
   }
   catch (Exception) {
      return false;
   }
}

Когда вызывается функция checkWebsite (), она пытается получить исходный код переданного в нее URL. Если он получает исходный код, он возвращает true. Если нет, возвращается false.

Пример кода:

//The checkWebsite command will return true:
bool websiteExists = this.checkWebsite("https://www.google.com");

//The checkWebsite command will return false:
bool websiteExists = this.checkWebsite("https://www.thisisnotarealwebsite.com/fakepage.html");
user6909992
источник
3

Вот еще вариант

public static bool UrlIsValid(string url)
{
    bool br = false;
    try {
        IPHostEntry ipHost = Dns.Resolve(url);
        br = true;
    }
    catch (SocketException se) {
        br = false;
    }
    return br;
}
Заин Али
источник
3
Это может быть полезно для проверки наличия хоста. Вопрос, очевидно, не в том, существует хост или нет. Это связано с обработкой неверного пути HTTP, если известно, что хост существует и все в порядке .
binki
3

Это решение кажется простым:

public static bool isValidURL(string url) {
    WebRequest webRequest = WebRequest.Create(url);
    WebResponse webResponse;
    try
    {
        webResponse = webRequest.GetResponse();
    }
    catch //If exception thrown then couldn't get response from address
    {
        return false ;
    }
    return true ;
}
abobjects.com
источник
1
не забудьте закрыть webResponse, иначе время отклика будет расти каждый раз, когда вы вызываете свой метод
Мадагага,
3
WebRequest request = WebRequest.Create("http://www.google.com");
try
{
     request.GetResponse();
}
catch //If exception thrown then couldn't get response from address
{
     MessageBox.Show("The URL is incorrect");`
}
Правин Дасаре
источник
1
Пожалуйста, добавьте пояснение к своему ответу. Ответы только на коде, как правило, сбивают с толку и бесполезны для будущих читателей и могут таким образом привлечь отрицательные голоса.
Джесси
2

У меня есть более простой способ определить, действителен ли URL-адрес.

if (Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
{
   //...
}
tsingroo
источник
4
Нет, этот метод не проверяет, действительно ли доступен URL-адрес. Он даже возвращает true, когда Uri.IsWellFformedUriString (" 192.168.1.421 ", ...), который использует явно неправильный URL-адрес
zhaorufei
2

Я всегда обнаруживал, что исключения обрабатываются намного медленнее.

Возможно, менее интенсивный способ даст лучший и более быстрый результат?

public bool IsValidUri(Uri uri)
{

    using (HttpClient Client = new HttpClient())
    {

    HttpResponseMessage result = Client.GetAsync(uri).Result;
    HttpStatusCode StatusCode = result.StatusCode;

    switch (StatusCode)
    {

        case HttpStatusCode.Accepted:
            return true;
        case HttpStatusCode.OK:
            return true;
         default:
            return false;
        }
    }
}

Тогда просто используйте:

IsValidUri(new Uri("http://www.google.com/censorship_algorithm"));
ржавый гвоздь
источник
1

Веб-серверы отвечают кодом состояния HTTP, указывающим результат запроса, например, 200 (иногда 202) означает успех, 404 - не найден и т. Д. (См. Здесь ). Предполагая, что адресная часть URL-адреса верна, и вы не получаете тайм-аут сокета, исключение, скорее всего, сообщает вам, что код состояния HTTP отличен от 200. Я бы предложил проверить класс исключения и посмотреть, несет ли исключение код состояния HTTP.

IIRC - рассматриваемый вызов вызывает исключение WebException или потомка. Проверьте имя класса, чтобы узнать, какой из них, и заключите вызов в блок try, чтобы перехватить условие.

Дэвид Тейлор
источник
2
На самом деле, все в диапазоне 200–299 означает успех, IIRC
Марк Грейвелл
Марк, ты абсолютно прав. Я намеренно избегал попадания в понятие «класс ошибки» (например, 5xx, 4xx, 3xx, 2xx и т. Д.), Поскольку это открывает целую банку червей. Даже обработка стандартных кодов (200, 302, 404, 500 и т.д.) намного лучше, чем полное игнорирование кодов.
Дэвид Тейлор
1

Следуя уже приведенным примерам, я бы сказал, что лучше всего также обернуть ответ таким использованием

    public bool IsValidUrl(string url)
    {
         try
         {
             var request = WebRequest.Create(url);
             request.Timeout = 5000;
             request.Method = "HEAD";

             using (var response = (HttpWebResponse)request.GetResponse())
             {
                response.Close();
                return response.StatusCode == HttpStatusCode.OK;
            }
        }
        catch (Exception exception)
        { 
            return false;
        }
   }
user3154431
источник