Vì vậy, tôi đã giải quyết vấn đề này hơi khác một chút. Tôi thực sự không thích ý tưởng phản ánh phương thức tĩnh riêng tư này để kết xuất bộ nhớ cache vì bạn không thực sự biết những gì bạn đang thực hiện bằng cách làm như vậy; về cơ bản bạn đang phá vỡ đóng gói và có thể gây ra các vấn đề không lường trước được. Nhưng thực sự, tôi đã lo lắng về điều kiện chủng tộc nơi tôi đổ bộ nhớ cache và trước khi tôi gửi yêu cầu, một số chủ đề khác đi vào và thiết lập một phiên mới để sau đó luồng đầu tiên của tôi vô tình chiếm đoạt phiên đó. Tin xấu ... dù sao, đây là những gì tôi đã làm.
Tôi dừng lại để suy nghĩ về việc có hay không có cách nào để phân loại cô lập quy trình và sau đó một đồng nghiệp Android của tôi nhớ lại tính sẵn có của AppDomains. Cả hai chúng tôi đồng ý rằng việc quay vòng một lần sẽ cho phép cuộc gọi Tcp/Ssl chạy, bị cô lập khỏi mọi thứ khác. Điều này sẽ cho phép logic bộ nhớ đệm duy trì nguyên vẹn mà không gây xung đột giữa các phiên SSL.
Về cơ bản, ban đầu tôi đã viết ứng dụng khách SSL của mình vào nội bộ thư viện riêng.Sau đó, trong thư viện đó, tôi đã có một hành động dịch vụ công cộng như một proxy/hòa giải cho khách hàng đó. Trong lớp ứng dụng, tôi muốn khả năng chuyển đổi giữa các dịch vụ (các dịch vụ HSM, trong trường hợp của tôi) dựa trên loại phần cứng, vì vậy tôi đã bọc nó vào bộ điều hợp và giao tiếp với một nhà máy. Ok, vậy điều đó có liên quan như thế nào? Vâng nó chỉ làm cho nó dễ dàng hơn để làm điều này AppDomain sạch, mà không cần buộc hành vi này bất kỳ người tiêu dùng khác của dịch vụ công cộng (các proxy/trung gian tôi nói). Bạn không phải tuân theo sự trừu tượng này, tôi chỉ muốn chia sẻ những ví dụ trừu tượng tốt bất cứ khi nào tôi tìm thấy chúng :)
Bây giờ, trong bộ điều hợp, thay vì gọi trực tiếp dịch vụ, về cơ bản tôi tạo miền. Đây là 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);
}
Tất cả điều này tạo ra tên miền mà dịch vụ thực tế của tôi sẽ chạy riêng. Bây giờ, hầu hết các bài viết mà tôi đã xem xét về cách thực sự thực thi trong phạm vi tên miền đơn giản hóa cách thức hoạt động của nó. Các ví dụ thường liên quan đến việc gọi số myDomain.DoCallback(() => ...);
không sai, nhưng cố gắng lấy dữ liệu vào và ra khỏi miền đó có thể sẽ trở thành vấn đề vì việc tuần tự hóa có thể sẽ khiến bạn chết trong các tuyến đường của mình. Nói một cách đơn giản, các đối tượng được khởi tạo bên ngoài DoCallback()
không phải là các đối tượng giống nhau khi được gọi từ bên trong của DoCallback
vì chúng được tạo ra bên ngoài miền này (xem đối tượng trộn). Vì vậy, bạn có thể sẽ nhận được tất cả các loại lỗi serialization. Đây không phải là vấn đề nếu chạy toàn bộ hoạt động, đầu vào và đầu ra và tất cả có thể xảy ra từ bên trong myDomain.DoCallback()
nhưng đây là vấn đề nếu bạn cần sử dụng các tham số bên ngoài và trả về một cái gì đó trên AppDomain này về miền gốc.
Tôi đã xem qua một mô hình khác ở đây trên SO đã làm việc cho tôi và giải quyết vấn đề này. Hãy xem _rklServiceRuntime =
trong ctor mẫu của tôi. Việc này đang thực sự yêu cầu miền khởi tạo một đối tượng để bạn hoạt động như một proxy từ miền đó. Điều này sẽ cho phép bạn marshall một số đối tượng trong và ngoài của nó. Dưới đây là implemenation của tôi về 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;
}
}
}
này kế thừa từ MarshallByRefObject cho phép nó vượt qua ranh giới AppDomain, và có một phương pháp duy nhất mà sẽ đưa các thông số bên ngoài của bạn và thực hiện logic của bạn từ bên trong lĩnh vực mà khởi tạo nó.
Vì vậy, bây giờ trở lại bộ điều hợp dịch vụ: Tất cả các bộ điều hợp dịch vụ phải làm bây giờ là gọi _rklServiceRuntime.Run(...)
và nạp thông số cần thiết, nối tiếp. Bây giờ, tôi chỉ tạo ra nhiều phiên bản của bộ điều hợp dịch vụ như tôi cần và tất cả chúng đều chạy trong miền riêng của chúng. Điều này làm việc cho tôi bởi vì các cuộc gọi SSL của tôi nhỏ và ngắn gọn và những yêu cầu này được thực hiện bên trong một dịch vụ web nội bộ, nơi yêu cầu instancing như thế này là rất quan trọng. Dưới đây là bộ điều hợp hoàn chỉnh:
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);
}
}
Lưu ý phương pháp vứt bỏ. Đừng quên dỡ bỏ tên miền. Dịch vụ này triển khai IRklService thực hiện IDisposable, vì vậy khi tôi sử dụng nó, nó được sử dụng với câu lệnh using
.
Điều này có vẻ hơi khó, nhưng nó thực sự không và bây giờ logic sẽ được chạy trên miền riêng của nó, trong sự cô lập, và do đó logic bộ nhớ đệm vẫn còn nguyên vẹn nhưng không có vấn đề. Tốt hơn nhiều so với việc can thiệp vào SSLSessionCache!
Hãy tha thứ cho bất kỳ sự mâu thuẫn nào khi đặt tên thật nhanh chóng sau khi viết bài. Tôi hy vọng điều này sẽ giúp một người!
Hoặc khắc phục SslClient Microsoft. – Alexis
@Andrey: Khi tôi hiểu câu hỏi ban đầu, việc sửa chữa máy chủ không phải là một lựa chọn: nó không nằm trong tầm kiểm soát của chúng tôi. – Vlad
@rufanov: Vâng, bạn đang vươn tới một khẩu súng lớn.Dĩ nhiên, sự phản chiếu phải làm việc. – Vlad