Menú
Está libre
registro
hogar  /  SOBRE/ Éxito irrevocable php. Un ejemplo simple usando PHP y AJAX

Éxito irrevocable php. Un ejemplo simple usando PHP y AJAX

¡Saludos, querido amigo!

"¿Qué es el éxito en la vida para ti?"

Por favor, piénselo, deténgase un minuto.

Bien, ahora déjame ayudarte. Lo que no es un éxito, escribí en la lista de correo anterior. Descartemos estos conceptos de inmediato.

El éxito es tranquilidad.
El éxito es ser feliz.
El éxito es la realización de uno mismo y la revelación de su potencial.
El éxito es plenitud en la vida.
El éxito es hacer lo que amas, lo que te enciende y puedes hacerlo todo el día.
El éxito es entregarse a los demás y hacer del mundo un lugar mejor y más feliz para otras personas.
.

El éxito está indisolublemente ligado al estado de ánimo. Nuestra alma vino a este mundo para disfrutar y darse cuenta de sí misma, y ​​nosotros (nuestra mente, nuestro cuerpo, nuestra conciencia) debemos apoyarla en esto. Cuando nuestra alma se crea y se da cuenta de sí misma, nos sentimos felices. Cuando sentimos y vemos que la creación de nuestra alma y lo que hacemos es de gran beneficio para otras personas, sentimos dicha. Esto es lo que se llama éxito. El éxito es la plenitud de la vida.

Cualquier realización de los talentos del alma es posible solo gracias a otras personas.... El alma no crea por sí misma. Crea para los demás, para ayudarlos y hacer que la vida de los demás sea plena y para darles una parte de su felicidad. Una persona feliz transmite una parte de su felicidad a los demás, una persona infeliz transmite su infelicidad a los demás. ¡Evita a las personas infelices!

Si de repente en el momento en que todas las personas desaparecen, la autorrealización se volverá imposible: ¿de qué sirve escribir libros, porque nadie los leerá? ¿De qué sirve crear nuevos modelos de ropa, porque nadie los usará? Cuál es el punto en la construcción de nuevas casas, en las que nadie vivirá?

Evidentemente no tiene sentido.

Aqui aparece la doble naturaleza del éxito: el alma se crea y se realiza a sí misma, y ​​también ayuda a otras personas a ser más felices.
El mas definición precisa El éxito que podría dar sonaría así: el éxito es la realización de sus verdaderos talentos, que hacen que nuestro mundo sea mejor, más perfecto y más feliz a la gente.

Quiero que te des cuenta profundamente de que Las personas que viven solo para sí mismas y acumulan riqueza solo para sí mismas son infelices.... Recogen esta riqueza para llenar el vacío espiritual que se ha formado como resultado de una vida sin sentido. Pero este vacío solo se puede llenar con amor, aportando valor a otras personas. El alma es feliz cuando se entrega sin excesos para hacer de este mundo un lugar mejor. ¿Y qué sentido tienen todas esas riquezas que ha acumulado una persona cuando fallece, porque no somos duraderos? El alma viene a crear valor, a darse cuenta de sí misma y luego regresa "a casa". Si no crea este valor, pero hace otra cosa, se siente mal. Siente que ha venido a este mundo y no está haciendo lo que quiere. Y la razón de esto es nuestra mente: está cegada por el "éxito" en la comprensión general de esta palabra. Persigue lo ilusorio, y cuando lo logra, si es que lo logra, se da cuenta de la falta de sentido de lo que ha logrado.

¿Y qué es el éxito en la comprensión general?
- riqueza (dinero, cosas materiales)
- gloria, poder, popularidad
- estado

Pero mira, todo viene del ego. Una persona quiere sentir su propio significado, pero no comprende que la riqueza, la fama, el estatus es una ilusión. Son como agua de mar, que no importa cuánto bebas, nunca saciarás tu sed. Por lo tanto, la gente toda su vida los persigue. Piensan que voy a ganar tanto dinero y seré feliz, que iré al nivel de ingresos de $ 100,000 por año y luego seré feliz, cuando suba al escenario y cante. sé feliz, me casaré, tendré hijos ... puedes comprobarlo, pero puedo decir con 100% de certeza que no serás feliz. Además, su nivel de felicidad será aún menor. Te alejas de tu vocación y, al darse cuenta de esto, el alma se vuelve aún más infeliz. Cuanta más riqueza, fama, estatus reciba, más control sobre la vida toma la mente y más se retrasa el papel del alma. Pero la verdadera felicidad viene del alma!!!

El éxito es la armonía entre el alma y la mente. El papel de la razón es ayudar alma para autorrealizarse. Estamos priorizando mal. Ponemos el cuerpo efímero y las cosas materiales en primer lugar, y el alma inmortal y la riqueza inagotable en el último lugar. La Biblia dice, "reúne riquezas en el cielo, no en la tierra". Nuestro cuerpo es un vehículo para el alma.... El alma está conectada con la Mente Superior y solo ella es capaz de comprender lo que se necesita para este mundo. El universo anima a las personas que siguen su propio camino.... Su camino es el que menos energía consume, y en nuestro mundo todo fluye por el camino de menor resistencia. Siempre digo que el éxito es un curso normal de eventos. El fracaso es una desviación de la norma. Si ahora no tiene tanto éxito como desea, entonces no está haciendo lo que debe hacer. El alma y la mente están en desacuerdo. Y cuanto más esta discordia, más infeliz es la persona.

Pero no creas que estoy diciendo que una persona no necesita cosas materiales. Mucho incluso necesario. Y aquí está la razón: cuando una persona no tiene dinero, se ve obligada a ir a trabajar y participar en algún tipo de "estupidez". Una persona dedica 10 horas al día a ganar dinero, pero al hacerlo no se da cuenta de sí mismo. El chef es la persona que se realiza a expensas de usted. (Digo cómo sucede en la mayoría de los casos. La mayoría de la gente odia su trabajo, pero trabaja porque necesita dinero para sobrevivir).

Las cosas materiales crean consuelo para el alma. Las cosas materiales equipan este mundo para el alma. Es mucho más agradable para el alma crear obras maestras en lugares que la inspiran. Es mucho mejor pintar un cuadro en una casa junto al mar que en un "pozo negro". El alma necesita paz y consuelo para poder crear. Pero qué paz puede haber si la familia no tiene suficiente dinero y todos los días el esposo y la esposa se pelean por esto.

