Cómo crear un cliente - UltraTesla/UTesla GitHub Wiki

No solo crearlo, también serlo, ya que necesitaremos comunicarnos con el servidor para poder hacer uso de sus servicios. La creación es un proceso relativamente sencillo.

simple_client()

simple_client() es una corutina que permite configurar rápidamente todos los parámetros necesarios, por lo que nos limitaríamos a configurar, conectar y usar los servicios que el servidor disponga.

Para usar esta corutina tenemos que importar el siguiente módulo:

from modules.Infrastructure import client

Nota: Se asume que está en la carpeta de UTesla.

Esta corutina tiene los siguientes parámetros que tendremos que rellenar:

host (str)

host es una cadena que indica la dirección del servidor. P. ej.: localhost

port (int)

port es un entero indicando el puerto del servidor. P. ej: 17000

user (str)

user es una cadena indicando el nombre de usuario registrado en el servidor a conectar. P. ej: utesla

server_key (bytes)

La clave de verificación del servidor.

timewait (Optional[int]) : None

El tiempo a esperar en caso de una falla al tratar de conectar. Por defecto tiene el valor None ajustado, y cuando es así se lee el archivo de configuración config/UTesla.ini para extraer el valor de la clave connect_timewait de la sección Client pero este parámetro acepta un entero.

retry (Optional[int]) : None

El número total de intentos en caso de una falla al tratar de conectar. Por defecto tiene el valor None ajustado, y cuando es así se lee el archivo de configuración config/UTesla.ini para extraer el valor de la clave connect_retry de la sección Client pero este parámetro acepta un entero.

public_key (Optional[bytes]) : None

La clave de verificación del usuario. Por defecto tiene el valor None ajustado, y cuando es así se lee el archivo de configuración config/UTesla.ini para extraer el valor de la clave pub_key de la sección Server.

private_key (Optional[bytes]) : None

La clave de firmado del usuario. Por defecto tiene el valor None ajustado, y cuando es así se lee el archivo de configuración config/UTesla.ini para extraer el valor de la clave priv_key de la sección Server.

max_buffer_size (Optional[int]) : None

El tamaño máximo en bytes que el cliente puede recibir y el tamaño máximo del almacenamiento en memoria. Por defecto tiene el valor None ajustado, y cuando es así se lee el archivo de configuración config/UTesla.ini para extraer el valor de la clave max_buffer_size de la sección Client pero este parámetro acepta un entero.

uteslaclient (Optional[Union[object, "tornado.tcpclient.TCPClient"]]) : None

Una instancia de la clave tornado.tcpclient.TCPClient o un derivado de éste. Es usado para conectar al servidor de destino. Por defecto tiene el valor None ajustado, y cuando es así se usa el objeto de la clase UTeslaClient().

Cuando se usa una clase diferente a UTeslaClient() el valor de los parámetros timewait y retry son ignorados.

params (Dict[str, Any]) : {}

Parámetros que serán incluidos en la corutina .connect() del objeto propuesto por el valor del parámetro uteslaclient.

*args y **kwargs

Argumentos y argumentos clave variables que son usados en la clase UTeslaStreamControl().

Retorno

Un objeto de la clase UTeslaStreamControl, el objeto del valor del parámetro uteslaclient y el resultado de la corutina connect() en tornado.tcpclient.TCPClient() o un derivado de éste.

UTeslaStreamControl()

Esta clase controla el flujo de datos que se intercambian entre el cliente y el servidor, además de que encarga del protocolo de UTesla: ProtoTesla; eso incluye el intercambio de claves x25519, el cifrado/descifrado y la facilitación en la comunicación.

Esta clase define los siguientes métodos:

__init__

Un método dunder para iniciar los datos y recibe los siguientes parámetros:

user (str)

user es una cadena indicando el nombre de usuario registrado en el servidor a conectar. P. ej: utesla

public_key_length (int) : options.PUBLIC_KEY_LENGTH

