C # Como posso verificar se um URL existe / é válido?


117

Estou criando um programa simples em visual c # 2005 que procura um símbolo de ação no Yahoo! Finanças, baixa os dados históricos e, a seguir, plota o histórico de preços para o símbolo de ação especificado.

Sei a URL exata de que preciso para adquirir os dados e, se o usuário inserir um símbolo de ação existente (ou pelo menos um com dados no Yahoo! Finance), ele funcionará perfeitamente. No entanto, eu tenho um erro de tempo de execução se o usuário inventar um símbolo de ação, enquanto o programa tenta extrair dados de uma página da web inexistente.

Estou usando a classe WebClient e a função DownloadString. Examinei todas as outras funções de membro da classe WebClient, mas não vi nada que pudesse usar para testar um URL.

Como posso fazer isso?


1
atualizado para mostrar o uso de C # 2.0 (VS2005)
Marc Gravell

Respostas:


110

Você poderia emitir uma solicitação "HEAD" em vez de "GET"?

(editar) - lol! Parece que já fiz isso antes !; alterado para wiki para evitar acusações de rep-grupamento. Portanto, para testar um URL sem o custo de baixar o conteúdo:

// 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");
}

Você faria try/ catchao redor do DownloadStringpara verificar se há erros; nenhum erro? Isso existe...


Com C # 2.0 (VS2005):

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

e

using(WebClient client = new MyClient())
{
    // code as before
}

FWIW - Não tenho certeza se isso realmente resolve o problema (exceto, talvez, um comportamento diferente do lado do cliente), pois você está simplesmente mudando o método HTTP. A resposta do servidor dependerá muito de como a lógica está codificada e pode não funcionar bem para um serviço dinâmico como o preço das ações. Para recursos estáticos (por exemplo, imagens, arquivos, etc.), o HEAD geralmente funciona conforme anunciado, pois é embutido no servidor. Muitos programadores não fazem solicitações HEAD explicitamente, pois o foco normalmente está em POST e GET. YMMV
David Taylor

Desculpe por demorar tanto para escolher uma resposta ... Eu me desviei da escola e do trabalho e meio que esqueci este post. Como nota lateral, não consegui fazer sua solução funcionar porque estou usando o Visual Studio 2005, que não tem o tipo 'var'. Não trabalho neste projeto há meses, mas existe uma solução simples para esse fato? Além disso, quando tentei implementar sua solução, lembro que fiquei bravo comigo por tentar definir a propriedade HeadOnly sem nenhum código nas definições 'get' e 'set'. Ou talvez eu estivesse apenas fazendo algo errado. Obrigado pela ajuda!
Daniel Waltrip

O que é MyClient ?
Kiquenet

@Kiquenet tem um link no corpo, para aqui: stackoverflow.com/questions/153451/…
Marc Gravell

136

Aqui está outra implementação desta solução:

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

De: http://www.dotnetthoughts.net/2009/10/14/how-to-check-remote-file-exists-using-c/


2
Estou usando este código para verificar se existem várias imagens, e é bastante lento (alguns segundos por URL). Alguém sabe se isso é um problema com este código ou apenas um fato da vida ao fazer esse tipo de chamada?
ssmith

@ssmith Uma maneira de acelerar seu código é fazer a verificação em um loop Parallel.Foreach, se ainda não tiver tentado. Isso tornou meu aplicativo de teste de url MUITO mais rápido.
Jack Fairfield

3
Este material lança DisposedObject em retorno (response.StatusCode == HttpStatusCode.OK); envolver usando
Lapenkov Vladimir

1
Há um problema com o código acima. se você fizer response.Close (); então você não pode verificar por response.StatusCode quando estiver próximo, ele lançará uma exceção.
Renascent de

@ssmith algum método muito mais rápido?
Kiquenet

36

Essas soluções são muito boas, mas eles se esquecem de que pode haver outros códigos de status além de 200 OK. Esta é uma solução que usei em ambientes de produção para monitoramento de status e tal.

Se houver um redirecionamento de url ou alguma outra condição na página de destino, o retorno será verdadeiro usando este método. Além disso, GetResponse () lançará uma exceção e, portanto, você não obterá um StatusCode para ela. Você precisa interceptar a exceção e verificar se há um ProtocolError.

Qualquer código de status 400 ou 500 retornará falso. Todos os outros retornam verdadeiros. Este código é facilmente modificado para atender às suas necessidades de códigos de status específicos.

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

1
Eu acrescentaria que alguns códigos de status na faixa 3xx realmente causarão um erro a ser lançado, por exemplo, 304 Não Modificado, caso em que você deve lidar com isso em seu bloco de captura
RobV

