Создание бизнес-приложений с помощью Silverlight - Междоменные политики с помощью служб TCP

ОГЛАВЛЕНИЕ

Междоменные политики с помощью служб TCP

Любой доступ службы TCP в Silverlight считается междоменным запросом, и серверу необходимо применить TCP-слушатель на том же IP-адресе, который привязан к порту 943. Сервер политик, показанный на рис. 3, является слушателем, примененным для данной цели. Этот сервер реализует процесс запроса/ответа для потоковой выдачи декларативных политик, которые необходимы среде выполнения Silverlight, перед тем как позволить сетевому стеку на клиенте подключиться к серверу всплывающих окон (диспетчеру звонков на рис. 3).

Для простоты я размещу сервер диспетчера звонков в приложении консоли. Это приложение консоли можно легко преобразовать в службу Windows для реальных реализаций. Рис. 3 показывает типичное взаимодействие с сервером политики; среда выполнения Silverlight может подключиться к серверу на порте 943 и отправить запрос политики, который будет содержать единственную строку текста: "<policy-file-request/>".

Политики на основе XML делают возможным сценарий, показанный на рис. 3. Раздел ресурсов сокета может указать группу портов внутри допустимого диапазона от 4502 до 4534. Причина ограничения их этим диапазоном заключается в минимизации направления, открытого для атаки, что снижает риск случайных слабостей в конфигурации брандмауэра. Поскольку сервер центра обработки звонков (диспетчер звонков с рис. 1) слушает на порте под номером 4530, ресурс сокета настроен следующим образом:

<access-policy>
  <policy>
  <allow-from> list of URIs</allow-from>
  <grant-to> <socket-resource port="4530" protocol="tcp"/></grant-to>
  </policy>  
</access-policy>

Также можно настроить <socket-resource> на допуск всех дозволенных номеров портов, указав port="4502–4534".

Чтобы сэкономить время, я переделаю код из сервера диспетчера звонков под новую задачу при реализации сервера политики. Клиент Silverlight подключается к серверу политики, отправляет запрос и читает ответ. Сервер политики закрывает подключение после того, как ответ политики успешно отослан. Содержимое политики читается сервером политики из локального файла, clientaccesspolicy.xml, который входит в загружаемые приложения к статье.

Реализация слушателя TCP для сервера политик показана на рис. 15. Она использует тот же асинхронный шаблон цикла, о котором рассказывалось ранее, применительно к TCP Accept. Clientaccesspolicy.xml считывается в буфер и повторно используется для отправки всем клиентам Silverlight. ClientConnection инкапсулирует допущенный сокет и буфер получения, который будет связан с SocketAsyncEventArgs.

Рис. 15. Реализация сервера политик TCP

class TcpPolicyServer
{
  private Socket _listener;
  private byte[] _policyBuffer;
  public static readonly string PolicyFileName = "clientaccesspolicy.xml";
  SocketAsyncEventArgs _socketAcceptArgs = new SocketAsyncEventArgs();
  public TcpPolicyServer()
  {
  //read the policy file into the buffer
  FileStream fs = new FileStream(PolicyServer.PolicyFileName, 
  FileMode.Open);
  _policyBuffer = new byte[fs.Length];
  fs.Read(_policyBuffer, 0, _policyBuffer.Length);
  _socketAcceptArgs.Completed += new 
  EventHandler<SocketAsyncEventArgs>(AcceptAsyncCallback);

  }
  public void Start(int port)
  {

  IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
  //Should be within the port range of 4502-4532
  IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);

  _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
  ProtocolType.Tcp);

  // Bind the socket to the local endpoint and listen for incoming connections 
  try
  {
  _listener.Bind(ipEndPoint);
  _listener.Listen(50);
  AcceptAsync();
  }
  //code omitted for brevity

  }
  void AcceptAsync()
  {
  AcceptAsync(socketAcceptArgs);
  }

  void AcceptAsync(SocketAsyncEventArgs socketAcceptArgs)
  {
  if (!_listener.AcceptAsync(socketAcceptArgs))
  {
  AcceptAsyncCallback(socketAcceptArgs.AcceptSocket, 
  socketAcceptArgs);
  }
  }

  void AcceptAsyncCallback(object sender, SocketAsyncEventArgs e)
  {
  if (e.SocketError == SocketError.Success)
  {
  ClientConnection con = new ClientConnection(e.AcceptSocket, 
  this._policyBuffer);
  con.ReceiveAsync();
  }
  //the following is necessary for the reuse of _socketAccpetArgs
  e.AcceptSocket = null;
  //schedule a new accept request
  AcceptAsync();
  }
}

Пример кода, показанный на рис. 15, повторно использует SocketAsyncEventArgs между несколькими допусками TCP. Чтобы это работало, e.AcceptSocket необходимо установить на null в AcceptAsyncCallback. Этот подход предотвратит замешивание в сбор мусора на сервере с высокими требованиями по масштабируемости.