Contenido
La estructura para poder llevar la información del cliente al servidor, es la siguiente llamada Usuario
message Usuario {
string name = 1;
string location = 2;
string gender = 3;
int64 age = 4;
string vaccine_type = 5;
}
Por otro lado, se encuentra dos estructuras que son las que trasmiten el mensaje de cliente a servidor (userRequest
) y viceversa (userResponse
)
userRequest
message userRequest {
Usuario user = 1;
}
userResponse
message userResponse {
string resultado = 1;
}
Por último se define el servicio rpc el cuál hace posible que se registre un nuevo usuario y de la posibilidad de que el servidor responda.
service userService {
rpc regUser(userRequest) returns (userResponse) {};
}
Estas estructuras de mensajería y el servicio de rpc se definen en un mismo archivo user.proto
, seguido este archivo se compila con el comando
protoc --go_out=. --go-grpc_out=. user.proto
Al momento de compilar, se genera un archivo user.pb.go
y otro user_grpc.pb.go
el cuál genera el compilador escrito en lenguaje Go que utiliza propiamente grpc para el servicio de mensajería.
Árbol de archivos de Protocol Buffers
Este mismo procedimiento se realizó tanto para el cliente como para el servidor, los cuáles se detallan a continuación.
Primero se crea el módulo del servidor llamado grpcserver
en go con el comando go mod init grpcserver
, seguido de eso se procede a definir los Protocol Buffers.
Luego se crea el servidor.go
de la siguiente manera:
- Una función
RegUser
donde se va a definir la acción de registrar un usuario para mandar a la API de NodeJS, esta recibe como parámetro el mensaje tipoUserRequest
definido en los Protocol Buffers que manda el cliente (definido más adelante) y retorna el mensaje tipoUserResponse
func (*servidor) RegUser(ctx context.Context, req *user_pb.UserRequest) (*user_pb.UserResponse, error)
- En la función
RegUser
se convierte el mensaje tipoUserRequest
a formato JSON para que sea enviado a NodeJS
cuerpoPeticion, _ := json.Marshal(usuario{
Name: req.User.Name,
Location: req.User.Location,
Gender: req.User.Gender,
Age: int(req.User.Age),
VaccineType: req.User.VaccineType,
Way: "GRPC",
})
- Se envía el
cuerpoPeticion
a la URL del endpoint (http://35.222.55.115:8080/nuevoRegistro
) encargado de almacenar un nuevo registro a la base de datos de Mongo
pet := bytes.NewBuffer(cuerpoPeticion)
resp, err := http.Post("http://35.222.55.115:8080/nuevoRegistro", "application/json", pet)
if err != nil {
log.Fatalln("Error al registrar nuevo: ", err)
}
- Se recupera la respuesta de la petición HTTP para convertirla a mensaje tipo
UserResponse
y retornarla al cliente
cuerpo, err := ioutil.ReadAll(resp.Body)
if err != nil{
log.Fatalln(err)
}
result := &user_pb.UserResponse{
Resultado: string(cuerpo),
}
return result, nil
- Finalmente, se define otra función
main()
encargada de iniciar en el puerto de preferencia, comenzar como servidor de GRPC y de llamar al serviciouserService
definido enuser.proto
el cual llama a la función definida anteriormenteRegUser
s := grpc.NewServer()
user_pb.RegisterUserServiceServer(s, &servidor{})
fmt.Println("Servidor a la espera ...")
if err := s.Serve(lis); err != nil {
log.Fatalf("El servidor no funciona: %v", err)
}
Primero se crea el módulo del servidor llamado grpccliente
en go con el comando go mod init grpccliente
, seguido de eso se procede a definir los Protocol Buffers.
Luego se crea el archivo cliente.go
de la siguiente manera:
- Primero se define una función
registrarUsuario
la cuál se encargará de tener comunicación directa con el servidor y recibe como parámetros la información que se define en la funciónhttp_server
.
func registrarUsuario(nameparam string, locationparam string, ageparam int64, infectedtypeparam string, stateparam string)
- En ella se llama al host del servidor y se inicializa el servicio como cliente
cc, err := grpc.Dial(server_host, grpc.WithInsecure())
if err != nil {
log.Fatalf("Error enviando peticion: %v", err)
}
defer cc.Close()
c := user_pb.NewUserServiceClient(cc)
- Seguido se crea el mensaje tipo
UserRequest
con los parámetros que recibe la funciónregistrarUsuario
para que la petición pueda ser enviada al servidor.
request := &user_pb.UserRequest{
User: &user_pb.Usuario{
Name: nameparam,
Location: locationparam,
Gender: genderparam,
Age: ageparam,
VaccineType: vaccinetypeparam,
},
}
fmt.Println("Enviando datos al servidor")
res, err := c.RegUser(context.Background(), request)
- Esta es una función que va a levantar un servicio HTTP y se va a encargar de responder y recibir peticiones de los datos que se envíen con Locust. Ésta recibe como parámetros un
http.ResponseWriter
que es quien va a responder a las peticiones y unhttp.Request
que es todo lo que trae de petición.
func http_server(w http.ResponseWriter, r *http.Request)
- Se hace un
switch
para saber el tipo de petición que se le hace al cliente de grpc
switch r.Method {
case "GET":
fmt.Println("Raiz de HTTP para cliente")
case "POST":
fmt.Println("Iniciando envio de mensajes")
default:
fmt.Fprintf(w, "Metodo %s no soportado \n", r.Method)
return
}
- En el caso de ser
GET
, se envía de regreso un estado 202
http.StatusText(202)
- En el caso de ser
POST
se decodifica el JSON de entrada y se envía el mensaje a la funciónregistrarUsuario
decoder := json.NewDecoder(r.Body)
var us userStruct
err := decoder.Decode(&us)
//enviar el mensaje
registrarUsuario(us.Name, us.Location, us.Age, us.Infectedtype, us.State)
NOTA: para decodificar el JSON, se utilizó una estructura para manejar los atributos del JSON:
type userStruct struct {
Name string
Location string
Gender string
Age int64
VaccineType string
}
- En este método principal, se va a levantar el servidor HTTP. Primero definiendo la función con la que se van a manejar las peticiones, siendo ésta la función
http_server
http.HandleFunc("/", http_server)
- Y finalmente se levanta el servicio HTTP en host del cliente de grpc para que quede a la espera de ser utilizado con el balanceador de carga
if err := http.ListenAndServe(client_host, nil); err != nil {
log.Fatal(err)
}
Para levantar los dos servicios (cliente y servidor) de GRPC, se colocó en un container de Docker y se conectó en una misma red para que pudieran tener comunicación entre ambas partes. Para cada servicio, se creó un archivo Dockerfile detallado a continuación.
- Servidor
Primero se especifica que el lenguaje de programación es GoLang. Se especifica que todo el directorio necesario para el contenedor, es en la raíz de dónde se encuente el archivo Dockerfile. Se copia de la raíz de donde esté el Dockerfile a la raíz del contenedor. Se define variables de entorno, en este caso únicamente el HOST dónde va a servir. Corre el comando go mod download
para que descargue todo lo necesario en el módulo grpcserver
de Go. Se expone el puerto 8081, el donde va a estar corriendo el servidor. Finalmente, ejecuta el comando go run servidor.go
dentro del contenedor para que levante el servidor de GRPC.
FROM golang
WORKDIR /
COPY . .
ENV HOST=0.0.0.0:8081
RUN go mod download
EXPOSE 8081
CMD ["go", "run", "servidor.go"]
- Cliente
Tal y como se definió en el Servidor, se especifica el lenguaje GO, el directorio raíz, la copia de archivos de raíz a raíz de contenedor, se descarga lo que necesite el módulo grpccliente
, se expone el puerto 8080 donde va a servir el cliente y por último se levanta el cliente con go run cliente.go
FROM golang
WORKDIR /
COPY . .
RUN go mod download
EXPOSE 8080
CMD ["go", "run", "cliente.go"]
Para levantar ambos contenedores definidos anteriormente, se levanta con docker-compose
y en un archivo tipo yml
, tal y como se detalla a continuación
version: "2.2"
services:
grpcserver:
container_name: servidor
restart: always
build: ./servidor
ports:
- "8081:8081"
networks:
- grpc
grpcclient:
container_name: cliente
restart: always
environment:
- CLIENT_HOST=:8080
- SERVER_HOST=grpcserver:8081
- NAME=grpcInstancia
build: ./cliente
ports:
- "8080:8080"
networks:
- grpc
networks:
grpc:
driver: "bridge"
En este archivo, se define el nombre para ambos contenedores, los puertos que se van a exponer, en qué red van a estar conectados y la ruta a buscar para levantar los contenedores. Así como especificar el nombre y tipo de red para conectarse entre sí.