El tamaño máximo de la clave pública.

headers_length (int) : options.HEADERS_LENGTH

El tamaño máximo de los encabezados.

memory_limit (int) : options.MEMORY_LIMIT

El tamaño máximo del cuerpo de la petición.

set_server_key

Ajusta la clave de verificación del servidor.

Recibe un argumento y debe ser la clave de verificación del servidor.

with open("keys/ed25519.pub", "rb") as fd:
    <Nombre de la instancia>.set_server_key(fd.read(32)) # 32 bytes es lo que pesa una clave

set_force

Ajusta el forzamiento de un nodo específico de la red. Es relativo a la infraestructura del servidor de destino.

No recibe argumentos; en vez de eso, lo que se hace es simplemente ejecutar una y otra vez este método para cambiar el valor, que puede ser True o False.

Por ejemplo:

<Nombre de la instancia>.set_force() # True
<Nombre de la instancia>.set_force() # False
<Nombre de la instancia>.set_force() # True

set_node

Ajusta el nodo a conectar. Este método va a la par con el método anterior (set_force()). Si force es False no se toma en cuenta, pero si es True sí.

Recibe dos argumentos; el primero es una cadena que indica la dirección del nodo y el segundo es el puerto del nodo.

Ejemplo:

<Nombre de la instancia>.set_node("10.42.0.1", 17000)

del_node

Borra el nodo ajustrado.

Ejemplo:

<Nombre de la instancia>.del_node()

set_packed

Indicarle al servidor si usar o no msgpack. msgpack es una forma rápida y compacta de intercambiar datos entre computadores; en UTesla es usado para intercambiar cualquier tipo de dato típico, como un booleano, un entero, un entero con coma flotante, un diccionario, una lista, etc.

Sin embargo, msgpack no es recomendable cuando se transfieren grandes cantidades de datos que vayan a ser almacenados o no se necesite un tipo de dato específico, lo cual sería una perdida de rendimiento a tener en cuenta.

No recibe argumentos; en vez de eso, lo que se hace es simplemente ejecutar una y otra vez este método para cambiar el valor, que puede ser True o False.

<Nombre de la instancia>.set_packed() # False
<Nombre de la instancia>.set_packed() # True
<Nombre de la instancia>.set_packed() # False

set_init_parameter

Recibe dos parámetros, uno es la clave y el otro el valor de ésta. Este método es usado para enviar parámetros al iniciar un servicio, o más especificamente será usado en el método dunder __init__ del servicio.

<Nombre de la instancia>.set_init_parameter("clave", "valor")

del_init_parameter

Borra un parámetro inicial definido.

<Nombre de la instancia>.del_init_parameter("clave")

add_init_parameter

Parecido a set_init_parameter pero en vez de reemplazar el valor de la clave lo agrega a una lista.

<Nombre de la instancia>.add_init_parameter("clave", "Valor 1")
<Nombre de la instancia>.add_init_parameter("clave", "Valor 2")
<Nombre de la instancia>.add_init_parameter("clave", "Valor 3")
<Nombre de la instancia>.get_init_parameter("clave") # ["Valor 1", "Valor 2", "Valor 3"]

get_init_parameter

Obtiene el valor de una clave en los parámetros iniciales.

set_parameter, del_parameter, add_parameter y get_parameter

Igual a los anteriores pero serán usados en la acción que se ejecutará en el servicio.

Los argumentos y claves variables (*args, **kwargs) de set_init_parameter, add_init_parameter, set_parameter y add_parameter aceptan:

  • type: Por defecto es None. Usado para indicar el tipo de dato que se espera. Cuando es None se espera que sea cualquiera.
  • index: Cuando type sea una lista o una tupla se puede indicar un índice del tipo de dato a usar.
  • convert: Convertir el tipo de dato (en caso que no sea el tipo de dato esperado) o generar una excepción.

set_header, del_header, add_header, get_header