3
Acabei de experimentar um problema de arrancar os cabelos com esta abordagem: HttpWebRequestnão gosto se você não pegar .Close()o responseobjeto antes de tentar baixar qualquer outra coisa. Demorou horas para encontrar aquele!
jbeldock

4
HttpWebResponseobjeto deve ser colocado em um usingbloco, uma vez que implementa o IDisposableque também irá garantir o fechamento da conexão. Isso pode causar problemas como @jbeldock enfrentou.
Habib

2
Ele está jogando 404 Not Founds em urls que funcionam bem em um navegador ...?
Michael Tranchida

Os servidores da Web @MichaelTranchida são notoriamente conhecidos por 404 quando você emite um método que não é compatível. No seu caso, Headpode não ser compatível com esse recurso, embora Getpossa ser. Deveria ter lançado 405 em vez disso.
Sriram Sakthivel

9

Se entendi sua pergunta corretamente, você poderia usar um pequeno método como este para fornecer os resultados do seu teste de 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;

Você pode envolver o código acima em um método e usá-lo para realizar a validação. Espero que isso responda à pergunta que você estava fazendo.


1
Sim, talvez você possa refinar a solução diferenciando entre diferentes casos (falha de conexão TCP - host recusa conexão, 5xx - Algo fatal aconteceu, 404 - Recurso não encontrado etc). Dê uma olhada na propriedade Status de WebException;)
David Taylor

Muito bom ponto David! Isso nos forneceria um feedback mais detalhado para que pudéssemos lidar com o erro com mais astúcia.
Software de calendário

1
Obrigado. Meu ponto é que existem várias camadas para esta cebola, cada uma das quais pode jogar uma chave no trabalho (.Net Framework, resolução de DNS, conectividade TCP, servidor da Web de destino, aplicativo de destino, etc.). IMHO um bom design deve ser capaz de discriminar entre as diferentes condições de falha para fornecer feedback informativo e diagnósticos utilizáveis. Também não podemos esquecer que o HTTP tem códigos de status por um motivo;)
David Taylor

6

Tente isto (certifique-se de usar System.Net):

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

Quando a função checkWebsite () é chamada, ela tenta obter o código-fonte do URL passado para ela. Se obtiver o código-fonte, ele retorna verdadeiro. Caso contrário, ele retorna falso.

Exemplo de código:

//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");

3

Aqui está outra opção

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
Isso pode ser útil para verificar se existe um host. A questão obviamente não está preocupada se o hospedeiro existe ou não. Ele está preocupado em lidar com um caminho HTTP incorreto, uma vez que o host é conhecido por existir e estar bem .
binki de

3

Esta solução parece fácil de seguir:

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

1
não se esqueça de fechar o webResponse, caso contrário, o tempo de resposta aumentará cada vez que você chamar seu método
Madagaga

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
Por favor, adicione alguma explicação à sua resposta. Respostas apenas em código tendem a ser confusas e não são úteis para leitores futuros e podem atrair votos negativos dessa maneira.
Jesse

2

Eu tenho uma maneira mais simples de determinar se um url é válido.

if (Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
{
   //...
}

4
Não, este método não verifica se o url está realmente acessível. Ele até retorna verdadeiro quando Uri.IsWellFormedUriString (" 192.168.1.421 ", ...), que usa um url obviamente incorreto
zhaorufei

2

Sempre descobri que as exceções são muito mais lentas para serem tratadas.

Talvez uma forma menos intensiva produzisse um resultado melhor e mais rápido?

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

Depois é só usar:

IsValidUri(new Uri("http://www.google.com/censorship_algorithm"));

1

Os servidores da Web respondem com um código de status HTTP indicando o resultado da solicitação, por exemplo, 200 (às vezes 202) significa sucesso, 404 - não encontrado etc (veja aqui ). Supondo que a parte do endereço do servidor da URL esteja correta e você não esteja obtendo um tempo limite de soquete, a exceção provavelmente está informando que o código de status HTTP era diferente de 200. Eu sugeriria verificar a classe da exceção e ver se ela carrega o código de status HTTP.

IIRC - A chamada em questão lança uma WebException ou um descendente. Verifique o nome da classe para ver qual deles e envolva a chamada em um bloco try para interceptar a condição.


2
Na verdade, qualquer coisa na faixa de 200-299 significa sucesso, IIRC
Marc Gravell

Marc, você está absolutamente correto. Evitei intencionalmente entrar no conceito de "classe de erro" (por exemplo, 5xx, 4xx, 3xx, 2xx etc.), pois isso abre uma nova lata de worms. Mesmo lidar com os códigos padrão (200, 302, 404, 500, etc.) é muito melhor do que ignorar os códigos completamente.
David Taylor

1

Seguindo com os exemplos já fornecidos, eu diria que é uma prática recomendada também envolver a resposta em um uso como este

    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;
        }
   }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.