Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Nov 14 18:08
    wqmeng commented #201
  • Nov 14 18:04
    arvanus commented #201
  • Nov 14 17:43
    wqmeng commented #201
  • Nov 14 17:37
    arvanus commented #201
  • Nov 14 17:31
    wqmeng commented #201
  • Nov 14 12:00
    arvanus commented #201
  • Nov 14 09:07
    wqmeng commented #201
  • Nov 14 09:05
    wqmeng commented #201
  • Oct 30 16:15
    rlebeau edited #260
  • Oct 16 04:22
    rlebeau labeled #269
  • Oct 16 04:22
    rlebeau opened #269
  • Oct 08 19:00

    Fulgan on Restructure

    Bug fix for a typo in TIdIMAP4.… (compare)

  • Oct 08 19:00

    Fulgan on master

    Bug fix for a typo in TIdIMAP4.… (compare)

  • Oct 02 21:00

    Fulgan on Restructure

    Updating TIdIMAP4's InternalSea… (compare)

  • Oct 02 21:00

    Fulgan on master

    Updating TIdIMAP4's InternalSea… (compare)

  • Sep 20 21:50

    Fulgan on master

    Embarcadero patch for race cond… (compare)

  • Sep 20 21:50

    Fulgan on Restructure

    Embarcadero patch for race cond… (compare)

  • Sep 10 18:50
    rlebeau closed #268
  • Sep 10 18:50
    rlebeau commented #268
  • Sep 10 18:50

    Fulgan on Restructure

    Fix for TIdResponseHeaderInfo.S… (compare)

Remy Lebeau
@rlebeau
@elekgeek If you are just trying to implement an echoing server with logging, I would suggest using this instead:
var
  RxBufStr: string;
begin
  with AContext.Connection.IOHandler do
  begin
    CheckForDataOnSource(10);
    CheckForDisconnect; // <-- add this
    if not InputBufferIsEmpty then
    begin
      RxBufStr := InputBufferAsString(IndyTextEncoding_8Bit); // <-- don't assume any encoding
      Log(RxBufStr);
      Write(RxBufStr, IndyTextEncoding_8Bit); // <-- use TIdIOHandler.Write() instead
    end;
  end;
end;
@elekgeek Personally, I would not use a string at all:
var
  RxBuf: TIdBytes;
begin
  with AContext.Connection.IOHandler do
  begin
    ReadBytes(RxBuf, -1);
    Log(RxBuf);
    Write(RxBuf);
  end;
end;
@elekgeek in which case, you might consider using TIdECHOServer instead of TIdTCPServer. For logging, you can assign a TIdLog... component (TIdLogFile, TIdLogEvent, etc) to the AContext.Connection.IOHandler.Intercept property, such as in the OnConnect event.
elekgeek
@elekgeek
I am trying to do echo thing just for testing purposes, other than that I will not need the echo functionality.
Remy Lebeau
@rlebeau
@ByteJuggler handling redirects should not be affecting TIdHTTP's ability to handle authentication. If anything, a redirect simply resets the AuthRetries and ProxyAuthRetries counters, giving TIdHTTP more chances to continue authentication attempts. Please don't assume what you think is happening, debug and find out exactly what is really happening.
elekgeek
@elekgeek
concerning the code above, it did not solve the issue. Once a client is connected, it is not possible to deactivate the server, so same issue. Also rubbish text is received.
Remy Lebeau
@rlebeau
@elekgeek the preferred way to use TIdTCPServeris to use blocking I/O that follows a defined protocol, raising exceptions on errors/disconnects/shutdowns, and let the server handle the exceptions. If you skip that logic, you become responsible for handling certain things manually, like shutdown. For instance, you could set a variable before setting Active=False (or, just look at Active itself, since it is toggled to false before thread shutdowns occur), and then have OnExecute look at that variable, and if set then call AContext.Connection.Disconnect, or raise your own exception, before exiting the event.
Remy Lebeau
@rlebeau
@elekgeek the code I gave works just fine for shutting down clients. So, the most likely possibility is if Log() itself is deadlocking, such as if it tries to synchronize with the main UI thread while the main UI thread is blocked waiting for the server to finish shutdown. Do not synchronize with the thread that is shutting down the server, that is a guaranteed deadlock. Either skip the synched operation during shutdown, or use a separate worker thread to shutdown the server so syncs can still be processed.
Kudzu
@czhower
I realize you are having issues elekgeek, but it is either your code, or milliions of Indy servers out there are all somehow not having the same Indy issue. I can tell you which I think it is.
Remy Lebeau
@rlebeau
@elekgeek As for "rubbish text", that is just because of the use of the 8bit encoding, which stores each byte as-is as a separate char and your code is not accounting for that. You can't use UTF-8 unless you ensure complete byte sequences for multi-byte characters, but your original code was not doing that. What kind of protocol is your server implementing? If the text is line-based, for instance, then you could just use TIdIOHandler.ReadLn(), in which case using UTF-8 would be OK. But if you use ReadBytes(-1) or ExtractString(-1) or InputBufferAsString then all guarantees about the completeness of multi-byte sequences go out the window
elekgeek
@elekgeek

