Construyendo una API RESTful liviana de alto rendimiento utilizando HTTP router en Go

En este artículo, vamos mostrar cómo crear una aplicación de servicio web API REST de alto rendimiento y alta eficiencia. Antes de empezar tenemos que ver en como cómo crear un servicio web en Go. El paquete estándar http en Go tiene la capacidad de manejar tanto el cliente HTTP como las tareas del servidor. El problema con este paquete es que su enrutamiento y multiplexación es bastante básico. Dependiendo de su necesidad, puede requerir algún código adicional que puede conducir a códigos de enrutamiento estándar. Si desea evitar esto, existen varios paquetes que brindan soluciones. Estos son dos de los pocos paquetes que se pueden utilizar:

  • Gorilla/mux, es un paquete que es parte del Gorilla web toolkit. Ese paquete proporciona un conjunto flexible de estándares con los que puede coincidir cada solicitud y que incluye host, esquemas, encabezados HTTP y más.
  • Pat es un paquete basada en la idea del paquete Sinatra en Ruby. El propósito es hacerlo más legible. Por lo tanto, la implementación contiene parámetros con nombre que facilitan su lectura.






 

  • Importar paquete julienschmidt

En primer lugar, asegúrese de que su GOPATH esté configurado en correctamente, como se describe en estos artículos:

 

– Para usuarios de MACOS

 

– Para usuarios de Windows

 

– Para usuarios de Linux user

 

Abra la terminal y ejecute el siguiente comando:

 

go get github.com/julienschmidt/httprouter

 

  • Escribir aplicación de servicio web

 

Después de importar el paquete, podemos empezar a escribir algunos códigos. En este ejemplo, solo vamos a escribir una sencillo servicio web que devuelva respuestas en JSON. El contexto de esa aplicación de servicio web implica puntos finales para recuperar, insertar, modificar y eliminar estudiantes. La lista representa una base de datos ficticia, ya que queremos que sea lo más simple posible.

 

Comenzaremos en creando la estructura necesaria (modelos de datos) requeridos para esta aplicación de servicio web

 

Estructura del estudiante

 

Esta estructura contiene las siguientes data fields:

 

ID -> La identificación de estudiante ficticia (de tipo entero)

 

Name -> El nombre del estudiante (de tipo cadena)

 

Age -> La edad del estudiante (de tipo entero)

 

type estudiante struct {
   ID     int    `json:"id"`
   Nombre string `json:"nombre"`
   Edad   int    `json:"edad"`
}

 

 

Estructura del mensaje de repuesta

 

La estructura del mensaje de respuesta sera utilizarada como una estructura genérica para enviar mensajes y contiene solo una data field:

 

Mensaje -> El mensaje de respuesta del servidor (de tipo cadena)

 

type mensajeDeRespuesta struct {
	Mensaje string `json:"mensaje"`
}

 

Lista de estudiantes (una representación ficticia de una base de datos)

 

Esta lista simulará una base de datos ficticia para la inserción, eliminación, modificación y eliminación de estudiantes.

 

var listaDeEstudiantes []estudiante

 

Contador de ID (contador para incrementar la ID de estudiante cuando se inserta)

 

Este contador se incrementará cada vez que se agrega un nuevo estudiante en la lista. En el contexto de una base de datos, eso puede ser la identificación del estudiante o la clave principal de la base de datos (que se puede incrementar automáticamente cada vez que un elemento es agregado). 

 

var idContador = 0

 

Implementación del alguna función crucial para mantener nuestro código lo mas limpio posible.

 

En Go, enviar una respuesta y mensaje(s) de error al cliente es un procedimiento estándar. Para evitar duplicaciones de códigos, vamos a implementar algunas funciones cruciales que se pueden reutilizar. Estas funciones son:

 

  • Asignado como json

 

Esta función establecerá el encabezado de respuesta como json, lo cual es importante para que el cliente sepa que la respuesta está en forma de json.

 

  func asignadoComoJson(escritorDeRespuesta http.ResponseWriter) {
	  escritorDeRespuesta.Header().Set("Content-Type", "application/json")
  } 

 

  • Repuesta de solicitud incorrecta

 

