Quindi ho risolto questo problema in modo leggermente diverso. Non mi piaceva davvero l'idea di riflettere questo metodo statico privato per fare il dump della cache, perché non sai davvero in cosa ti stai cacciando; stai praticamente aggirando l'incapsulamento e questo potrebbe causare problemi imprevisti. In realtà, ero preoccupato per le condizioni di gara in cui eseguo il dump della cache e prima di inviare la richiesta, arriva un altro thread e stabilisce una nuova sessione, quindi il mio primo thread dirotta inavvertitamente quella sessione. Cattive notizie ... comunque, ecco cosa ho fatto.
Mi sono fermato a pensare se esistesse o meno un modo per isolare il processo e quindi un mio collaboratore Android ha ricordato la disponibilità di AppDomains. Entrambi abbiamo convenuto che girare uno su dovrebbe consentire alla chiamata Tcp/Ssl di funzionare, isolata da qualsiasi altra cosa. Ciò consentirebbe alla logica di memorizzazione nella cache di rimanere intatta senza causare conflitti tra le sessioni SSL.
Fondamentalmente, avevo originariamente scritto il mio client SSL per essere interno a una libreria separata.Quindi all'interno di quella biblioteca, ho avuto un servizio pubblico come proxy/mediatore per quel cliente. Nel livello dell'applicazione, volevo la possibilità di passare da un servizio all'altro (servizi HSM, nel mio caso) basato sul tipo di hardware, quindi l'ho racchiuso in un adattatore e interfacciato per essere utilizzato con una fabbrica. Ok, quindi quanto è rilevante? Bene, ha reso più semplice fare questa cosa AppDomain in modo pulito, senza forzare questo comportamento a nessun altro consumatore del servizio pubblico (il proxy/mediatore di cui parlavo). Non devi seguire questa astrazione, mi piace solo condividere buoni esempi di astrazione ogni volta che li trovo :)
Ora, nell'adattatore, invece di chiamare direttamente il servizio, in pratica creo il dominio. Ecco il ctor:
public VCRklServiceAdapter(
string hostname,
int port,
IHsmLogger logger)
{
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
ClientId = Guid.NewGuid();
_logger = logger;
_hostname = hostname;
_port = port;
// configure the domain
_instanceDomain = AppDomain.CreateDomain(
$"vcrypt_rkl_instance_{ClientId}",
null,
AppDomain.CurrentDomain.SetupInformation);
// using the configured domain, grab a command instance from which we can
// marshall in some data
_rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
typeof(VCServiceRuntime).Assembly.FullName,
typeof(VCServiceRuntime).FullName);
}
Tutto questo non fa altro che crea un nome di dominio da cui il mio servizio effettivo verrà eseguito in isolamento. Ora, la maggior parte degli articoli che ho trovato su come eseguire effettivamente all'interno del dominio semplificano eccessivamente il suo funzionamento. Gli esempi in genere coinvolgono chiamando myDomain.DoCallback(() => ...);
che non è errato, ma il tentativo di ottenere dati dentro e fuori da quel dominio sarà probabilmente problematico in quanto la serializzazione probabilmente ti fermerà nelle tue tracce. In parole povere, gli oggetti istanziati al di fuori di DoCallback()
non sono gli stessi oggetti quando vengono chiamati dall'interno di DoCallback
poiché sono stati creati al di fuori di questo dominio (vedere il marshalling degli oggetti). Quindi probabilmente avrai tutti i tipi di errori di serializzazione. Questo non è un problema se si esegue l'intera operazione, input e output e tutti possono verificarsi dall'interno di myDomain.DoCallback()
ma questo è problematico se è necessario utilizzare parametri esterni e restituire qualcosa attraverso questo AppDomain al dominio di origine.
Mi sono imbattuto in un modello diverso qui su SO che ha funzionato per me e risolto questo problema. Guarda _rklServiceRuntime =
nel mio campione. Ciò che sta facendo in realtà è chiedere al dominio di creare un'istanza di un oggetto affinché tu possa agire come proxy da quel dominio. Questo ti permetterà di piazzare alcuni oggetti dentro e fuori di esso. Qui è la mia implemenation di IRklServiceRuntime
:
public interface IRklServiceRuntime
{
RklResponse Run(RklRequest request, string hostname, int port, Guid clientId, IHsmLogger logger);
}
public class VCServiceRuntime : MarshalByRefObject, IRklServiceRuntime
{
public RklResponse Run(
RklRequest request,
string hostname,
int port,
Guid clientId,
IHsmLogger logger)
{
Ensure.IsNotNull(request, nameof(request));
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
// these are set here instead of passed in because they are not
// serializable
var clientCert = ApplicationValues.VCClientCertificate;
var clientCerts = new X509Certificate2Collection(clientCert);
using (var client = new VCServiceClient(hostname, port, clientCerts, clientId, logger))
{
var response = client.RetrieveDeviceKeys(request);
return response;
}
}
}
Questa eredita da MarshallByRefObject che le permette di attraversare i confini AppDomain, e ha un unico metodo che accetta i parametri esterni ed esegue la logica dall'interno del dominio che un'istanza di esso.
Quindi ora di nuovo all'adattatore di servizio: tutto ciò che gli adattatori di servizio devono fare ora è chiamare _rklServiceRuntime.Run(...)
e immettere i parametri serializzabili necessari. Ora, creo tutte le istanze dell'adattatore di servizio di cui ho bisogno e funzionano tutte nel proprio dominio. Questo funziona per me perché le mie chiamate SSL sono piccole e brevi e queste richieste sono fatte all'interno di un servizio web interno dove istanze come questa sono molto importanti. Ecco l'adattatore completo:
public class VCRklServiceAdapter : IRklService
{
private readonly string _hostname;
private readonly int _port;
private readonly IHsmLogger _logger;
private readonly AppDomain _instanceDomain;
private readonly IRklServiceRuntime _rklServiceRuntime;
public Guid ClientId { get; }
public VCRklServiceAdapter(
string hostname,
int port,
IHsmLogger logger)
{
Ensure.IsNotNullOrEmpty(hostname, nameof(hostname));
Ensure.IsNotDefault(port, nameof(port), failureMessage: $"It does not appear that the port number was actually set (port: {port})");
Ensure.IsNotNull(logger, nameof(logger));
ClientId = Guid.NewGuid();
_logger = logger;
_hostname = hostname;
_port = port;
// configure the domain
_instanceDomain = AppDomain.CreateDomain(
$"vc_rkl_instance_{ClientId}",
null,
AppDomain.CurrentDomain.SetupInformation);
// using the configured domain, grab a command instance from which we can
// marshall in some data
_rklServiceRuntime = (IRklServiceRuntime)_instanceDomain.CreateInstanceAndUnwrap(
typeof(VCServiceRuntime).Assembly.FullName,
typeof(VCServiceRuntime).FullName);
}
public RklResponse GetKeys(RklRequest rklRequest)
{
Ensure.IsNotNull(rklRequest, nameof(rklRequest));
var response = _rklServiceRuntime.Run(
rklRequest,
_hostname,
_port,
ClientId,
_logger);
return response;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
public void Dispose()
{
AppDomain.Unload(_instanceDomain);
}
}
Si noti il metodo di smaltimento. Non dimenticare di scaricare il dominio. Questo servizio implementa IRklService che implementa IDisposable, quindi quando lo uso, viene utilizzato con un'istruzione using
.
Questo sembra un po 'forzato, ma in realtà non lo è e ora la logica verrà eseguita sul proprio dominio, in isolamento, e quindi la logica di memorizzazione nella cache rimane intatta ma non problematica. Molto meglio di intromettersi con SSLSessionCache!
Si prega di perdonare qualsiasi incongruenza di denominazione come stavo disinfettando i nomi attuali rapidamente dopo aver scritto il post .. Spero che questo aiuti qualcuno!
Oppure correggere SslClient Microsoft. – Alexis
@Andrey: Come ho capito la domanda originale, la riparazione del server non è un'opzione: non è sotto il nostro controllo. – Vlad
@rufanov: Beh, stai cercando una grossa pistola.La riflessione deve funzionare, ovviamente. – Vlad