@rlebeau this has worked for me and solved my problem:

     with IdTCPServer1.Contexts.LockList do
        try
           for iA := Count - 1 downto 0 do
           begin
              Context := Items[iA];
              if Context = nil then
                 Continue;
              Context.Connection.IOHandler.WriteBufferClear;
              Context.Connection.IOHandler.InputBuffer.Clear;
              Context.Connection.IOHandler.Close;
              if Context.Connection.Connected then
                 Context.Connection.Disconnect;
           end;
        finally
           IdTCPServer1.Contexts.UnlockList;
           IdTCPServer1.Active := False;
        end;

What do you think?

This code also suggested by you works very well:

  with AContext.Connection.IOHandler do
  begin
    CheckForDataOnSource(10);
    CheckForDisconnect; // <-- add this
    if not InputBufferIsEmpty then
    begin
      RxBufStr := InputBufferAsString(IndyTextEncoding_8Bit); // <-- don't assume any encoding
      Log(RxBufStr);
      Write(RxBufStr, IndyTextEncoding_8Bit); // <-- use TIdIOHandler.Write() instead
    end;
  end;

Now my question:

  • why were you not been able to suggest such a solution, no offense intended, but for sure you have a good reason.
  • How can the client check if the server went down and close its own connection?
Remy Lebeau
@rlebeau
@elekgeek you don't need any of that. Just set Active to false, it already disconnects the clients for you, and then waits for their threads to terminate. Your issue is that your code is blocking the threads from terminating correctly. That is what you need to fix properly, not hack around it.
elekgeek
@elekgeek

Look again please, I edited my previous answer

man my code is very simple, Look at my last answer, I am not blocking anything

and BTW
procedure TForm2.Log(const s: string);
begin
  mLog.Lines.Add(s);
end;
elekgeek
@elekgeek
@rlebeau Just set Active to false, it already disconnects the clients for you apparently it is not shutting down any threads.. my code is very simple and you have already seen it..
Remy Lebeau
@rlebeau

@elekgeek your Log() function is directly accessing a UI component (a TMemo?). THAT IS NOT THREAD-SAFE! That could easily cause deadlocks (amongst many other problems). OnExecute is fired in a worker thread, so Log() MUST synchronize with the main UI thread. I suggest it use TThread.Queue() or TIdNotify for that purpose (depending on your version of Delphi). That would avoid any cross-thread access issues, avoid any deadlock scenarios, and avoid blocking the OnExecute code (which should not need to wait on the UI to display log messages). For example:

procedure TForm2.Log(const s: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      mLog.Lines.Add(s);
    end
  );
end;

or:

uses
  ..., IdSync;