Sigue la misma sintaxis que los anteriores pero es para la manipulación de los encabezados.

set_status

Ajusta el estado.

set_status_code

Ajusta el código de estado

get_status

Obtiene el estado.

get_status_code

Obtiene el código de estado.

write_status

Escribe o envía datos y a la misma vez ajusta el estado y el código de estado, y con un argumento adicional, los datos a mandar.

await <Nombre de la instancia>.write_stattus(0, "Estado", "Datos a enviar")

set_path

Ajusta el nombre del servicio a usar y la acción.

Ejemplo:

<Nombre de la instancia>.set_path("/calculator", "sum")

get_path

Obtiene el nombre del servicio y la accion definidas en una tupla.

set_token

Ajusta el token de acceso.

get_token

Obtiene el token de acceso definido.

write

Escribe o envía datos al servicio del servidor ya conectado.

Recibe varios argumentos, entre ellos están:

data (str)

Los datos a enviar.

headers (dict)

Los encabezados a enviar. Por defecto tiene el valor None ajustado, y cuando es así se usan los que están configurados por el atributo .headers.

is_packed (bool)

Esta es la variante para el cliente de set_packed() para el servidor.

read

Lee o recibe datos además de configurar los encabezados.

Recibe varios argumentos, entre ellos están:

timeout (Optional[int]) : None

El tiempo a esperar para recibir datos. Si no se recibe dentro de lo propuesto, se pasa desconectar al cliente. Si es None o es 0 o menor que éste, el tiempo de espera será indefinido.

is_packed (bool)

Esta es la variante para el cliente de set_packed() para el servidor.

Propiedades

También se definen algunas propiedades interesantes que pueden ser a la misma vez muy útiles:

headers (dict)

Un diccionario con todos los encabezados definidos.

init_params (dict)

Todos los parámetros iniciales definidos.

params

Todos los parámetros definidos.

request.headers (dict)

Un diccionario con todos los encabezados definidos por parte del servidor.

Creación

Ya hemos presenciado los parámetros a usar en la corutina simple_client(), he aquí un ejemplo:

import asyncio
import pprint
import tornado.tcpclient

from modules.Infrastructure import client

async def main():
    (host, port) = "localhost", 17000
    user = "administrador"
    server_key = "keys/ed25519.pub"
    public_key, private_key = "/tmp/pubkey", "/tmp/privkey"

    with open(server_key, "rb") as fd:
        server_key = fd.read()

    with open(public_key, "rb") as fd:
        public_key = fd.read()

    with open(private_key, "rb") as fd:
        private_key = fd.read()

    (UControl, _, stream) = await client.simple_client(
        host,
        port,
        user,
        server_key,
        public_key = public_key,
        private_key = private_key,
        uteslaclient = tornado.tcpclient.TCPClient()

    )

    UControl.set_token("b77c93e122153a542a32a49eb5da85c22eeb4d80fb41b35bab396273db3da7d4")
    UControl.set_path("/index", "get")

    await UControl.write(None)
    print(await UControl.read())

    print()
    print("Encabezados del servidor:")
    pprint.pprint(UControl.request.headers)
    
    print()
    print("Encabezados del cliente:")
    pprint.pprint(UControl.headers)

    stream.close()

if __name__ == "__main__":
    asyncio.run(main())

Se obtendría:

¡Hola Mundo!

Encabezados del servidor:
{'action': 'get',
 'force': False,
 'init_params': {},
 'is_packed': True,
 'limit': 104857600,
 'node': (),
 'params': {},
 'path': '/index',
 'status': '',
 'status_code': 0,
 'token': '',
 'version': '2.0.1'}

Encabezados del cliente:
{'action': 'get',
 'init_params': {},
 'params': {},
 'path': '/index',
 'status': '',
 'status_code': 0,
 'token': 'b77c93e122153a542a32a49eb5da85c22eeb4d80fb41b35bab396273db3da7d4'}