Проблема деактивации IdMappedPortTCP

У меня есть одна внешняя программа, которая не поддерживает прокси для доступа в Интернет, но мне нужен прокси.

В качестве решения я написал одно простое приложение Delphi, используя Indy 10.6.0.5040 и его компонент TIdMappedPortTCP. Как это работает просто, внешнее приложение подключается к IdMappedPortTCP локально, а IdMappedPortTCP подключается к реальному серверу, используя мои настройки прокси.

Чтобы выполнить настройку прокси, я обработал событие OnConnect для IdMappedPortTCP, как показано ниже:

procedure TForm1.IdMappedPortTCP1Connect(AContext: TIdContext);
var
  io: TIdIOHandlerStack;
  proxy: TIdConnectThroughHttpProxy;

begin
  if Assigned(TIdMappedPortContext(AContext).OutboundClient) then
   begin
    io := TIdIOHandlerStack.Create(TIdMappedPortContext(AContext).OutboundClient);

    proxy := TIdConnectThroughHttpProxy.Create(io);

    proxy.Enabled := False;
    proxy.Host := FSettings.ProxyAddress;
    proxy.Port := FSettings.ProxyPort;
    proxy.Username := FSettings.ProxyUserName;
    proxy.Password := FSettings.ProxyPassword;
    If (proxy.Username <> '') or (proxy.Password <> '') then proxy.AuthorizationRequired(True);
    proxy.Enabled := True;

    io.DefaultPort := FSettings.DestinationPort[0];
    io.Port := FSettings.DestinationPort[0];
    io.Destination := FSettings.DestinationHostAddress[0];
    io.Host := FSettings.DestinationHostAddress[0];
    io.TransparentProxy := proxy;
    io.OnStatus := StackStatus;

    TIdMappedPortContext(AContext).OutboundClient.IOHandler := io;
   end;

  Log(Format('Listener connected at %s:%d', [TIdMappedPortContext(AContext).Server.MappedHost, TIdMappedPortContext(AContext).Server.MappedPort]));
end;

{ TIdConnectThroughHttpProxyHelper }

procedure TIdConnectThroughHttpProxyHelper.AuthorizationRequired(const val: boolean);
begin
  Self.FAuthorizationRequired := val;
end;

procedure TForm1.Log(const s: string);
begin
  Memo1.Lines.Add(Format('(%s)  %s', [FormatDateTime('hh:nn:ss:zzz', Now), s]));
end;

procedure TForm1.IdMappedPortTCP1Disconnect(AContext: TIdContext);
begin
//  Log(Format('Listener disconnected at %s:%d', [TIdMappedPortContext(AContext).Server.MappedHost, TIdMappedPortContext(AContext).Server.MappedPort]));
end;

procedure TForm1.IdMappedPortTCP1Exception(AContext: TIdContext;
  AException: Exception);
begin
  Log(Format('Exception: %s (%s:%d)', [AException.Message,TIdMappedPortContext(AContext).Server.MappedHost, TIdMappedPortContext(AContext).Server.MappedPort]));
end;

procedure TForm1.IdMappedPortTCP1ListenException(AThread: TIdListenerThread;
  AException: Exception);
begin
  Log(Format('Listener Exception: %s', [AException.Message]));
end;

procedure TForm1.IdMappedPortTCP1OutboundConnect(AContext: TIdContext);
begin
  Log('MappedPort Destination connected.');
end;

procedure TForm1.IdMappedPortTCP1OutboundDisconnect(AContext: TIdContext);
begin
  Log('MappedPort Destination disconnected.');
end;

procedure TForm1.StackStatus(ASender: TObject;
  const AStatus: TIdStatus; const AStatusText: string);
begin
  Log(Format('Stack Status: %s', [AStatusText]));
end;

У меня много активных подключений и все работают без нареканий. Моя проблема в том, что если я попытаюсь деактивировать IdMappedPortTCP, используя "IdMappedPortTCP.Active:= false;" пока есть активные трафики, соединения, он там висит и мне пришлось завершать приложение delphi с помощью диспетчера задач.