Esta es la respuesta de solicitud incorrecta predeterminada que sera utilizada en esta aplicación de servicio web.

 

func RepuestaDeSolicitudIncorrecta(respuesta http.ResponseWriter, mensaje string) {

asignadoComoJson(respuesta)
respuesta.WriteHeader(http.StatusBadRequest)
mensajeRepuesta := mensajeDeRespuesta{Mensaje: mensaje}
respuestaEnJson, errorJson := json.Marshal(mensajeRepuesta)

if errorJson != nil {
_, _ = respuesta.Write([]byte(""))
} else {
_, _ = respuesta.Write(respuestaEnJson)
}
}

 

 

  • Enviar error interno del servidor

 

Esta función es casi la misma que la función anterior, pero en este caso tiene que ver con la respuesta de error interno del servidor. Se utilizará cuando algo vaya totalmente mal en el lado del servidor.

 

func enviarErrorInternoDelServidor(respuesta http.ResponseWriter) {
	asignadoComoJson(respuesta)
	respuesta.WriteHeader(http.StatusInternalServerError)
	mensajeRepuesta := mensajeDeRespuesta{Mensaje: "No se puede manejar la solicitud"}
	respuestaEnJson, errorJson := json.Marshal(mensajeRepuesta)
	if errorJson != nil {
		_, _ = respuesta.Write([]byte(""))
	} else {
		_, _ = respuesta.Write(respuestaEnJson)
	}
}

 

 

  • Enviar respuesta como un mensaje

 

Esta función maneja las respuestas que contienen un mensaje específico al cliente. Dicho mensaje puede ser un mensaje de error o si desea informar sobre algo (por ejemplo: la lista de estudiantes está vacía, etc.).

 

func enviarRespuestaComoUnMensaje(codigoDeEstado int, respuesta http.ResponseWriter, mensaje mensajeDeRespuesta) {
	if codigoDeEstado != 0 && respuesta != nil {
		asignadoComoJson(respuesta)
		respuesta.WriteHeader(codigoDeEstado)
		repuestaEnJson, errorJson := json.Marshal(mensaje)
		if errorJson != nil {
			enviarErrorInternoDelServidor(respuesta)
		} else {
			_, repuestaJsonError := respuesta.Write(repuestaEnJson)
			if repuestaJsonError != nil {
				enviarErrorInternoDelServidor(respuesta)
			}
		}
	}
}

 

 

  • Enviar repuesta

 

Al igual que la función anterior, esta maneja respuestas que contienen información del estudiante. En la mayoría de los casos, sera utilizado cuando una solicitud es realizado correctamente.

 

func enviarRepuesta(codigoDeEstado int, respuesta http.ResponseWriter, estudiante estudiante) {
    if codigoDeEstado != 0 && respuesta != nil {
	asignadoComoJson(respuesta)
	respuesta.WriteHeader(codigoDeEstado)
	repuestaEnJson, errorJson := json.Marshal(estudiante)
	if errorJson != nil {
	    enviarErrorInternoDelServidor(respuesta)
	} else {
	    _, respuestaJsonError := respuesta.Write(repuestaEnJson)
	    if respuestaJsonError != nil {
		enviarErrorInternoDelServidor(respuesta)
	    }
	}
    }
}

 

 

  • Lista contiene registro por ID

 

Esta función verifica si la lista de estudiantes (base de datos ficticia en nuestro caso) contiene un estudiante específico según la identificación de estudiante proporcionada. Dicha identificación ha sido generada por la propiedad idContador durante la inserción del estudiante.

 

func listaContieneRegistroPorID(identificacion int) bool {
	contieneDatos := false
	for _, estudianteEncontrado := range listaDeEstudiantes {
		if estudianteEncontrado.ID == identificacion {
			contieneDatos = true
		}
	}
	return contieneDatos
}

 

 

  • Lista contiene registro

 

