问题
I have inherited an extensive (199 commands) Delphi 7 Indy 9 app that I am upgrading to Indy 10 (in D10.1). I have upgraded all the code, and it compiles and runs. The problem I have is that now in Indy 10 all the handlers also return a response code (and text) in addition to the coded response that they did under Indy 9.
For example:
// server
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
myClient := TClientData.Create;
myClient.ClientName := ASender.Params[0];
myClient.ClientHost := #32; // indy9 was .Thread.Connection.LocalName;
myClient.ID := Now;
ASender.Context.Data := myClient;
ListBox1.Items.AddObject(
PadR(myClient.ClientName,12,' ') + '=' +
FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
TDateTimeO.Create(myClient.ID));
ASender.Context.Connection.IOHandler.WriteLn('SUCCESS' + ' ' + Rights)
end
else
ASender.Context.Connection.IOHander.WriteLn('Login failed!');
end;
...
// client side
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
var
response, response1: String;
begin
frmMain.IdTCPClient1.IOHandler.WriteLn('login' + ' ' +
username + ' ' + password)
response := frmMain.IdTCPClient1.IOHandler.ReadLn();
// I have to add this now to capture the response code too!
response1 := frmMain.IdTCPClient1.IOHandler.ReadLn(); // 200 OK
// ------------------------------------------------------
if Copy(response,1,7) = 'SUCCESS' then
begin
rights := Copy(response,9,4);
There are a lot of command handlers, and they all have their own custom responses. That's a lot of code to change at the client. Is there a way I can tell the IdCmdTCPServer
to suppress the standard '200 Ok' response if the command handler already provides it's own? Or am I in for a long night?
Thanks
回答1:
If you need to suppress the default command responses, you can either:
clear the
TIdCommandHandler
'sReplyNormal
andExceptionReply
properties (this also works in Indy 9, except thatExceptionReply
wasReplyExceptionCode
in that version), and the server'sCommandHandlers.ExceptionReply
property (Indy 10 only).set the
TIdCommand.PerformReply
property to false in yourOnCommand
handler (this also works in Indy 9):procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand); var ... begin ASender.PerformReply := False; ... end;
set the server's
CommandHandlers.PerformReplies
property to false (Indy 10 only - it will setTIdCommand.PerformReply
to false by default):IdCmdTCPServer1.CommandHandlers.PerformReplies := False;
On the other hand, you should consider using the command handler responses the way they are designed to be used, eg:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if ASender.Params.Count = 2 then
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
...
ASender.Reply.SetReply('SUCCESS', Rights);
end
else
ASender.Reply.SetReply('ERROR', 'Login failed!');
end
else
ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
end;
I would even go as far as saying that you should set the TIdCommandHandler.NormalReply.Code
property to SUCCESS
and the TIdCommandHandler.ExceptionReply.Code
property to ERROR
, and then you can do this inside your OnCommand
handler:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
begin
if ASender.Params.Count <> 2 then
raise Exception.Create('Wrong number of parameters!');
if not BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
raise Exception.Create('Login failed!');
...
ASender.Text.Text := Rights;
end;
With that said, any of these approaches should work fine without changing your existing client code. However, in Indy 10, I would suggest using SendCmd()
instead of WriteLn()
/ReadLn()
directly:
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
var
response: String;
begin
response := frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password);
if response = 'SUCCESS' then
begin
rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
...
end else begin
// error message in frmMain.IdTCPClient1.LastCmdResult.Text.Text ...
end;
end;
Alternatively, you can let SendCmd()
raise an exception if it does not receive a SUCCESS
reply:
function TfrmLogin.VerifyUserNameAndPassword(username, password: String): Boolean;
begin
try
frmMain.IdTCPClient1.SendCmd('login ' + username + ' ' + password, 'SUCCESS');
except
on E: EIdReplyRFCError do begin
// error message in E.Message ...
...
Exit;
end;
end;
rights := frmMain.IdTCPClient1.LastCmdResult.Text.Text;
...
end;
SendCmd()
does exist in Indy 9, but it only supports numeric-based response codes, which you are not using. As you can see above, SendCmd()
in Indy 10 supports string-based response codes as well as numeric ones.
On a side note: in your server code, the OnCommand
handler runs in a worker thread, so your use of ListBox1.Items.AddObject()
is not thread-safe. Any access to the UI must be synchronized with the main UI thread, using techniques like TThread.Synchronize()
, TThread.Queue()
, TIdSync
, TIdNotify
, etc, eg:
procedure TFormMain.IdCmdTCPServer1loginCommand(ASender: TIdCommand);
var
Rights: String;
myClient: TClientData;
begin
if ASender.Params.Count = 2 then
begin
if BillingUserRegistered(ASender.Params[0], ASender.Params[1], Rights) then
begin
myClient := TClientData(ASender.Context.Data);
if myClient = nil then
begin
myClient := TClientData.Create;
ASender.Context.Data := myClient;
end;
myClient.ID := Now;
myClient.ClientName := ASender.Params[0];
myClient.ClientHost := GStack.HostByAddress(ASender.Context.Binding.PeerIP, ASender.Context.Binding.IPVersion);
// In Indy 9, this would be:
// myClient.ClientHost := GStack.WSGetHostByAddr(ASender.Thread.Connection.Socket.PeerIP);
// NOT ASender.Thread.Connection.LocalName!
TThread.Queue(nil,
procedure
begin
ListBox1.Items.AddObject(
PadR(myClient.ClientName,12,' ') + '=' + FormatDateTime('yyyy-mm-dd hh:nn:ss', myClient.ID),
TDateTimeO.Create(myClient.ID));
end
);
ASender.Reply.SetReply('SUCCESS', Rights);
end
else
ASender.Reply.SetReply('ERROR', 'Login failed!');
end
else
ASender.Reply.SetReply('ERROR', 'Wrong number of parameters!');
end;
Make sure your BillingUserRegistered()
function is similarly thread-safe, if it is not already.
来源:https://stackoverflow.com/questions/40114391/upgrading-delphi-7-indy-9-app-to-indy-10