type
  TLog = class(TIdNotify)
  protected
    FMsg: string;
    procedure DoNotify; override;
  public
    constructor Create(const s: string); reintroduce;
  end;

constructor TLog.Create(const s: string);
begin
  inherited Create;
  FMsg := s;
end;

procedure TLog.DoNotify;
begin
  Form2.mLog.Lines.Add(FMsg);
end;

procedure TForm2.Log(const s: string);
begin
  TLog.Create(s).Notify;
end;
Justin
@klsyzzz
hi there, just wondering is there an example for using TIdHTTP and NTLM Authentication in delphi?
elekgeek
@elekgeek

@rlebeau fabulous, both work like a charm

thanks a lot

Justin
@klsyzzz
I need to call a httpPost action from within Delphi and using NTLM auth
Remy Lebeau
@rlebeau
@klsyzzz NTLM support in Indy is spotty at best. There are two different NTLM classes available for TIdHTTP - TIdNTLMAuthentication in IdAuthenticationNTLM.pas (portable but untested), and TIdSSPINTLMAuthentication in IdAuthenticationSSPI.pas (Windows only). In theory, you should be able to just add one of those units to your uses clause and let TIdHTTP handle the rest. In practice, .... umm, good luck? They either work or they don't. I have no way of testing either one, as I have no access to any HTTP servers that use NTLM
Justin
@klsyzzz
thank you @rlebeau , I will have a try and let you know how it goes
Justin
@klsyzzz
hi @rlebeau , I added IdAuthenticationNTLM.pas to my uses clause, and created an instance of TidHTTP, what I need to do next to enable NTLM auth? I didn't find any property on TidHTTP to select auth type, do I need to do the following:
auth := TIdNTLMAuthentication.Create;
IdHTTP1.AuthenticationManager.AddAuthentication(auth);
Remy Lebeau
@rlebeau
@klsyzzz just add the unit to the uses clause to register the class with TIdHTTP. TIdHTTP should then automatically use the class on any server that supports NTLM (though you can use the TIdHTTP.OnSelectAuthorization event to make sure that is what TIdHTTP picks, if multiple authentications are supported and registered). Now put your NTLM credentials in the TIdHTTP.Request.UserName and TIdHTTP.Request.Password properties, and assign a TIdHTTP.OnAuthorization event handler to promt the user for new credentials if the initial credentials don't work
Justin
@klsyzzz
thanks @rlebeau, having issues getting it work, will ask you further questions once I'm more clear with it.
DelphiWorlds
@DelphiWorlds
I'm back on the Multicast issue - it seems I just need to find a multicast group that works. The information available is pretty confusing, as is iOS's apparent refusal to accept what I thought are valid groups. Any ideas on how to find something definitive?
I'm using a TIdIPMCastClient, and the following code:
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  BroadcastListenerIPv6.DefaultPort := 6000;
  for I := 0 to BroadcastListenerIPv6.Bindings.Count - 1 do
    BroadcastListenerIPv6.Bindings.Items[I].Port := BroadcastListenerIPv6.DefaultPort;
  BroadcastListenerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1';
  BroadcastListenerIPv6.Active := True;
end;
Remy Lebeau
@rlebeau
@DelphiWorlds Obviously, you have to use whatever group IP is actually running on your local network. Is there an actual multicast group FF02:0:0:0:0:0:0:1 on your network?
DelphiWorlds
@DelphiWorlds
That's my point.. how do I know if there is one? Do I actually need to create it, and how?
Remy Lebeau
@rlebeau
@DelphiWorlds of course you need to have something setup on the network that will be sending packets to the group, and you need to know what that group is ahead of time. What else are you going to listen for packets from? TIdIPMCastClient joins a group. Anyone can send packets to the same group, and they will be delivered to whoever is joined
DelphiWorlds
@DelphiWorlds
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  BroadcastServerIPv6.Port := 6000;
  BroadcastServerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1';
  BroadcastServerIPv6.Active := True;

  BroadcastListenerIPv6.DefaultPort := 6000;
  for I := 0 to BroadcastListenerIPv6.Bindings.Count - 1 do
    BroadcastListenerIPv6.Bindings.Items[I].Port := BroadcastListenerIPv6.DefaultPort;
  BroadcastListenerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1';
  BroadcastListenerIPv6.Active := True;
