C # ¿Cómo puedo verificar si una URL existe / es válida?

117

Estoy creando un programa simple en visual c # 2005 que busca un símbolo bursátil en Yahoo! Finance, descarga los datos históricos y luego traza el historial de precios para el símbolo de cotización especificado.

Sé la URL exacta que necesito para adquirir los datos, y si el usuario ingresa un símbolo de cotización existente (o al menos uno con datos en Yahoo! Finance), funciona perfectamente bien. Sin embargo, tengo un error en tiempo de ejecución si el usuario crea un símbolo de cotización, ya que el programa intenta extraer datos de una página web que no existe.

Estoy usando la clase WebClient y la función DownloadString. Revisé todas las demás funciones miembro de la clase WebClient, pero no vi nada que pudiera usar para probar una URL.

¿Cómo puedo hacer esto?

Daniel Waltrip
fuente
1
actualizado para mostrar el uso de C # 2.0 (VS2005)
Marc Gravell

Respuestas:

110

¿Podría emitir una solicitud "HEAD" en lugar de "GET"?

(editar) - ¡lol! ¡Parece que he hecho esto antes ! cambiado a wiki para evitar acusaciones de repetición. Entonces, para probar una URL sin el costo de descargar el contenido:

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

Debería try/ catchalrededor del DownloadStringpara comprobar si hay errores; ¿No hay error? Existe...


Con C # 2.0 (VS2005):

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

y

using(WebClient client = new MyClient())
{
    // code as before
}
Marc Gravell
fuente
FWIW: no estoy seguro de si eso realmente resuelve el problema (aparte de quizás un comportamiento diferente del lado del cliente), ya que simplemente está cambiando el método HTTP. La respuesta del servidor dependerá en gran medida de cómo esté codificada la lógica y es posible que no funcione bien para un servicio dinámico como el precio de las acciones. Para recursos estáticos (por ejemplo, imágenes, archivos, etc.) HEAD normalmente funciona como se anuncia, ya que está integrado en el servidor. Muchos programadores no realizan solicitudes HEAD explícitamente, ya que el enfoque normalmente está en POST y GET. YMMV
David Taylor
Perdón por tomarme tanto tiempo para elegir una respuesta ... Me desvié de la escuela y el trabajo y me olvidé de esta publicación. Como nota al margen, no pude hacer que su solución funcione porque estoy usando Visual Studio 2005 que no tiene el tipo 'var'. No he trabajado en este proyecto en meses, pero ¿hay una solución simple para ese hecho? Además, cuando intenté implementar su solución, recuerdo que se enojó conmigo por intentar definir la propiedad HeadOnly sin código en las definiciones 'get' y 'set'. O tal vez solo estaba haciendo algo mal. ¡Gracias por la ayuda!
Daniel Waltrip
¿Qué es MyClient ?
Kiquenet
@Kiquenet hay un enlace en el cuerpo, aquí: stackoverflow.com/questions/153451/…
Marc Gravell
136

Aquí hay otra implementación de esta solución:

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.dotnetilstts.net/2009/10/14/how-to-check-remote-file-exists-using-c/

BigJoe714
fuente
2
Estoy usando este código para verificar si existen varias imágenes y es bastante lento (un par de segundos por URL). ¿Alguien sabe si esto es un problema con este código, o simplemente un hecho de la vida al hacer este tipo de llamadas?
ssmith
@ssmith Una forma de acelerar su código es hacer la verificación en un bucle Parallel.Foreach si aún no lo ha intentado. Hizo que mi aplicación de prueba de URL sea MUCHO más rápida.
Jack Fairfield
3
Esto arroja DisposedObject a cambio (response.StatusCode == HttpStatusCode.OK); envolver en el uso
Lapenkov Vladimir
1
Hay un problema con el código anterior. si responde.Close (); entonces no puede verificar response.StatusCode ya que está cerca lanzará una excepción.
Renacimiento
@ssmith ¿Algún método mucho más rápido?
Kiquenet
36

Estas soluciones son bastante buenas, pero olvidan que puede haber otros códigos de estado además de 200 OK. Esta es una solución que he usado en entornos de producción para monitorear el estado y demás.

Si hay una redirección de URL o alguna otra condición en la página de destino, la devolución será verdadera usando este método. Además, GetResponse () lanzará una excepción y, por lo tanto, no obtendrá un StatusCode para ella. Debe capturar la excepción y verificar un ProtocolError.

