Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:ajax

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
informatica:programacion:cursos:programacion_avanzada_javascript:ajax [2024/10/14 11:29] – [DEMO: Una base de datos de películas con JSONP] tempwininformatica:programacion:cursos:programacion_avanzada_javascript:ajax [2024/10/30 15:46] (actual) – [Funcionamiento básico de CORS] tempwin
Línea 20: Línea 20:
 La tendencia actual en todos los desarrollos web, sin embargo, es crear **aplicaciones y páginas cada vez más parecidas a aplicaciones de escritorio**, desdibujando las fronteras entre la Web y los programas que se ejecutan en el equipo local. Esto implica que los molestos y a la vez inevitables viajes al servidor no deberían ser percibidos por los usuarios y que la interfaz de la página debe responder en todo momento, jamás bloquearse. La sensación para los usuarios debe ser la de que la aplicación está todo el tiempo en su equipo, dejando de lado al servidor, como en una aplicación de escritorio tradicional. Seguro que has utilizado alguna vez GMail, Outlook.com, Facebook o alguna otra aplicación web reciente, así que sabes perfectamente de qué estoy hablando. La tendencia actual en todos los desarrollos web, sin embargo, es crear **aplicaciones y páginas cada vez más parecidas a aplicaciones de escritorio**, desdibujando las fronteras entre la Web y los programas que se ejecutan en el equipo local. Esto implica que los molestos y a la vez inevitables viajes al servidor no deberían ser percibidos por los usuarios y que la interfaz de la página debe responder en todo momento, jamás bloquearse. La sensación para los usuarios debe ser la de que la aplicación está todo el tiempo en su equipo, dejando de lado al servidor, como en una aplicación de escritorio tradicional. Seguro que has utilizado alguna vez GMail, Outlook.com, Facebook o alguna otra aplicación web reciente, así que sabes perfectamente de qué estoy hablando.
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:08-ajax.jpg |}}
-AJAX +
-</WRAP>+
  
 Ya hemos aprendido a lo largo de este curso las posibilidades que ofrecen los navegadores actuales para trabajar en el lado cliente con HTML, CSS y JavaScript. La tendencia imparable en las aplicaciones web es la de **llevar cada vez más procesamiento y lógica al navegador**, dejando el servidor para procesar las reglas de negocio y el acceso a datos. Ya hemos aprendido a lo largo de este curso las posibilidades que ofrecen los navegadores actuales para trabajar en el lado cliente con HTML, CSS y JavaScript. La tendencia imparable en las aplicaciones web es la de **llevar cada vez más procesamiento y lógica al navegador**, dejando el servidor para procesar las reglas de negocio y el acceso a datos.
Línea 653: Línea 651:
 </html> </html>
 </code> </code>
 +
 +Scripts usados:
 +
 +  * ''EventHandlerHelper.js'': compatibilidad de eventos en distintos navegadores.
 +  * ''JSONPHelper.js'': facilita llamadas para trabajar con JSONP.
  
 El contenido de ''IMDBInfo.js'': El contenido de ''IMDBInfo.js'':
Línea 661: Línea 664:
  
 function hacerBusqueda() { function hacerBusqueda() {
- var titPelicula = document.getElementById("titPelicula").value; +    var titPelicula = document.getElementById("titPelicula").value; 
- if (titPelicula.trim() == "") { +    if (titPelicula.trim() == "") { 
- alert("Introduzca el título de una película para buscarla en la Internet Movie DataBase"); +        alert("Introduzca el título de una película para buscarla en la Internet Movie DataBase"); 
- document.getElementById("titPelicula").focus(); +        document.getElementById("titPelicula").focus(); 
- }+    }
  
- sendJsonpRequest(IMDB_API_URL + "?apikey=" + IMDB_API_KEY + "&s=" + titPelicula, "mostrarResultadosBusquedaCallback", error404);+    sendJsonpRequest(IMDB_API_URL + "?apikey=" + IMDB_API_KEY + "&s=" + titPelicula, "mostrarResultadosBusquedaCallback", error404);
 } }
  
 function titPelicula_OnEnter(event) { function titPelicula_OnEnter(event) {
- event = EventHandlerHelper.fixEvent(event); +    event = EventHandlerHelper.fixEvent(event); 
- if (event.which == 13) +    if (event.which == 13) 
- hacerBusqueda();+        hacerBusqueda();
 } }
  
 //Muestra resultados de búsqueda (función callback de JSONP) //Muestra resultados de búsqueda (función callback de JSONP)
 function mostrarResultadosBusquedaCallback(resultados) { function mostrarResultadosBusquedaCallback(resultados) {
- var resHTML = ""; +    var resHTML = ""; 
-  +     
- if (!resultados.Search || resultados.Search.length == 0){ +    if (!resultados.Search || resultados.Search.length == 0){ 
- resHTML = "<span>¡¡No hay películas con este título!!</span>"; +        resHTML = "<span>¡¡No hay películas con este título!!</span>"; 
-+    
- else { +    else { 
- for(var i = 0; i<resultados.Search.length; i++)  +        for(var i = 0; i<resultados.Search.length; i++)  
- +        
- var res = resultados.Search[i]; +            var res = resultados.Search[i]; 
- resHTML += "<p data-imdbid='" + res.imdbID + "' onclick='mostrarInfoPeli(this);'>" + res.Title + "(" + res.Year + ") - " + res.Type + "</p>"; +            resHTML += "<p data-imdbid='" + res.imdbID + "' onclick='mostrarInfoPeli(this);'>" + res.Title + "(" + res.Year + ") - " + res.Type + "</p>"; 
- +        
-+    
- document.getElementById("resBusqueda").innerHTML = resHTML; +    document.getElementById("resBusqueda").innerHTML = resHTML; 
- ocultarDetalles();+    ocultarDetalles();
 } }
  
Línea 697: Línea 700:
 function error404() function error404()
 { {
- document.getElementById("resBusqueda").innerHTML = "<span>¡¡Error al conectar con la API!!</span>"; +    document.getElementById("resBusqueda").innerHTML = "<span>¡¡Error al conectar con la API!!</span>"; 
- ocultarDetalles();+    ocultarDetalles();
 } }
  
Línea 704: Línea 707:
 //Se le pasa el elemento sobre el que se ha pulsado //Se le pasa el elemento sobre el que se ha pulsado
 function mostrarInfoPeli(elto) { function mostrarInfoPeli(elto) {
- var idPelicula = elto.attributes["data-imdbid"].value; +    var idPelicula = elto.attributes["data-imdbid"].value; 
- //Desmarco las que hubiese seleccionadas +    //Desmarco las que hubiese seleccionadas 
- var seleccionados = document.getElementsByClassName("seleccionado"); +    var seleccionados = document.getElementsByClassName("seleccionado"); 
- for(var i = 0; i< seleccionados.length; i++) { +    for(var i = 0; i< seleccionados.length; i++) { 
- seleccionados[i].className = ""; +        seleccionados[i].className = ""; 
-+    
- //La marco como seleccionada +    //La marco como seleccionada 
- elto.className = "seleccionado"; +    elto.className = "seleccionado"; 
- //Pido la info sobre la película +    //Pido la info sobre la película 
- sendJsonpRequest(IMDB_API_URL + "?apikey=" + IMDB_API_KEY + "&i=" + idPelicula, "mostrarInfoPeliculaCallback", error404);+    sendJsonpRequest(IMDB_API_URL + "?apikey=" + IMDB_API_KEY + "&i=" + idPelicula, "mostrarInfoPeliculaCallback", error404);
 } }
  
 //Muetra información de una película (función callback de JSONP) //Muetra información de una película (función callback de JSONP)
 function mostrarInfoPeliculaCallback(peli){ function mostrarInfoPeliculaCallback(peli){
- if (peli != null && !peli.Error) +    if (peli != null && !peli.Error) 
-+    
- //Asignamos a mano la imagen +        //Asignamos a mano la imagen 
- if (peli.Poster == "N/A"+        if (peli.Poster == "N/A"
- document.getElementById("imgCaratula").style.visibility= "hidden"; +            document.getElementById("imgCaratula").style.visibility= "hidden"; 
- else { +        else { 
- document.getElementById("imgCaratula").src = peli.Poster; +            document.getElementById("imgCaratula").src = peli.Poster; 
- document.getElementById("imgCaratula").style.visibility= "visible"; +            document.getElementById("imgCaratula").style.visibility= "visible"; 
- +        
- //Ahora asignamos el resto de los campos +        //Ahora asignamos el resto de los campos 
- enlazarCamposConDOM(peli); +        enlazarCamposConDOM(peli); 
- mostrarDetalles(); +        mostrarDetalles(); 
- }+    }
 } }
  
 //Se encarga de localizar campos en la página con el mismo nombre que campos de un objeto que se le pasa, e introduce su valor como contenido en cada uno de ellos //Se encarga de localizar campos en la página con el mismo nombre que campos de un objeto que se le pasa, e introduce su valor como contenido en cada uno de ellos
 function enlazarCamposConDOM(obj) { function enlazarCamposConDOM(obj) {
- //Si no se pasa un elemento concreto del DOM a partir del cual buscar, se usa document y se busca en toda la página+    //Si no se pasa un elemento concreto del DOM a partir del cual buscar, se usa document y se busca en toda la página
     //Se recorren las propiedades del objeto     //Se recorren las propiedades del objeto
- for (prop in obj) { +    for (prop in obj) { 
- //Verificamos si existe un elemento con el mismo id que la propiedad +        //Verificamos si existe un elemento con el mismo id que la propiedad 
- var e = document.getElementById(prop); +        var e = document.getElementById(prop); 
- if (e != null) { +        if (e != null) { 
- e.textContent = obj[prop]; //Asignamos el valor como contenido de texto +                e.textContent = obj[prop];  //Asignamos el valor como contenido de texto 
- }+        }
     }     }
  
Línea 749: Línea 752:
 //Mostrar detalles película //Mostrar detalles película
 function mostrarDetalles(){ function mostrarDetalles(){
- document.getElementById("detalle").style.display= "block";+    document.getElementById("detalle").style.display= "block";
 } }
  
 //Ocultar detalles película //Ocultar detalles película
 function ocultarDetalles(){ function ocultarDetalles(){
- document.getElementById("detalle").style.display= "none";+    document.getElementById("detalle").style.display= "none";
 } }
  
 function initialize() { function initialize() {
- EventHandlerHelper.addEventListener(document.getElementById("cmdGetInfo"), "click", hacerBusqueda); +    EventHandlerHelper.addEventListener(document.getElementById("cmdGetInfo"), "click", hacerBusqueda); 
- EventHandlerHelper.addEventListener(document.getElementById("titPelicula"), "keyup", titPelicula_OnEnter);+    EventHandlerHelper.addEventListener(document.getElementById("titPelicula"), "keyup", titPelicula_OnEnter);
 } }
  
Línea 766: Línea 769:
  
 <WRAP center round tip 60%> <WRAP center round tip 60%>
-Visor online para JSON: https://jsonviewer.stack.hu/. Pegamos un objeto JSON y podremos visualizar en formato árbol.+Visor online para JSON: https://jsonviewer.stack.hu/. Pegamos un objeto JSON y podremos visualizar en formato árbol o formatearlo.
 </WRAP> </WRAP>
  
 +===== Control de acceso entre dominios: CORS =====
  
-===== =====+La funcionalidad de JSONP que acabamos de estudiar es muy interesante para crear //mashups// y manejar información de otros dominios de manera directa desde el navegador. Sin embargo, hay que tener cuidado cuando se usa con información privada importante ya que **no está exenta de problemas de seguridad**. Por algo los que diseñan los navegadores se empeñan en no permitir el acceso entre dominios.
  
-===== Control de acceso entre dominios: CORS =====+<WRAP center round info 60%> 
 +**Mashups**: Aplicaciones que centralizan en un solo punto elementos de otras aplicaciones o páginas web. Por ejemplo, una página que integre en una sola interfaz todas las notificaciones de tus redes sociales. 
 +</WRAP> 
 + 
 +No voy a entrar en detalles aquí sobre técnicas de //phising// que usan JavaScript para obtener ilícitamente información de otros dominios mediante JSONP, pero baste decir que Twitter o el mismísimo GMail fueron craqueados hace años usando técnicas de [[https://en.wikipedia.org/wiki/Cross-site_request_forgery|Cross Site Request Forgery]] (también llamado XSRF) basadas en llamadas JSONP. 
 + 
 +El protegerse de esas técnicas de //hacking// depende del servicio que implementa la API JSONP. Sólo deberíamos usarlo en nuestras páginas si confiamos en estos sitios. Utilizar APIs de servicios poco recomendables podría resultar en problemas de seguridad para nuestros usuarios. 
 + 
 +Además JSONP tiene bastantes limitaciones: 
 + 
 +  * Sólo sirve para peticiones GET 
 +  * Los datos deben devolverse en formato JSON, o sea, JavaScript. Si necesitamos otros formatos no lo tenemos fácil. 
 +  * La información que se puede enviar al servidor es muy limitada (únicamente datos planos como parámetros de la URL). 
 +  * El servidor debe colaborar explícitamente con el método para devolver el código envuelto en una llamada a un método JavaScript, algo que no siempre está en nuestra mano. 
 +  * No existe un método estándar para controlar el acceso a este tipo de recursos remotos por parte del servidor que los provee, es decir, decidir quién y cuándo puede acceder al recurso. 
 + 
 +Así que, realmente, **la mejor manera de crear aplicaciones AJAX sigue siendo usar el objeto ''XmlHttpRequest''** (XHR). Y con las modernas aplicaciones web cada vez más extendidas y ávidas de funcionalidad, el poder acceder a otros dominios de manera lícita pero a la vez controlada es una necesidad acuciante. 
 + 
 +Conscientes de ello el W3C trabajó en un **estándar que modifica el modo de funcionamiento tradicional** del objeto ''XmlHttpRequest'' para dotarlo de capacidades de **acceso a otros dominios**. También define las convenciones necesarias para que los servidores que ofrecen servicios puedan regular el acceso de los navegadores a los mismos. La especificación se denomina **Cross-Origin Resource Sharing** (compartición de recursos entre diferentes orígenes) o más concisamente con el acrónimo **CORS**. 
 + 
 +Los principales navegadores lo han implementado hace bastante tiempo (Internet Explorer desde la versión 8, Opera desde la 12). Si te interesa puedes leer el estándar completo aquí: [[http://www.w3.org/TR/cors/|http://www.w3.org/TR/cors/]]. 
 + 
 +<WRAP center round important 60%> 
 +Nota: Muchos programadores cuando oyen hablar de CORS piensan en un protocolo de seguridad para proteger el acceso a los recursos, de manera similar a tener una clave o usar un mecanismo de cifrado o algo similar. Se trata de **un error de concepto**. CORS no pretende proteger el acceso a las aplicaciones de servidor. No es un método de seguridad para servicios en Internet. Lo que busca es **proteger a los usuarios frente a ataques de phishing y XSRF** para que no sean víctimas de un pirata de forma inadvertida, que es algo muy diferente. 
 +</WRAP> 
 + 
 +==== Funcionamiento básico de CORS ==== 
 + 
 +CORS define un protocolo entre los servidores Web y los navegadores para que colaboren a la hora de delimitar políticas de seguridad de acceso entre dominios
 + 
 +Así, en su forma más básica, CORS define que cuando se haga una llamada con XHR a un dominio diferente del actual, **dicha llamada debe incluir automáticamente información sobre quién realiza la petición**. Así ésta incluirá automáticamente una cabecera llamada "Origin" que le indicará al servidor de destino el protocolo, dominio y puerto desde el que se está originando la petición. 
 + 
 +<WRAP center round info 60%> 
 +NotaEsta cabecera difiere de la cabecera "Referer" tradicional en que no se indica ningún tipo de ruta, por lo que las preocupaciones de privacidad de los posibles usuarios son menores. 
 +</WRAP> 
 + 
 +Con esta cabecera el servidor decide si permite o no el acceso al recurso, devolviendo en su respuesta una cabecera de tipo "**Access-Control-Allow-Origin**" en la que indica al navegador qué dominios tienen permiso para realizar las peticiones. 
 + 
 +Por ejemplo, puede devolver: 
 + 
 +<code> 
 +Access-Control-Allow-Origin: http://www.tuservidor.com 
 +</code> 
 + 
 +Al recibir esta cabecera el navegador sabe si debe permitir o no que se realice la llamada. De esta forma se protege a los usuarios, ya que si alguien intenta un ataque XSRF contra un servicio como GMail, al realizar la petición desde otro dominio distinto al esperado (''mail.google.com''), el navegador la bloqueará y su código malicioso no servirá de nada. 
 + 
 +Por defecto, si el servidor no incluye esta cabecera el navegador bloquea la llamada, con lo que el comportamiento es exactamente el mismo que el tradicional. 
 + 
 +En muchos casos, si el servicio quiere que pueda consumirse desde cualquier dominio, puede devolver esto: 
 + 
 +<code> 
 +Access-Control-Allow-Origin:
 +</code> 
 + 
 +Esto significa que admite peticiones desde cualquier dominio externo. Ideal para servicios que no juegan con datos privados y quieren abrirse a cuantos más usuarios externos mejor. 
 + 
 +La especificación CORS define todavía más cabeceras para cuestiones algo más especializadas, como por ejemplo para cachear el resultado de una valoración de seguridad y cuánto tiempo permanece guardada o las "//preflight requests//" para lanzar peticiones previas con el método ''HTTP OPTIONS'' y obtener las condiciones de seguridad de las llamadas desde ese origen para toda la sesión. 
 + 
 +Lo mejor de CORS es que nosotros, como programadores JavaScript no tenemos que hacer nada especial para soportar estas llamadas entre dominios. Nos limitamos a utilizar el objeto XHR como siempre, de la manera explicada en este tema. La única diferencia es que tenemos que incluir el URL completo, con el nombre de otro dominio. 
 + 
 +A la hora de utilizar un servicio externo desde uno de tus desarrollos JavaScript, intenta averiguar si el servicio implementa cabeceras CORS de tipo "*". Te simplificará enormemente el desarrollo pues podrás sacar partido al objeto XHR como si fuera un servicio albergado en tu propio dominio. 
 +===== Recursos ===== 
 + 
 +  * [[https://www.sitepoint.com/an-in-depth-look-at-cors/|An In-depth Look at CORS]]
informatica/programacion/cursos/programacion_avanzada_javascript/ajax.1728898155.txt.gz · Última modificación: por tempwin