One place for hosting & domains

      aplicaciones

      Crear aplicaciones de Go para diferentes sistemas operativos y arquitecturas


      En el ámbito del desarrollo de software, es importante considerar el sistema operativo y la arquitectura subyacente del procesador para los que se busca compilar un binario. Debido a que a menudo la ejecución de un binario en una plataforma de SO o arquitectura diferente resulta lenta o imposible, es una práctica común la de crear un binario final para muchas plataformas diferentes, a fin de maximizar el público de su programa. Sin embargo, esto puede ser difícil cuando la plataforma que usando para el desarrollo es diferente de la plataforma en la que desea implementar su programa. En el pasado, por ejemplo, desarrollar un programa en Windows e implementarlo en un equipo con Linux o macOS implicaba configurar las máquinas para cada uno de los entornos a los que se buscaba destinar los binarios. También se debían mantener las herramientas sincronizadas y existían otras consideraciones que sumaban costos y dificultaban la prueba y distribución colaborativas.

      Go resuelve este problema creando compatibilidad para varias plataformas directamente en la herramienta go build y en el resto de las herramientas de Go. Al usar variables de entorno y etiquetas de compilación, puede controlar el SO y la arquitectura para los cuales se creó su binario final y crear un flujo de trabajo que puede alternar rápidamente la inclusión de código dependiente de la plataforma sin cambiar su base de código.

      A través de este tutorial, creará una aplicación de muestra que un a cadenas en una ruta de archivo, creará e incluirá selectivamente fragmentos dependientes de la plataforma y formará binarios para diferentes sistemas operativos y arquitecturas de sistema en su propio sistema, lo que le mostrará la forma de usar esta potente capacidad en el lenguaje de programación Go.

      Requisitos previos

      Para seguir el ejemplo de este artículo, necesitará lo siguiente:

      Posibles plataformas para GOOS y GOARCH

      Antes de mostrarle la manera de controlar el proceso de creación para crear binarios en diferentes plataformas, inspeccionaremos los tipos de plataformas para los cuales Go puede realizar compilaciones y la forma en que Go hace referencia a estas plataformas usando las variables de entorno GOOS y GOARCH.

      Las herramientas de Go tienen un comando que puede imprimir una lista de las posibles plataformas en las que Go puede realizar compilaciones. La lista puede cambiar con cada nueva versión de Go, de modo que las combinaciones explicadas aquí posiblemente no sean las mismas en otra versión de Go. En el momento en que se redactó este tutorial, la versión actual de Go era la 1.13.

      Para buscar esta lista de posibles plataformas, ejecute lo siguiente:

      Verá un resultado similar al siguiente:

      Output

      aix/ppc64 freebsd/amd64 linux/mipsle openbsd/386 android/386 freebsd/arm linux/ppc64 openbsd/amd64 android/amd64 illumos/amd64 linux/ppc64le openbsd/arm android/arm js/wasm linux/s390x openbsd/arm64 android/arm64 linux/386 nacl/386 plan9/386 darwin/386 linux/amd64 nacl/amd64p32 plan9/amd64 darwin/amd64 linux/arm nacl/arm plan9/arm darwin/arm linux/arm64 netbsd/386 solaris/amd64 darwin/arm64 linux/mips netbsd/amd64 windows/386 dragonfly/amd64 linux/mips64 netbsd/arm windows/amd64 freebsd/386 linux/mips64le netbsd/arm64 windows/arm

      Este resultado es un conjunto de pares clave-valor separados por una /. La primera parte de la combinación, antes de la /, corresponde al sistema operativo. En Go, estos sistemas operativos son posibles valores para la variable de entorno GOOS, que se pronuncia como “goose” y significa Go Operating System (Sistema operativo GO). La segunda parte, después de la /, corresponde a la arquitectura. Como antes, estos son todos los valores posibles para una variable de entorno: GOARCH. Esto se pronuncia como “gore-ch” y significa Go Architecture (Arquitectura de Go).

      Vamos a desglosar una de estas combinaciones para comprender su significado y funcionamiento usando linux/386 como ejemplo. El par clave-valor comienza con el GOOS, que en este ejemplo sería linux haciendo referencia al SO Linux. GOARCH aquí sería 386, que representa el microprocesador Intel 80386.

      Existen muchas plataformas disponibles con el comando go build, pero la mayoría del tiempo usará linux, windows o darwin como valor para GOOS. Estos abarcan las tres principales plataformas de SO: Linux, Windows y macOS, que se basa en el sistema operativo Darwin y por tanto se denomina darwin. Sin embargo, Go puede abarcar plataformas menos predominantes, como nacl, que representa a Google Native Client.

      Cuando se ejecuta un comando como go build, Go utiliza el GOOS y GOARCH de la plataforma actual para determinar la forma de crear el binario. Para conocer la combinación a la que responde su plataforma, puede usar el comando go env y pasar GOOS y GOARCH como argumentos:

      Al probar este ejemplo, ejecutamos este comando en macOS en una máquina con una arquitectura AMD64, por lo que obtendremos el siguiente resultado:

      Output

      darwin amd64

      Aquí, el resultado del comando nos indica que nuestro sistema tiene GOOS=darwin y GOARCH=amd64.

      Ahora sabe lo que GOOS y GOARCH representan en Go, además de sus posibles valores. A continuación, preparará un programa para usarlo como ejemplo de cómo emplear estas variables de entorno y etiquetas de compilación a fin de crear binarios para otras plataformas.

      Escribie un programa que dependa de la plataforma con filepath.Join()

      Antes de comenzar a crear binarios para otras plataformas, vamos a crear un programa de ejemplo. Un buen ejemplo para este fin es la función Join en el paquete path/filepath de la biblioteca estándar de Go. Esta función toma varias cadenas y muestra una que se une con el separador de ruta de archivo correcto.

      Este es un buen ejemplo de programa porque el funcionamiento depende del sistema operativo en el que se ejecute. En Windows, el separador de ruta es una barra diagonal inversa, , mientras que en los sistemas basados en Unix se utiliza una barra diagonal /.

      Comenzaremos creando una aplicación que utilice file path.Join() y, después, escribirá su propia implementación de la función Join() que personaliza el código para los binarios específicos de la plataforma.

      Primero, cree una carpeta en su directorio src con el nombre de su aplicación:

      Posiciónese en ese directorio:

      A continuación, cree un nuevo archivo llamado main.go en su editor de texto. Para este tutorial, usaremos Nano:

      Una vez abierto el archivo, añada el siguiente código:

      src/app/main.go

      package main
      
      import (
        "fmt"
        "path/filepath"
      )
      
      func main() {
        s := filepath.Join("a", "b", "c")
        fmt.Println(s)
      }
      

      La función main() de este archivo utiliza filepath.Join() para concatenar tres cadenas juntas con el separador de ruta dependiente de plataforma correcto.

      Guarde el archivo y ciérrelo. Luego, ejecute el programa:

      Cuando ejecute este programa, recibirá diferentes resultados dependiendo de la plataforma que utilice. En Windows, verá las cadenas separadas por ​​​:

      Output

      abc

      En sistemas Unix como macOS y Linux, verá lo siguiente:

      Output

      a/b/c

      Esto muestra que, debido a los diferentes protocolos de sistemas de archivos empleados en estos sistemas operativos, el programa tendrá que crear un código diferente para las diferentes plataformas. Sin embargo, ya que utiliza un separador de archivos diferente dependiendo del SO, sabemos que filepath.Join() ya tiene en cuenta la diferencia en la plataforma. Esto es porque la cadena de herramientas de Go detecta el GOOS y GOARCH de su máquina y utiliza esta información para usar un fragmento de código con las etiquetas de compilación y el separador de archivos correctos.

      Consideraremos la ubicación de la que obtiene su separador la función filepath.Join(). Ejecute el siguiente comando para inspeccionar el fragmento pertinente de la biblioteca estándar de Go.

      • less /usr/local/go/src/os/path_unix.go

      Con esto, se mostrará el contenido de path_unix.go. Busque la siguiente parte del archivo:

      /usr/local/go/os/path_unix.go

      . . .
      // +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
      
      package os
      
      const (
        PathSeparator     = '/' // OS-specific path separator
        PathListSeparator = ':' // OS-specific path list separator
      )
      . . .
      

      Esta sección define el PathSeparator para todas las variedades de sistemas similares a Unix compatibles con Go. Observe todas las etiquetas de compilación de la parte superior, de las cuales cada una es una posible plataforma GOOS asociada con Unix. Cuando GOOS coincida con estos términos, su programa mostrará el separador de ruta de archivos con el estilo Unix.

      Pulse q para volver a la línea de comandos.

      A continuación, abra el archivo que define el comportamiento de filepath.Join() cuando se utiliza en Windows:

      • less /usr/local/go/src/os/path_windows.go

      Verá lo siguiente:

      /usr/local/go/os/path_unix.go

      . . .
      package os
      
      const (
              PathSeparator     = '\' // OS-specific path separator
              PathListSeparator = ';'  // OS-specific path list separator
      )
      . . .
      

      Aunque el valor de PathSeparator es \ aquí, el código representará la barra diagonal inversa única () necesaria para las rutas de archivos de Windows, ya que la primera barra diagonal inversa solo es necesaria como un carácter de escape.

      Observe que, a diferencia de lo que sucede con el archivo de Unix, no hay etiquetas de compilación en la parte superior. Esto es porque GOOS y GOARCH también pueden pasarse a go build añadiendo un guión bajo (_) y el valor de la variable de entorno como sufijo al nombre de archivo, algo que veremos en mayor profundidad en la sección Usar los sufijos de nombre de archivo de GOOS y GOARCH. Aquí, la parte _windows de path_windows.go hace que el archivo actúe como si tuviese una etiqueta de compilación //+build windows en la parte superior del archivo. Debido a esto, cuando nuestro programa se ejeute en Windows, usará las constantes de PathSeparator y PathListSeparator del fragmento de código path_windows.go.

      Para volver a la línea de comandos, cierre less pulsando q.

      En este paso, creó un programa que mostró la forma en que Go convierte GOOS y GOARCH automáticamente en etiquetas de compilación. Teniendo esto en cuenta, ahora puede actualizar su programa y escribir su propia implementación de filepath.Join() usando las etiquetas de compilación a fin de establecer el PathSeparator correcto para las plataformas Windows y Unix.

      Implementar una función específica de plataforma

      Ahora que conoce la forma en que la biblioteca estándar de Go implementa código específico de una plataforma, puede usar etiquetas de compilación para hacer esto en su propio programa app. Para hacer esto, escribirá su propia implementación de filepath.Join().

      Abra su archivo main.go:

      Reemplace el contenido de main.go por lo siguiente usando su propia función llamada Join():

      src/app/main.go

      package main
      
      import (
        "fmt"
        "strings"
      )
      
      func Join(parts ...string) string {
        return strings.Join(parts, PathSeparator)
      }
      
      func main() {
        s := Join("a", "b", "c")
        fmt.Println(s)
      }
      

      La función Join toma varias parts y las une usando el método strings.Join() del paquete strings para concatenar las parts usando PathSeparator.

      Aún no definido PathSeparator. Hágalo en otro archivo. Guarde main.go y ciérrelo, abra su editor favorito y cree un nuevo archivo llamado path.go:

      nano path.go
      

      Defina PathSeparator y configúrelo de modo que sea igual al separador de ruta de archivo de Unix, /:

      src/app/path.go

      package main
      
      const PathSeparator = "/"
      

      Compile y ejecute la aplicación:

      Obtendrá el siguiente resultado:

      Output

      a/b/c

      Esto se ejecutará correctamente para obtener una ruta de archivo de estilo Unix. Sin embargo, esto no es aún lo que queremos: el resultado es siempre a/b/c, independientemente de la plataforma en la que funcione. Para añadir la funcionalidad de creación de rutas de archivo de estilo Windows, deberá añadir una versión de Windows de PathSeparator e indicar al comando go build la versión que se usará. En la siguiente sección, usará las etiquetas de compilación para conseguir esto:

      Usar etiquetas de compilación de GOOS o GOARCH

      Para tener en cuenta plataformas basadas en Windows, creará un archivo alternativo a path.go y usará las etiquetas de compilación para garantizar que los fragmentos de código solo se ejecuten cuando GOOS y GOARCH son la plataforma correspondiente.

      No obstante, primero añada una etiqueta de compilación a path.go para indicarle que realice operaciones de compilación para todo, a excepción Windows. Abra el archivo:

      Añada la siguiente etiqueta de compilación resaltada al archivo:

      src/app/path.go

      // +build !windows
      
      package main
      
      const PathSeparator = "/"
      

      Las etiquetas de compilación de Go admiten inversión, lo cual significa que puede indicar a Go que compile este archivo para cualquier plataforma, a excepción de Windows. Para invertir una etiqueta de compilación, disponga un ! antes de la etiqueta.

      Guarde el archivo y ciérrelo.

      Ahora, si ejecutara este programa en Windows, vería el siguiente error:

      Output

      ./main.go:9:29: undefined: PathSeparator

      En este caso, Go no podría incluir path.go para definir la variable PathSeparator.

      Ahora que se aseguró de que path.go no se ejecutará cuando GOOS tenga el valor Windows, añada un nuevo archivo, windows.go:

      En windows.go, defina el PathSeparator de Windows, además de la etiqueta de compilación para indicar al comando go build que es la implementación Windows:

      src/app/windows.go

      // +build windows
      
      package main
      
      const PathSeparator = "\"
      

      Guarde el archivo y cierre el editor de texto. La aplicación ahora podrá realizar compilaciones de una forma para Windows y de otra para todas las demás plataformas.

      Aunque los binarios ahora se compilarán correctamente para sus plataformas, existen cambios adicionales que debe aplicar a fin de realizar compilaciones para una plataforma a la que no tenga acceso. Para hacer esto, alterará sus variables de entorno GOOS y GOARCH locales en el siguiente paso.

      Utilizar sus variables de entorno GOOS y GOARCH locales

      Anteriormente, ejecutó el comando go env GOOS GOARCH para conocer el sistema operativo y la arquitectura en los que estaba trabajando. Cuando ejecutó el comando go env, buscó las dos variables de entorno GOOS y GOARCH; si las encontró, se usan los valores de estas, pero si no las encontró Go las configura con la información de la plataforma actual. Esto significa que puede cambiar GOOS o GOARCH para que no se establezcan de forma predeterminada en su SO y arquitectura locales.

      El comando go build tiene un comportamiento similar al del comando go env. Puede configurar las variables de entorno GOOS o GOARCH de modo que realicen tareas de compilación para una plataforma diferente usando go build.

      Si no usa un sistema Windows, compile un binario windows de app fijando la variable de entorno GOOS en windows cuando se ejecute el comando go build:

      A continuación, enumere los archivos en su directorio actual:

      En el resultado del listado del directorio se muestra que ahora hay un ejecutable app.exe de Windows en el directorio del proyecto:

      Output

      app app.exe main.go path.go windows.go

      Usando el comando file, puede obtener más información sobre este archivo y confirmar su compilación:

      Verá lo siguiente:

      Output

      app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

      También puede establecer una o ambas variables de entorno en el momento de la compilación. Ejecute lo siguiente:

      • GOOS=linux GOARCH=ppc64 go build

      Su ejecutable app ahora se reemplazará por un archivo para una arquitectura diferente. Ejecute el comando file en este binario:

      Verá un resultado como el siguiente:

      app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped
      

      Al establecer sus variables de entorno GOOS y GOARCH locales, ahora puede crear binarios para cualquiera de las plataformas compatibles con Go sin complicaciones en la configuración. A continuación, usará las convenciones de nombres de archivo a fin de mantener sus archivos bien organizados y realizar compilaciones para plataformas específicas automáticamente sin etiquetas de compilación.

      Utilizar los sufijos de nombre de archivo GOOS y GOARCH

      Como vio previamente, la biblioteca estándar de Go utiliza de forma intensiva las etiquetas de compilación para simplificar el código separando las implementaciones de las diferentes plataformas en diferentes archivos. Cuando abrió el archivo os/path_unix.go, había una etiqueta de compilación que listaba todas las posibles combinaciones que se consideran plataformas similares a Unix. El archivo os/path_windows.go, sin embargo, no contuvo etiquetas de compilación porque el sufijo del nombre de archivo bastó para indicar a Go la plataforma a la cual se destinó el archivo.

      Veremos la sintaxis de esta función. Al nombrar un archivo .go, puede añadir GOOS y GOARCH como sufijos al nombre del archivo en ese orden y separar los valores mediante guiones bajos (_). Si tuviese un archivo de Go llamado filename.go, podría especificar el SO y la arquitectura cambiando el nombre del archivo a filename_GOOS_GOARCH.go. Por ejemplo, si deseara compilarlo para Windows con arquitectura ARM de 64 bits, el nombre del archivo tendría que ser filename_windows_arm64.go. La convención de nomenclatura permite mantener bien organizado el código.

      Actualice su programa para que utilice los sufijos de nombre de archivo en vez de etiquetas de compilación. Primero, cambie el nombre de los archivos path.go y windows.go para usar la convención empleada en el paquete os:

      • mv path.go path_unix.go
      • mv windows.go path_windows.go

      Una vez cambiados los dos nombres de archivo, podrá eliminar la etiqueta de compilación que añadió a path_windows.go:

      Elimine // +build windows para que su archivo tenga este aspecto:

      path_windows.go

      package main
      
      const PathSeparator = "\"
      

      Guarde el archivo y ciérrelo.

      Debido a que unix no es un GOOS válido, el sufijo _unix.go no tiene significado para el compilador de Go. Sin embargo, transmite el propósito previsto del archivo. Al igual que el archivo os/path_unix.go, su archivo path_unix.go de todos modos necesita usar etiquetas de compilación. Por lo tanto, mantenga ese archivo inalterado.

      Usando las convenciones de nombres de archivo, eliminó las etiquetas de compilación innecesarias de su código fuente y aportó limpieza y claridad al sistema de archivos.

      Conclusión

      La capacidad de generar binarios para varias plataformas que no requieren dependencias es una función potente de la cadena de herramientas de Go. En este tutorial, recurrió a esta capacidad añadiendo etiquetas de compilación y sufijos de nombres de archivos a fin de marcar determinados fragmentos de código para que solo realicen tareas de compilación orientadas a ciertas arquitecturas. Creó su propio programa dependiente de plataforma y luego manipuló las variables de entorno GOOS y GOARCH a fin de generar binarios para plataformas diferentes de su plataforma actual. Esta es una capacidad valiosa, porque es común disponer de un proceso de integración continuo que se ejecute automáticamente a través de estas variables de entorno a fin de compilar binarios para todas las plataformas.

      Para continuar aprendiendo sobre go build, consulte nuestro tutorial Personalizar binarios de Go con etiquetas de compilación. Si desea obtener más información acerca del lenguaje de programación Go en general, consulte toda la serie Cómo realizar codificaciones en Go.



      Source link

      Cómo usar ldflags para configurar la información de versión de aplicaciones de Go


      Introducción

      Al implementar aplicaciones en un entorno de producción, la creación de binarios con información de versión y otros metadatos mejorará sus procesos de monitoreo, registro y depuración al agregar información de identificación para ayudar a realizar un seguimiento de sus compilaciones con el tiempo. Esta información de versión, a menudo, puede incluir datos muy dinámicos, como el tiempo de compilación, la máquina o el usuario que compiló el binario y el sistema de control de versiones (VCS) con el que se creó, entre otros. Debido a que estos valores cambian constantemente, codificar estos datos directamente en el código fuente y modificarlos antes de cada compilación nueva es tedioso y está expuesto errores: los archivos de código fuente se pueden mover y las variables y constantes pueden cambiar los archivos a lo largo del desarrollo, lo que interrumpe el proceso de compilación.

      Una manera de resolver esto en Go es usar -ldflags con el comando go build para insertar información dinámica en el binario en el tiempo de compilación, sin necesidad de modificar el código fuente. En este indicador, ld significa enlazador, que es el programa que vincula las diferentes piezas del código fuente compilado en el binario final. Por lo tanto, ldflags quiere decir indicadores de enlazador. Se denomina de esta manera porque pasa un indicador al enlazador subyacente de la cadena de herramientas de Go, cmd/link, que le permite cambiar los valores de los paquetes importados en el tiempo de compilación desde la línea de comandos.

      En este tutorial, usará -ldflags para cambiar el valor de las variables en el tiempo de compilación e introducir su propia información dinámica en un binario usando una aplicación de muestra que imprime información de versión en la pantalla.

      Requisitos previos

      Para seguir el ejemplo de este artículo, necesitará lo siguiente:

      Crear su aplicación de muestra

      Antes de usar ldflags para introducir datos dinámicos, primero, necesita una aplicación para insertar la información. En este paso, creará esta aplicación, que, en esta etapa, solo imprimirá información de control de versiones estática. Crearemos esa aplicación ahora.

      En su directorio src, cree un directorio que lleve el nombre de su aplicación. En ese tutorial, se usará el nombre de aplicaciónapp:

      Cambie su directorio de trabajo a esta carpeta:

      A continuación, usando el editor de texto que prefiera, cree el punto de entrada de su programa, main.go:

      Ahora, haga que su aplicación imprima información de versión añadiendo el siguiente contenido:

      app/main.go

      package main
      
      import (
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
      }
      

      Dentro de la función main(), declaró la variable Version, luego imprimió la cadena Version: seguida de un carácter de tabulación, t, y por último la variable declarada.

      En este punto, la variable Version se define como development, que será la versión predeterminada de esta aplicación. Posteriormente, cambiará este valor para que sea un número de versión oficial, que se dispondrá conforme al formato de control de versiones semántico.

      Guarde el archivo y ciérrelo. Una vez hecho esto, cree y ejecute la aplicación para confirmar que imprima la versión correcta:

      Verá lo siguiente:

      Output

      Ahora, dispondrá de una aplicación que imprime información de versión predeterminada, pero todavía no cuenta con una forma de transmitir información de versión actual en el tiempo de compilación. En el siguiente paso, usará -ldflags y go build para resolver este problema.

      Usar ldflags con go build

      Como se mencionó anteriormente, ldflags significa *indicadores de enlazador *y se utiliza para pasar indicadores al enlazador subyacente de la cadena de herramientas de Go. Esto funciona de acuerdo con la siguiente sintaxis:

      • go build -ldflags="-flag"

      En este ejemplo, pasamos flag al comando go tool link subyacente que se ejecuta como parte de go build. En este comando, se utilizan comillas dobles alrededor del contenido que se pasa a ldflags para evitar romper sus caracteres o la presencia de caracteres que la línea de comandos podría interpretar como algo distinto de lo que deseamos. Desde aquí, podría pasar muchos indicadores link diferentes. A los efectos de este tutorial, usaremos el indicador -X para escribir información en la variable en el tiempo de enlace, seguido de la ruta del paquete a la variable y su nuevo valor:

      • go build -ldflags="-X 'package_path.variable_name=new_value'"

      Dentro de las comillas, ahora se encuentran la opción -X y un par clave-valor que representa la variable que se debe cambiar y su nuevo valor. El carácter . separa la ruta del paquete y el nombre de la variable, y se utilizan comillas simples para evitar la ruptura de los caracteres en el par clave-valor.

      Para sustituir la variable Version en su aplicación de ejemplo, utilice la sintaxis del último bloque de comandos a fin de establecer un nuevo valor y compilar el nuevo binario:

      • go build -ldflags="-X 'main.Version=v1.0.0'"

      En este comando, main es la ruta del paquete de la variable Version, dado que esta variable se encuentra en el archivo main.go. Version es la variable en la que escribe y v1.0.0 es el nuevo valor.

      Para usar ldflags, el valor que desea cambiar debe existir y ser una variable de nivel de paquetes de tipo string. Esta variable puede ser exportada o no exportada. El valor no puede ser const y el resultado de la invocación de una función no puede fijarlo. Afortunadamente, Version cumple todos estos requisitos: ya se declaró como variable en el archivo main.go, y tanto el valor actual (development) como el valor deseado (v1.0.0) son cadenas.

      Una vez compilado su nuevo binario app, ejecute la aplicación:

      Recibirá el siguiente resultado:

      Output

      Con -ldflags, cambió de forma exitosa la variable Version de development a v1.0.0.

      De esta manera, modificó una variable string dentro de una aplicación simple en el tiempo de compilación. Con ldflags, puede insertar detalles de versión, información de licenciamiento y otros datos en un binario listo para la distribución usando solo la línea de comandos.

      En este ejemplo, la variable que cambió se encontraba en el programa main, lo que redujo la dificultad de determinar el nombre de la ruta. Sin embargo, a veces la ruta hacia estas variables es más difícil de encontrar. En el siguiente paso, escribirá valores en variables dentro de subpaquetes para demostrar la mejor manera de determinar rutas de paquetes más complejas.

      Proporcionar orientación hacia variables de subpaquetes

      En la última sección, manipuló la variable Version que se encontraba en el paquete de nivel superior de la aplicación. Sin embargo, esto no siempre es así. Suele ser más práctico disponer estas variables en otro paquete, ya que el paquete main no se puede importar. Para simular esto en su aplicación de muestra, creará un nuevo subpaquete, app/build, que almacenará información sobre el momento en que se compiló el binario y el nombre del usuario que ejecutó el comando de compilación.

      Para añadir un nuevo subpaquete, primero agregue a su proyecto un nuevo directorio denominado build:

      A continuación, cree un nuevo archivo llamado build.go para contener las variables nuevas:

      En su editor de texto, añada nuevas variables para Time y User:

      app/build/build.go

      package build
      
      var Time string
      
      var User string
      

      La variable Time contendrá una representación de cadena del momento en que se compiló el binario. La variable User contendrá el nombre del usuario que compiló el binario. Dado que estas dos variables siempre tendrán valores, no es necesario inicializarlas con valores predeterminados, como lo hizo para Version.

      Guarde el archivo y ciérrelo.

      A continuación, abra main.go para añadir estas variables a su aplicación:

      Dentro de main.go, agregue las siguientes líneas resaltadas:

      main.go

      package main
      
      import (
          "app/build"
          "fmt"
      )
      
      var Version = "development"
      
      func main() {
          fmt.Println("Version:t", Version)
          fmt.Println("build.Time:t", build.Time)
          fmt.Println("build.User:t", build.User)
      }
      

      En estas líneas, primero importó el paquete app/build y luego imprimió build.Time y build.User de la misma manera en que imprimió Version.

      Guarde el archivo y cierre su editor de texto.

      A continuación, para brindar orientación a estas variables con ldflags podría usar la ruta de importación app/build seguida de .User o .Time, dado que ya conoce la ruta de importación. Sin embargo, para simular una situación más compleja en la cual la ruta hacia la variable no sea tan evidente, usaremos el comando nm de la cadena de herramientas de Go.

      Con el comando go tool nm se mostrarán los símbolos presentes en un ejecutable, un objeto o un archivo. En este caso, “símbolo” se refiere a un objeto en el código, como una variable o función definida o importada. Generando una tabla de símbolos con nm y usando grep para buscar una variable, puede obtener rápidamente información sobre su ruta.

      Nota: El comando nm no lo ayudará a encontrar la ruta de su variable si el nombre del paquete tiene caracteres no ASCII, o bien los caracteres " o %, ya que es una limitación de la herramienta.

      Para usar este comando, primero, compile el binario para app:

      Ahora que se compiló app, oriente la herramienta nm hacia ella y realice una búsqueda en el resultado:

      • go tool nm ./app | grep app

      Cuando se ejecute, la herramienta nm mostrará muchos datos. Debido a esto, el comando anterior utilizó | para canalizar el resultado al comando grep, que luego buscó términos con la app de nivel superior en el título.

      Recibirá un resultado similar a este:

      Output

      55d2c0 D app/build.Time 55d2d0 D app/build.User 4069a0 T runtime.appendIntStr 462580 T strconv.appendEscapedRune . . .

      En este caso, las primeras dos líneas del resultado establecido contienen las rutas de las dos variables que busca: app/build.Time y app/build.Time.

      Ahora que conoce las rutas, compile la aplicación de nuevo, pero, esta vez, cambie Version, User y Time en el tiempo de compilación. Para hacerlo, pase varios indicadores -X a -ldflags:

      • go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

      Aquí, pasó el comando Bash id -u -n para mostrar el usuario actual y el comando date para mostrar la fecha actual.

      Una vez compilado el ejecutable, ejecute el programa:

      Este comando, cuando se ejecuta en un sistema Unix, genera un resultado similar al siguiente:

      Output

      Version: v1.0.0 build.Time: Fri Oct 4 19:49:19 UTC 2019 build.User: sammy

      Ahora, dispondrá de un binario que contiene información de control de versiones y de compilación, y que puede proporcionar ayuda fundamental para la producción a la hora de resolver problemas.

      Conclusión

      En este tutorial, se demostró que ldflags, cuando se aplica correctamente, puede ser una potente herramienta para introducir información valiosa en binarios en el tiempo de compilación. De esta manera, puede controlar indicadores de características, información de entorno, información de control de versiones y otros elementos sin introducir cambios en su código fuente. Agregando ldflags a su flujo de trabajo de compilación actual, puede maximizar los beneficios del formato de distribución binaria autónoma de Go.

      Si desea obtener más información acerca del lenguaje de programación Go, consulte toda la serie Cómo programar en Go. Si busca más soluciones para el control de versiones, consulte nuestra guía de referencia Cómo usar Git.



      Source link

      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": "admin@example.com"
      }
      
      # Internationalization
      # https://docs.djangoproject.com/en/2.0/topics/i18n/
      
      ...
      

      No olvide sustituir los valores del marcador de posición your_vapid_publickey, `yourvapidpublickeyyadmin@example.com` 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