El alma necesita tiempo para expresarse. Solo después de que haya pasado algún tiempo, el valor creado por el alma se puede vender y vender cientos, o incluso miles de veces más caro de lo que una persona obtiene en el trabajo. Pero se necesita tiempo para crear ese valor. Personalmente, me tomó 5 meses conseguir unos ingresos escasos. Después de 8 meses, mi sitio comenzó a generar ingresos con los que una familia pobre ya podría vivir. Y solo 17 meses después, mi sitio comenzó a generar ingresos, que ya reemplazarán los ingresos de un trabajo muy bien remunerado.

Se necesitaron 17 meses para reemplazar el trabajo. ¡Pero ahora soy libre! Hago lo que amo y esto es solo el comienzo. No hay límites para mis sueños y, por lo tanto, no hay límites para mí. Cuando te dedicas a tu negocio, tus ingresos están limitados solo por tu imaginación y nada más. ¿Quién en el trabajo gana $ 1,000,000 al año? Sí, puede haber uno. Pero haciendo lo tuyo, incluso esto no es una capilla.
El material es importante, pero solo para satisfacer las necesidades de la vida.

Seré honesto: al no recibir ingresos, es más difícil crear y crear obras maestras... La mente dice constantemente: "lo que haces es bueno, pero ¿para qué vamos a vivir?" Y esta pregunta distrae constante y fuertemente la creatividad. Nos quita la felicidad. Para desactivar este diálogo, su pasatiempo favorito debe traer dinero. Por supuesto, la mente comienza a hacer otras preguntas, pero ¿cómo mas dinero trae lo que amas, menos dolorosos y distractores se vuelven estos problemas.

A menudo, la gente trabaja en el trabajo, gana dinero, pero todavía tiene un pasatiempo. ¿Qué es un hobby?
Un hobby es un hobby que no genera ingresos. Pero, ¿por qué no convertir un pasatiempo en un trabajo? Las personas más felices son aquellas cuyo pasatiempo es el trabajo.... No dejan de hacer lo que aman.
De todo lo que hablo, sobre el trabajo, sobre el dinero, quiero transmitirles dos pensamientos importantes: 1) El alma y la mente deben estar en armonía.
2) Lo intangible siempre debe ser lo primero

¡El foco debe estar solo en lo intangible! Material que se adjuntará como consecuencia... Aquí están las prioridades correctas en la vida:
felicidad -> salud -> riqueza Y mucha gente vive según el esquema
riqueza-> salud-> felicidad
Y lo que es peor, hay personas que viven según el patrón
riqueza-> riqueza-> riqueza

No es de extrañar que no estén contentos. Estas personas tienen millones, pero no tienen amigos, tienen problemas familiares. Tienen problemas en las relaciones con la gente. Porque piensan que todas las personas que los rodean están con ellos solo por su dinero y nada más. No sé ustedes, pero no querría tanta felicidad. Cuando las prioridades en la vida se establecen correctamente, surge la riqueza como resultado. No tiene sentido concentrarse en ello. Los altos niveles de felicidad y salud conducen inevitablemente a altos niveles de ingresos..

Las cosas materiales y nuestra riqueza solo pueden servir como una adición a nuestra felicidad. No pueden servir como base. Cuál es la base, ya lo hemos discutido anteriormente.

