LA CLASSE BILANCIATOREAPPLICATIVO
import sqlite3
import base64
import requests
import os
import Coda
import random
from requests.exceptions import RequestException, HTTPError, Timeout, ConnectionError
class BilanciatoreApplicativo:
def __init__(self):
self.db_path = '../ProgettoBilanciatore/bilanciatore.db'
#self.setup_database()
self.db_connection = sqlite3.connect('../ProgettoBilanciatore/bilanciatore.db')
#istanza di self.Coda
self.Coda = Coda.Coda()
self.servers = ['PKBOX1=localhost:8443', 'PKBOX2=localhost:8442', 'PKBOX3=localhost:8441','PKBOX4=localhost:8444'] #IP dei middleware per l'invio dei documenti da firmare
def soap_call(self, endpoint, action, request_xml):
# Implementazione della chiamata SOAP (da completare)
headers = {
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": action # Alcuni servizi SOAP richiedono l'intestazione SOAPAction.
}
try:
response = requests.post(endpoint, data=request_xml, headers=headers)
response.raise_for_status() # Solleva un'eccezione per risposte HTTP non riuscite (es. 4xx, 5xx)
return response.text, True
except RequestException as e:
print(f"Errore nella chiamata SOAP: {e}")
return None, False
def is_server_active_soap(self, server):
#endpoint = f'https://{server}:8443/pkserver/servlet/defaulthandler'
endpoint = f'http://localhost:8443/getEnvironment'
action = 'getEnvironment'
request_xml = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://localhost:8443/">'+'<soapenv:Header/>'+'<soapenv:Body>'+'<soap:environment>default</soap:environment>'+'</soapenv:Body>'+'</soapenv:Envelope>'
response, success = self.soap_call(endpoint, action, request_xml)
return success
def rest_call(self, url, method,data=None):
""" Effettua una chiamata REST GET O POST e restituisce la risposta. """
try:
if method == 'get':
response = requests.get(url)
elif method == 'post':
response = requests.post(url,json=data)
else:
response = requests.get(url)
response.raise_for_status() # Solleva un'eccezione per risposte HTTP non riuscite (es. 4xx, 5xx)
return response.json(), True # Assumendo che la risposta sia in formato JSON
except HTTPError as http_err:
print(f"Errore HTTPError: {http_err} ")
return http_err, False
except ConnectionError as conn_err:
print(f"Connessione : {conn_err} ")
return conn_err, False
except Timeout as timeout_err:
print(f"Errore Timeout: {timeout_err} ")
return timeout_err, False
except requests.RequestException as e:
print(f"Errore nella chiamata REST: {e}")
return e, False
except Exception as e:
print(f"Gestione altre richieste non previste: {e}")
return e, False
def is_server_active(self, prefserver):
""" Verifica se il server è attivo tramite una chiamata REST. """
url = f'http://{prefserver}/getEnvironment' # URL per la chiamata REST
response, success = self.rest_call(url,'get')
return success # Il successo dipende dal risultato della chiamata REST
def find_zeros(self,lista):
count = 0
positions = []
for i, value in enumerate(lista):
if value == 0:
count += 1
positions.append(i)
return count, positions
def elemento_random_minimi(self,lista):
if not lista:
return None
# Trova il valore minimo guardando il primo elemento di ogni tupla
minimo = min(lista, key=lambda x: x[0])[0]
# Filtra per ottenere solo gli elementi con il valore minimo
elementi_minimi = [elemento for elemento in lista if elemento[0] == minimo]
# Seleziona un elemento casuale tra quelli minimi
elemento_casuale = random.choice(elementi_minimi)
print("Elemento casuale: ")
print(elemento_casuale)
print(elemento_casuale[0])
# Trova la posizione dell'elemento casuale nella lista originale
posizione = lista.index(elemento_casuale)
return posizione
def calculate_pref_server(self,stato):
# Implementazione semplificata
active_servers = [server.split('=')[1] for server in self.servers if self.is_server_active(server.split('=')[1])]
print(active_servers)
# Calcolare il carico di lavoro in termini di KB
lista = []
stato = ['I','R']
# Preparare i segnaposto SQL per la lista degli stati
# Genera una stringa del tipo '?,?,?...' per il numero di elementi in stati
segnaposto_stati = ','.join('?' * len(stato))
print('Segnaposto stati')
print(segnaposto_stati)
#ciclo sugli active_servers
for elemento in active_servers:
# Aggiungi un elemento alla lista (ad esempio, il quadrato dell'elemento corrente)
try:
result = self.Coda.getDoc_Size(segnaposto_stati,stato,elemento)
if not (result[0] == None and result[1] == None):
lista.append(result)
else:
lista.append((0,elemento))
# gestione degli errori
except Exception as e:
print(f"Errore durante la selezione dei server in coda: {e}")
print("restituisco il primo server...in ogni caso")
return active_servers[0] if active_servers else None #Se hai un errore nel calcolo del totale ritorna cmq il primo
print('Server scelto ',str(self.elemento_random_minimi(lista)))
#Restituisce il secondo valore all'interno del primo elemento della lista
print('Scrivi la lista')
print(lista)
return lista[self.elemento_random_minimi(lista)][1]
def send_document_to_pkbox_rest(self, prefserver, doc_base64, signer, pin):
url = f'http://{prefserver}/SendDocument' # URL per la chiamata REST
data = {
'doc_base64': doc_base64,
'signer': signer,
'pin' : pin
}
response, success = self.rest_call(url, 'post',data)
return success,response # Il successo dipende dal risultato della chiamata REST
def send_document_to_pkbox(self, pref_server, doc_base64, signer, pin):
endpoint = f'https://{pref_server}:8443/pkserver/services/Envelope.EnvelopeHttpSoap11Endpoint/'
request_xml = f'''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://soap.remote.pkserver.it">
<soapenv:Header/>
<soapenv:Body>
<soap:pdfsign3>
<!-- Altri parametri XML necessari -->
<soap:document>{doc_base64}</soap:document>
<soap:signer>{signer}</soap:signer>
<soap:pin>{pin}</soap:pin>
<!-- Altri parametri XML necessari -->
</soap:pdfsign3>
</soapenv:Body>
</soapenv:Envelope>
'''
response, success = self.soap_call(endpoint, 'pdfsign3', request_xml)
return success, response
def process_document(self, doc, firme):
pref_server = self.calculate_pref_server(['I'])
if not pref_server:
print("Nessun server attivo disponibile")
return False
if self.Coda.insert_queue(doc, pref_server,'I'): #lo stato Inserted permette di inserire quelli inseriti
doc_base64 = doc.read_stream()
for signer, pin in firme:
success, response = self.send_document_to_pkbox_rest(pref_server, doc_base64, signer, pin)
if not success:
print("Errore durante la firma del documento")
#cambiare lo stato del documento nella coda perchè il documento deve essere riprocessato
print('Il documento non è stato firmato e quindi cambio lo stato a R, ripetere')
self.Coda.update_queue(doc,pref_server,'I', 'R')
return False
#Passaggio allo stato successo del documento
print('Passaggio allo stato S di successo.....'+' '+str(pref_server)+' '+str(doc.documentID))
if not self.Coda.update_queue(doc,pref_server,'I', 'S'):
print('Non è andata a buon fine la procedura di update dl documento')
print('Riprova ad applicare lo stato S')
if not self.Coda.update_queue(doc,pref_server,'I', 'S'):
print('Errore imprevisto.....log')
return False
#Passaggio allo stato delete del documento
if self.Coda.delete_queue(doc,pref_server,'S'):
print("Documento gestito con successo, e applicazione dello stato a successo")
return True
else:
print("Errore durante la gestione del documento")
print('Cambio dello stato del documento a R')
#ritenta
if self.Coda.update_queue(doc,pref_server,'I', 'S'):
print('Documento gestito con successo e riprovo a salvare lo stato')
return False
else:
return True
else:
print("Errore nell'inserimento del documento in coda")
return False
def close(self):
self.db_connection.close()
Il codice fornito definisce la classe Python chiamata BilanciatoreApplicativo, la quale è progettata per gestire l’invio e la firma di documenti in un ambiente distribuito con più server “PKBOX”. Il codice utilizza SQLite per la gestione dei dati, e richieste HTTP REST per la comunicazione con i server per l’invio di documenti.
Nelle prime righe di codice vengono importati i moduli necessari come sqlite3, base64, requests, e os.
Poi viene definita la classe vera e propria del bilanciatore applicativo documentale. In questa, si definisce in primis, il metodo init che Inizializza la classe, impostando il percorso del database, configurando il database, stabilendo una connessione al database e definendo un elenco di server.
Il metodo rest_call esegue le chiamate REST (GET o POST) e restituisce la risposta.
Segue il metodo is_server_Active che verifica se il server passato come parametro è attivo attraverso una chiamata REST.
Il metodo calculate_pref_server determina quale server dello strato middleware (servers interni LAN del provider) è meno carico (in termini di documenti in attesa nello stato “I“) e lo sceglie per l’invio del documento all’applicativo che applicherà la firma.
Nel caso oggetto di simulazione, ho costruito diversi server con NodeJS che simulano i server middleware per le seguenti attività: invio del documento al provider, applicazione della firma (FEQ) e restituzione del documento firmato.
Uno dei metodi più importanti è sicuramente il metodo send_document_to_pkbox_rest che invia un documento a un server specificato (variabile “pref_server“) tramite una chiamata REST.
Infine, il metodo principale process_document processa il documento nel flusso, sceglie il server preferito (uno dei middleware), lo inserisce in una coda, invia il documento al server per la firma, aggiorna lo stato del documento e infine elimina il documento dalla coda.
Il metodo close chiude la connessione al database.