Esta función verifica si la lista contiene un registro de estudiante según el nombre y la edad proporcionados. Como puede ver, devuelve múltiples variables, para obtener más información sobre cómo manejar funciones de múltiples variables, consulte este artículo.

 

func listaContieneRegistro(nombre string, edad int) (bool, estudiante) {
	contieneDatos := false
	var estudiante estudiante
	for _, estudianteEncontrado := range listaDeEstudiantes {
		if strings.ToLower(estudianteEncontrado.Nombre) == strings.ToLower(nombre) && estudianteEncontrado.Edad == edad {
			contieneDatos = true
			estudiante = estudianteEncontrado
		}
	}
	return contieneDatos, estudiante
}

 

En este caso, devuelve una boolean y estructura de estudiante.

 

 

Puntos finales del servidor

 

En la siguiente sección, implementaremos los puntos finales del servidor. Además, inicie y vincule los puntos finales al enrutador http.

 

  • Iniciar la instancia del enrutador

 

Para utilizar el paquete de http router, cree una nueva instancia en la función principal (main).

 

func main() {
   router := httprouter.New()
}

 

  • Punto final para obtener todos los estudiantes 

 

Comencemos con el punto final para obtener todos los estudiantes. Este punto final devolverá la lista de estudiantes que se ha agregado en la lista. La ruta a este punto final será /estudiante/todos y es una solicitud GET.

 

“GET: /estudiante/todos” 

 

Primero comencemos definiéndolo en la instancia de enrutador recién creada:

 

func main() {
   router := httprouter.New()
   router.GET("/estudiante/todos", manejarObtenerTodosLosEstudiantes)
} 

 

Implemente la función de controlador para este punto final:

 

func manejarObtenerTodosLosEstudiantes(respuesta http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	asignadoComoJson(respuesta)
	respuesta.WriteHeader(http.StatusOK)
	if len(listaDeEstudiantes) == 0 {
		respuestaJson, _ := json.Marshal([]estudiante{})
		_, error := respuesta.Write(respuestaJson)
		if error != nil {
			enviarErrorInternoDelServidor(respuesta)
		}
	} else {
		respuestaJson, _ := json.Marshal(listaDeEstudiantes)
		_, error := respuesta.Write(respuestaJson)
		if error != nil {
			enviarErrorInternoDelServidor(respuesta)
		}
	}
}

 

 

  • Punto final para obtener estudiante

 

Con este punto final, puede recuperar un estudiante específico de la lista según la identificación proporcionada. La respuesta es un objeto json que contiene información del estudiante o un mensaje genérico de que no contiene el estudiante. La ruta de este punto final es /estudiante y es una solicitud GET. La identificación del estudiante debe proporcionarse en el parámetro de solicitud.

 

“GET: /estudiante”

 

func manejarObtenerEstudiantes(respuesta http.ResponseWriter, peticion *http.Request, _ httprouter.Params) {
	parametros := peticion.URL.Query()
	if parametros == nil || len(parametros) == 0 || parametros["id"] == nil || parametros["id"][0] ==  
        "" 
        {
		RepuestaDeSolicitudIncorrecta(respuesta, "identificación invalida")
		return
	}
	identificacionDelEstudiante := parametros["id"][0]
	estudianteIDEnFormaEntero, error := strconv.Atoi(identificacionDelEstudiante)
	if error != nil {
		RepuestaDeSolicitudIncorrecta(respuesta, "identificación invalida")
		return
	}
	var estudianteSolicitado = estudiante{
		ID:     -1,
		Nombre: "",
		Edad:   -1,
	}
	for _, estudianteEncontrado := range listaDeEstudiantes {
		if estudianteEncontrado.ID == estudianteIDEnFormaEntero {
			estudianteSolicitado = estudianteEncontrado
		}
	}
	if estudianteSolicitado.ID == -1 {
		enviarRespuestaComoUnMensaje(http.StatusNotFound, respuesta, mensajeDeRespuesta{Mensaje:
                "Registro no encontrado"})
	} else {
		enviarRepuesta(http.StatusOK, respuesta, estudianteSolicitado)
	}
}

 

 

  • Punto final para agregar estudiante

 

