¿Qué es JSONP y por qué se creó?

2130

Entiendo JSON, pero no JSONP. El documento de Wikipedia sobre JSON es (fue) el principal resultado de búsqueda para JSONP. Dice esto:

JSONP o "JSON con relleno" es una extensión JSON en la que se especifica un prefijo como argumento de entrada de la llamada en sí.

¿Eh? ¿Que llamada? Eso no tiene ningún sentido para mí. JSON es un formato de datos. No hay llamada

El segundo resultado de búsqueda es de un tipo llamado Remy , que escribe esto sobre JSONP:

JSONP es inyección de etiqueta de script, que pasa la respuesta del servidor a una función especificada por el usuario.

Puedo entender eso, pero todavía no tiene ningún sentido.


Entonces, ¿qué es JSONP? ¿Por qué se creó (qué problema resuelve)? ¿Y por qué lo usaría?


Anexo : Acabo de crear una nueva página para JSONP en Wikipedia; ahora tiene una descripción clara y exhaustiva de JSONP, basada en la respuesta de jvenema .

Cheeso
fuente
29
Para el registro, NO use JSONP si no confía en el servidor con el que está hablando al 100%. Si se ve comprometido, su página web se verá trivialmente comprometida.
ninjagecko
77
También tenga en cuenta que JSONP puede ser secuestrado si no se implementa correctamente.
Pacerier
3
Me gustaría dar crédito al autor de JSONP que dio la filosofía detrás de esto: el archivo de Bob Ippolito en JSONP . Presenta JSONP como "una nueva metodología estándar agnóstica de tecnología para el método de etiqueta de script para la obtención de datos entre dominios".
harshvchawla

Respuestas:

2048

En realidad no es demasiado complicado ...

Digamos que estás en el dominio example.comy quieres hacer una solicitud de dominio example.net. Para hacerlo, debe cruzar los límites del dominio , un no-no en la mayoría de los navegadores.

El único elemento que evita esta limitación son las <script>etiquetas. Cuando utiliza una etiqueta de secuencia de comandos, se ignora la limitación de dominio, pero en circunstancias normales, no puede hacer nada con los resultados, solo se evalúa la secuencia de comandos.

Introduzca JSONP. Cuando realiza su solicitud a un servidor que está habilitado para JSONP, pasa un parámetro especial que le informa al servidor un poco sobre su página. De esa manera, el servidor puede concluir su respuesta de una manera que su página pueda manejar.

Por ejemplo, supongamos que el servidor espera un parámetro llamado callbackpara habilitar sus capacidades JSONP. Entonces su solicitud se vería así:

http://www.example.net/sample.aspx?callback=mycallback

Sin JSONP, esto podría devolver algún objeto JavaScript básico, como este:

{ foo: 'bar' }

Sin embargo, con JSONP, cuando el servidor recibe el parámetro "devolución de llamada", finaliza el resultado un poco diferente, devolviendo algo como esto:

mycallback({ foo: 'bar' });

Como puede ver, ahora invocará el método que especificó. Entonces, en su página, define la función de devolución de llamada:

mycallback = function(data){
  alert(data.foo);
};

Y ahora, cuando se carga el script, se evaluará y se ejecutará su función. ¡Voila, solicitudes de dominio cruzado!

También vale la pena señalar el principal problema con JSONP: pierde mucho control de la solicitud. Por ejemplo, no hay una forma "agradable" de recuperar los códigos de falla adecuados. Como resultado, terminas usando temporizadores para monitorear la solicitud, etc., que siempre es un poco sospechoso. La propuesta para JSONRequest es una gran solución para permitir secuencias de comandos de dominio cruzado, mantener la seguridad y permitir el control adecuado de la solicitud.

En estos días (2015), CORS es el enfoque recomendado frente a JSONRequest. JSONP sigue siendo útil para la compatibilidad con navegadores antiguos, pero dadas las implicaciones de seguridad, a menos que no tenga otra opción, CORS es la mejor opción.