end;
Still does not work
Same error, i.e. 49 (Address invalid)
Remy Lebeau
@rlebeau
@DelphiWorlds well, you didn't say that earlier. That is an OS error, not an Indy error. Where exactly are you seeing that error? What local IP(s) are you binding the client to? You are only setting TIdSocketHandle.Port in code, are you leaving TIdSocketHandle.IP blank? I don't know if you can do that when joining a multicast group as a client
DelphiWorlds
@DelphiWorlds
when setsockopt is called.. that returns -1, and WSGetLastError returns 49
the client binds to a zero address (apparently meaning all)
Remy Lebeau
@rlebeau
@DelphiWorlds when calling setsockopt() with IPV6_ADD_MEMBERSHIP? It binds to interface index 0 (not address 0), which means use the default multicast interface.
DelphiWorlds
@DelphiWorlds
when calling setsockopt() with IPV6_ADD_MEMBERSHIP it passes the group, not an IP address
Remy Lebeau
@rlebeau
@DelphiWorlds IPV6_ADD_MEMBERSHIP specifies the group to join and the network interface to join with
DelphiWorlds
@DelphiWorlds
it's when it calls setsockopt() with IPV6_ADD_MEMBERSHIP that it fails
yes, I have worked that out
the interface index being used is 0, which coincides with other examples I've seen in my searching
Remy Lebeau
@rlebeau
@DelphiWorlds like I said, that just tells the OS to use the default multicast interface, whatever it happens to be, instead of having to specify a specific interface. You could always try stepping into the code with the debugger and play around with the indexes. But I think the first thing you should try is setting the TIdSocketHandle.IP property to the device's local IP when setting up the Bindings collection so TIdIPMCastClient has something specific to bind to locally before joining the group.
DelphiWorlds
@DelphiWorlds
I'm away from my Mac, so I'll check tonight (in about 8 hours from now).. thanks
DelphiWorlds
@DelphiWorlds
I've modified the code to set the only Binding to the first IPv6 address:
procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  LIP: string;
begin
  FLocalAddresses := TIdStackLocalAddressList.Create;
  GStack.GetLocalAddressList(FLocalAddresses);

  for I := 0 to FLocalAddresses.Count - 1 do
  begin
    if FLocalAddresses[I].IPVersion = TIdIPVersion.Id_IPv6 then
    begin
      LIP := FLocalAddresses[I].IPAddress;
      Break;
    end;
  end;

  if LIP.IsEmpty then
    Exit; // <======

  BroadcastServerIPv6.Port := 6000;
  BroadcastServerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1';
  BroadcastServerIPv6.Active := True;

  BroadcastListenerIPv6.DefaultPort := 6000;
  for I := 0 to BroadcastListenerIPv6.Bindings.Count - 1 do
  begin
    BroadcastListenerIPv6.Bindings.Items[I].Port := BroadcastListenerIPv6.DefaultPort;
    BroadcastListenerIPv6.Bindings.Items[I].IP := LIP;
  end;
  BroadcastListenerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1';
  BroadcastListenerIPv6.Active := True;
end;
when I say "only" I mean that BroadcastListenerIPv6.Bindings.Count = 1
I've been through iterations of this before, one where I've created Bindings for each IPv6 address that the device has.. always results the same: error 49
I'm about to resort to using:
https://github.com/robbiehanson/CocoaAsyncSocket
at least for iOS
DelphiWorlds
@DelphiWorlds
I'd rather not though.. having single source has advantages, however when I've fought with this as much as I have, it might be time to move on