Go y Golang

Servidor web completo en Go

Vamos a ver cómo crear un servidor web en el lenguaje de programación Go, también conocido como Golang. Lo que haremos será crear un servidor simple que responderá a peticiones GET, es decir, a las direcciones URL y que servirá archivos.

Explicado de otra manera, nuestro server hará esto:

  • Responder a peticiones dependiendo de la URL
  • Leer parámetros que pasamos por la URL
  • Servir un directorio y archivos, por ejemplo HTML, imágenes, vídeos, etcétera; algo así como lo hace el buen Apache o el servidor en Python.

No necesitaremos librerías extras ni frameworks, pues el maravilloso lenguaje provee todo lo que necesitamos.

Por cierto, si no lo tienes, mira cómo instalar Go en Windows.

Este tutorial es completo, explicaré a detalle cada cosa, te aseguro que aunque es un poco largo te enseñará lo robusto que es el lenguaje Go en cuanto a la web.

¿Interesado en algo más robusto? conoce mux.

Sobre Go y HTTP

Todo lo que usaremos para este servidor web está dentro del paquete net/http. Go es un lenguaje excelente para la web, y sus librerías incluidas por defecto hacen el trabajo muy bien.

El puerto y la dirección

A diferencia de Apache u otros servidores que ya vienen configurados por defecto, en Go podemos cambiar el puerto en una simple línea de código; nada de archivos de configuración.

Pero no sólo eso, sino que podemos especificar en cuál IP escuchar. En el código de abajo verás que a veces definimos el puerto como cadena, así:

":8080"

Y tú dirás, ¿no el puerto debería ser un entero, o bueno, no debería ir sin los dos puntos? La respuesta es que sí, pero no estamos especificando el puerto, sino la dirección. De otro modo, esa dirección podría quedar así:

"127.0.0.1:8080" o también "localhost:8080", de igual manera podría quedar como "192.168.1.76:8080" en donde 192.168.1.76 es la dirección del servidor.

¿Entonces, cuál es la diferencia? bueno, si especificamos únicamente :8080 entonces escuchamos en localhost, en la ip pública y en 127.0.0.1.

En caso de que sólo especifiquemos, por ejemplo, 127.0.0.1:8080, el servidor sólo respondería a peticiones que se hagan en la computadora que es al mismo tiempo el servidor, no desde otras. Tómalo en cuenta por si no quieres exponer tu servidor.

En resumen, si quieres que cualquier PC se conecte al servidor sabiendo su IP, especifica únicamente el puerto con :8080.

Por cierto, :8080 es un ejemplo, pero el puerto puede ser el que tú quieras, siempre y cuando esté desocupado.

Hola mundo, con un servidor HTTP en Go

El código para tener un servidor HTTP es este:

/*
 Ejemplo 1: hola mundo en servidor HTTP con Go
 @author parzibyte
*/ 
package main
 
import (
 "fmt"// Imprimir en consola
 "io"// Ayuda a escribir en la respuesta
 "log"//Loguear si algo sale mal
 "net/http"// El paquete HTTP
)
 
func main() {
 
 http.HandleFunc("/hola", func(w http.ResponseWriter, peticion *http.Request) {
  io.WriteString(w, "Solicitaste hola")
 })
 direccion := ":8080" // Como cadena, no como entero; porque representa una dirección
 fmt.Println("Servidor listo escuchando en " + direccion)
 log.Fatal(http.ListenAndServe(direccion, nil))
}

Lo que hacemos es llamar a HandleFunc, le ponemos la ruta y la función que lo manejará. En el caso de que visiten /hola se mostrará un simple mensaje que dirá “Solicitaste hola”.

De esa manera podemos definir más rutas, y más manejadores. Por cierto, si quieres usar el mismo manejador para dos rutas, define la función primero y guárdala, para más tarde mandarla como argumento.

Después de definir todas nuestras rutas, imprimimos un mensaje para decir que ya está listo nuestro servidor; y llamamos al método http.ListenAndServe; como primer argumento le ponemos la dirección y como segundo sería un manejador que no necesitamos por el momento (más información aquí).