El conjunto de pares clave / valor que personalizan la solicitud. AJAX. Todos los parámetros son opcionales... Está permitido, pero no se recomienda, establecer un valor predeterminado para cualquier parámetro utilizando el método $ .ajaxSetup ().
Método $ .ajax () admite los siguientes parámetros:

    acepta(predeterminado: depende de tipo de datos).

    Tipo: PlainObject.
    El conjunto de pares clave / valor que se envían a Aceptar encabezado de solicitud. Este encabezado le dice al servidor qué tipo de respuesta aceptará la solicitud como respuesta. Tenga en cuenta que el valor del parámetro especificado en tipo de datos(el tipo de datos que esperamos del servidor) coincide con el especificado en el parámetro. Además, para el correcto procesamiento de la respuesta del servidor, es necesario en el parámetro convertidores especifique una función que devuelva el valor de respuesta convertido. Por ejemplo: $ .ajax (( acepta: (mycustomtype: " application / x-some-custom-type" } , // especificar cómo procesar la respuesta convertidores: ("text mycustomtype": función ( resultado) { // devuelve el valor de respuesta transformado return newresult; )), // Tipo de datos esperado ("mycustomtype") tipo de datos: "mycustomtype"));

    asincrónico(predeterminado: verdadero).

    Tipo: booleano.
    De forma predeterminada, todas las solicitudes se envían de forma asincrónica; si necesita organizar las solicitudes sincrónicas, establezca este parámetro en falso. Tenga en cuenta que las solicitudes entre dominios y el elemento, parámetro tipo de datos que importa "jsonp" no admiten solicitudes síncronas. Tenga en cuenta que al utilizar solicitudes sincrónicas, puede bloquear temporalmente el navegador desactivando cualquier acción mientras la solicitud está activa.

    antesEnviar... Tipo: Función (jqXHR jqXHR, PlainObject ajustes).
    Función de devolución de llamada que se llamará antes de realizar la solicitud AJAX. Esta función le permite modificar el objeto jqXHR (en jQuery 1.4.x el objeto XMLHTTPRequest) antes de que se envíe. El objeto jqXHR es un complemento que extiende el objeto XMLHttpRequest, el objeto contiene muchas propiedades y métodos que le permiten obtener más información completa sobre la respuesta del servidor, así como el objeto contiene métodos Promise. Si la función antesEnviar devuelve falso, entonces se cancelará la solicitud AJAX. De la versión jQuery 1.5 función antesEnviar se llamará independientemente del tipo de solicitud.

    cache(predeterminado: verdadero, para tipo de datos "texto" y "jsonp" falso).

    Tipo: booleano.
    Si se establece en falso, esto hará que el navegador no almacene en caché las páginas solicitadas. Tenga en cuenta que false solo funcionará correctamente con CABEZA y OBTENER peticiones.

    completo.

    Tipo: Función (jqXHR jqXHR, Cuerda textStatus).
    Una función que se llama cuando finaliza la solicitud (la función se ejecuta después de los eventos AJAX "éxito" o "error"). Se pasan dos parámetros a la función: jqXHR(en el objeto jQuery 1.4.x XMLHTTPRequest) y una línea correspondiente al estado de la solicitud ( "éxito", "no modificado", "sin contenido", "error", "se acabó el tiempo", "abortar", o "parsererror"). Desde jQuery 1.5, el parámetro completo puede tomar una serie de funciones que se llamarán a su vez.

    contenido.

    Tipo: PlainObject.
    Un objeto que consta de pares de cadena / expresión regular que definen cómo jQuery analizará (analizará) la respuesta según el tipo de contenido. Agregado en jQuery 1.5.

    tipo de contenido(defecto: "application / x-www-form-urlencoded; charset = UTF-8").

    Tipo: booleano o cadena.
    Determina el tipo de contenido que se especifica en la solicitud al enviar datos al servidor. Desde jQuery 1.6 se permite especificar el valor falso, en cuyo caso jQuery no pasa el campo en el encabezado Tipo de contenido en absoluto.

    contexto.

    Tipo: PlainObject.
    Cuando se ejecutan devoluciones de llamada AJAX, su contexto de ejecución es el objeto de la ventana. Parámetro contexto le permite configurar el contexto de ejecución de una función de tal manera que $ (this) se referirá a un elemento u objeto DOM específico. Por ejemplo: $ .ajax (( url: "test.html", contexto: $ (". myClass"), // nuevo contexto de ejecución de la función éxito: función () ( // si la solicitud es exitosa, llama a la función$ (esto) .html ("Todo está bien"); // agrega contenido de texto al elemento con class.myClass } } );

    convertidores

    Valores predeterminados:
    ("* texto": window.String, // cualquier tipo de texto"texto html": verdadero, // texto en html "texto json": jQuery.parseJSON, // texto en JSON "texto xml": jQuery.parseXML // texto en XML) Tipo: PlainObject.
    Un objeto que contiene el tipo de datos para convertir y cómo convertirlo. El valor de cada transformador es una función que devuelve el valor de respuesta transformado. Agregado en jQuery 1.5.

    dominio cruzado(predeterminado: falso para solicitudes dentro del mismo dominio, verdadero para solicitudes entre dominios).

    Tipo: booleano.
    Si desea realizar una solicitud entre dominios mientras se encuentra en el mismo dominio (por ejemplo, una solicitud jsonp), establezca este parámetro en verdadero. Esto permitirá, por ejemplo, redirigir la solicitud a otro dominio desde su servidor. Agregado en jQuery 1.5.

    Tipo: PlainObject o String o Array.
    Datos a enviar al servidor. Si no son una cadena, se convierten en una cadena de consulta. Para OBTENER la cadena de consulta se agregará a la URL. Para evitar el procesamiento automático, puede utilizar el parámetro procesar datos con el valor falso. Si los datos se transfieren como parte de un objeto, deben constar de pares clave / valor. Si el valor es una matriz, jQuery serializa varios valores con la misma clave (dependiendo del valor del parámetro tradicional que nos permite utilizar el tipo de serialización tradicional basado en el método $ .param).

    dataFilter.

    Tipo: Función (Cadena datos, Cuerda escribe) => Cualquier cosa.
    La función se llama después de la ejecución exitosa de la solicitud AJAX y le permite procesar los datos "sin procesar" recibidos de la respuesta del servidor. Los datos deben devolverse inmediatamente después del procesamiento. La función toma dos argumentos: datos- datos recibidos del servidor como una cadena y escribe- el tipo de estos datos (valor del parámetro tipo de datos).

    tipo de datos(defecto: xml, json, texto, o html).

    Tipo: Cadena.
    Determina el tipo de datos que espera recibir del servidor. Si no se especifica ningún tipo de datos, jQuery intentará determinarlo en función del tipo MIME de la respuesta ( XML tipo de MÍMICA resultará en XML, a partir de jQuery 1.4 json dará un objeto JavaScript, texto ejecutará el script y todo lo demás se devolverá como una cadena).

    Tipos básicos (el resultado se pasa como primer argumento a la función de devolución de llamada éxito):

    • "xml"- devoluciones XML un documento que se puede renderizar con jQuery.
    • "html"- devoluciones HTML como texto sin formato, etiquetas

      La forma más sencilla de trabajar con AJAX Es conectar el marco jQuery, que en realidad hice. jQuery nos proporciona una sintaxis fácil de entender y de usar para enviar AJAX solicitudes, ¿por qué no aprovechar esto?

      Creación de script js

      La sintaxis del archivo validate.js es

      $ (documento) .ready (function () (var email = ""; $ ("# email"). keyup (function () (var value = $ (this) .val (); $ .ajax ((type: "POST", url: "email.php", datos: "email =" + valor, éxito: función (msg) (if (msg == "valid") ($ ("# mensaje"). Html (" Este correo electrónico se puede utilizar.Este correo electrónico ya está en uso.");))));)); $ (" # enviar "). haga clic en (función () (if (email ==" ") (alert (" Por favor, ponga los datos en todos los correos electrónicos ");) else ( $ .ajax ((tipo: "POST", url: "email.php", datos: "add_email =" + correo electrónico, éxito: función (msg) ($ ("# mensaje"). html (msg);)) );)));));

      Controlador php

      Este script recibirá CORREO solicitud del cliente, procesarla y devolver el resultado. AJAX lee el resultado y toma una decisión basada en él.
      La sintaxis del archivo email.php es

      $ conexión = mysqli_connect ("localhost", "correo electrónico", "correo electrónico", "correo electrónico"); if (isset ($ _ POST ["correo electrónico"]) && $ _POST ["correo electrónico"]! = "") ($ correo electrónico = $ _POST ["correo electrónico"]; $ correo electrónico = mysqli_real_escape_string ($ conexión, $ correo electrónico); if (! filter_var ($ email, FILTER_VALIDATE_EMAIL)) (echo "invalid";) else ($ sql = "SELECT id FROM email WHERE email =" $ email ""; $ resultado = mysqli_query ($ conexión, $ sql); if (mysqli_num_rows ($ resultado) == 1) (echo "inválido";) else (echo "válido";))) if (isset ($ _ POST ["add_email"]) && $ _POST ["add_email"]! = "") ($ email = mysqli_real_escape_string ($ conexión, $ _ POST ["add_email"]); $ sql = "INSERT INTO email (email) VALUES (" $ email ")"; if (mysqli_query ($ conexión, $ sql )) (eco Éxito";) else (echo" Error"; } }

      En nuestro script php, el código más común que procesa una solicitud de publicación e imprime algo de texto en la página. Como resultado AJAX envía una solicitud secuencia de comandos php, el script lo procesa y produce el resultado, AJAX lee el resultado y cambia la página en tiempo real.

      AJAX pasa la solicitud POST al script a través de este código:

      $ .ajax ((tipo: "POST", url: "email.php", datos: "email =" + valor, éxito: función (msg) (if (msg == "válido") ($ ("# mensaje ") .html (" Este correo electrónico se puede utilizar."); email = valor;) else ($ (" # mensaje "). html (" Este correo electrónico ya está en uso."); } } });

      type: el tipo de solicitud, POST o GET. En nuestro caso, POST;
      url: la dirección del script al que se envía la solicitud;
      datos: datos que se transmiten en la solicitud;
      éxito: qué hacer como resultado de la ejecución exitosa de la solicitud. En nuestro caso, la función se llama;

      En el propio script, se realiza una verificación de la presencia de correo electrónico en la base de datos cada vez que se ingresa un carácter en el campo de correo electrónico. En el script, $ ("# email"). Keyup (function () ()); que comprueba si se pulsa una tecla en el campo con id = "email".
      Como puede ver, el código es bastante simple y no requiere habilidades particularmente grandes para comprender, todo está relacionado con el manejo de eventos keyup (): presionar una tecla, hacer clic (), hacer clic en un elemento. Seguido por AJAX solicitud y respuesta del script. Por lo tanto, usando php y ajax, puede obtener posibilidades casi infinitas para crear páginas interactivas.
      Este código no pretende ser de alta calidad, pero si lo desarrolla, agrega las validaciones correctas a nivel de cliente y servidor, ingresa css, entonces puede usarlo en sus proyectos.
      Si tiene alguna pregunta, no dude en escribir comentarios.
      Que tengas un buen día y hasta pronto 🙂

      Visa: Blaue Karte UE

      Al momento de la presentación de los documentos 29 años

      De Astracán

      Ciudad de la embajada: Moscú

      Universidad, especialidad: Universidad Técnica Estatal de Astracán, soporte Complejo seguridad de información sistemas automatizados

      Idiomas: inglés intermedio

      Cómo todo empezó:

      El deseo de mudarse a algún lugar ha existido durante mucho tiempo. Sin embargo, se consideraron principalmente países cálidos con mar. Dos veces sondearon seriamente el suelo con miras a mudarse a Montenegro o Bulgaria. Como resultado, en el último momento, por una razón u otra, cambiaron de opinión. La última vez fue en septiembre de 2014 después de serios preparativos para la venta del automóvil.

      En octubre, vi accidentalmente un anuncio sobre la búsqueda de programadores con una reubicación en Alemania. En ese momento, no tenía idea de la existencia de la Tarjeta Azul y consideraba a Alemania como un país con una política migratoria increíblemente dura hacia los ciudadanos de fuera de la UE.

      Con cierto escepticismo y desconfianza, escribí a Skype. En el otro lado de la pantalla, respondió una reclutadora (Alina), que se dedica a la selección de personal de TI, con la posterior reubicación a Alemania. En el momento de nuestra primera comunicación, hubo una contratación de programadores para una gran tienda en línea con sede en Berlín. Envié mi currículum y esperé.

      Después de un tiempo, Alina dijo que su colega de Alemania me hablaría para evaluar el nivel y la adecuación del idioma. La entrevista es más bien una conversación con dos tareas lógicas, duró 30 minutos por Skype. Luego me dijeron que esperara. Aproximadamente una semana después, se programó la primera entrevista técnica. La entrevista técnica también fue vía Skype con uno de los desarrolladores de la empresa. En mi opinión, salió bastante bien, pero una semana después me dijeron que no era adecuado. Por cierto, ni un solo candidato de Alina pasó por ciertas razones.

      Cómo resultó todo:

      Un poco molesto, pero la vida sigue. Y unos días después, Alina dijo que tenían un nuevo cliente de Stuttgart que estaba buscando desarrolladores y que tenía programada una entrevista para mí. La primera parte de la entrevista se comparte con el jefe de departamentos de TI y RR.HH. Una conversación general sobre la experiencia, yo y la empresa, muchas risas y bromas de ambos lados. Al parecer, mi humor era de mi agrado, por lo que unos días después se programó una entrevista técnica con un posible gerente de línea. Esta parte de la entrevista me sorprendió un poco, porque, como dijo más tarde uno de los candidatos, fue como "una conversación entre dos programadores tomando una cerveza". Más tarde esa noche, recibí una invitación para una entrevista personal en la oficina.

      En ese momento, no tenía una visa Schengen abierta. Se recogieron con urgencia los documentos necesarios. En el Centro de Solicitud de Visados ​​de Alemania en Moscú, solicité visa urgente y al día siguiente saqué mi pasaporte con una visa. Yo solicité visa de negocios por invitación que me enviaron desde Esslingen es solo una carta en la que se me invita a comunicarme y en la que se indica claramente que todos los problemas económicos relacionados con los vuelos, los traslados, las comidas y el alojamiento son asumidos por la empresa.

      La comunicación personal en la oficina se llevó a cabo con los tres principales líderes de TI de la empresa. La primera parte es, nuevamente, solo la comunicación sobre la experiencia, las habilidades y la comprensión general de algunos temas. El segundo es en la computadora. Honestamente, tareas muy, muy fáciles del nivel "junior con experiencia en pruebas" :). Las asignaciones estaban hechas. Después de eso, almuerzo e inmediatamente una oferta en forma de dos copias de contratos de trabajo (puesto Desarrollador PHP Senior) firmado por la empresa. Me tomé un tiempo para pensar y dije que respondería en una semana.

      Se tomó la decisión y comencé a prepararme para solicitar una visa.

      Cómo nos mudamos:

      La empresa pagó el vuelo para mí y mi familia (esposa e hija de 2,5 años), alquiló un apartamento para nosotros durante los primeros tres meses (en mi caso, un lugar ideal con vistas a la plaza central de Marktplatz) y asignó una persona para ayuda por primera vez. Este no es un agente de reubicación en forma pura, pero resolvimos todos los problemas emergentes a través de ella. Fui el primer empleado de la empresa fuera de la UE, por lo que me plantearon muchas preguntas. Ahora, además de mí, otro tipo de Kiev trabaja en la empresa (voló un mes después que yo) y un desarrollador de Odessa se está preparando para mudarse. Todos ellos también fueron empleados no sin la ayuda de Alina.

      Aquí me gustaría decir que estoy muy agradecido con Alina, quien resolvió todas las dudas que tuve en el proceso de contratación. Tuve mucha suerte de que en todas las etapas del empleo y posterior adaptación hubo una persona que estuvo dispuesta a ayudar y solucionar el problema necesario.

      Primero volé solo, dos semanas después llegó mi familia. Al llegar, nadie se encuentra, durante los primeros días viví en un hotel, esperando que desocuparan mi apartamento. Me recogieron del hotel y me llevaron al lugar 🙂

      Tomaron lo mínimo necesario de las cosas.

      Con ABH, todo fue muy rápido. Todos estos problemas se resolvieron conjuntamente con un empleado de la empresa. ABH designó el término con suficiente anticipación después de la llegada, enviamos los documentos y tres semanas después recibimos nuestras tarjetas eAT.

      Cómo nos establecimos:

      Sobre este momento vivimos en Esslingen, una ciudad increíblemente hermosa y limpia a solo 15 minutos de Stuttgart. Aún no estamos experimentando ninguna molestia por no conocer el idioma, en la mayoría de los casos podemos explicarnos en inglés o, en casos extremos, con gestos. El único problema que existe en este momento es alquilar un apartamento. Hay muy pocas ofertas y la demanda es increíblemente alta. La situación de la vivienda en Stuttgart es un poco más fácil, pero me gustaría quedarme en Esslingen.

      Breve resumen con fechas aproximadas:

      A mitad de octubre 2014- vio un anuncio sobre la búsqueda de programadores

      Finales de octubre - mediados de noviembre - entrevistas con la primera empresa

      Mediados de noviembre - finales de noviembre: entrevistas con mi empresa actual, recibir una oferta para una entrevista cara a cara

      20 de enero - 1 de febrero 2015 g.- solicitar una visa nacional, obtener pasaportes con visas

      ). La nube está diseñada para ejecutar varios scripts PHP en un horario o mediante una API. Normalmente, estos scripts procesan colas y la carga se "distribuye" en aproximadamente 100 servidores. Anteriormente, nos enfocamos en cómo se implementa la lógica de control, que es responsable de distribuir uniformemente la carga en tal cantidad de servidores y generar tareas en un horario. Pero, además de esto, necesitábamos escribir un demonio que pudiera ejecutar nuestros scripts PHP en la CLI y monitorear el estado de su ejecución.

      Originalmente estaba escrito en C, como todos los demás demonios de nuestra empresa. Sin embargo, nos enfrentamos al hecho de que una parte importante del tiempo del procesador (alrededor del 10%) se desperdició, de hecho, en vano: esto es lanzar el intérprete y cargar el "core" de nuestro framework. Por lo tanto, para poder inicializar el intérprete y nuestro marco solo una vez, se decidió reescribir el demonio en PHP. Lo llamamos Php Roca syd (por analogía con Phproxyd - PHP Proxy Daemon, el demonio C que teníamos antes). Acepta solicitudes para lanzar clases individuales y hace fork () para cada solicitud, y también sabe cómo informar el estado de ejecución de cada uno de los lanzamientos. Esta arquitectura es en muchos aspectos similar al modelo de servidor web Apache, cuando toda la inicialización se realiza una vez en el "asistente" y los "hijos" ya están manejando la solicitud. Como ventaja adicional, tenemos la capacidad de habilitar la caché de código de operación en la CLI, que funcionará correctamente ya que todos los hijos heredan la misma área de memoria compartida que el proceso maestro. Para reducir los retrasos en el procesamiento de una solicitud de inicio, puede fork () por adelantado (modelo prefork), pero en nuestro caso, los retrasos de fork () son de aproximadamente 1 ms, lo cual está bien para nosotros.

      Sin embargo, dado que actualizamos el código con bastante frecuencia, este demonio también debe reiniciarse con frecuencia, de lo contrario, el código que se carga en él puede quedar desactualizado. Dado que cada reinicio vendría acompañado de una gran cantidad de errores de forma conexión restablecida por par, incluidas las denegaciones de servicio a los usuarios finales (el demonio es útil no solo para la nube, sino también para parte de nuestro sitio), decidimos buscar formas de reiniciar el demonio sin perder el conexiones establecidas... Hay una técnica popular que se utiliza para hacer recarga elegante para demonios: se realiza un fork-exec y se pasa un descriptor del conector de escucha al hijo. Por tanto, ya se aceptan nuevas conexiones. nueva versión demonio, y los antiguos se "modifican" utilizando la versión antigua.

      En este artículo, veremos una opción más complicada. recarga elegante: las conexiones antiguas seguirán siendo procesadas por la nueva versión del demonio, lo cual es importante en nuestro caso, porque de lo contrario ejecutará el código antiguo.

      Teoría

      Pensemos primero: ¿es posible lo que queremos conseguir? Y si es así, ¿cómo se puede lograr?

      Dado que el demonio se ejecuta en Linux, que es compatible con POSIX, tenemos disponibles las siguientes opciones:

      1. Todos los archivos y sockets abiertos son números correspondientes al número de descriptor abierto. Los flujos de entrada, salida y error estándar tienen descriptores 0, 1 y 2, respectivamente.
      2. No hay diferencias significativas entre abrir documento, socket y pipe no lo son (por ejemplo, puede trabajar con sockets usando tanto lectura / escritura como sendto / recv desde llamadas al sistema).
      3. Cuando se ejecuta la llamada al sistema fork (), todos los descriptores abiertos se heredan, conservando sus números y posiciones de lectura / escritura (en archivos).
      4. Al ejecutar la llamada al sistema execve (), todos los descriptores abiertos también se heredan y, además, se conservan. PID del proceso y por tanto apego a sus hijos.
      5. La lista de descriptores de procesos abiertos está disponible en el directorio / dev / fd, que en Linux es un enlace simbólico a / proc / self / fd.
      Por lo tanto, tenemos todas las razones para creer que nuestra tarea es alcanzable y sin mucho esfuerzo. Entonces empecemos.

      Parches PHP

      Desafortunadamente, hay un pequeño detalle que complica nuestro trabajo: en PHP no hay forma de obtener el número de descriptor de archivo para transmisiones y abrir el descriptor de archivo por número (en su lugar, se abre una copia del descriptor de archivo, que no es adecuada para nuestro demonio, ya que estamos monitoreando con mucho cuidado los descriptores abiertos para no crear fugas durante el reinicio y al iniciar procesos secundarios).

      Primero, haremos un par de pequeños parches al código PHP para agregar la capacidad de obtener fd de un flujo y hacerlo de modo que fopen (php: // fd / ) no abrió una copia del identificador (el segundo cambio es incompatible con el comportamiento actual de PHP, por lo que puede agregar una nueva "dirección" en su lugar, por ejemplo, php: // fdraw / ):

      Código de parche

      diff --git a / ext / standard / php_fopen_wrapper.cb / ext / standard / php_fopen_wrapper.c índice f8d7bda..fee964c 100644 --- a / ext / standard / php_fopen_wrapper.c +++ b / ext / standard / php_fopen_wrapper. c @@ -24.6 +24.7 @@ #if HAVE_UNISTD_H #include #endif + # include #include "php.h" #include "php_globals.h" @@ -296.11 +297.11 @@ php_stream * php_stream_url_wrap_php (php_stream_wrapper * wrapper, char * path, ch "Los descriptores de archivo deben ser números no negativos menores que% d" , dtablesize); return NULL; ) - - fd = dup (fildes_ori); - if (fd == -1) (+ + fd = fildes_ori; + if (fcntl (fildes_ori, F_GETFD) == -1) (php_stream_wrapper_log_error (envoltorio, opciones TSRMLS_CC, - "Error al duplicar el descriptor de archivo% ld; posiblemente no "t existe:" + "Descriptor de archivo% ld inválido:" "[% d]:% s", fildes_ori, errno, strerror (errno)); return NULL;) diff --git a / ext / standard / streamsfuncs. cb / ext / standard / streamsfuncs.c index 0610ecf..14fd3b0 100644 --- a / ext / standard / streamsfuncs.c +++ b / ext / standard / streamsfuncs.c @@ -24.6 +24.7 @ @ #include " ext / standard / flock_compat.h "#include" ext / standard / file.h "#include" ext / standard / php_filestat.h "+ # incluye" ext / standard / php_fopen_wrappers.h "#include" php_open_temporary_file .h "# incluir "ext / standard / basic_functions.h" #include "php_ini.h" @@ -484.6 +485.7 @@ PHP_FUNCTION (stream_get_meta_data) zval * arg1; php_stream * stream; zval * newval; + int tmp_fd; if (zend_parse_ARGSUM_NUM () TSRMLS_CC, "r", & arg1) == FALLO) (return; @@ -502.6 +504.9 @@ PHP_FUNCTION (stream_get_m eta_data) add_assoc_string (return_value, "wrapper_type", (char *) stream-> wrapper-> wops-> etiqueta, 1); ) add_assoc_string (return_value, "stream_type", (char *) stream-> ops-> label, 1); + if (SUCCESS == php_stream_cast (stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void *) & tmp_fd, 1) && tmp_fd! = -1) (+ add_assoc_long (return_value, "t, modo_valor_fd," 1);


      Hemos agregado el campo fd al resultado devuelto por la función stream_get_meta_data (), si tiene sentido (por ejemplo, para flujos zlib, el campo fd no estará presente). También reemplazamos la llamada dup () del descriptor de archivo pasado con una simple verificación. Desafortunadamente, este código no funcionará sin modificaciones en Windows, ya que la llamada fcntl () es específica de POSIX, por lo que el parche completo debe contener ramas de código adicionales para otros sistemas operativos.

      Un demonio sin capacidad de reinicio

      Primero, escriba un servidor pequeño que pueda aceptar solicitudes en formato JSON y dar algún tipo de respuesta. Por ejemplo, devolverá el número de elementos de la matriz que se incluyeron en la solicitud.

      El demonio está escuchando en el puerto 31337. La salida debería ser algo como esto:

      $ telnet localhost 31337 Intentando 127.0.0.1 ... Conectado a localhost. El carácter de escape es "^]". ("hash": 1) # entrada de usuario "La solicitud tenía 1 claves" ("hash": 1, "cnt": 2) # entrada de usuario "La solicitud tenía 2 claves"

      Usaremos stream_socket_server () para comenzar a escuchar en el puerto y stream_select () para determinar qué descriptores están listos para leer / escribir.

      Código de implementación más simple (Simple.php)

      flujo) * / $ flujos privados =; / ** @var string (client_id => leer búfer) * / private $ read_buf =; / ** @var string (client_id => buffer de escritura) * / private $ write_buf =; / ** @var resource (client_id => flujo desde el cual leer) * / private $ read =; / ** @var resource (client_id => stream donde escribir) * / private $ write =; / ** @var int Recuento total de conexiones * / private $ conn_count = 0; función pública run () ($ this-> listen (); echo "Ingresando al bucle principal \ n"; $ this-> mainLoop ();) función protegida listen () ($ port = self :: PORT; $ ip_port = " 0.0.0.0:$port "; $ dirección =" tcp: // $ ip_port "; $ servidor = stream_socket_server ($ dirección, $ errno, $ errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); if (! $ Server) (fwrite (STDERR, "stream_socket_server falló: $ errno $ errstr \ n"); exit (1);) $ this-> read = $ server; echo "Escuchando en $ dirección \ n";) respuesta de función pública ($ stream_id, $ respuesta) ( $ json_resp = json_encode ($ respuesta); echo "stream $ stream_id". $ json_resp. "\ n"; $ this-> write ($ stream_id, $ json_resp. "\ n");) función pública write ($ stream_id, $ buf) ($ this-> write_buf [$ stream_id]. = $ buf; if (! isset ($ this-> write [$ stream_id])) ($ this-> write [$ stream_id] = $ this-> streams [$ stream_id];)) función pública accept ($ server) (echo "Aceptando nueva conexión \ n"; $ client = stream_socket_accept ($ server, 1, $ peername); $ stream_id = ($ this-> conn_count + +); if (! $ cliente) (fwrite (STDERR, "Aceptación fallida \ n"); return;) stream_set_read_buffer ($ cliente, 0); stream_set_write_buffer ($ cliente, 0); stream_set_blocking ($ cliente, 0); stream_set_timeout ($ cliente, 1); $ esto-> read_buf [$ stream_id] = ""; $ esto-> write_buf [$ stream_id] = ""; $ this-> leer [$ stream_id] = $ this-> streams [$ stream_id] = $ cliente; echo "Secuencia conectada $ stream_id: $ peername \ n"; ) función privada desconectar ($ stream_id) (echo "Desconectar corriente $ stream_id \ n"; desarmar ($ this-> read_buf [$ stream_id], $ this-> write_buf [$ stream_id]); desarmar ($ this-> streams [ $ stream_id]); unset ($ this-> write [$ stream_id], $ this-> read [$ stream_id]);) función privada handleRead ($ stream_id) ($ buf = fread ($ this-> streams [$ stream_id ], 8192); if ($ buf === false || $ buf === "") (echo "obtuvo EOF de la secuencia $ stream_id \ n"; if (vacío ($ this-> write_buf [$ stream_id]) ) ($ this-> desconectar ($ stream_id);) else (unset ($ this-> read [$ stream_id]);) return;) $ this-> read_buf [$ stream_id]. = $ buf; $ esto-> processJSONRequests ($ stream_id);) función privada processJSONRequests ($ stream_id) (if (! strpos ($ this-> read_buf [$ stream_id], "\ n")) return; $ solicitudes = explotar ("\ n", $ this -> read_buf [$ stream_id]); $ this-> read_buf [$ stream_id] = array_pop ($ solicitudes); foreach ($ solicitudes como $ req) ($ res = json_decode (rtrim ($ req), true); if ( $ res! == falso) ($ this-> response ($ stream_id, "Request had". count ($ res). "keys");) else ($ this-> response ($ stream_id, "JSON inválido");)) ) función privada handleWrite ($ stream_id) (if (! isset ($ this-> write_buf [$ stream_id])) (return;) $ escribió = fwrite ($ this-> streams [$ stream_id], substr ($ this-> write_buf [$ stream_id], 0, 65536)); if ($ escribió === falso) (fwrite (STDERR, "error de escritura en el flujo # $ stream_id \ n"); $ this-> desconectar ($ stream_id); return ;) if ($ escribió === strlen ($ this-> write_buf [$ stream_id])) ($ this-> write_buf [$ stream_id] = ""; unset ($ this-> write [$ stream_id]); si (vacío ($ this-> read [$ stream_id])) ($ this-> desconectar ($ stream_id);)) else ($ this-> write_buf [$ stream_id] = substr ($ this-> write_buf [$ stream_id] , $ escribió);)) función pública mainLoop () (while (true) ($ read = $ this-> read; $ write = $ this-> write; $ except = null; echo "Seleccionando para". count ($ leer). "lee". contar ($ escribir). "escribe \ n"; $ n = stream_select ($ leer, $ escribir, $ excepto, NULO); if (! $ n) (fwrite (STDERR, "No se pudo seleccionar stream_select () \ n");) if (count ($ read)) (echo "Se puede leer desde". count ($ read). "streams \ n" ;) if (count ($ write)) (echo "Puede escribir en". count ($ write). "streams \ n";) if (isset ($ read)) ($ this-> accept ($ read); unset ($ leído);) foreach ($ leído como $ stream_id => $ _) ($ this-> handleRead ($ stream_id);) foreach ($ escribir como $ stream_id => $ _) ($ this-> handleWrite ( $ stream_id);)))) $ instancia = nuevo Simple (); $ instancia-> ejecutar ();


      El código para este demonio es más que estándar, pero me gustaría señalar un detalle de implementación: almacenamos todos los búferes de lectura y escritura con enlaces a conexiones específicas y realizamos el procesamiento de solicitudes en el mismo lugar donde leemos la solicitud. Esto es importante porque una de estas solicitudes se puede reiniciar, en cuyo caso no llegará a procesar las siguientes solicitudes. Sin embargo, dado que aún no hemos leído las solicitudes, la próxima vez que stream_select () de los mismos descriptores devolverá el mismo resultado. Así, no perderemos ni una sola solicitud si reiniciamos directamente desde el manejador de comandos (excepto en el caso en el que se nos envíen varios comandos a la vez a la misma conexión, y uno de estos comandos se reiniciará).

      Entonces, ¿cómo se hace posible reiniciar el demonio?

      Daemon con reiniciar y guardar conexiones establecidas

      Nuestro ejemplo más simple no sabía cómo hacer nada útil, así que sigamos escribiendo el demonio que se discutió al principio. Queremos recibir algo como lo siguiente (los comandos se envían al demonio con el formato "command_name [JSON-data]", la respuesta tiene el formato JSON):
      $ telnet localhost 31337 Intentando 127.0.0.1 ... Conectado a localhost. El carácter de escape es "^]". # pedir inmediatamente al demonio que reinicie reiniciar # la respuesta es enviada por el demonio ya reiniciado "Reiniciado exitosamente" # ejecutar la clase de prueba ejecutar ("hash": 1, "params" :, "class": "TestClass1") # iniciado exitosamente ("error_text": "OK") # reiniciar el demonio nuevamente (su hijo TestClass1 todavía se está ejecutando) reiniciar "Reiniciado exitosamente" # verificar el estado del trabajo: aún en ejecución check ("hash": 1) ("error_text" : "Todavía en ejecución") # espere 5 segundos y verifique nuevamente: la clase TestClass1 funcionó con éxito check ("hash": 1) ("retcode": 0) # el demonio recuerda todos los lanzamientos, por lo que debe liberar check ("hash ": 1) (" retcode ": 0) free (" hash ": 1) (" error_text ":" OK ") restart" Reiniciado con éxito "# Actualicé el código, así que la segunda vez vemos una respuesta diferente para reiniciar reiniciar ("error_text": "Reiniciado con éxito") bye Conexión cerrada por un host externo.

      La idea para reiniciar es simple: crearemos un archivo con toda la información necesaria, y al iniciar intentaremos leerlo y restaurar los descriptores de archivos abiertos.

      Primero, escribamos el código para escribir en el archivo de reinicio:

      Echo "Creando archivo de reinicio ... \ n"; if (! $ res = $ this-> getFdRestartData ()) (fwrite (STDERR, "No se pudieron reiniciar los datos de FD, saliendo, no se admite el reinicio correcto \ n"); exit (0);) / * Cerrar todos los extra descriptores de archivo que no conocemos, incluido el descriptor opendir () :) * / $ dh = opendir ("/ proc / self / fd"); $ fds =; while (false! == ($ file = readdir ($ dh))) (if ($ file === ".") continue; $ fds = $ file;) foreach ($ fds como $ fd) (if (! isset ($ esto-> conocido_fds [$ fd])) (fclose (fopen ("php: // fd /". $ fd, "r +"));)) $ contenido = serializar ($ res); if (file_put_contents (self :: RESTART_DIR. self :: RESTART_FILENAME, $ contents)! == strlen ($ contents)) (fwrite (STDERR, "No se pudo escribir completamente el archivo de reinicio \ n"); desvincular (self :: RESTART_DIR. self :: RESTART_FILENAME);)

      El código para obtener una matriz de datos (la función getFdRestartData ()) se muestra a continuación:

      $ res =; foreach (self :: $ restart_fd_resources as $ prop) ($ res [$ prop] =; foreach ($ this -> $ prop as $ k => $ v) ($ meta = stream_get_meta_data ($ v); if (! isset ($ meta ["fd"])) (fwrite (STDERR, "No hay fd en los metadatos del flujo para el recurso $ v (clave $ k en $ prop), tengo". var_export ($ meta, true). "\ n") ; return false;) $ res [$ prop] [$ k] = $ meta ["fd"]; $ this-> known_fds [$ meta ["fd"]] = true;)) foreach (self :: $ restart_fd_props as $ prop) ($ res [$ prop] = $ this -> $ prop;) return $ res;
      El código tiene en cuenta que tenemos 2 tipos de propiedades:

      1. Propiedades que contienen recursos con conexiones: $ restart_fd_resources = ["leer", "escribir", "flujos"].
      2. Propiedades que contienen búferes y otra información de conexión que se pueden "serializar" en su forma original: $ restart_fd_props = ["read_buf", "write_buf", "conn_count"].
      También recordamos todos los fd guardados en el archivo de reinicio y cerramos todos los demás (si los hay), porque de lo contrario podríamos filtrar descriptores de archivos.

      A continuación, debemos cargar este archivo al inicio y continuar usando descriptores abiertos, como si no hubiera pasado nada :). El código para dos funciones (cargar el archivo de reinicio y cargar información sobre descriptores de archivo) se muestra a continuación:

      If (! File_exists (self :: RESTART_DIR. Self :: RESTART_FILENAME)) (return;) echo "Reiniciar archivo encontrado, intentando adoptarlo \ n"; $ contenido = file_get_contents (self :: RESTART_DIR. self :: RESTART_FILENAME); desvincular (self :: RESTART_DIR. self :: RESTART_FILENAME); if ($ contenido === falso) (fwrite (STDERR, "No se pudo leer el archivo de reinicio \ n"); return;) $ res = unserialize ($ contenido); if (! $ res) (fwrite (STDERR, "No se pudo anular la serialización del contenido del archivo de reinicio"); return;) foreach (self :: $ restart_props as $ prop) (if (! array_key_exists ($ prop, $ res)) (fwrite (STDERR, "No hay propiedad $ prop en el archivo de reinicio \ n"); continuar;) $ esto -> $ prop = $ res [$ prop];) $ this-> loadFdRestartData ($ res);

      La función loadFdRestartData () para expandir la matriz del descriptor de archivo nuevamente:

      $ fd_resources =; foreach (self :: $ restart_fd_resources as $ prop) (if (! isset ($ res [$ prop])) (fwrite (STDERR, "Property" $ prop "no está presente en restart fd resources \ n"); continuar; ) $ pp =; foreach ($ res [$ prop] as $ k => $ v) (if (isset ($ fd_resources [$ v])) ($ pp [$ k] = $ fd_resources [$ v];) else ($ fp = fopen ("php: // fd /". $ v, "r +"); if (! $ fp) (fwrite (STDERR, "No se pudo abrir fd = $ v, saliendo de \ n") ; exit (1);) stream_set_read_buffer ($ fp, 0); stream_set_write_buffer ($ fp, 0); stream_set_blocking ($ fp, 0); stream_set_timeout ($ fp, self :: CONN_TIMEOUT); $ fd_resources [$ v] = $ fp; $ pp [$ k] = $ fp;)) $ this -> $ prop = $ pp;) foreach (self :: $ restart_fd_props as $ prop) (if (! isset ($ res [$ prop])) (fwrite (STDERR, "Propiedad" $ prop "no está presente en las propiedades de reinicio fd \ n"); continuar;) $ this -> $ prop = $ res [$ prop];)
      Reajustamos los valores read_buffer y write_buffer para los descriptores de archivos abiertos y establecemos los tiempos de espera. Curiosamente, después de estas manipulaciones, PHP hace accept () con bastante calma en estos descriptores de archivo y continúa leyendo / escribiendo en ellos normalmente, aunque no sabe que se trata de sockets.

      Al final, tenemos que escribir la lógica para iniciar y monitorear el estado de ejecución de los trabajadores. Dado que esto no es relevante para el tema del artículo, la implementación completa del demonio se ha publicado en el repositorio de github, cuyo enlace se proporciona a continuación.

      Conclusión

      Entonces, este artículo describió la implementación de un demonio que se comunica usando el protocolo JSON y es capaz de ejecutar clases arbitrarias en procesos separados con monitoreo del proceso de su ejecución. Para ejecutar clases individuales, se utiliza el modelo tenedor () por solicitud, por lo tanto, para procesar la solicitud, no es necesario reiniciar el intérprete y cargar el marco, mientras es posible utilizar la caché de código de operación en la CLI. Dado que el demonio debe reiniciarse cada vez que se actualiza el código, es necesario proporcionar un mecanismo para reiniciar sin problemas este demonio (en nuestra empresa, el código a veces se actualiza cada pocos minutos, en forma de "revisiones").

      El reinicio ocurre al ejecutar la llamada al sistema execve (), como resultado de lo cual todos los hijos permanecen adjuntos al padre (ya que el PID del proceso no cambia durante execve ()). Además, todos los descriptores de archivos abiertos se guardan, lo que le permite continuar procesando solicitudes de usuarios en conexiones ya abiertas. Todos los búferes de red, la información sobre los niños en ejecución y los descriptores abiertos se guardan en un archivo de reinicio separado, que es leído por una nueva instancia del demonio, después de lo cual el trabajo continúa en el ciclo de eventos estándar.

      El código de implementación completo se puede ver en GitHub en la siguiente URL.