jvenema
fuente
180
Tenga en cuenta que el uso de JSONP tiene algunas implicaciones de seguridad. Como JSONP es realmente javascript, puede hacer todo lo que javascript puede hacer, por lo que debe confiar en el proveedor de los datos de JSONP. He escrito una publicación de blog sobre esto aquí: erlend.oftedal.no/blog/?blogid=97
Erlend
72
¿Existe realmente alguna nueva implicación de seguridad en JSONP que no esté presente en una etiqueta <script>? Con una etiqueta de script, el navegador confía implícitamente en el servidor para entregar Javascript no dañino, que el navegador evalúa ciegamente. ¿JSONP cambia ese hecho? Parece que no.
Cheeso
23
No, no lo hace. Si confía en que entregará el javascript, lo mismo se aplica para JSONP.
jvenema
15
Vale la pena señalar que puede aumentar un poco la seguridad cambiando la forma en que se devuelven los datos. Si devuelve el script en formato JSON verdadero como mycallback ('{"foo": "bar"}') (tenga en cuenta que el parámetro ahora es una cadena), entonces puede analizar los datos manualmente usted mismo para "limpiarlos" antes evaluando
jvenema
8
CURL es una solución del lado del servidor, no del lado del cliente. Sirven dos propósitos diferentes.
jvenema
712

JSONP es realmente un truco simple para superar la misma política de dominio XMLHttpRequest . (Como sabe, uno no puede enviar la solicitud AJAX (XMLHttpRequest) a un dominio diferente).

Entonces, en lugar de usar XMLHttpRequest , tenemos que usar etiquetas HTML de script , las que usualmente usas para cargar archivos js, para que js obtenga datos de otro dominio. ¿Suena raro?

La cosa es que resulta que las etiquetas de script se pueden usar de manera similar a XMLHttpRequest . Mira esto:

script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.someWebApiServer.com/some-data';

Terminará con un segmento de secuencia de comandos que se verá así después de cargar los datos:

<script>
{['some string 1', 'some data', 'whatever data']}
</script>

Sin embargo, esto es un poco inconveniente, porque tenemos que buscar esta matriz desde la etiqueta del script . Entonces, los creadores de JSONP decidieron que esto funcionará mejor (y lo es):

script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.someWebApiServer.com/some-data?callback=my_callback';

¿ Notó la función my_callback allí? Entonces, cuando el servidor JSONP recibe su solicitud y encuentra el parámetro de devolución de llamada, en lugar de devolver una matriz js simple, devolverá esto:

my_callback({['some string 1', 'some data', 'whatever data']});

Vea dónde está el beneficio: ahora obtenemos una devolución de llamada automática (my_callback) que se activará una vez que obtengamos los datos.
Eso es todo lo que hay que saber sobre JSONP : es una devolución de llamada y etiquetas de script.

NOTA: estos son ejemplos simples del uso de JSONP, estos no son scripts listos para producción.

Ejemplo básico de JavaScript (feed de Twitter simple usando JSONP)

<html>
    <head>
    </head>
    <body>
        <div id = 'twitterFeed'></div>
        <script>
        function myCallback(dataWeGotViaJsonp){
            var text = '';
            var len = dataWeGotViaJsonp.length;
            for(var i=0;i<len;i++){
                twitterEntry = dataWeGotViaJsonp[i];
                text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'
            }
            document.getElementById('twitterFeed').innerHTML = text;
        }
        </script>
        <script type="text/javascript" src="http://twitter.com/status/user_timeline/padraicb.json?count=10&callback=myCallback"></script>
    </body>
</html>

Ejemplo básico de jQuery (feed de Twitter simple usando JSONP)

<html>
    <head>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
        <script>
            $(document).ready(function(){
                $.ajax({
                    url: 'http://twitter.com/status/user_timeline/padraicb.json?count=10',
                    dataType: 'jsonp',
                    success: function(dataWeGotViaJsonp){
                        var text = '';
                        var len = dataWeGotViaJsonp.length;
                        for(var i=0;i<len;i++){
                            twitterEntry = dataWeGotViaJsonp[i];
                            text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'
                        }
                        $('#twitterFeed').html(text);
                    }
                });
            })
        </script>
    </head>
    <body>
        <div id = 'twitterFeed'></div>
    </body>
</html>


JSONP significa JSON con relleno . (técnica muy mal nombrada, ya que realmente no tiene nada que ver con lo que la mayoría de la gente pensaría como "relleno").