Ahora compilamos y ejecutamos, visitamos la dirección y ya está nuestro servidor web con Go:

Hola mundo en servidor http con Go

Otras rutas con Go

Como lo dije, podemos definir más rutas y manejadores distintos. No importa cómo sea la ruta o qué tan larga sea. Aquí vemos otro ejemplo:

/*
 Ejemplo 2: otras rutas en servidor HTTP con Go
 @author parzibyte
*/ 
package main
 
import (
 "fmt"// Imprimir en consola
 "io"// Ayuda a escribir en la respuesta
 "log"//Loguear si algo sale mal
 "net/http"// El paquete HTTP
)
 
func main() {
 
 http.HandleFunc("/hola", func(w http.ResponseWriter, peticion *http.Request) {
  io.WriteString(w, "Solicitaste hola")
 })
 
 
 http.HandleFunc("/ruta/un/poco/larga", func(w http.ResponseWriter, peticion *http.Request) {
  io.WriteString(w, "Solicitaste ruta/un/poco/larga")
 })
 direccion := ":8080" // Como cadena, no como entero; porque representa una dirección
 fmt.Println("Servidor listo escuchando en " + direccion)
 log.Fatal(http.ListenAndServe(direccion, nil))
}

De esta manera estamos sirviendo otra ruta. Más tarde traeré un tutorial para rutas en Go, con expresiones regulares, como lo hicimos con PHP.

Si visitamos ahora la ruta, se ve esto:

Más rutas en servidor web

Leer parámetros de la URL

La mayoría de veces usamos la URL para mandar datos. Por ejemplo: sitio.com?id=1&nombre=parzibyte

Con Go, podemos leer los parámetros GET fácilmente. Veamos este ejemplo de lectura de parámetro de la url:

/*
 Ejemplo 3: parámetros de la url
 @author parzibyte
*/ 
package main
 
import (
 "fmt"      // Imprimir en consola
 "io"       // Ayuda a escribir en la respuesta
 "log"      //Loguear si algo sale mal
 "net/http" // El paquete HTTP
)
 
func main() {
 
 http.HandleFunc("/saludo", func(w http.ResponseWriter, peticion *http.Request) {
  listaDeNombres, existen := peticion.URL.Query()["nombre"]
  // Imprimir para depurar
  fmt.Printf("%v\n", listaDeNombres)
  if existen {
   io.WriteString(w, "Hola, "+listaDeNombres[0])
  } else {
   io.WriteString(w, "Hola, desconocido")
  }
 })
 
 direccion := ":8080" // Como cadena, no como entero; porque representa una dirección
 fmt.Println("Servidor listo escuchando en " + direccion)
 log.Fatal(http.ListenAndServe(direccion, nil))
}

Para leer un parámetro utilizamos:

peticion.URL.Query()["clave"]

En donde clave es la clave que queremos leer. Esta función devolverá false como segunda respuesta si la clave no existe; por eso es que primero comprobamos si existen.

Otra cosa, ¿por qué es listaDeNombres y no simplemente nombre? es porque devolverá un arreglo con los valores que tengan esa clave. Por ejemplo, si la URL es:

http://localhost:8080/saludo?nombre=Parzibyte

Entonces devolverá [Parzibyte]

Pero si la URL es:

http://localhost:8080/saludo?nombre=Parzibyte&nombre=Maggie

Sí, dos veces la misma clave, entonces devuelve:

[Parzibyte, Maggie]

Esto permitiría mandar una lista de valores con la misma clave, pero ese no es el punto, por eso leemos el primer elemento: listaDeNombres[0]

Lo puedes ejecutar y dependiendo de los valores te dirá desconocido o tu nombre:

Parámetros en URL con servidor Go

Servir archivos con servidor web de Go

Un servidor web sirve archivos, y no se limita a responder a rutas. Servir una carpeta pública con Go es muy fácil. Veamos este ejemplo:

/*
 Ejemplo 4: Servir archivos, por ejemplo imágenes, HTML, vídeos, etcétera
 @author parzibyte
*/ 
package main
 
import (
 "fmt"      // Imprimir en consola
 "log"      //Loguear si algo sale mal
 "net/http" // El paquete HTTP
)
 
func main() {
 http.Handle("/", http.FileServer(http.Dir("./archivos")))
 
 direccion := ":8080" // Como cadena, no como entero; porque representa una dirección
 fmt.Println("Servidor listo escuchando en " + direccion)
 log.Fatal(http.ListenAndServe(direccion, nil))
}

En una línea servimos un directorio:

http.Handle("/", http.FileServer(http.Dir("./archivos")))

En este caso, cuando visitemos la raíz, se servirá todo lo que hay en la carpeta archivos. Compilamos y visitamos la página; veremos que muestra todo lo que hay en la carpeta:

Servir archivos o carpeta con servidor web en Go

Servidor de archivos con ruta distinta

En ese caso estamos sirviendo a partir de la raíz, en la url:

localhost:8080/

¿Qué pasa si queremos servir la carpeta pero en otra ruta? por ejemplo, en la url:

localhost:8080/archivos/

Eso no es posible con el código de arriba, porque Go sirve relativamente a la ruta del archivo. Si queremos servir en otra ruta, el código se complica pero sólo un poco:

/*
 Ejemplo 5: Servir archivos, por ejemplo imágenes, HTML, vídeos, etcétera en
 una ruta que no es la raíz
 @author parzibyte
*/ 
package main
 
import (
 "fmt"      // Imprimir en consola
 "log"      //Loguear si algo sale mal
 "net/http" // El paquete HTTP
)
 
func main() {
 http.Handle("/archivos/", http.StripPrefix("/archivos/", http.FileServer(http.Dir("./archivos")))) 
 
 direccion := ":8080" // Como cadena, no como entero; porque representa una dirección
 fmt.Println("Servidor listo escuchando en " + direccion)
 log.Fatal(http.ListenAndServe(direccion, nil))
}

Lo que hacemos es quitar un prefijo de la ruta. Para que se te haga fácil, lo mismo que pongas en la ruta ponlo en StripPrefix y listo.

Cabe mencionar que el argumento que se pasa a http.Dir puede ser una ruta absoluta si así se requiere.

Servir fuera de la carpeta raíz

Evitar confusiones al servir

Una cosa es la ruta en donde se sirve, y otra es la carpeta que se sirve. El nombre y la ruta no deben ser obligatoriamente las mismas; es decir, podríamos servir en la ruta publico la carpeta llamada archivos:

http.Handle("/publico/", http.StripPrefix("/publico/", http.FileServer(http.Dir("./archivos"))))

En ese caso servimos en la ruta publico, quitamos el prefijo publico, pero en realidad servimos lo que hay en archivos.

Servir otra carpeta

Puedes servir una carpeta que no es relativa al archivo. Así:

http.Handle("/publico/", http.StripPrefix("/publico/", http.FileServer(http.Dir(`D:`))))

De esa manera exponemos el disco local D  en la ruta /publico; por cierto, no lo hagas en casa.

Conclusión

Quedan pendientes más tutoriales que traeré pronto.

Actualización: aquí hay un tutorial con enrutador y middleware usando mux.

Por ejemplo para leer datos de un formulario, parsear rutas, comunicarse a través de JSON, renderizar plantillas y otras cosas interesantes.

Te invito a leer más sobre el módulo HTTP en Gohttps://golang.org/pkg/net/http/

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Buenas tardes, una pregunta, tengo una pagina con acceso de login y tambien para logearse, esto hecho en golang con mysql, la pregunta es: 1- con localhost:8089 me funciona perfecto al grabar el logeo en mysql , opara entrar mediante login, pero como hago para que desde culaquier parte en internet se puedan loguear o entrar con el login, no he podido lograrlo, como puede ser la situacion

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.