Progetto di un bilanciatore applicativo documentale

Progetto di un bilanciatore applicativo documentale

Marzo 12, 2024 0 Di Ruggero Grando

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.

Pagina Successiva | Pagina Precedente

No votes yet.
Please wait...

Pagine: 1 2 3 4 5 6 7 8 9 10 11