Ese tipo
fuente
34
Gracias por la explicación de la etiqueta del script. No pude entender cómo JSONP eludió la política de seguridad de dominio cruzado. Después de la explicación, me siento un poco estúpido por perder el punto ...
Eduard
13
Esta es una muy buena respuesta complementaria a la respuesta de jvenema: no entendí por qué la devolución de llamada era necesaria hasta que señaló que, de lo contrario, habría que acceder a los datos json a través del elemento de script.
Matt
55
Gracias por tan lúcida explicación. Ojalá mis libros de texto de la universidad fueran escritos por personas como tú :)
hashbrown
1
Buena explicación en lugar de la anterior. Por supuesto, su extracto "los que usualmente usa para cargar archivos js, para que js obtenga datos de otro dominio. ¿Suena raro?" También me abre los ojos. Código de ejemplo en muy ilustre.
SIslam
el relleno no tiene que ser literal. Es una especie de metáfora. entonces puede significar "JSON con algunos 'espacios'". lol
marvinIsSacul
48

JSONP funciona mediante la construcción de un elemento "script" (ya sea en marcado HTML o insertado en el DOM a través de JavaScript), que solicita una ubicación de servicio de datos remota. La respuesta es un javascript cargado en su navegador con el nombre de la función predefinida junto con el parámetro que se pasa, que son los datos JSON que se solicitan. Cuando se ejecuta el script, se llama a la función junto con los datos JSON, lo que permite que la página solicitante reciba y procese los datos.

Para más información, visite: https://blogs.sap.com/2013/07/15/secret-behind-jsonp/

fragmento de código del lado del cliente

    <!DOCTYPE html>
    <html lang="en">
    <head>
     <title>AvLabz - CORS : The Secrets Behind JSONP </title>
     <meta charset="UTF-8" />
    </head>
    <body>
      <input type="text" id="username" placeholder="Enter Your Name"/>
      <button type="submit" onclick="sendRequest()"> Send Request to Server </button>
    <script>
    "use strict";
    //Construct the script tag at Runtime
    function requestServerCall(url) {
      var head = document.head;
      var script = document.createElement("script");

      script.setAttribute("src", url);
      head.appendChild(script);
      head.removeChild(script);
    }

    //Predefined callback function    
    function jsonpCallback(data) {
      alert(data.message); // Response data from the server
    }

    //Reference to the input field
    var username = document.getElementById("username");

    //Send Request to Server
    function sendRequest() {
      // Edit with your Web Service URL
      requestServerCall("http://localhost/PHP_Series/CORS/myService.php?callback=jsonpCallback&message="+username.value+"");
    }    

  </script>
   </body>
   </html>

Pieza del lado del servidor del código PHP

<?php
    header("Content-Type: application/javascript");
    $callback = $_GET["callback"];
    $message = $_GET["message"]." you got a response from server yipeee!!!";
    $jsonResponse = "{\"message\":\"" . $message . "\"}";
    echo $callback . "(" . $jsonResponse . ")";
?>
Ajain Vivek
fuente
3
el enlace en la parte superior solo 404s ahora
Kevin Beal
El contenido de ese enlace ahora está disponible en http://scn.sap.com/community/developer-center/front-end/blog/2013/07/15/secret-behind-jsonp .
ᴠɪɴᴄᴇɴᴛ
42

Porque puede pedirle al servidor que anteponga un prefijo al objeto JSON devuelto. P.ej

function_prefix(json_object);

para que el navegador eval"en línea" la cadena JSON como una expresión. Este truco hace posible que el servidor "inyecte" código javascript directamente en el navegador del Cliente y esto evita las restricciones del "mismo origen".

En otras palabras, puede lograr el intercambio de datos entre dominios .


Normalmente, XMLHttpRequestno permite el intercambio de datos entre dominios directamente (uno debe pasar por un servidor en el mismo dominio) mientras que:

<script src="some_other_domain/some_data.js&prefix=function_prefix> `se puede acceder a los datos desde un dominio diferente al del origen.


También vale la pena señalar: aunque el servidor debe considerarse como "confiable" antes de intentar ese tipo de "truco", pueden contener los efectos secundarios de un posible cambio en el formato del objeto, etc. Si function_prefixse utiliza una (es decir, una función js adecuada) para recibir el objeto JSON, dicha función puede realizar comprobaciones antes de aceptar / procesar más los datos devueltos.

jldupont
fuente
"agregar un prefijo" es confuso :)
jub0bs
19

JSONP es una excelente opción para evitar errores de secuencias de comandos entre dominios. Puede consumir un servicio JSONP únicamente con JS sin tener que implementar un proxy AJAX en el lado del servidor.

Puede usar el servicio b1t.co para ver cómo funciona. Este es un servicio JSONP gratuito que le permite minimizar sus URL. Aquí está la url para usar para el servicio:

http://b1t.co/Site/api/External/MakeUrlWithGet?callback=[resultsCallBackfont>&url=[escapedUrlToMinify]

Por ejemplo, la llamada, http://b1t.co/Site/api/External/MakeUrlWithGet?callback=whateverJavascriptName&url=google.com

volvería

whateverJavascriptName({"success":true,"url":"http://google.com","shortUrl":"http://b1t.co/54"});

Y así, cuando eso se carga en su js como un src, ejecutará automáticamente lo que sea JavaScriptName que debe implementar como su función de devolución de llamada:

function minifyResultsCallBack(data)
{
    document.getElementById("results").innerHTML = JSON.stringify(data);
}

Para hacer realmente la llamada JSONP, puede hacerlo de varias maneras (incluido el uso de jQuery) pero aquí hay un ejemplo puro de JS:

function minify(urlToMinify)
{
   url = escape(urlToMinify);
   var s = document.createElement('script');
   s.id = 'dynScript';
   s.type='text/javascript';
   s.src = "http://b1t.co/Site/api/External/MakeUrlWithGet?callback=resultsCallBack&url=" + url;
   document.getElementsByTagName('head')[0].appendChild(s);
}

Un ejemplo paso a paso y un servicio web jsonp para practicar están disponibles en: esta publicación

dardawk
fuente
2
Gracias por publicar tu respuesta! Tenga en cuenta que debe publicar las partes esenciales de la respuesta aquí, en este sitio, o su publicación corre el riesgo de ser eliminada. Consulte las Preguntas frecuentes donde menciona respuestas que son "apenas más que un enlace". Aún puede incluir el enlace si lo desea, pero solo como una 'referencia'. La respuesta debe ser independiente sin necesidad del enlace.
Taryn
14

Un ejemplo simple para el uso de JSONP.

cliente.html

    <html>
    <head>
   </head>
     body>


    <input type="button" id="001" onclick=gO("getCompany") value="Company"  />
    <input type="button" id="002" onclick=gO("getPosition") value="Position"/>
    <h3>
    <div id="101">

    </div>
    </h3>

    <script type="text/javascript">

    var elem=document.getElementById("101");

    function gO(callback){

    script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'http://localhost/test/server.php?callback='+callback;
    elem.appendChild(script);
    elem.removeChild(script);


    }

    function getCompany(data){

    var message="The company you work for is "+data.company +"<img src='"+data.image+"'/   >";
    elem.innerHTML=message;
}

    function getPosition(data){
    var message="The position you are offered is "+data.position;
    elem.innerHTML=message;
    }
    </script>
    </body>
    </html>

server.php

  <?php

    $callback=$_GET["callback"];
    echo $callback;

    if($callback=='getCompany')
    $response="({\"company\":\"Google\",\"image\":\"xyz.jpg\"})";

    else
    $response="({\"position\":\"Development Intern\"})";
    echo $response;

    ?>    
sarath joseph
fuente
8

Antes de comprender JSONP, debe conocer el formato JSON y XML. Actualmente, el formato de datos más utilizado en la web es XML, pero XML es muy complicado. Hace que los usuarios sean incómodos al procesar incrustado en páginas web.

Para que JavaScript pueda intercambiar datos fácilmente, incluso como el programa de procesamiento de datos, utilizamos la redacción de acuerdo con los objetos de JavaScript y desarrollamos un formato de intercambio de datos simple, que es JSON. JSON se puede usar como datos o como un programa de JavaScript.

