One place for hosting & domains

      aplicaciones

      Cómo enviar notificaciones push web desde aplicaciones de Django


      El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

      Introducción

      La web evoluciona de manera constante y ahora puede lograr las funcionalidades que antes solo estaban disponibles en dispositivos móviles nativos. La introducción de los trabajos de servicio de JavaScript incorporó a la Web habilidades recién descubiertas para actividades como la sincronización en segundo plano, el almacenamiento en caché fuera de línea y el envío de notificaciones push.

      Las notificaciones push permiten a los usuarios recibir actualizaciones para aplicaciones móviles y web. También permiten que estos vuelvan a usar aplicaciones existentes mediante contenido personalizado y pertinente.

      A través de este tutorial, configurará una en Ubuntu 18.04 aplicación de Django que envíe notificaciones push cuando haya alguna actividad en la cual se requiera que el usuario ingrese a la aplicación. Para crear estas notificaciones, utilizará el paquete Django-Webpush, y configurará y registrará un trabajo de servicio para mostrar las notificaciones al cliente. La aplicación, en condiciones de funcionamiento y con las notificaciones, tendrá este aspecto:

      Push web final

      Requisitos previos

      Para completar esta guía, necesitará lo siguiente:

      Paso 1: Instalar Django-Webpush y generar claves de Vapid

      Django-Webpush es un paquete que permite a los desarrolladores integrar y enviar notificaciones push web en aplicaciones de Django. Usaremos este paquete para activar y enviar las notificaciones desde nuestra aplicación. En este paso, instalará Django-Webpush y obtendrá las claves de Identificación voluntaria del servidor de aplicaciones (VAPID) necesarias para identificar su servidor y garantizar la singularidad de cada solicitud.

      Asegúrese de posicionarse en el directorio del proyecto ~/djangopush que creó en los requisitos previos:

      Active su entorno virtual:

      • source my_env/bin/activate

      Actualice su versión de pip para garantizar que esté vigente:

      • pip install --upgrade pip

      Instale Django-Webpush:

      • pip install django-webpush

      Después de instalar el paquete, agréguelo a la lista de aplicaciones de su archivo settings.py. Primero abra settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Añada webpush a la lista de INSTALLED_APPS:

      ~/djangopush/djangopush/settings.py

      ...
      
      INSTALLED_APPS = [
          ...,
          'webpush',
      ]
      ...
      

      Guarde el archivo y cierre el editor.

      Ejecute migraciones en la aplicación para implementar los cambios que realizó en el esquema de su base de datos:

      El resultado tendrá el siguiente aspecto, lo cual indicará que la migración se realizó con éxito:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, webpush Running migrations: Applying webpush.0001_initial... OK

      El siguiente paso para configurar las notificaciones push web consiste en obtener claves de VAPID. Estas claves identifican el servidor de la aplicación y pueden utilizarse para reducir la confidencialidad de las URL de suscripciones push, ya que limitan las suscripciones a un servidor específico.

      Para obtener claves de VAPID, diríjase a la aplicación web de wep-push-codelab. Aquí, recibirá claves generadas de forma automática. Copie las claves privadas y públicas.

      A continuación, cree una nueva entrada en settings.py para su información de VAPID. Primero, abra el archivo:

      • nano ~/djangopush/djangopush/settings.py

      A continuación, agregue una nueva directiva llamada WEBPUSH_SETTINGS con sus claves públicas y privadas de VAPID y su correo electrónico por debajo de AUTH_PASSWORD_VALIDATORS:

      ~/djangopush/djangopush/settings.py

      ...
      
      AUTH_PASSWORD_VALIDATORS = [
          ...
      ]
      
      WEBPUSH_SETTINGS = {
         "VAPID_PUBLIC_KEY": "your_vapid_public_key",
         "VAPID_PRIVATE_KEY": "your_vapid_private_key",
         "VAPID_ADMIN_EMAIL": "[email protected]"
      }
      
      # Internationalization
      # https://docs.djangoproject.com/en/2.0/topics/i18n/
      
      ...
      

      No olvide sustituir los valores del marcador de posición your_vapid_publickey, `yourvapidpublickeyy[email protected]` por su propia información. A través de su dirección de correo electrónico, se le notificará si el servidor push experimenta problemas.

      A continuación, configuraremos las vistas que mostrarán la página de inicio de la aplicación y activarán notificaciones push para los usuarios suscritos.

      Paso 2: Configurar vistas

      En este paso, configuraremos una vista home básica con el objeto response HttpResponse para nuestra página de inicio, junto con una vista de send_push. Las vistas son funciones que muestran objetos response de solicitudes web. La vista de send_push usará la biblioteca de Django-Webpush para enviar notificaciones push que contienen los datos ingresados por un usuario en la página de inicio.

      Diríjase a la carpeta ~/djangopush/djangopush:

      • cd ~/djangopush/djangopush

      Ejecutar ls dentro de la carpeta le mostrará los archivos principales del proyecto:

      Output

      /__init__.py /settings.py /urls.py /wsgi.py

      Los archivos de esta carpeta se generan de forma automática a través de la utilidad django-admin que utilizó para crear su proyecto en los requisitos previos. El archivo settings.py contiene configuraciones de todo el proyecto, como las aplicaciones instaladas y la carpeta root estática. El archivo urls.py contiene las configuraciones de URL para el proyecto. Aquí es donde establecerá las rutas para que coincidan con las vistas que creó.

      Cree un nuevo archivo dentro del directorio ~/djangopush/djangopush llamado views.py, que contendrá las vistas para su proyecto:

      • nano ~/djangopush/djangopush/views.py

      La primera vista que haremos es home, que mostrará la página de inicio en la cual los usuarios pueden enviar notificaciones push. Añada el siguiente código al archivo:

      ~/djangopush/djangopush/views.py

      from django.http.response import HttpResponse
      from django.views.decorators.http import require_GET
      
      @require_GET
      def home(request):
          return HttpResponse('<h1>Home Page<h1>')
      

      La vista home está representada por el decorador require_GET, que la limita a exclusivamente a solicitudes GET. Una vista suele mostrar una respuesta a cada solicitud que se le hace. Esta vista muestra una etiqueta HTML simple como respuesta.

      La siguiente vista que crearemos es send_push, que se encargará de las notificaciones push enviadas usando el paquete django-webpush. Se limitará únicamente a solicitudes POST y quedará exento de la protección contra la* falsificación de solicitud entre sitios cruzados* (CSRF). Realizar esto le permitirá probar la vista usando Postman o cualquier otro servicio de RESTful. Sin embargo, para la producción debe quitar este decorador a fin de evitar que sus vistas sean vulnerables a CSRF.

      Para crear la vista send_push, primero agregue las siguientes importaciones a fin de habilitar las respuestas de JSON y acceder a la función send_user_notification en la biblioteca webpush:

      ~/djangopush/djangopush/views.py

      from django.http.response import JsonResponse, HttpResponse
      from django.views.decorators.http import require_GET, require_POST
      from django.shortcuts import get_object_or_404
      from django.contrib.auth.models import User
      from django.views.decorators.csrf import csrf_exempt
      from webpush import send_user_notification
      import json
      

      A continuación, agregue el decorador require_POST, que usará el cuerpo de la solicitud enviada por el usuario para crear y activar una notificación push.

      ~/djangopush/djangopush/views.py

      @require_GET
      def home(request):
          ...
      
      
      @require_POST
      @csrf_exempt
      def send_push(request):
          try:
              body = request.body
              data = json.loads(body)
      
              if 'head' not in data or 'body' not in data or 'id' not in data:
                  return JsonResponse(status=400, data={"message": "Invalid data format"})
      
              user_id = data['id']
              user = get_object_or_404(User, pk=user_id)
              payload = {'head': data['head'], 'body': data['body']}
              send_user_notification(user=user, payload=payload, ttl=1000)
      
              return JsonResponse(status=200, data={"message": "Web push successful"})
          except TypeError:
              return JsonResponse(status=500, data={"message": "An error occurred"})
      

      Usaremos dos decoradores para la vista send_push: el decorador require_POST, que limita la vista únicamente a las solicitudes de POST, y el decorador de csrf_exempt, que exenta a la vista de la protección CSRF.

      Esta vista espera datos de POST y realiza lo siguiente: obtiene el body de la solicitud y, usando el paquete de json, deserializa el documento JSON a un objeto de Python con json.loads. json.loads obtiene un documento JSON estructurado y lo convierte en un objeto de Python.

      La vista espera que el objeto body de la solicitud tenga tres propiedades:

      • head: el título de la notificación push.
      • body: el cuerpo de la notificación.
      • id: el id del usuario de la solicitud.

      Si falta alguna de las propiedades necesarias, en la vista se mostrará una respuesta JSONResponse con un estado 404 “Not Found”. Si el usuario con la clave primaria dada existe, la vista mostrará el user con la clave primaria correspondiente usando la función get_objet_or_404 de la biblioteca django.shortcuts. Si el usuario no existe, la función mostrará un error 404.

      La vista también utiliza la función send_user_notification de la biblioteca webpush. Esta función toma tres parámetros:

      • User: el destinatario de la notificación push.
      • payload: la información de la notificación, que incluye el head y el body de esta.
      • ttl: el tiempo máximo en segundos durante el cual la notificación debe almacenarse si el usuario se encuentra fuera de línea.

      Si no se producen errores, la vista muestra una respuesta JSONResponse con un estado 200 “Success” y un objeto de datos. Si se produce un KeyError, la vista mostrará un estado 500 de “Internal Server Error”. Un KeyError se produce cuando no existe la clave solicitada de un objeto.

      En el siguiente paso, crearemos las rutas URL correspondientes para que coincidan con las vistas que creamos.

      Paso 3: Asignar URL a vistas

      Django permite crear URL que establezcan conexión con vistas mediante un módulo de Python llamado URLconf. Este módulo asigna expresiones de rutas de URL a funciones de Python (sus vistas). Normalmente, se genera de forma automática un archivo de configuración de URL cuando se crea un proyecto. Al completar este paso, actualizará este archivo a fin de incluir nuevas rutas para las vistas que creó en el paso anterior, junto con las URL para la aplicación django-webpush; esta proporcionará extremos para suscribir usuarios a notificaciones push.

      Para obtener más información sobre vistas, consulte Cómo crear vistas de Django.

      Abra urls.py:

      • nano ~/djangopush/djangopush/urls.py

      El archivo tendrá este aspecto:

      ~/djangopush/djangopush/urls.py

      
      """untitled URL Configuration
      
      The `urlpatterns` list routes URLs to views. For more information please see:
          https://docs.djangoproject.com/en/2.1/topics/http/urls/
      Examples:
      Function views
          1. Add an import:  from my_app import views
          2. Add a URL to urlpatterns:  path('', views.home, name='home')
      Class-based views
          1. Add an import:  from other_app.views import Home
          2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
      Including another URLconf
          1. Import the include() function: from django.urls import include, path
          2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
      """
      from django.contrib import admin
      from django.urls import path
      
      urlpatterns = [
          path('admin/', admin.site.urls),
      ]
      

      El siguiente paso es asignar a URL las vistas que creó. Primero, agregue la importación include a fin de garantizar que todas las rutas para la biblioteca de Django-Webpush se añadan a su proyecto:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      

      A continuación, importe las vistas que creó en el último paso y actualice la lista de urlpatterns para asignar sus vistas:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      
      from .views import home, send_push
      
      urlpatterns = [
                        path('admin/', admin.site.urls),
                        path('', home),
                        path('send_push', send_push),
                        path('webpush/', include('webpush.urls')),
                    ]
      

      Aquí, la lista de urlpatterns registra las URL para el paquete django-webpush y asigna sus vistas a las URL /send_push y /home.

      Realicemos una prueba de la vista de /home para asegurarnos de que funcione como se pretende. Asegúrese de estar posicionado en el directorio root del proyecto:

      Inicie su servidor ejecutando el siguiente comando:

      • python manage.py runserver your_server_ip:8000

      Diríjase a http://your_server_ip:8000. Debería ver la siguiente página de inicio:

      Vista inicial de la página principal

      En este punto, puede detener el servidor con CTRL+C. A continuación, procederemos a crear plantillas y a suministrarlas en nuestras vistas usando la función render.

      Paso 4: Crear plantillas

      El motor de plantillas de Django le permite definir las capas de su aplicación orientadas al usuario con plantillas similares a archivos HTML. En este paso, creará y representará una plantilla para la vista home.

      Cree una carpeta llamada templates en el directorio root de su proyecto:

      • mkdir ~/djangopush/templates

      Si ejecuta ls en la carpeta root de su proyecto en este punto, el resultado tendrá este aspecto:

      Output

      /djangopush /templates db.sqlite3 manage.py /my_env

      Cree un archivo llamado home.html en la carpeta templates:

      • nano ~/djangopush/templates/home.html

      Añada el siguiente código al archivo para crear un formulario en el que los usuarios puedan introducir información y crear notificaciones push:

      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <meta name="vapid-key" content="{{ vapid_key }}">
          {% if user.id %}
              <meta name="user_id" content="{{ user.id }}">
          {% endif %}
          <title>Web Push</title>
          <link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
      </head>
      
      <body>
      <div>
          <form id="send-push__form">
              <h3 class="header">Send a push notification</h3>
              <p class="error"></p>
              <input type="text" name="head" placeholder="Header: Your favorite airline 😍">
              <textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
              <button>Send Me</button>
          </form>
      </div>
      </body>
      </html>
      

      El body del archivo incluye un formulario con dos campos: un elemento input contendrá el encabezado o título de la notificación y un elemento textarea contendrá el cuerpo de la notificación.

      En la sección head del archivo, existen dos etiquetas meta que almacenarán la clave pública de VAPID y la identificación del usuario. Estas dos variables son necesarias para registrar un usuario y enviarle notificaciones push. Se requiere aquí la identificación del usuario, ya que enviará solicitudes AJAX al servidor y el id se usará para identificar el usuario. Si el usuario actual es un usuario registrado, la plantilla creará una etiqueta meta con su id como contenido.

      El siguiente paso es indicar a Django dónde encontrar sus plantillas. Para realizar esto, editará settings.py y actualizará la lista TEMPLATES.

      Abra el archivo settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Añada lo siguiente a la lista DIRS para especificar la ruta al directorio de plantillas:

      ~/djangopush/djangopush/settings.py

      ...
      TEMPLATES = [
          {
              'BACKEND': 'django.template.backends.django.DjangoTemplates',
              'DIRS': [os.path.join(BASE_DIR, 'templates')],
              'APP_DIRS': True,
              'OPTIONS': {
                  'context_processors': [
                      ...
                  ],
              },
          },
      ]
      ...
      

      A continuación, en su archivo views.py, actualice la vista de home para representar la plantilla de home.html. Abra el archivo:

      • nano ~/djangpush/djangopush/views.py

      Primero agregue algunas importaciones, incluida la configuración de settings, que contiene todas las configuraciones del proyecto del archivo settings.py, y la función render de django.shortcuts:

      ~/djangopush/djangopush/views.py

      ...
      from django.shortcuts import render, get_object_or_404
      ...
      import json
      from django.conf import settings
      
      ...
      

      A continuación, elimine el código inicial que agregó a la vista de home y agregue lo siguiente, lo cual especifica cómo se representará la plantilla que acaba de crear:

      ~/djangopush/djangopush/views.py

      ...
      
      @require_GET
      def home(request):
         webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
         vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
         user = request.user
         return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})
      

      El código asigna las siguientes variables:

      • webpush_settings: se asigna el valor del atributo de WEBPUSH_SETTINGS desde la configuración de settings.
      • vapid_key: obtiene el valor de VAPID_PUBLIC_KEY del objeto webpush_settings para enviarlo al cliente. Esta clave pública se verifica con la clave privada a fin de de garantizar que el cliente que dispone de la clave pública tenga permiso para recibir mensajes push del servidor.
      • user: esta variable proviene de la solicitud entrante. Cuando un usuario realiza una solicitud al servidor, los detalles para ese usuario se almacenan en el campo user.

      La función render proporcionará un archivo HTML y un objeto de contexto que contiene el usuario actual y la clave pública de vapid del servidor. Aquí se utilizan tres parámetros: la request, la template que se representará y el objeto que contiene las variables que se utilizarán en la plantilla.

      Una vez que creemos nuestra plantilla y actualicemos la vista de home, podremos configurar Django para proporcionar nuestros archivos estáticos.

      Paso 5: Proporcionar archivos estáticos

      Las aplicaciones web incluyen CSS, JavaScript y otros archivos de imagen que en Django se denominan “archivos estáticos”. Django le permite recopilar todos los archivos estáticos de cada aplicación en su proyecto en una sola ubicación desde la que se proporcionan. Esta solución se llama django.contrib.staticfiles. En este paso, actualizaremos nuestra configuración para indicar a Django dónde almacenar nuestros archivos estáticos.

      Abra settings.py:

      • nano ~/djangopush/djangopush/settings.py

      En settings.py, primero asegúrese de que se haya definido STATIC_URL:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      

      A continuación, agregue una lista de directorios llamada STATICFILES_DIRS donde Django buscará archivos estáticos:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, "static"),
      ]
      

      Ahora podrá añadir STATIC_URL a la lista de las rutas definidas en su archivo urls.py.

      Abra el archivo:

      • nano ~/djangopush/djangopush/urls.py

      Añada el siguiente código, que importará la configuración de la url static y actualizará la lista de urlpatterns. La función auxiliar aquí utiliza las propiedades de STATIC_URL y STATIC_ROOT que aportamos en el archivo settings.py para proporcionar los archivos estáticos del proyecto:

      ~/djangopush/djangopush/urls.py

      
      ...
      from django.conf import settings
      from django.conf.urls.static import static
      
      urlpatterns = [
          ...
      ]  + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      Una vez configurados los ajustes de nuestros archivos estáticos, podremos aplicar retoques de estilo a la página de inicio de la aplicación.

      Paso 6: Aplicar retoques de estilo a la página de inicio

      Después de configurar su aplicación para presentar los archivos estáticos, puede crear una hoja de estilo externa y enlazarla al archivo home.html para aplicar ajustes de estilo a la página de inicio. Todos sus archivos estáticos se almacenarán en un directorio static de la carpeta root de su proyecto.

      Cree una carpeta static y una carpeta css dentro de la carpeta static:

      • mkdir -p ~/djangopush/static/css

      Abra un archivo css llamado styles.css dentro de la carpeta css:

      • nano ~/djangopush/static/css/styles.css

      Añada los siguientes estilos para la página de inicio:

      ~/djangopush/static/css/styles.css

      
      body {
          height: 100%;
          background: rgba(0, 0, 0, 0.87);
          font-family: 'PT Sans', sans-serif;
      }
      
      div {
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
      }
      
      form {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          width: 35%;
          margin: 10% auto;
      }
      
      form > h3 {
          font-size: 17px;
          font-weight: bold;
          margin: 15px 0;
          color: orangered;
          text-transform: uppercase;
      }
      
      form > .error {
          margin: 0;
          font-size: 15px;
          font-weight: normal;
          color: orange;
          opacity: 0.7;
      }
      
      form > input, form > textarea {
          border: 3px solid orangered;
          box-shadow: unset;
          padding: 13px 12px;
          margin: 12px auto;
          width: 80%;
          font-size: 13px;
          font-weight: 500;
      }
      
      form > input:focus, form > textarea:focus {
          border: 3px solid orangered;
          box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
          outline: unset;
      }
      
      form > button {
          justify-self: center;
          padding: 12px 25px;
          border-radius: 0;
          text-transform: uppercase;
          font-weight: 600;
          background: orangered;
          color: white;
          border: none;
          font-size: 14px;
          letter-spacing: -0.1px;
          cursor: pointer;
      }
      
      form > button:disabled {
          background: dimgrey;
          cursor: not-allowed;
      }
      

      Una vez creada la hoja de estilo, podrá enlazarla al archivo home.html usando etiquetas de plantillas estáticas. Abra el archivo home.html:

      • nano ~/djangopush/templates/home.html

      Actualice la sección head para incluir un enlace a la hoja de estilo externa:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          ...
          <link href="https://www.digitalocean.com/{% static"/css/styles.css' %}" rel="stylesheet">
      </head>
      <body>
          ...
      </body>
      </html>
      

      Asegúrese de posicionarse en el directorio principal de su proyecto y vuelva a iniciar su servidor para inspeccionar su trabajo:

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

      Cuando visite http://your_server_ip:8000, deberá tener el siguiente aspecto:

      Vista de la página de inicio Una vez más, podrá detener el servidor con CTRL+C.

      Ahora que creó la página home.html y le aplicó ajustes de estilo con éxito, puede suscribir usuarios para recibir notificaciones push cuando visiten la página de inicio.

      Paso 7: Registrar un trabajo de servicio y suscribir usuarios para recibir notificaciones push

      Las notificaciones push web pueden dar aviso a los usuarios cuando existen actualizaciones de las aplicaciones a las que están suscritos o solicitarles que se vuelvan a conectar con las aplicaciones que han utilizaron en el pasado. Se basan en dos tecnologías: la API push y la API de notificaciones. Ambas tecnologías dependen de la presencia de un trabajo de servicio.

      Una notificación push se invoca cuando el servidor proporciona información al trabajo de servicio y este último utiliza la API de notificaciones para mostrar esta información.

      Suscribiremos a nuestros usuarios a las notificaciones push y luego enviaremos la información de la suscripción al servidor para registrarlos.

      En el directorio static, cree una carpeta llamada js:

      • mkdir ~/djangopush/static/js

      Cree un archivo llamado registerSw.js:

      • nano ~/djangopush/static/js/registerSw.js

      Añada el siguiente código, que comprueba si los trabajados de servicio son compatibles con el navegador del usuario antes de intentar registrar un trabajo de servicio:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      

      Primero, la función registerSw comprueba si el navegador es compatible con los trabajados de servicio antes de registrarlos. Después del registro, llama a la función initializeState con los datos de registro. Si los trabajados de servicio no son compatibles con el navegador, llama a la función showNotAllowed.

      A continuación, agregue el siguiente código debajo de la función registerSw a fin de comprobar si un usuario reúne las condiciones para recibir notificaciones push antes de intentar suscribirlos:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      

      La función initializeState comprueba lo siguiente:

      • Si el usuario habilitó o no las notificaciones, usando el valor de reg.showNotification.
      • Si el usuario concedió permiso o no a la aplicación para mostrar notificaciones.
      • Si el navegador es compatible o no con la API PushManager. Si alguna de estas comprobaciones falla, se llama a la función showNotAllowed y se cancela la suscripción.

      La función showNotAllowed muestra un mensaje en el botón y lo deshabilita si un usuario no reúne las condiciones para recibir notificaciones. También muestra mensajes correspondientes si un usuario restringió la aplicación para no mostrar notificaciones o si el navegador no admite notificaciones push.

      Una vez que nos aseguremos de que el usuario reúna las condiciones para recibir notificaciones push, el siguiente paso es suscribirlo usando pushManager. Añada el siguiente código debajo de la función showNotAllowed:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      

      Al llamar a la función pushManager.getSubscription, se muestran los datos de una suscripción activa. Cuando existe una suscripción activa, se llama a la función sendSubData con la información de suscripción transmitida como un parámetro.

      Cuando no existe una suscripción activa, la clave pública de VAPID, la cual cuenta con codificación segura de URL Base64, se convierte a un Uint8Array mediante la función urlB64ToUint8Array. Luego se a llama pushManager.subscribe con la clave pública de VAPID y el valor de userVisible como opciones. Puede obtener más información sobre las opciones disponibles aquí.

      Después de suscribir con éxito a un usuario, el siguiente paso es enviar los datos de la suscripción al servidor. Los datos se enviarán al extremo webpush/save_information proporcionado por el paquete de django-webpush. Añada el siguiente código debajo de la función subscribe:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      El extremo save_information requiere información sobre el estado de la suscripción (subscribe y unsubscribe), los datos de suscripción y el navegador. Por último, llamaremos a la función registerSw() para iniciar el proceso de suscripción del usuario.

      El archivo completo tiene el siguiente aspecto:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      A continuación, agregue una etiqueta script para el archivo registerSw.js en home.html. Abra el archivo:

      • nano ~/djangopush/templates/home.html

      Añada la etiqueta script antes de la etiqueta de cierre del elemento body:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/registerSw.js' %}"></script>
      </body>
      </html>
      

      Debido a que aún no existe un trabajo de servicio, si dejó su aplicación en ejecución o intentó iniciarla obtendrá un mensaje de error. Corregiremos esto creando un trabajo de servicio.

      Paso 8: Crear un trabajo de servicio

      Para mostrar una notificación push, necesitará un trabajo de servicio activo instalado en la página de inicio de su aplicación. Crearemos un trabajo de servicio que escuche eventos push y muestre los mensajes cuando esté listo.

      Debido a que queremos que el alcance del trabajador de servicio comprenda el dominio completo, debemos instalarlo en el directorio root de la aplicación. Puede obtener más información más sobre el proceso en este artículo acerca de cómo registrar un trabajo de servicio. Nuestro enfoque consistirá en crear un archivo sw.js en la carpeta templates, que luego registraremos como una vista.

      Cree el archivo:

      • nano ~/djangopush/templates/sw.js

      Añada el siguiente código, que indica al trabajador de servicio que debe escuchar eventos push:

      ~/djangopush/templates/sw.js

      
      // Register event listener for the 'push' event.
      self.addEventListener('push', function (event) {
          // Retrieve the textual payload from event.data (a PushMessageData object).
          // Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
          // on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
          const eventInfo = event.data.text();
          const data = JSON.parse(eventInfo);
          const head = data.head || 'New Notification 🕺🕺';
          const body = data.body || 'This is default content. Your notification didn't have one 🙄🙄';
      
          // Keep the service worker alive until the notification is created.
          event.waitUntil(
              self.registration.showNotification(head, {
                  body: body,
                  icon: 'https://i.imgur.com/MZM3K5w.png'
              })
          );
      });
      

      El trabajo de servicio aguarda un evento push. En la función de devolución de llamada, los datos de event se convierten a texto. Utilizamos las cadenas title y body predeterminadas si no se encuentran en los datos de event. La función showNotification toma el título de la notificación, el encabezado de la notificación que se mostrará y un objeto options como parámetros. El objeto de options contiene varias propiedades para configurar las opciones visuales de una notificación.

      Para que su trabajo de servicio funcione en la totalidad de su dominio, deberá instalarlo en el directorio root de la aplicación. Usaremos TemplateView para permitir que el trabajo de servicio tenga acceso a todo el dominio.

      Abra el archivo urls.py:

      • nano ~/djangopush/djangopush/urls.py

      Añada una nueva instrucción de import y una ruta en la lista de urlpatterns para crear una vista basada en clases:

      ~/djangopush/djangopush/urls.py

      ...
      from django.views.generic import TemplateView
      
      urlpatterns = [
                        ...,
                        path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript'))
                    ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      Las vistas basadas en clases como TemplateView permiten crear vistas flexibles y reutilizables. En este caso, el método TemplateView.as_view crea una ruta para el trabajo de servicio al pasar el trabajo de servicio recién creado como una plantilla y application/x-javascript como el content_type de la plantilla.

      Con esto, habrá creado un trabajo de servicio y lo habrá registrado como una ruta. A continuación, configurará el formulario de la página de inicio para enviar notificaciones push.

      Paso 9: Instalar notificaciones push

      Con el formulario de la página de inicio, los usuarios deben poder enviar notificaciones push mientras su servidor está en ejecución. También puede enviar notificaciones push usando cualquier servicio de RESTful como Postman. Cuando el usuario envíe las notificaciones push desde el formulario en la página de inicio, los datos incluirán un head y un body, así como el id del usuario receptor. Los datos deben estructurarse de la siguiente manera:

      {
          head: "Title of the notification",
          body: "Notification body",
          id: "User's id"
      }
      

      Para escuchar el evento submit del formulario y enviar los datos ingresados por el usuario al servidor, crearemos un archivo llamado site.js en el directorio ~/djangopush/static/js.

      Abra el archivo:

      • nano ~/djangopush/static/js/site.js

      Primero, agregue una escucha de eventos submit al formulario que le permitirá obtener los valores de las entradas del formulario y el id del usuario almacenado en la etiqueta meta de su plantilla:

      ~/djangopush/static/js/site.js

      
      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
          ...
          // TODO: make an AJAX request to send notification
      });
      

      La función pushForm obtiene input, textarea y button dentro del formulario. También obtiene la información de la etiqueta meta, incluido el atributo de nombre user_id y el id de usuario almacenado en el atributo content de la etiqueta. Con esta información, puede enviar una solicitud POST al extremo de /send_push en el servidor.

      Para enviar las solicitudes al servidor, usaremos la API nativa Fetch. Usaremos Fetch aquí, ya que es compatible con la mayoría de los navegadores y no necesita bibliotecas externas para funcionar. Debajo del código que agregó, actualice la función pushForm para incluir el código que sirve para enviar solicitudes de AJAX:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
           ...
          const id = meta ? meta.content : null;
      
           if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }
      });
      

      Si están presentes los tres parámetros necesarios head, body e id, se envía la solicitud y se deshabilita temporalmente el botón de enviar.

      El archivo completo tiene el siguiente aspecto:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
      
          if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }    
      });
      

      Por último, agregue el archivo site.js a home.html:

      • nano ~/djangopush/templates/home.html

      Añada la etiqueta script:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/site.js' %}"></script>
      </body>
      </html>
      

      En este punto, si dejó su aplicación en ejecución o intentó iniciarla, verá un error, ya que los trabajos de servicio solo pueden funcionar en dominios seguros o en localhost. En el siguiente paso, usaremos ngrok para crear un túnel seguro hacia nuestro servidor web.

      Paso 10: Crear un túnel seguro para probar la aplicación

      Los trabajadores de servicio requieren conexiones seguras para funcionar en cualquier sitio, a excepción de localhost, ya que pueden permitir la infiltración maliciosa en las conexiones y la filtración y generación de respuestas. Por este motivo, crearemos un túnel seguro para nuestro servidor con ngrok.

      Abra una segunda ventana de terminal y asegúrese de estar en su directorio de inicio:

      Si comenzó con un servidor 18.04 limpio en los requisitos previos, entonces tendrá que instalar unzip:

      • sudo apt update && sudo apt install unzip

      Descargue ngrok:

      • wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
      • unzip ngrok-stable-linux-amd64.zip

      Mueva ngrok a /usr/local/bin, para tener acceso al comando ngrok desde el terminal:

      • sudo mv ngrok /usr/local/bin

      En la primera ventana de su terminal, asegúrese de estar posicionado en el directorio de su proyecto e inicie su servidor:

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

      Deberá hacerlo antes de crear un túnel seguro para su aplicación.

      En la segunda ventana de su terminal, diríjase a la carpeta de su proyecto y active su entorno virtual:

      • cd ~/djangopush
      • source my_env/bin/activate

      Cree el túnel seguro a su aplicación:

      • ngrok http your_server_ip:8000

      Visualizará el siguiente resultado, que incluye información sobre su URL de ngrok segura:

      Output

      ngrok by @inconshreveable (Ctrl+C to quit) Session Status online Session Expires 7 hours, 59 minutes Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://ngrok_secure_url -> 203.0.113.0:8000 Forwarding https://ngrok_secure_url -> 203.0.113.0:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00

      Copie ngrok_secure_url del resultado de la consola. Necesitará añadirlo a la lista de ALLOWED_HOSTS en su archivo settings.py.

      Abra otra ventana de terminal, diríjase a la carpeta de su proyecto y active su entorno virtual:

      • cd ~/djangopush
      • source my_env/bin/activate

      Abra el archivo settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Actualice la lista de ALLOWED_HOSTS con el túnel seguro de ngrok:

      ~/djangopush/djangopush/settings.py

      ...
      
      ALLOWED_HOSTS = ['your_server_ip', 'ngrok_secure_url']
      ...
      
      

      Diríjase a la página de administración segura para iniciar sesión: https://ngrok_secure_url/admin/. Verá una pantalla similar a esta:

      Inicio de sesión de administrador de ngrok

      Introduzca la información de su usuario administrador de Django en esta pantalla. Esta deberá ser la misma información que ingresó cuando inició sesión en la interfaz de administrador en los pasos de los requisitos previos. Con esto, estará listo para enviar notificaciones push.

      Visite https://ngrok_secure_url en su navegador. Visualizará un mensaje en el que se solicitará permiso para mostrar notificaciones. Haga clic en el botón Allow para permitir que su navegador muestre notificaciones push:

      Solicitud de notificaciones push

      Con el envío de un formulario completo se mostrará una notificación similar a la siguiente:

      Captura de pantalla de la notificación

      Nota: Asegúrese de que su servidor esté activo antes de intentar enviar notificaciones.

      Si recibió notificaciones, significa que su aplicación funciona según lo previsto.

      Pudo crear una aplicación web que activa notificaciones push en el servidor y, con la ayuda de los trabajados de servicio, recibe y muestra notificaciones. También completó los pasos para obtener las claves de VAPID que se necesitan para enviar notificaciones push desde un servidor de aplicaciones.

      Conclusión

      A través de este tutorial, aprendió a suscribir usuarios a notificaciones push,instalar trabajados de servicio y mostrar notificaciones push mediante la API de notificaciones.

      Puede dar un paso más configurando las notificaciones para que abran áreas específicas de su aplicación cuando se haga clic en ellas. Puede encontrar el código fuente para este tutorial aquí.



      Source link

      Cómo preparar aplicaciones de Flask con Gunicorn y Nginx en Ubuntu 18.04


      Introducción

      A través de esta guía, creará una aplicación de Python utilizando el microframework de Flask en Ubuntu 18.04. En la mayor parte de este artículo se abordarán la configuración del servidor de la aplicación Gunicorn y la forma de iniciar la aplicación y configurar Nginx para que funcione como un proxy inverso de cliente.

      Requisitos previos

      Antes de comenzar con esta guía, deberá contar con lo siguiente:

      • Un servidor con Ubuntu 18.04 instalado y un usuario no root con privilegios sudo. Siga nuestra guía de configuración inicial para servidores a modo de orientación.
      • Nginx instalado conforme a los pasos 1 y 2 de Cómo instalar Nginx en Ubuntu 18.04.
      • Un nombre de dominio configurado para que apunte a su servidor. Puede adquirir uno en Namecheap u obtener uno de forma gratuita en Freenom. Puede aprender a apuntar dominios a DigitalOcean siguiendo la documentación sobre dominios y DNS pertinente. Asegúrese de crear los siguientes registros DNS:

        • Un registro A con your_domain orientado a la dirección IP pública de su servidor.
        • Un registro A con www.your_domain orientado a la dirección IP pública de su servidor.
      • Conocimientos sobre la especificación WSGI, que el servidor de Gunicorn usará para comunicarse con su aplicación Flask. En esta discusión se abarca WSGI de forma más deallada.

      Paso 1: Instalar los componentes desde los repositorios de Ubuntu

      Nuestro primer paso será instalar todo lo que necesitamos desde los repositorios de Ubuntu. Esto incluye pip, el administrador de paquetes de Python, que gestionará nuestros componentes de Python. También obtendremos los archivos de desarrollo de Python necesarios para crear algunos de los componentes de Gunicorn.

      Primero, actualizaremos el índice de paquetes locales e instalaremos los paquetes que nos permitirán crear nuestro entorno de Python. Entre ellos está phyton3-pip, junto con paquetes y herramientas de desarrollo adicionales que se necesitan para un entorno de programación sólido:

      • sudo apt update
      • sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

      Una vez implementados estos paquetes, crearemos un entorno virtual para nuestro proyecto.

      Paso 2: Crear un entorno virtual de Python

      A continuación, configuraremos un entorno virtual para aislar nuestra aplicación de Flask de los otros archivos de Python del sistema.

      Comience instalando el paquete phyton3-venv, que instalará el módulo venv:

      • sudo apt install python3-venv

      Luego, crearemos un directorio principal para nuestro proyecto de Flask. Después de crearlo, posiciónese en él:

      • mkdir ~/myproject
      • cd ~/myproject

      Cree un entorno virtual para almacenar los requisitos de Python de su proyecto de Flask escribiendo lo siguiente:

      • python3.6 -m venv myprojectenv

      Con esto se instalará una copia local de Python y pip en un directorio llamado myprojectenv dentro del directorio de su proyecto.

      Antes de instalar aplicaciones dentro del entorno virtual, deberá activarlo. Hágalo escribiendo lo siguiente:

      • source myprojectenv/bin/activate

      Su mensaje cambiará para indicar que ahora realiza operaciones dentro del entorno virtual. Se parecerá a esto: (myprojectenv)user@host:~/myproject$.

      Paso 3: Configurar una aplicación de Flask

      Ahora que se encuentra en su entorno virtual, podrá instalar Flask y Gunicorn y comenzar a diseñar su aplicación.

      Primero, instalaremos wheel con la instancia local de pip para asegurarnos de que nuestros paquetes se instalen aunque falten archivos de wheel:

      Nota: Independientemente de la versión de Phyton que use, cuando se active el entorno virtual deberá utilizar el comando pip (no pip3).

      A continuación, instalaremos Flask y Gunicorn:

      • pip install gunicorn flask

      Creación de una aplicación de ejemplo

      Ahora que dispone de Flask, puede crear una aplicación sencilla. Flask es un microframework. No cuenta con muchas de las herramientas que podrían incluirse en frameworks con más características y existe sobre todo como un módulo que puede importar a sus proyectos para que pueda inicializar una aplicación web.

      Aunque la complejidad podría ser mayor, crearemos nuestra aplicación de Flask en un único archivo, llamado myproject.py:

      • nano ~/myproject/myproject.py

      El código de aplicación residirá en este archivo. Importará Flask y creará una instancia de un objeto de Flask. Puede utilizarlo para definir las funciones que deberían ejecutarse cuando se solicita una ruta específica:

      ~/myproject/myproject.py

      from flask import Flask
      app = Flask(__name__)
      
      @app.route("/")
      def hello():
          return "<h1 style='color:blue'>Hello There!</h1>"
      
      if __name__ == "__main__":
          app.run(host='0.0.0.0')
      

      Esto define básicamente el contenido que se presentará al acceder al dominio root. Guarde y cierre el archivo cuando termine.

      Si siguió la guía de configuración inicial para servidores, debería tener activado un firewall UFW. Para probar la aplicación, debe permitir el acceso al puerto 5000:

      Ahora podrá probar su aplicación de Flask escribiendo lo siguiente:

      Verá un resultado como el siguiente, en el cual se incluirá una advertencia útil que le recordará no utilizar esta configuración de servidor en la producción:

      Output

      * Serving Flask app "myproject" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

      Agregue :5000 al final de la dirección IP de su servidor en su navegador web y visítela:

      http://your_server_ip:5000
      

      Debería ver algo como esto:

      Aplicación de ejemplo de Flask

      Cuanto termine, pulse CTRL-C en la ventana de su terminal para detener el servidor de desarrollo Flask.

      Creación de un punto de entrada de WSGI

      A continuación, crearemos un archivo que servirá como punto de entrada para nuestra aplicación. Esto indicará a nuestro servidor de Gunicorn cómo interactuar con la aplicación.

      Llamemos al archivo wsgi.py:

      En él, importaremos la instancia de Flask desde nuestra aplicación y luego la ejecutaremos:

      ~/myproject/wsgi.py

      from myproject import app
      
      if __name__ == "__main__":
          app.run()
      

      Guarde y cierre el archivo cuando termine.

      Paso 4: Configurar Gunicorn

      Su aplicación quedará, así, escrita con un punto de entrada establecido. Ahora, podemos continuar con la configuración de Gunicorn.

      Antes de continuar, debe comprobar que Gunicorn pueda proveer correctamente la aplicación.

      Podemos hacerlo con solo pasarle el nombre de nuestro punto de entrada. Se construye como el nombre del módulo (menos la extensión .py) más el nombre del elemento invocable dentro de la aplicación. En nuestro caso, es wsgi:app.

      También especificaremos la interfaz y el puerto que se vinculará para que la aplicación se inicie en una interfaz disponible de forma pública:

      • cd ~/myproject
      • gunicorn --bind 0.0.0.0:5000 wsgi:app

      Debería ver un resultado como el siguiente:

      Output

      [2018-07-13 19:35:13 +0000] [28217] [INFO] Starting gunicorn 19.9.0 [2018-07-13 19:35:13 +0000] [28217] [INFO] Listening at: http://0.0.0.0:5000 (28217) [2018-07-13 19:35:13 +0000] [28217] [INFO] Using worker: sync [2018-07-13 19:35:13 +0000] [28220] [INFO] Booting worker with pid: 28220

      Visite de nuevo la dirección IP de su servidor con :5000 agregado al final en su navegador web:

      http://your_server_ip:5000
      

      Debería ver el resultado de su aplicación:

      Aplicación de ejemplo de Flask

      Cuando confirme que funciona correctamente, pulse CTRL-C en la ventana de su terminal.

      Ya completamos las tareas de nuestro entorno virtual, por lo que podemos desactivarlo:

      Ahora todos los comandos de Python usarán de nuevo el entorno de Phyton del sistema.

      A continuación, crearemos el archivo de unidad de servicio systemd. Crear un archivo de unidad systemd permitirá que el sistema init de Ubuntu inicie automáticamente Gunicorn y haga funcionar la aplicación de Flask cuando el servidor se cargue.

      Cree un archivo de unidad terminado en .service dentro del directorio /etc/systemd/system para empezar:

      • sudo nano /etc/systemd/system/myproject.service

      En su interior, empezaremos con la sección [Unit] que se usa para especificar metadatos y dependencias. Aquí agregaremos una descripción de nuestro servicio e indicaremos al sistema init que lo inicie solo tras haber alcanzado el objetivo de red:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      

      A continuación, abriremos la sección [Service]. Esto especificará el usuario y el grupo con los cuales deseamos que se ejecute el proceso. Otorgaremos la propiedad del proceso a nuestra cuenta de usuario normal, ya que tiene la propiedad de todos los archivos pertinentes. También otorgaremos la propiedad del grupo al grupo www-data para que Nginx pueda comunicarse fácilmente con los procesos de Gunicorn. No se olvide de sustituir el nombre de usuario por el suyo:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      

      A continuación, planearemos los detalles del directorio de trabajo y estableceremos el entorno variable PATH para que el sistema init sepa que los ejecutables para el proceso están ubicados dentro de nuestro entorno virtual. También especificaremos el comando para iniciar el servicio. Este comando hará lo siguiente:

      • Iniciar 3 procesos de trabajadores (debería, no obstante, ajustar esto si es necesario)
      • Crear un archivo de socket de Unix, myproject<^>.sock, dentro del directorio de nuestro proyecto y establecer un vínculo con él. Estableceremos un valor sin máscara de 007 para que se cree el archivo de socket, se proporcione acceso al propietario y, al mismo tiempo, se restrinjan otros accesos.
      • Especifique el nombre del archivo del punto de entrada de WSGI junto con el elemento invocable de Python dentro de ese archivo (wsgi:app).

      Systemd necesita que le proporcionemos la ruta completa al ejecutable de Gunicorn, que se instala dentro de nuestro entorno virtual.

      No se olvide de sustituir el nombre del usuario y las rutas del proyecto por su propia información:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
      

      Por último, vamos a añadiremos una sección [Install]. Esto indicará a systemd a qué deberá vincular este servicio si lo habilitamos para que se cargue en el inicio. Queremos que este servicio se inicie cuando el sistema multiusuario normal esté en funcionamiento:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=Gunicorn instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app
      
      [Install]
      WantedBy=multi-user.target
      

      Con eso, nuestro archivo de servicio de systemd quedará completo. Guárdelo y ciérrelo ahora.

      Ya podemos iniciar el servicio Gunicorn que creamos y activarlo para que se cargue en el inicio:

      • sudo systemctl start myproject
      • sudo systemctl enable myproject

      Comprobemos el estado:

      • sudo systemctl status myproject

      Debería ver el siguiente resultado:

      Output

      ● myproject.service - Gunicorn instance to serve myproject Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2018-07-13 14:28:39 UTC; 46s ago Main PID: 28232 (gunicorn) Tasks: 4 (limit: 1153) CGroup: /system.slice/myproject.service ├─28232 /home/sammy/myproject/myprojectenv/bin/python3.6 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 ├─28250 /home/sammy/myproject/myprojectenv/bin/python3.6 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 ├─28251 /home/sammy/myproject/myprojectenv/bin/python3.6 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007 └─28252 /home/sammy/myproject/myprojectenv/bin/python3.6 /home/sammy/myproject/myprojectenv/bin/gunicorn --workers 3 --bind unix:myproject.sock -m 007

      Si detecta errores, asegúrese de resolverlos antes de continuar con el tutorial.

      Paso 5: Configurar Nginx para solicitudes de proxy

      Ahora, nuestro servidor de aplicación Gunicorn debería estar funcionando, esperando solicitudes en el archivo de socket del directorio del proyecto. Configuraremos Nginx para que transmita las solicitudes web al socket haciendo algunas pequeñas adiciones a su archivo de configuración.

      Comencemos creando un nuevo archivo de configuración de bloque de servidor en el directorio sites-available de Nginx. Lo llamaremos myproject para que se adecue al resto de esta guía:

      • sudo nano /etc/nginx/sites-available/myproject

      Abra un bloque de servidor e indique a Nginx que escuche en el puerto predeterminado 80. También le indicaremos que utilice este bloque para solicitudes para el nombre de dominio de nuestro servidor:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      }
      

      A continuación, agregaremos un bloque de ubicación que coincida con cada solicitud. Dentro de este bloque, incluiremos el archivo proxy_params que especifica algunos parámetros de proxy generales que deben configurarse. Luego, pasaremos las solicitudes al socket que definimos usando la directiva proxy_pass:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      
          location / {
              include proxy_params;
              proxy_pass http://unix:/home/sammy/myproject/myproject.sock;
          }
      }
      

      Guarde y cierre el archivo al finalizar.

      Para habilitar la configuración del bloque de servidor de Nginx que acaba de crear, vincule el archivo al directorio sites-enabled​​​:

      • sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

      Con el archivo en ese directorio, puede realizar una verificación en busca de errores de sintaxis:

      Si no se indican problemas, reinicie el proceso de Nginx para que lea la nueva configuración:

      • sudo systemctl restart nginx

      Por último, ajustaremos el firewall de nuevo. Ya no necesitamos acceso a través del puerto 5000, por lo que podemos eliminar esta regla. Luego, podemos permitir el acceso completo al servidor de Nginx:

      • sudo ufw delete allow 5000
      • sudo ufw allow 'Nginx Full'

      Ahora debería poder visitar el nombre de dominio de su servidor en su navegador web:

      http://your_domain
      

      Debería ver el resultado de su aplicación:

      Aplicación de ejemplo de Flask

      Si encuentra algún error, intente verificar lo siguiente:

      • sudo less /var/log/nginx/error.log: verifica los registros de error de Nginx.
      • sudo less /var/log/nginx/access.log: verifica los registros de acceso de Nginx.
      • sudo journalctl -u nginx: verifica los registros de proceso de Nginx.
      • sudo journalctl -u myproject: verifica los registros de Gunicorn de su aplicación de Flask.

      Paso 6: Proteger la aplicación

      Para asegurarse de que el tráfico hacia su servidor siga siendo seguro, obtendremos un certificado SSL para su dominio. Existen varias formas de hacerlo. Entre otras, obtener un certificado gratuito de Let’s Encrypt, generar un certificado autofirmado o adquirir uno de otro proveedor y configurar Nginx para que lo utilice siguiendo los pasos 2 a 6 de Cómo crear un certificado SSL autofirmado para Nginx en Ubuntu 18.04. Por motivos de conveniencia, elegiremos la primera opción.

      Primero, agregue el repositorio de Certbot de Ubuntu:

      • sudo add-apt-repository ppa:certbot/certbot

      Deberá seleccionar ENTER para aceptar.

      Instale el paquete de Nginx de Certbot con apt:

      • sudo apt install python-certbot-nginx

      Certbot ofrece varias alternativas para obtener certificados SSL a través de complementos. El complemento de Nginx se encargará de reconfigurar Nginx y volver a cargar la configuración cuando sea necesario. Para utilizar este complemento, escriba lo siguiente:

      • sudo certbot --nginx -d your_domain -d www.your_domain

      Con esto, se ejecuta certbot con el complemento --nginx usando -d para especificar los nombres para los cuales deseamos que el certificado tenga validez.

      Si es la primera vez que ejecuta certbot, se le solicitará introducir una dirección de correo electrónico y aceptar las condiciones de servicio. A continuación, certbot se comunicará con el servidor de Let’s Encrypt y, luego, realizará una comprobación para verificar que usted controle el dominio para el que solicita un certificado.

      Si la comprobación se realiza correctamente, certbot le preguntará cómo desea configurar sus ajustes de HTTPS:

      Output

      Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

      Seleccione su elección y luego ENTER. La configuración se actualizará y Nginx se volverá a cargar para aplicar los ajustes nuevos. certbot concluirá con un mensaje que le indicará que el proceso tuvo éxito e indicará la ubicación de almacenamiento de sus certificados:

      Output

      IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain/privkey.pem Your cert will expire on 2018-07-23. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le

      Si siguió las instrucciones de instalación de Nginx en los requisitos previos, ya no necesitará la asignación de perfil HTTP redundante:

      • sudo ufw delete allow 'Nginx HTTP'

      Para verificar la configuración, acceda una vez más a su dominio utilizando https://:

      https://your_domain
      

      Una vez más, debería ver el resultado de su aplicación junto con el indicador de seguridad de su navegador, el cual debería indicar que el sitio está protegido.

      Conclusión

      A través de esta guía, creó y aseguró una aplicación de Flask simple dentro de un entorno virtual de Python. Creó un punto de entrada de WSGI para que cualquier servidor de aplicación con capacidad para WSGI pueda interactuar con él y configuró el servidor de aplicación de Gunicorn para proporcionar esta función. Luego, creó un archivo de servicio systemd para iniciar automáticamente el servidor de aplicación en el inicio. También creó un bloque de servidor de Nginx que transmite el tráfico de clientes web al servidor de la aplicación, y reenvía solicitudes externas, y protegió el tráfico hacia su servidor con Let’s Encrypt.

      Flask es un framework muy sencillo, pero extremadamente flexible, diseñado para proporcionar funcionalidad a sus aplicaciones sin ser demasiado restrictivo respecto de la estructura y del diseño. Puede utilizar la pila general descrita en esta guía para hacer funcionar las aplicaciones de Flask que diseñe.



      Source link

      Cómo hacer funcionar aplicaciones de Flask con uWSGI y Nginx en Ubuntu 18.04


      Introducción

      A través de esta guía, creará una aplicación de Python utilizando el microframework de Flask en Ubuntu 18.04. En la mayor parte de este artículo se abordarán la configuración del servidor de la aplicación uWSGI y la forma de iniciar la aplicación y configurar Nginx para que funcione como un proxy inverso de cliente.

      Requisitos previos

      Antes de comenzar con esta guía, deberá contar con lo siguiente:

      • Un servidor con Ubuntu 18.04 instalado y un usuario no root con privilegios sudo. Siga nuestra guía de configuración inicial para servidores a modo de orientación.
      • Nginx instalado conforme a los pasos 1 y 2 de Cómo instalar Nginx en Ubuntu 18.04.
      • Un nombre de dominio configurado para que apunte a su servidor. Puede adquirir uno en Namecheap obtener uno de forma gratuita en Freenom. Puede aprender a apuntar dominios a DigitalOcean siguiendo la documentación sobre dominios y DNS pertinente. Asegúrese de crear los siguientes registros DNS:

        • Un registro A con your_domain orientado a la dirección IP pública de su servidor.
        • Un registro A con www.your_domain orientado a la dirección IP pública de su servidor.
      • Conocimientos sobre uWSGI, nuestro servidor de aplicaciones y la especificación WSGI. En esta discusión de definiciones y conceptos se abordan ambos en profundidad.

      Paso 1: Instalar los componentes desde los repositorios de Ubuntu

      Nuestro primer paso será instalar todo lo que necesitamos desde los repositorios de Ubuntu. Instalaremos pip, el administrador de paquetes de Python, para administrar nuestros componentes de Python. También obtendremos los archivos de desarrollo de Python necesarios para crear uWSGI.

      Primero, actualizaremos el índice de paquetes locales e instalaremos los paquetes que nos permitirán crear nuestro entorno de Python. Entre ellos está phyton3-pip, junto con paquetes y herramientas de desarrollo adicionales que se necesitan para un entorno de programación sólido:

      • sudo apt update
      • sudo apt install python3-pip python3-dev build-essential libssl-dev libffi-dev python3-setuptools

      Una vez implementados estos paquetes, crearemos un entorno virtual para nuestro proyecto.

      Paso 2: Crear un entorno virtual de Python

      A continuación, configuraremos un entorno virtual para aislar nuestra aplicación de Flask de los otros archivos de Python del sistema.

      Comience instalando el paquete phyton3-venv, que instalará el módulo venv:

      • sudo apt install python3-venv

      Luego, crearemos un directorio principal para nuestro proyecto de Flask. Después de crearlo, posiciónese en él:

      • mkdir ~/myproject
      • cd ~/myproject

      Cree un entorno virtual para almacenar los requisitos de Python de su proyecto de Flask escribiendo lo siguiente:

      • python3.6 -m venv myprojectenv

      Con esto se instalará una copia local de Python y pip en un directorio llamado myprojectenv dentro del directorio de su proyecto.

      Antes de instalar aplicaciones dentro del entorno virtual, deberá activarlo. Hágalo escribiendo lo siguiente:

      • source myprojectenv/bin/activate

      Su mensaje cambiará para indicar que ahora realiza operaciones dentro del entorno virtual. Se parecerá a esto: (myprojectenv)user@host:~/myproject$.

      Paso 3: Configurar una aplicación de Flask

      Ahora que se encuentra en su entorno virtual, podrá instalar Flask y uWSGI y comenzar a diseñar su aplicación.

      Primero, instalaremos wheel con la instancia local de pip para asegurarnos de que nuestros paquetes se instalen aunque falten archivos de wheel:

      Nota: Independientemente de la versión de Phyton que use, cuando se active el entorno virtual deberá utilizar el comando pip (no pip3).

      A continuación, instalaremos Flask y uWSGI:

      Creación de una aplicación de ejemplo

      Ahora que dispone de Flask, puede crear una aplicación sencilla. Flask es un microframework. No cuenta con muchas de las herramientas que podrían incluirse en frameworks con más características y existe sobre todo como un módulo que puede importar a sus proyectos para que pueda inicializar una aplicación web.

      Aunque la complejidad podría ser mayor, crearemos nuestra aplicación de Flask en un único archivo, llamado “myproject.py:

      • nano ~/myproject/myproject.py

      El código de aplicación residirá en este archivo. Importará Flask y creará una instancia de un objeto de Flask. Puede utilizarlo para definir las funciones que deberían ejecutarse cuando se solicita una ruta específica:

      ~/myproject/myproject.py

      from flask import Flask
      app = Flask(__name__)
      
      @app.route("/")
      def hello():
          return "<h1 style='color:blue'>Hello There!</h1>"
      
      if __name__ == "__main__":
          app.run(host='0.0.0.0')
      

      Esto define básicamente el contenido que se presentará al acceder al dominio root. Guarde y cierre el archivo cuando termine.

      Si siguió la guía de configuración inicial para servidores, debería tener activado un firewall UFW. Para probar la aplicación, debe permitir el acceso al puerto 5000:

      Ahora podrá probar su aplicación de Flask escribiendo lo siguiente:

      Verá un resultado como el siguiente, en el cual se incluirá una advertencia útil que le recordará no utilizar esta configuración de servidor en la producción:

      Output

      * Serving Flask app "myproject" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

      Agregue :5000 al final de la dirección IP de su servidor en su navegador web y visítela:

      http://your_server_ip:5000
      

      Debería ver algo como esto:

      Aplicación de ejemplo de Flask

      Cuanto termine, pulse CTRL-C en la ventana de su terminal para detener el servidor de desarrollo Flask.

      Creación de un punto de entrada de WSGI

      A continuación, crearemos un archivo que servirá como punto de entrada para nuestra aplicación. Esto le indicará a nuestro servidor de uWSGI cómo interactuar con él.

      Llamaremos al archivo wsgi.py:

      En él, importaremos la instancia de Flask desde nuestra aplicación y luego la ejecutaremos:

      ~/myproject/wsgi.py

      from myproject import app
      
      if __name__ == "__main__":
          app.run()
      

      Guarde y cierre el archivo cuando termine.

      Paso 4: Configurar uWSGI

      Su aplicación quedará, así, escrita con un punto de entrada establecido. Ahora, podemos continuar con la configuración de uWSGI.

      Prueba del servicio de uWSGI

      Haremos una prueba para comprobar que uWSGI pueda hacer funcionar nuestra aplicación.

      Lo haremos con solo pasarle el nombre de nuestro punto de entrada. Se construye como el nombre del módulo (menos la extensión .py) más el nombre del elemento invocable dentro de la aplicación. En nuestro caso, es wsgi:app.

      También especificaremos el socket, de modo que se inicie en una interfaz disponible de forma pública, además del protocolo, para que utilice HTTP en lugar del protocolo binario uwsgi. Utilizaremos el mismo número de puerto, 5000, que abrimos antes:

      • uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app

      Visite de nuevo la dirección IP de su servidor con :5000 agregado al final en su navegador web:

      http://your_server_ip:5000
      

      Debería volver a ver el resultado de su aplicación:

      Aplicación de ejemplo de Flask

      Cuando confirme que funciona correctamente, pulse CTRL-C en la ventana de su terminal.

      Ya completamos las tareas de nuestro entorno virtual, por lo que podemos desactivarlo:

      Ahora todos los comandos de Python usarán de nuevo el entorno de Phyton del sistema.

      Creación de un archivo de configuración de uWSGI

      Ya comprobó que uWSGI puede hacer funcionar su aplicación. Sin embargo, en última instancia le convendrá algo más sólido para el uso a largo plazo. Puede crear un archivo de configuración de uWSGI con las opciones pertinentes para esto.

      Dispondremos ese archivo en nuestro directorio de proyectos y lo llamaremos myproject.ini:

      • nano ~/myproject/myproject.ini

      En su interior, empezaremos con el encabezado de [uwsgi] a fin de que uWSGI esté al tanto para aplicar la configuración. Especificaremos dos cosas: el propio módulo, haciendo referencia al archivo wsgi.py menos la extensión, y el elemento invocable dentro del archivo, app:

      ~/myproject/myproject.ini

      [uwsgi]
      module = wsgi:app
      

      A continuación, le diremos a uWSGI que se inicie en el modo maestro y cree cinco procesos de trabajador para proporcionar solicitudes reales:

      ~/myproject/myproject.ini

      [uwsgi]
      module = wsgi:app
      
      master = true
      processes = 5
      

      Cuando hizo pruebas, expuso uWSGI en un puerto de red. Sin embargo, utilizará Nginx para gestionar las conexiones de clientes reales, que luego transmitirán solicitudes a uWSGI. Debido a que estos componentes funcionan en la misma computadora, es preferible un socket de Unix porque es más rápido y seguro. Llamaremos al socket myproject.sock y lo dispondremos en este directorio.

      También cambiaremos los permisos del socket. Más adelante, daremos al grupo de Nginx la propiedad del proceso de uWSGI, por lo que debemos verificar que el propietario de grupo del socket pueda leer información de él y enviarle texto. También eliminaremos el socket cuando se detenga el proceso agregando la opción vacuum:

      ~/myproject/myproject.ini

      [uwsgi]
      module = wsgi:app
      
      master = true
      processes = 5
      
      socket = myproject.sock
      chmod-socket = 660
      vacuum = true
      

      Lo último que haremos será establecer la opción die-on-term. Esto puede ayudar a garantizar que el sistema init y uWSGI tengan los mismos supuestos sobre lo que significa cada señal de proceso. Al configurar esto se alinean los dos componentes del sistema y se implementa el comportamiento esperado:

      ~/myproject/myproject.ini

      [uwsgi]
      module = wsgi:app
      
      master = true
      processes = 5
      
      socket = myproject.sock
      chmod-socket = 660
      vacuum = true
      
      die-on-term = true
      

      Posibleemente haya observado que no especificamos un protocolo como hicimos desde la línea de comandos. Esto se debe a que, de forma predeterminada, uWSGI se comunica usando el protocolo de uwsgi, un protocolo binario rápido diseñado para la comunicación con otros servidores. Nginx puede comunicarse a través de este protocolo de forma nativa, por lo que es mejor usarlo que forzar la comunicación por HTTP.

      Cuando termine, guarde y cierre el archivo.

      Paso 5: Crear un archivo de unidad systemd

      A continuación, crearemos el archivo de unidad de servicio systemd. Crear un archivo de unidad systemd permitirá que el sistema init de Ubuntu inicie automáticamente uWSGI y haga funcionar la aplicación de Flask cuando el servidor se cargue.

      Cree un archivo de unidad terminado en .service dentro del directorio /etc/systemd/system para empezar:

      • sudo nano /etc/systemd/system/myproject.service

      En su interior, empezaremos con la sección [Unit] que se usa para especificar metadatos y dependencias. Aquí agregaremos una descripción de nuestro servicio e indicaremos al sistema init que lo inicie solo tras haber alcanzado el objetivo de red:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=uWSGI instance to serve myproject
      After=network.target
      

      A continuación, abriremos la sección [Service]. Esto especificará el usuario y el grupo con los cuales deseamos que se ejecute el proceso. Otorgaremos la propiedad del proceso a nuestra cuenta de usuario normal, ya que tiene la propiedad de todos los archivos pertinentes. También otorgaremos la propiedad del grupo al grupo www-data para que Nginx pueda comunicarse fácilmente con los procesos de Gunicorn. No se olvide de sustituir el nombre de usuario por el suyo:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=uWSGI instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      

      A continuación, planearemos los detalles del directorio de trabajo y estableceremos el entorno variable PATH para que el sistema init sepa que los ejecutables para el proceso están ubicados dentro de nuestro entorno virtual. También especificaremos el comando para iniciar el servicio. Systemd necesita que le proporcionemos la ruta completa al ejecutable de uWSGI, que se instala dentro de nuestro entorno virtual. Pasaremos el nombre del archivo de configuración .ini que creamos en el directorio de nuestro proyecto.

      No se olvide de sustituir el nombre del usuario y las rutas del proyecto por su propia información:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=uWSGI instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
      

      Por último, vamos a añadiremos una sección [Install]. Esto indicará a systemd a qué deberá vincular este servicio si lo habilitamos para que se cargue en el inicio. Queremos que este servicio se inicie cuando el sistema multiusuario normal esté en funcionamiento:

      /etc/systemd/system/myproject.service

      [Unit]
      Description=uWSGI instance to serve myproject
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/myproject
      Environment="PATH=/home/sammy/myproject/myprojectenv/bin"
      ExecStart=/home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini
      
      [Install]
      WantedBy=multi-user.target
      

      Con eso, nuestro archivo de servicio de systemd quedará completo. Guárdelo y ciérrelo ahora.

      Ya podemos iniciar el servicio uWSGI que creamos y activarlo para que se cargue en el inicio:

      • sudo systemctl start myproject
      • sudo systemctl enable myproject

      Comprobaremos el estado:

      • sudo systemctl status myproject

      Debería ver el siguiente resultado:

      Output

      ● myproject.service - uWSGI instance to serve myproject Loaded: loaded (/etc/systemd/system/myproject.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2018-07-13 14:28:39 UTC; 46s ago Main PID: 30360 (uwsgi) Tasks: 6 (limit: 1153) CGroup: /system.slice/myproject.service ├─30360 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini ├─30378 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini ├─30379 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini ├─30380 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini ├─30381 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini └─30382 /home/sammy/myproject/myprojectenv/bin/uwsgi --ini myproject.ini

      Si detecta errores, asegúrese de resolverlos antes de continuar con el tutorial.

      Paso 6: Configurar Nginx para solicitudes de proxy

      Ahora, nuestro servidor de aplicación uWSGI debería estar funcionando, esperando solicitudes en el archivo de socket del directorio del proyecto. Configuraremos Nginx para que transmita las solicitudes web a ese socket usando el protocolo uwsgi.

      Comencemos creando un nuevo archivo de configuración de bloque de servidor en el directorio sites-available de Nginx. Lo llamaremos myproject para que se adecue al resto de esta guía:

      • sudo nano /etc/nginx/sites-available/myproject

      Abra un bloque de servidor e indique a Nginx que escuche en el puerto predeterminado 80. También le indicaremos que utilice este bloque para solicitudes para el nombre de dominio de nuestro servidor:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      }
      

      A continuación, agregaremos un bloque de ubicación que coincida con cada solicitud. Dentro de este bloque, incluiremos el archivo proxy_params que especifica algunos parámetros de proxy generales de uWSGI que deben configurarse. Luego, pasaremos las solicitudes al socket que definimos usando la directiva proxy_pass:

      /etc/nginx/sites-available/myproject

      server {
          listen 80;
          server_name your_domain www.your_domain;
      
          location / {
              include uwsgi_params;
              uwsgi_pass unix:/home/sammy/myproject/myproject.sock;
          }
      }
      

      Guarde y cierre el archivo cuando termine.

      Para habilitar la configuración del bloque de servidor de Nginx que acaba de crear, vincule el archivo al directorio sites-enabled​​​:

      • sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

      Con el archivo en ese directorio, podemos probar si hay errores de sintaxis escribiendo lo siguiente:

      Si no se indican problemas, reinicie el proceso de Nginx para que lea la nueva configuración:

      • sudo systemctl restart nginx

      Por último, ajustaremos el firewall de nuevo. Ya no necesitamos acceso a través del puerto 5000, por lo que podemos eliminar esta regla. Luego podemos permitir el acceso al servidor de Nginx:

      • sudo ufw delete allow 5000
      • sudo ufw allow 'Nginx Full'

      Ahora debería poder visitar el nombre de dominio de su servidor en su navegador web:

      http://your_domain
      

      Debería ver el resultado de su aplicación:

      Aplicación de ejemplo de Flask

      Si encuentra algún error, intente verificar lo siguiente:

      • sudo less /var/log/nginx/error.log: verifica los registros de error de Nginx.
      • sudo less /var/log/nginx/access.log: verifica los registros de acceso de Nginx.
      • sudo journalctl -u nginx: verifica los registros de proceso de Nginx.
      • sudo journalctl -u myproject: verifica los registros de uWSGI de su aplicación de Flask.

      Paso 7: Proteger la aplicación

      Para asegurarse de que el tráfico hacia su servidor siga siendo seguro, obtendremos un certificado SSL para su dominio. Existen varias formas de hacerlo. Entre otras, obtener un certificado gratuito de Let’s Encrypt, generar un certificado autofirmado o adquirir uno de otro proveedor y configurar Nginx para que lo utilice siguiendo los pasos 2 a 6 de Cómo crear un certificado SSL autofirmado para Nginx en Ubuntu 18.04. Por motivos de conveniencia, elegiremos la primera opción.

      Primero, agregue el repositorio de Certbot de Ubuntu:

      • sudo add-apt-repository ppa:certbot/certbot

      Deberá seleccionar ENTER para aceptar.

      Instale el paquete de Nginx de Certbot con apt:

      • sudo apt install python-certbot-nginx

      Certbot ofrece varias alternativas para obtener certificados SSL a través de complementos. El complemento de Nginx se encargará de reconfigurar Nginx y volver a cargar la configuración cuando sea necesario. Para utilizar este complemento, escriba lo siguiente:

      • sudo certbot --nginx -d your_domain -d www.your_domain

      Con esto, se ejecuta certbot con el complemento --nginx usando -d para especificar los nombres para los cuales deseamos que el certificado tenga validez.

      Si es la primera vez que ejecuta certbot, se le solicitará introducir una dirección de correo electrónico y aceptar las condiciones de servicio. Después de esto, certbot se comunicará con el servidor de Let’s Encrypt y realizará una comprobación a fin de verificar que usted controle el dominio para el cual solicite un certificado.

      Si la comprobación se realiza correctamente, certbot le preguntará cómo desea configurar sus ajustes de HTTPS:

      Output

      Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. ------------------------------------------------------------------------------- 1: No redirect - Make no further changes to the webserver configuration. 2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for new sites, or if you're confident your site works on HTTPS. You can undo this change by editing your web server's configuration. ------------------------------------------------------------------------------- Select the appropriate number [1-2] then [enter] (press 'c' to cancel):

      Seleccione su elección y luego ENTER. La configuración se actualizará y Nginx se volverá a cargar para aplicar los ajustes nuevos. certbot concluirá con un mensaje que le indicará que el proceso tuvo éxito e indicará la ubicación de almacenamiento de sus certificados:

      Output

      IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain/privkey.pem Your cert will expire on 2018-07-23. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le

      Si siguió las instrucciones de instalación de Nginx en los requisitos previos, ya no necesitará la asignación de perfil HTTP redundante:

      • sudo ufw delete allow 'Nginx HTTP'

      Para verificar la configuración, acceda una vez más a su dominio utilizando https://:

      https://your_domain
      

      Una vez más, debería ver el resultado de su aplicación junto con el indicador de seguridad de su navegador, que debería indicar que el sitio está protegido.

      Conclusión

      A través de esta guía, creó y aseguró una aplicación de Flask simple dentro de un entorno virtual de Python. Creó un punto de entrada de WSGI para que cualquier servidor de aplicación con capacidad para WSGI pueda interactuar con él y configuró el servidor de aplicación de uWSGI para proporcionar esta función. Luego, creó un archivo de servicio systemd para iniciar automáticamente el servidor de aplicación en el inicio. También creó un bloque de servidor de Nginx que transmite el tráfico de clientes web al servidor de la aplicación, y reenvía solicitudes externas, y protegió el tráfico hacia su servidor con Let’s Encrypt.

      Flask es un framework muy sencillo, pero extremadamente flexible, diseñado para proporcionar funcionalidad a sus aplicaciones sin ser demasiado restrictivo respecto de la estructura y del diseño. Puede utilizar la pila general descrita en esta guía para hacer funcionar las aplicaciones de Flask que diseñe.



      Source link