Так что я решил эту проблему немного по-другому. Мне действительно не понравилась идея отразить этот частный статический метод, чтобы сбросить кеш, потому что вы действительно не знаете, что вы делаете, делая это; вы в основном обходите инкапсуляцию, и это может вызвать непредвиденные проблемы. Но на самом деле, я был обеспокоен условиями гонки, когда я удаляю кеш, и прежде чем отправить запрос, появится какой-то другой поток и установит новый сеанс, чтобы мой первый поток случайно захватил этот сеанс. Плохие новости ... во всяком случае, вот что я сделал.
Я остановился, чтобы подумать о том, есть ли способ изолировать процесс, а затем мой коллега из Android отозвал доступность AppDomains. Мы оба согласились, что прядение одного из них должно позволить запустить вызов Tcp/Ssl, изолированный от всего остального. Это позволит логике кэширования оставаться неповрежденной, не вызывая конфликтов между сеансами SSL.
В принципе, я изначально написал свой SSL-клиент, чтобы быть внутренним в отдельной библиотеке.Затем в этой библиотеке у меня был публичный сервис как посредник/посредник для этого клиента. На прикладном уровне я хотел иметь возможность переключаться между службами (службы HSM, в моем случае) на основе аппаратного типа, поэтому я завернул это в адаптер и связал его с фабрикой. Хорошо, так как это важно? Ну, это просто упростило это приложение AppDomain чисто, не заставляя это поведение никому другому потребителю публичной службы (прокси/посредник, о котором я говорил). Вам не обязательно следовать этой абстракции, я просто хочу поделиться хорошими примерами абстракции всякий раз, когда я их нахожу :)
Теперь, в адаптере, вместо прямого вызова службы, я в основном создаю домен. Вот т е р:
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);
}
Все это делает создает именованный домен, из которого моя фактическая служба будет работать в изоляции. Теперь, большинство статей, которые я натолкнулся на то, как на самом деле выполнять в домене, упрощают работу. Примеры обычно включают вызов myDomain.DoCallback(() => ...);
, который не является ошибочным, но попытка получить данные в этом домене и из него, скорее всего, станет проблематичной, поскольку сериализация, скорее всего, остановит вас на ваших треках. Проще говоря, объекты, созданные за пределами DoCallback()
, не являются одинаковыми объектами при вызове изнутри DoCallback
, так как они были созданы за пределами этого домена (см. Маршаллирование объектов). Таким образом, вы, вероятно, получите всевозможные ошибки сериализации. Это не проблема при запуске всей операции, ввода и вывода, и все это может произойти изнутри myDomain.DoCallback()
, но это проблематично, если вам нужно использовать внешние параметры и вернуть что-то через этот AppDomain обратно в исходный домен.
Я столкнулся с другим шаблоном здесь, на котором я работал, и решил эту проблему. Посмотрите на _rklServiceRuntime =
в моем примере ctor. То, что это делает, - это попросить домен создать экземпляр объекта, чтобы вы выступали в качестве прокси-сервера из этого домена. Это позволит вам маршировать некоторые объекты в и из него. Вот мой 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;
}
}
}
Это наследует от MarshallByRefObject, которая позволяет ему пересекать границы AppDomain, и имеет один метод, который принимает ваши внешние параметры и выполняет свою логику внутри домена, который конкретизируется его.
Теперь вернемся к сервисному адаптеру: теперь все необходимые адаптеры услуг - это вызов _rklServiceRuntime.Run(...)
и передача необходимых, сериализуемых параметров. Теперь я просто создаю столько экземпляров адаптера службы, сколько мне нужно, и они все работают в своем собственном домене. Это работает для меня, потому что мои вызовы SSL небольшие и короткие, и эти запросы сделаны внутри внутренней веб-службы, где запросы на запросы, подобные этому, очень важны. Вот полный адаптер:
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);
}
}
Обратите внимание на способ утилизации. Не забывайте выгружать домен. Эта служба реализует IRklService, которая реализует IDisposable, поэтому, когда я ее использую, она используется с оператором using
.
Это кажется немного надуманным, но на самом деле это не так, и теперь логика будет выполняться в собственном домене изолированно, и поэтому логика кэширования остается неповрежденной, но не проблематичной. Гораздо лучше, чем вмешаться в SSLSessionCache!
Пожалуйста, простите любые несоответствия в именах, поскольку я быстро дезинформировал фактические имена после написания сообщения. Надеюсь, это поможет кому-то!
Или исправить SslClient Microsoft. – Alexis
@ Andrey: Насколько я понимаю исходный вопрос, исправление сервера не является вариантом: оно не под нашим контролем. – Vlad
@rufanov: Ну, вы добираетесь до большой пистолета.Разумеется, рефлексия должна работать. – Vlad