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 esNone
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'}