Есть ли что-нибудь, что мне нужно сделать вручную, прежде чем установить для Active значение false?

Спасибо.


person Mehmet Fide    schedule 02.02.2014    source источник
comment
Не вызывайте AuthorizationRequired() вручную, пусть TIdConnectThroughHttpProxy сделает это. И вам не нужно устанавливать какие-либо свойства TIdIOHandlerStack, кроме TransparentProxy и OnStatus, пусть TIdTCPClient справится с этим.   -  person Remy Lebeau    schedule 02.02.2014
comment
Привет, Реми, FAuthorizationRequired прокси-сервера по умолчанию имеет значение false при его создании. Он всегда сначала пытается получить доступ к прокси-серверу без аутентификации, а затем понимает, что ему нужно использовать имя пользователя, пароль, и пытается снова. Чтобы пропустить первый шаг, я написал вспомогательную функцию, чтобы всегда аутентифицироваться. Событие OnStatus предназначено только для того, чтобы сообщить о статусе памятки (я добавил все функции в вопрос). Во время отладки я нашел кое-что, связанное с моей проблемой. Если я использую Memo1 в событии OnMappedPortDisconnect, моя программа зависает во время деактивации. Но если я прокомментирую эту строку, кажется, все идеально.   -  person Mehmet Fide    schedule 03.02.2014
comment
FAuthorizationRequired — это внутренняя переменная, не связывайтесь с ней. По умолчанию он всегда равен false, а затем временно устанавливается в true, если IOHandler необходимо закрыть и снова открыть, поскольку прокси-сервер отправил ответ 407 с заголовком ответа Connection: close или Proxy-Connection: close. Повторное открытие IOHandler приводит к повторному вызову MakeConnection(), когда он уже запущен, поэтому FAuthorizationRequired используется для обнаружения второго подключения, поэтому к новому запросу добавляется заголовок запроса Proxy-Authorization.   -  person Remy Lebeau    schedule 03.02.2014
comment
Отсрочка авторизации до второго подключения является преднамеренной. Не все прокси требуют авторизации, поэтому TIdConnectThroughHttpProxy не отправляет авторизацию, если только прокси явно не запрашивает ее. TIdHTTP работает так же.   -  person Remy Lebeau    schedule 03.02.2014
comment
Серверы Indy являются многопоточными. События OnConnect и OnDisconnect запускаются в контексте рабочего потока, а не основного потока пользовательского интерфейса. Таким образом, вы должны синхронизироваться с основным потоком, например, с методами TThread.Synchronize() или TThread.Queue() или классами Indy TIdSync или TIdNotify, чтобы безопасно получать доступ к компонентам пользовательского интерфейса. Если основной поток деактивирует сервер, асинхронный подход (TThread.Queue() или TIdNotify) предпочтительнее синхронного (TThread.Synchronize() или TIdSync), чтобы избежать взаимоблокировки.   -  person Remy Lebeau    schedule 03.02.2014
comment
Спасибо за объяснение Реми. Я могу принять ваш комментарий, если вы ответите на него.   -  person Mehmet Fide    schedule 04.02.2014


Ответы (1)


Серверы Indy являются многопоточными. Их события (например, OnConnect, OnDisconnect, OnExecute, OnException и OnListenException) запускаются в контексте рабочих потоков, а не в контексте основного потока пользовательского интерфейса. Таким образом, вы должны синхронизироваться с основным потоком, например, с методами TThread.Synchronize() или TThread.Queue() или классами Indy TIdSync или TIdNotify, чтобы безопасно получать доступ к компонентам пользовательского интерфейса.

Если основной поток занят деактивацией сервера, он не может обрабатывать запросы синхронизации, поэтому асинхронный подход (TThread.Queue() или TIdNotify) предпочтительнее синхронного (TThread.Synchronize() или TIdSync), чтобы избежать взаимоблокировки. В качестве альтернативы можно деактивировать сервер в рабочем потоке, чтобы основной поток мог свободно обрабатывать запросы синхронизации.

person Remy Lebeau    schedule 04.02.2014