Cualquier código de estado 400 o 500 devolverá falso. Todos los demás vuelven verdaderos. Este código se modifica fácilmente para adaptarse a sus necesidades de códigos de estado 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;
}
jsmith
fuente
1
Agregaría que algunos códigos de estado en el rango 3xx en realidad causarán un error, por ejemplo, 304 No modificado, en cuyo caso debería manejar eso en su bloque de captura
RobV
3
Acabo de experimentar un problema de tirarse los pelos con este enfoque: HttpWebRequestno le gusta si no utiliza .Close()el responseobjeto antes de intentar descargar cualquier otra cosa. ¡Tomó horas encontrar ese!
jbeldock
4
HttpWebResponseEl objeto debe estar encerrado en un usingbloque ya que implementa IDisposablelo que también asegurará el cierre de la conexión. Esto podría causar problemas como @jbeldock, ha enfrentado.
Habib
2
¿Está lanzando 404 Not Founds en URL que funcionan bien en un navegador ...?
Michael Tranchida
Los servidores web de @MichaelTranchida son notoriamente conocidos por 404 cuando emite un método que no es compatible. En su caso, Headpuede que no sea compatible con ese recurso, aunque Getpodría serlo. Debería haber arrojado 405 en su lugar.
Sriram Sakthivel
9

Si entiendo tu pregunta correctamente, puedes usar un pequeño método como este para darte los resultados de tu prueba 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;

Puede envolver el código anterior en un método y usarlo para realizar la validación. Espero que esto responda a la pregunta que estaba haciendo.

Software de calendario
fuente
1
Sí, quizás pueda refinar la solución diferenciando entre diferentes casos (falla de conexión TCP - el host rechaza la conexión, 5xx - Ocurrió algo fatal, 404 - Recurso no encontrado, etc.). Eche un vistazo a la propiedad Status de WebException;)
David Taylor
¡Muy buen punto David! Eso nos daría una retroalimentación más detallada para que pudiéramos manejar el error con mayor astucia.
Software de calendario
1
Gracias. Mi punto es que hay varias capas en esta cebolla, cada una de las cuales puede complicar las cosas (.Net Framework, resolución DNS, conectividad TCP, servidor web de destino, aplicación de destino, etc.). En mi humilde opinión, un buen diseño debería poder discriminar entre las diferentes condiciones de falla para proporcionar comentarios informativos y diagnósticos utilizables. Tampoco olvidemos que HTTP tiene códigos de estado por una razón;)
David Taylor
6

Pruebe esto (asegúrese 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;
   }
}

Cuando se llama a la función checkWebsite (), intenta obtener el código fuente de la URL que se le pasa. Si obtiene el código fuente, devuelve verdadero. Si no, devuelve falso.

Ejemplo 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");
usuario6909992
fuente
3

Aqui hay otra opcion

public static bool UrlIsValid(string url)
{
    bool br = false;
    try {
        IPHostEntry ipHost = Dns.Resolve(url);
        br = true;
    }
    catch (SocketException se) {
        br = false;
    }
    return br;
}
Zain Ali
fuente
3
Eso podría resultar útil para comprobar si existe un host. Obviamente, la pregunta no se refiere a si el anfitrión existe o no. Se ocupa de manejar una ruta HTTP incorrecta dado que se sabe que el host existe y está bien .
binki
3

Esta solución 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 ;
}
abobjects.com
fuente
1
no olvide cerrar webResponse, de lo contrario, el tiempo de respuesta aumentará cada vez que llame a su 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");`
}
Praveen Dasare
fuente
1
Agregue alguna explicación a su respuesta. Las respuestas de solo código tienden a ser confusas y no ayudan a los futuros lectores y pueden atraer votos negativos de esa manera.
Jesse
2

Tengo una forma más sencilla de determinar si una URL es válida.

if (Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
{
   //...
}
tsingroo
fuente
4
No, este método no comprueba si la URL es realmente accesible. Incluso devuelve verdadero cuando Uri.IsWellFormedUriString (" 192.168.1.421 ", ...), que usa una URL obviamente incorrecta
zhaorufei
2

Siempre he encontrado que las excepciones son mucho más lentas de manejar.

¿Quizás una forma menos intensiva produciría un resultado mejor y más 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;
        }
    }
}

Entonces solo usa:

IsValidUri(new Uri("http://www.google.com/censorship_algorithm"));
clavo oxidado
fuente
1

Los servidores web responden con un código de estado HTTP que indica el resultado de la solicitud, por ejemplo, 200 (a veces 202) significa éxito, 404 - no encontrado, etc. (ver aquí ). Suponiendo que la parte de la dirección del servidor de la URL es correcta y no está obteniendo un tiempo de espera de socket, lo más probable es que la excepción le indique que el código de estado HTTP era distinto de 200. Sugeriría verificar la clase de la excepción y ver si la excepción se lleva el código de estado HTTP.

IIRC: la llamada en cuestión genera una WebException o un descendiente. Verifique el nombre de la clase para ver cuál y envuelva la llamada en un bloque try para capturar la condición.

David Taylor
fuente
2
En realidad, cualquier cosa en el rango 200-299 significa éxito, IIRC
Marc Gravell
Marc, tienes toda la razón. Intencionalmente evité entrar en el concepto de "clase de error" (por ejemplo, 5xx, 4xx, 3xx, 2xx, etc.) ya que eso abre otra lata de gusanos. Incluso manejar los códigos estándar (200, 302, 404, 500, etc.) es mucho mejor que ignorarlos por completo.
David Taylor
1

Siguiendo los ejemplos ya dados, diría que es una buena práctica envolver también la respuesta en un 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;
        }
   }
usuario3154431
fuente