El propósito de este punto final es agregar estudiantes a la lista. Los datos requeridos deben ser proporcionados en el cuerpo del solicitud, en este caso el nombre y la edad. La respuesta es un objeto json que contiene la información del estudiante recién agregado (incluida la identificación del registro). Como ruta de este punto final usaremos /estudiante y es una solicitud POST.

 

“POST: /estudiante”

 

func manejarAgregarEstudiante(respuesta http.ResponseWriter, peticion *http.Request, _ httprouter.Params) {
	var nuevoEstudiante estudiante
	error := json.NewDecoder(peticion.Body).Decode(&nuevoEstudiante)
	if error != nil {
		if error == io.EOF {
			RepuestaDeSolicitudIncorrecta(respuesta, "Datos incompletos")
		} else {
			enviarErrorInternoDelServidor(respuesta)
		}
		return
	} else if nuevoEstudiante.Nombre == "" || nuevoEstudiante.Edad == 0 {
		RepuestaDeSolicitudIncorrecta(respuesta, "Datos perdidos")
		return
	}
	listaContieneDatos, estudianteEncontrado := listaContieneRegistro(nuevoEstudiante.Nombre, 
        nuevoEstudiante.Edad)
	if listaContieneDatos {
		enviarRepuesta(http.StatusOK, respuesta, estudianteEncontrado)
	} else {
		idContador++
		alumnoParaAgregar := estudiante{
			ID:     idContador,
			Nombre: nuevoEstudiante.Nombre,
			Edad:   nuevoEstudiante.Edad,
		}
		listaDeEstudiantes = append(listaDeEstudiantes, alumnoParaAgregar)
		enviarRepuesta(http.StatusOK, respuesta, alumnoParaAgregar)
	}
}

 

 

  • Punto final para eliminar estudiante

 

Este punto final es para eliminar estudiante de la lista. En este caso, solo se debe proporcionar la identificación del estudiante como parte del parámetro de solicitud. La ruta sera /estudiante y es una solicitud DELETE.

 

“DELETE: /estudiante”

 