JSON se puede incrustar directamente en JavaScript, usándolos puede ejecutar directamente cierto programa JSON, pero debido a restricciones de seguridad, el mecanismo Sandbox del navegador deshabilita la ejecución de código JSON entre dominios.

Para hacer que JSON se pueda pasar después de la ejecución, desarrollamos un JSONP. JSONP omite los límites de seguridad del navegador con la funcionalidad de devolución de llamada de JavaScript y la etiqueta <script>.

En resumen, explica qué es JSONP, qué problema resuelve (cuándo usarlo).

Marcus Thornton
fuente
44
Lo rechacé porque no creo en la declaración de que XML era el formato de datos más utilizado en la web en diciembre del '15.
RobbyD
Todavía no responde por qué se usa jsonp en lugar de json. ¿De dónde vienen todas esas restricciones de seguridad? ¿Por qué podemos usar jsonp pero no json para dominios cruzados?
Merunas Grincalaitis
6

TL; DR

JSONP es un viejo truco inventado para evitar la restricción de seguridad que nos prohíbe obtener datos JSON de un servidor diferente (un origen diferente * ).

El truco funciona mediante el uso de una <script>etiqueta que solicita el JSON desde ese lugar, por ejemplo:, { "user":"Smith" }pero envuelto en una función, el JSONP real ("JSON with Padding"):

peopleDataJSONP({"user":"Smith"})

Recibirlo de esta forma nos permite usar los datos dentro de nuestra peopleDataJSONPfunción. JSONP es una mala práctica , no lo use (lea a continuación)


El problema

Digamos que estamos navegando ourweb.comy queremos obtener datos JSON (o cualquier información en bruto realmente) anotherweb.com. Si tuviéramos que usar la solicitud GET (como XMLHttpRequestuna fetchllamada, $.ajaxetc.), nuestro navegador nos diría que no está permitido con este error feo:

Error de consola de Chrome CORS

¿Cómo obtener los datos que queremos? Bueno, ¡las <script>etiquetas no están sujetas a toda esta restricción del servidor (origen *)! Es por eso que podemos cargar una biblioteca como jQuery o Google Maps desde cualquier servidor, como un CDN, sin ningún error.

Punto importante : si lo piensa, esas bibliotecas son códigos JS reales y ejecutables (generalmente una función masiva con toda la lógica dentro). ¿Pero datos en bruto? Los datos JSON no son código . No hay nada que correr; son solo datos simples.

Por lo tanto, no hay forma de manejar o manipular nuestros preciosos datos. El navegador descargará los datos señalados por nuestra <script>etiqueta y, cuando lo procese, se quejará legítimamente:

wtf es esta {"user":"Smith"}basura que cargamos? No es codigo. No puedo calcular, error de sintaxis!


El hack de JSONP

¿La forma antigua / hacky de utilizar esos datos? Necesitamos que ese servidor lo envíe con cierta lógica, por lo que cuando esté cargado, su código en el navegador podrá usar dichos datos. Entonces, el servidor externo nos envía los datos JSON dentro de una función JS. Los datos en sí se configuran como la entrada de esa función. Se parece a esto:

peopleDataJSONP({"user":"Smith"})

¡lo que lo convierte en código JS que nuestro navegador analizará sin quejarse! Exactamente como lo hace con la biblioteca jQuery. Ahora, para que sea así, el cliente "pregunta" al servidor compatible con JSONP, generalmente hecho así:

<script src="https://anotherweb.com/api/data-from-people.json?myCallback=peopleDataJSONP"></script>

Nuestro navegador recibirá el JSONP con ese nombre de función, por lo tanto, necesitamos una función con el mismo nombre en nuestro código, como esta:

const peopleDataJSONP = function(data){
  alert(data.user); // "Smith"
}

O así, el mismo resultado:

function peopleDataJSONP(data){
  alert(data.user); // "Smith"
}

El navegador descargará el JSONP y lo ejecutará, que llama a nuestra función , donde el argumento dataserá nuestro JSON. Ahora podemos hacer con nuestros datos lo que queramos.


No uses JSONP, usa CORS

JSONP es un truco entre sitios con algunas desventajas:

  • Solo podemos realizar solicitudes GET
  • Como se trata de una solicitud GET activada por una etiqueta de script simple, no obtenemos errores útiles ni información de progreso
  • También hay algunos problemas de seguridad, como ejecutar en el código JS de su cliente que podría cambiarse a una carga maliciosa
  • Solo resuelve el problema con los datos JSON, pero la política de seguridad del mismo origen se aplica a otros datos (WebFonts, imágenes / video dibujados con drawImage () ...)
  • No es muy elegante ni legible.

La conclusión es que no hay necesidad de usarlo hoy en día .

JSONP es el truco para obtener datos JSON de otro servidor, pero violaremos el mismo principio de seguridad (Same-Origin) si necesitamos otros tipos de cosas entre sitios.

Debería leer sobre CORS aquí , pero lo esencial es:

Cross-Origin Resource Sharing (CORS) es un mecanismo que utiliza encabezados HTTP adicionales para indicar a los navegadores que den a una aplicación web que se ejecuta en un origen, acceso a recursos seleccionados de un origen diferente. Una aplicación web ejecuta una solicitud HTTP de origen cruzado cuando solicita un recurso que tiene un origen (dominio, protocolo o puerto) diferente al suyo.



* El origen está definido por 3 cosas: protocolo , puerto y host . Entonces, por ejemplo, https://web.comes un origen diferente que http://web.com(protocolo diferente) y https://web.com:8081(puerto diferente) y obviamente https://thatotherweb.net(host diferente)

Carles Alcolea
fuente
1
Hola hombre, ¡esto proporcionó 100% de claridad como una nota al pie de la respuesta aprobada! Gracias por esto ....
M'Baku
4

Ya se han dado las excelentes respuestas, solo necesito dar mi pieza en forma de bloques de código en javascript (también incluiré una solución más moderna y mejor para solicitudes de origen cruzado: CORS con encabezados HTTP):

JSONP:

1.client_jsonp.js

$.ajax({
    url: "http://api_test_server.proudlygeek.c9.io/?callback=?",
    dataType: "jsonp",
    success: function(data) {
        console.log(data);    
    }
});​​​​​​​​​​​​​​​​​​

2.server_jsonp.js

var http = require("http"),
    url  = require("url");

var server = http.createServer(function(req, res) {

    var callback = url.parse(req.url, true).query.callback || "myCallback";
    console.log(url.parse(req.url, true).query.callback);

    var data = {
        'name': "Gianpiero",
        'last': "Fiorelli",
        'age': 37
    };

    data = callback + '(' + JSON.stringify(data) + ');';

    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(data);
});

server.listen(process.env.PORT, process.env.IP);

console.log('Server running at '  + process.env.PORT + ':' + process.env.IP);

CORS :

3.client_cors.js

$.ajax({
    url: "http://api_test_server.proudlygeek.c9.io/",
    success: function(data) {
        console.log(data);    
    }
});​

4.server_cors.js

var http = require("http"),
    url  = require("url");

var server = http.createServer(function(req, res) {
    console.log(req.headers);

    var data = {
        'name': "Gianpiero",
        'last': "Fiorelli",
        'age': 37
    };

    res.writeHead(200, {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    });

    res.end(JSON.stringify(data));
});

server.listen(process.env.PORT, process.env.IP);

console.log('Server running at '  + process.env.PORT + ':' + process.env.IP);
Humoyun Ahmad
fuente
1

JSONP significa JSON con relleno .

Aquí está el sitio, con excelentes ejemplos , con la explicación desde el uso más simple de esta técnica hasta el JavaScript más avanzado en el plano:

w3schools.com / JSONP

Una de mis técnicas favoritas descritas anteriormente es Dynamic JSON Result , que permite enviar JSON al archivo PHP en el parámetro URL , y permite que el archivo PHP también devuelva un objeto JSON en función de la información que obtiene .

Herramientas como jQuery también tienen facilidades para usar JSONP :

jQuery.ajax({
  url: "https://data.acgov.org/resource/k9se-aps6.json?city=Berkeley",
  jsonp: "callbackName",
  dataType: "jsonp"
}).done(
  response => console.log(response)
);
simhumileco
fuente