Golang es un lenguaje utilizado del lado del servidor debido a su simplicidad de uso, potencia y velocidad.
Sin embargo, con Go también se pueden hacer peticiones HTTP. Es decir, Go también puede funcionar como cliente HTTP, no solo como servidor.
Como sabemos, hay 4 métodos básicos para las peticiones HTTP: POST para enviar datos, GET para obtener, PUT para actualizar y DELETE para eliminar.
En este post veremos ejemplos de cómo hacer estas 4 peticiones con Go usando el paquete net/http para lograr hacer una petición GET, POST, PUT y DELETE usando Golang.
También vamos a ver cómo escribir y enviar encabezados o el código de respuesta (200 para OK, 500 de error, 405 de no permito, etcétera)
El sitio web que vamos a usar para probar será httpbin.org, el cual sirve como espejo para probar que realmente estamos haciendo las peticiones y enviando datos.
Peticiones HTTP con Golang
Voy a explicar lo básico y más abajo dejaré ejemplos.
Para comenzar importa el paquete net/http; es lo único que necesitas para las conexiones HTTP, aunque si vas a codificar con JSON debes importar encoding/json.
Ya veremos en el código cuáles cosas hacen falta. Comenzamos creando un cliente HTTP:
clienteHttp := &http.Client{}
Y también creamos una petición:
peticion, err := http.NewRequest("GET", url, nil)
La petición recibe 3 argumentos: el tipo o verbo, la url y los datos. Si es GET o simplemente no queremos enviar datos pasamos nil
.
A la petición le podemos agregar encabezados:
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
Para realizar la petición (ahora sí hacerla) invocamos al método Do
del cliente HTTP:
respuesta, err := clienteHttp.Do(peticion)
En caso de que err
sea nil
debemos cerrar el cuerpo, podemos usar defer para cerrarlo al final:
defer respuesta.Body.Close()
Para acceder al cuerpo lo leemos con ioutil.ReadAll
:
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
Podemos convertirlo a cadena, pues ReadAll
lo devuelve como bytes. Para convertirlo hacemos esto:
respuestaString := string(cuerpoRespuesta)
Si queremos acceder al código de respuesta lo hacemos a través de StatusCode
:
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
Los encabezados se encuentran en respuesta.Header en forma de mapa. No hay necesidad de parsear los valores por nosotros, pues ya existe el método Get
:
contentType := respuesta.Header.Get("Content-Type")
Sobra decir que podemos escribir la respuesta en un archivo, decodificarla como JSON o ignorarla.
Ahora sí veamos el código de ejemplo.
Petición GET
El código es el siguiente:
/*
Cliente HTTP en Go con net/http
Ejemplo de petición HTTP Get en Golang
@author parzibyte
*/
package main
import (
"io/ioutil"
"log"
"net/http"
)
func main() {
clienteHttp := &http.Client{}
// Si quieres agregar parámetros a la URL simplemente haz una
// concatenación :)
url := "https://httpbin.org/get"
peticion, err := http.NewRequest("GET", url, nil)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error creando petición: %v", err)
}
// Podemos agregar encabezados
peticion.Header.Add("Content-Type", "application/json")
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error haciendo petición: %v", err)
}
// No olvides cerrar el cuerpo al terminar
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
log.Fatalf("Error leyendo respuesta: %v", err)
}
respuestaString := string(cuerpoRespuesta)
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
log.Printf("Encabezados: '%q'", respuesta.Header)
contentType := respuesta.Header.Get("Content-Type")
log.Printf("El tipo de contenido: '%s'", contentType)
// Aquí puedes decodificar la respuesta si es un JSON, o convertirla a cadena
log.Printf("Cuerpo de respuesta del servidor: '%s'", respuestaString)
}
No estamos enviando nada en el cuerpo, pues es una petición GET. Si se desea indicar parámetros en la URL simplemente se usa una concatenación.
Petición POST
El código queda como a continuación:
/*
Cliente HTTP en Go con net/http
Ejemplo de petición HTTP POST enviando datos JSON
en Golang
@author parzibyte
*/
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
func main() {
clienteHttp := &http.Client{}
// Si quieres agregar parámetros a la URL simplemente haz una
// concatenación :)
url := "https://httpbin.org/post"
type Usuario struct {
Nombre string
Edad int
}
usuario := Usuario{
Nombre: "Luis",
Edad: 22,
}
usuarioComoJson, err := json.Marshal(usuario)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error codificando usuario como JSON: %v", err)
}
peticion, err := http.NewRequest("POST", url, bytes.NewBuffer(usuarioComoJson))
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error creando petición: %v", err)
}
// Podemos agregar encabezados
peticion.Header.Add("Content-Type", "application/json")
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error haciendo petición: %v", err)
}
// No olvides cerrar el cuerpo al terminar
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
log.Fatalf("Error leyendo respuesta: %v", err)
}
// Aquí puedes decodificar la respuesta si es un JSON, o convertirla a cadena
respuestaString := string(cuerpoRespuesta)
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
log.Printf("Encabezados: '%q'", respuesta.Header)
contentType := respuesta.Header.Get("Content-Type")
log.Printf("El tipo de contenido: '%s'", contentType)
log.Printf("Cuerpo de respuesta del servidor: '%s'", respuestaString)
}
Ahora sí enviamos el cuerpo, el cual es un búfer que contiene un dato JSON. Podríamos enviar el JSON directamente como cadena o codificar un struct.
Petición PUT
Es casi igual que POST, solo cambia el verbo y la URL para httpbin. Desconozco cuál es la verdadera diferencia pero el código para hacer una petición PUT es el siguiente:
/*
Cliente HTTP en Go con net/http
Ejemplo de petición HTTP PUT enviando datos JSON
en Golang
@author parzibyte
*/
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
func main() {
clienteHttp := &http.Client{}
// Si quieres agregar parámetros a la URL simplemente haz una
// concatenación :)
url := "https://httpbin.org/put"
type Usuario struct {
Nombre string
Edad int
}
usuario := Usuario{
Nombre: "Luis",
Edad: 22,
}
usuarioComoJson, err := json.Marshal(usuario)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error codificando usuario como JSON: %v", err)
}
peticion, err := http.NewRequest("PUT", url, bytes.NewBuffer(usuarioComoJson))
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error creando petición: %v", err)
}
// Podemos agregar encabezados
peticion.Header.Add("Content-Type", "application/json")
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error haciendo petición: %v", err)
}
// No olvides cerrar el cuerpo al terminar
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
log.Fatalf("Error leyendo respuesta: %v", err)
}
// Aquí puedes decodificar la respuesta si es un JSON, o convertirla a cadena
respuestaString := string(cuerpoRespuesta)
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
log.Printf("Encabezados: '%q'", respuesta.Header)
contentType := respuesta.Header.Get("Content-Type")
log.Printf("El tipo de contenido: '%s'", contentType)
log.Printf("Cuerpo de respuesta del servidor: '%s'", respuestaString)
}
Petición DELETE
Aquí entra una discusión que no tiene que ver con Go y es que si al hacer una llamada DELETE se pueden pasar datos en el cuerpo.
En algunos lugares es posible, es decir, algunos servidores lo aceptan, otros no. Yo recomiendo no pasar nada en el cuerpo.
El siguiente código hace una petición HTTP DELETE:
/*
Cliente HTTP en Go con net/http
Ejemplo de petición HTTP DELETE enviando datos JSON
en Golang
Nota: recuerda que DELETE tal vez no debería llevar
cuerpo de petición, sin embargo es posible. Personalmente
no lo recomiendo, pero queda en ti
@author parzibyte
*/
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
func main() {
clienteHttp := &http.Client{}
// Si quieres agregar parámetros a la URL simplemente haz una
// concatenación :)
url := "https://httpbin.org/delete"
type Usuario struct {
Nombre string
Edad int
}
usuario := Usuario{
Nombre: "Luis",
Edad: 22,
}
usuarioComoJson, err := json.Marshal(usuario)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error codificando usuario como JSON: %v", err)
}
peticion, err := http.NewRequest("DELETE", url, bytes.NewBuffer(usuarioComoJson))
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error creando petición: %v", err)
}
// Podemos agregar encabezados
peticion.Header.Add("Content-Type", "application/json")
peticion.Header.Add("X-Hola-Mundo", "Ejemplo")
respuesta, err := clienteHttp.Do(peticion)
if err != nil {
// Maneja el error de acuerdo a tu situación
log.Fatalf("Error haciendo petición: %v", err)
}
// No olvides cerrar el cuerpo al terminar
defer respuesta.Body.Close()
cuerpoRespuesta, err := ioutil.ReadAll(respuesta.Body)
if err != nil {
log.Fatalf("Error leyendo respuesta: %v", err)
}
// Aquí puedes decodificar la respuesta si es un JSON, o convertirla a cadena
respuestaString := string(cuerpoRespuesta)
log.Printf("Código de respuesta: %d", respuesta.StatusCode)
log.Printf("Encabezados: '%q'", respuesta.Header)
contentType := respuesta.Header.Get("Content-Type")
log.Printf("El tipo de contenido: '%s'", contentType)
log.Printf("Cuerpo de respuesta del servidor: '%s'", respuestaString)
}
Conclusión
Golang funciona como servidor y como cliente HTTP. Podemos hacer múltiples llamadas a otros servidores utilizando el protocolo HTTP y enviando datos.
Personalmente utilicé las peticiones en Go para probar mi servidor. Es decir, programé el servidor y después hice los tests automatizados para probar que el servidor funcionaba.