func manejarEliminarEstudiante(respuesta http.ResponseWriter, peticion *http.Request, _ httprouter.Params) {
	parametro := peticion.URL.Query()
	if parametro == nil || len(parametro) == 0 || parametro["id"] == nil || parametro["id"][0] == "" {
		RepuestaDeSolicitudIncorrecta(respuesta, "Identificación invalida")
		return
	}
	identificacionDelEstudiante := parametro["id"][0]
	estudianteIDEnFormaEntero, err := strconv.Atoi(identificacionDelEstudiante)
	if err != nil {
		RepuestaDeSolicitudIncorrecta(respuesta, "Identificación invalida")
		return
	}
	for indice, studentFound := range listaDeEstudiantes {
		if studentFound.ID == estudianteIDEnFormaEntero {
			listaDeEstudiantes = append(listaDeEstudiantes[:indice], 
                                                    listaDeEstudiantes[indice+1:]...)
		}
	}
	enviarRespuestaComoUnMensaje(http.StatusOK, respuesta, mensajeDeRespuesta{Mensaje: "Estudiante 
        eliminado"})
}

 

 

  • Punto final para actualizar estudiante

 

Por último, el punto final destinado para actualizar la información de los estudiantes. En este caso, la identificación del estudiante debe proporcionarse como parte del parámetro de solicitud. La información del usuario recién actualizada formará parte del cuerpo de la solicitud. La ruta sera: /estudiante/actualizar y es una solicitud PATCH.

 

“PATCH: /estudiante/actualizar”

 

func manejarActualizacionEstudiante(respuesta http.ResponseWriter, peticion *http.Request, _ httprouter.Params) {
	parametro := peticion.URL.Query()
	if parametro == nil || len(parametro) == 0 || parametro["id"] == nil || parametro["id"][0] == "" {
		RepuestaDeSolicitudIncorrecta(respuesta, "Identificación invalida")
		return
	}
	identificacionDelEstudiante := parametro["id"][0]
	identificacionDelEstudianteEnEntero, err := strconv.Atoi(identificacionDelEstudiante)
	if err != nil {
		RepuestaDeSolicitudIncorrecta(respuesta, "Identificación invalida")
		return
	}
	listaContieneDatos := listaContieneRegistroPorID(identificacionDelEstudianteEnEntero)
	var estudianteActualizado estudiante
	errorDeAnalisis := json.NewDecoder(peticion.Body).Decode(&estudianteActualizado)
	if !listaContieneDatos {
		enviarRespuestaComoUnMensaje(http.StatusNotFound, respuesta, mensajeDeRespuesta{Mensaje: 
                "Registro no encontrado!"})
		return
	}
	if errorDeAnalisis != nil {
		if errorDeAnalisis == io.EOF {
			RepuestaDeSolicitudIncorrecta(respuesta, "Datos incompletos")
		} else {
			enviarErrorInternoDelServidor(respuesta)
		}
		return
	}
	for indice, estudianteEncontrado := range listaDeEstudiantes {
		if estudianteEncontrado.ID == identificacionDelEstudianteEnEntero {
			estudianteParaActualizar := &listaDeEstudiantes[indice]
			if estudianteParaActualizar.Nombre != "" {
				estudianteParaActualizar.Nombre = estudianteActualizado.Nombre
			}
			if estudianteParaActualizar.Edad != 0 {
				estudianteParaActualizar.Edad = estudianteActualizado.Edad
			}
			estudianteActualizado = *estudianteParaActualizar
		}
	}
	enviarRepuesta(http.StatusOK, respuesta, estudianteActualizado)
}

 

 

  • Crear función principal y instancia de enrutador 

 

Después de implementar todos los puntos finales, debe vincularlos al enrutador para comenzar a alojar y escuchar al solicitudes del clientes. Eso puede ser implementado a la siguiente forma en la función principal:

 

func main() {
	router := httprouter.New()
	router.GET("/estudiante/todos", manejarObtenerTodosLosEstudiantes)
	router.GET("/estudiante", manejarObtenerEstudiantes)
	router.PATCH("/estudiante/actualizar", manejarActualizacionEstudiante)
	router.POST("/estudiante", manejarAgregarEstudiante)
	router.DELETE("/estudiante", manejarEliminarEstudiante)
	errorDelServidor := http.ListenAndServe(":3000", router)
	if errorDelServidor != nil {
		log.Fatal("No se puede iniciar el servidor web, causa: ", errorDelServidor)
	}
}

 

 

Ejecute y pruebe el servicio web

 

Para probar esa aplicación de servicio web vamos a utilizar postman para realizar todas las solicitudes y demuestra las respuestas. Si aún no lo tiene instalado, descárguelo atraves de su sitio web oficial.

 

Para iniciar el servidor, debe configurar su GOPATH. En caso de que desee obtener más información sobre cómo configurar GOPATH, consulte estos artículos:

 

– Para usuarios de MACOS

 

– Para usuarios de Windows

 

– Para usuarios de Linux

 

Para iniciar el servidor, navegue hasta el proyecto y ejecute el siguiente comando:

 

go run servicio_web_efficiente_de_alto_rendimiento.go

 

En este ejemplo, el nombre de archivo Go es: servicio_web_efficiente_de_alto_rendimiento.go

 

  • Agregar estudiante

 

 

  • Obtener estudiante

 

 

  • Consigue todos los estudiantes

 

 

  • Actualizar estudiante

 

 

  • Eliminar estudiante

 

 

Visite nuestro repositorio de GitHub para el código fuente del proyecto y clone nuestro repositorio para todos los proyectos en el futuro.

 

 

Síguenos:

 

 

 






 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *