Ok, I have Idhttp created dynamically like the following
procedure TForm1.Button1Click(Sender: TObject);
Var
Resp : String;
begin
Resp := webSession('https://www.website.com'); // HTTPS site requires session to keep alive
if Length(Resp)>0 then
MessageDlg('Got the body ok',mtInformation,[mbOk],0);
end;
function TForm1.webSession(sURL : ansistring) : ansistring;
var
SStream : Tstringstream;
HTTPCon : TIdHTTP;
AntiFreeze : TIdAntiFreeze;
CompressorZLib: TIdCompressorZLib;
ConnectionIntercept: TIdConnectionIntercept;
SSLIOHandlerSocketOpenSSL: TIdSSLIOHandlerSocketOpenSSL;
CookieManager: TIdCookieManager;
begin
CompressorZLib := TIdCompressorZLib.Create;
ConnectionIntercept :=TIdConnectionIntercept.Create;
SSLIOHandlerSocketOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create;
Result := '';
if Length(SettingsForm.edtProxyServer.text) >= 7 then // 0.0.0.0
Try
SStream := NIL;
AntiFreeze := NIL;
HTTPCon := NIL;
Try
SStream := tstringstream.Create('');
{ Create & Set IdHTTP properties }
HTTPCon := TIdHTTP.create;
HTTPCon.AllowCookies:=true;
HTTPCon.CookieManager :=CookieManager;
HTTPCon.Compressor := CompressorZLib;
HTTPCon.Intercept := ConnectionIntercept;
HTTPCon.IOHandler := SSLIOHandlerSocketOpenSSL;
HTTPCon.HandleRedirects := true;
{ Check Proxy }
if checkproxy('http://www.google.com') then
Begin
HTTPCon.ProxyParams.ProxyServer := SettingsForm.edtProxyServer.text;
HTTPCon.ProxyParams.ProxyPort := StrToInt(SettingsForm.edtProxyPort.Text);
HTTPCon.ProxyParams.BasicAuthentication := True;
HTTPCon.ProxyParams.ProxyUsername := SettingsForm.edtProxyServer.Text;
HTTPCon.ProxyParams.ProxyPassword := SettingsForm.edtProxyUserName.Text;
End;
{ Create another AntiFreeze - only 1/app }
AntiFreeze := TIdAntiFreeze.Create(nil);
AntiFreeze.Active := true;
HTTPCon.Get(sURL,SStream);
Result := UTF8ToWideString(SStream.DataString);
Finally
If Assigned(HTTPCon) then FreeAndNil(HTTPCon);
If Assigned(AntiFreeze) then FreeAndNil(AntiFreeze);
If Assigned(SStream) then FreeAndNil(SStream);
If Assigned(CookieManager) then FreeAndNil (CookieManager );
If Assigned(CompressorZLib) then FreeAndNil (CompressorZLib );
If Assigned(ConnectionIntercept) then FreeAndNil (ConnectionIntercept );
If Assigned(SSLIOHandlerSocketOpenSSL) then FreeAndNil (SSLIOHandlerSocketOpenSSL);
End;
Except
{ Handle exceptions }
On E:Exception do
MessageDlg('Exception: '+E.Message,mtError, [mbOK], 0);
End;
end;
function TForm1.checkproxy(sURL : ansistring) : boolean;
var
HTTPCon : TIdHTTP;
AntiFreeze : TIdAntiFreeze;
begin
Result := False;
Try
{ Inti vars }
AntiFreeze := NIL;
HTTPCon := NIL;
Try
{ AntiFreeze }
AntiFreeze := TIdAntiFreeze.Create(NIL);
AntiFreeze.Active := true;
{ Create & Set IdHTTP properties }
HTTPCon := TIdHTTP.Create(NIL);
HTTPCon.ProxyParams.ProxyServer := SettingsForm.edtProxyServer.text;
HTTPCon.ProxyParams.ProxyPort := StrToInt(SettingsForm.edtProxyPort.Text);
HTTPCon.ProxyParams.BasicAuthentication := True;
HTTPCon.ProxyParams.ProxyUsername := SettingsForm.edtProxyServer.Text;
HTTPCon.ProxyParams.ProxyPassword := SettingsForm.edtProxyUserName.Text;
HTTPCon.HandleRedirects := true;
HTTPCon.ConnectTimeout := 1000;
HTTPCon.Request.Connection := 'close';
HTTPCon.Head(sURL);
Finally
{ Cleanup }
if Assigned(HTTPCon) then
Begin
{ Return Success/Failure }
Result := HTTPCon.ResponseCode = 200;
If HTTPCon.Connected then HTTPCon.Disconnect;
FreeAndNil(HTTPCon);
End;
if Assigned(AntiFreeze) then FreeAndNil(AntiFreeze);
End;
Except
On E:EIdException do ;
{ Handle exceptions }
On E:Exception do
MessageDlg('Exception: '+E.Message,mtError, [mbOK], 0);
End;
end;
I've got a website that requires me to keep a session alive. How would I do this? With similar code to above.
If I create a visual component for everything, and use it everything is great, but when I dynamically create the component (which I REALLY want to leave it this way) it fails to keep the session alive.
Any help is appreciated.
I don't see where you instantiate CookieManager, but that's where you should keep track of the session. The server will send some cookie that represents the current session, and all further requests that you send to the server should include that cookie so the server knows which session to use.
You'll have to either keep the cookie-manager object around for the duration of the session, or you'll have to save its data somewhere else and then re-load it each time you create a new cookie-manager object. I'd prefer the former. In fact, you might consider keeping the entire HTTP object around.
As you mentioned in your comments, you are creating CookieManager in OnCreate event-handler, so that when TForm1.webSession is called, CookieManager is available, but in the finally block of TForm1.webSession you are freeing CookieManager, so once you leave TForm1.webSession method, CookieManager is out of memory. So, next time TForm1.webSession is called, CookieManager is Nil, and no cookie is saved for you.
There are two other notes that are not related to your question, but are related to your source code:
1- Your method is returning AnsiString, but you are using Utf8ToWideString for assigning value to Result variable. Utf8ToWideString returns WideString, so compiler has to convert WideString to AnsiString, and not only this reduces the performance, but also it loses the unicode characters in the returning string. You should change your method signature to return either String (D2009 & D2010) or WideString (Older versions of Delphi).
2- You don't need to check if SStream, AntiFreeze, or HTTPCon are assigned in the finally block. You can simply call the Free method, or use FreeAndNil procedure.
Regards
As Rob said your TIdCookieManager is key for maintaining a Cookie based session. The TIdCookieManager could be created in a datamodule's create event or the mainforms OnCreate() event and then set every time you create a IdHTTP component.
Related
I'm using TIdFTP (Indy 10.6) for a client application and I need to be able to copy all files from one folder on the server to another. Can this be done?
I know how to rename or move a file, we can use TIdFTP.Rename(Src, Dst).
How about the copy? Would I need to use Get() and Put() with a new path / name, knowing that the number of files in the server can exceed 500,000 files.
In our company, we have some files whose size exceeds 1.5 GB. By using my code, it consumes a lot of memory and the file is not copied from one directory to another: in less code, the source directory is named "Fichiers" and the destination directory is named "Sauvegardes".
Here is my code:
var
S , directory : String;
I: Integer;
FichierFTP : TMemoryStream;
begin
IdFTP1.Passive := True;
idftp1.ChangeDir('/Fichiers/');
IdFTP1.List();
if IdFTP1.DirectoryListing.Count > 0 then begin
IdFTP1.List();
for I := 0 to IdFTP1.DirectoryListing.Count-1 do begin
with IdFTP1.DirectoryListing.Items[I] do begin
if ItemType = ditFile then begin
FichierFTP := TMemoryStream.Create;
S := FileName;
idftp1.Get( FileName , FichierFTP , false );
Application.ProcessMessages
idftp1.ChangeDir('/Sauvegardes/' );
idftp1.Put(FichierFTP , S );
Application.ProcessMessages;
FichierFTP.Free;
end;
end;
end;
IdFTP1.Disconnect;
end;
Does anyone have any experience with this? How can I change my code to resolve this problem?
There are no provisions in the FTP protocol, and thus no methods in TIdFTP, to copy/move multiple files at a time. Only to copy/move individual files one at a time.
Moving a file from one FTP folder to another is easy, that can be done with the TIdFTP.Rename() method. However, copying a file typically requires issuing separate commands to download the file locally first and then re-upload it to the new path.
Some FTP servers support custom commands for copying files, so that you do not need to download/upload them locally. For example, ProFTPD's mod_copy module implements SITE CPFR/CPTO commands for this purpose. If your FTP server supports such commands, you can use the TIdFTP.Site() method, eg:
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
try
IdFTP1.Site('CPFR ' + Item.FileName);
IdFTP1.Site('CPTO /Sauvegardes/' + Item.FileName);
except
// fallback to another transfer option, see further below...
end;
end;
If that does not work, another possibility to avoid having to copy each file locally is to use a site-to-site transfer between 2 separate TIdFTP connections to the same FTP server. If the server allows this, you can use the TIdFTP.SiteToSiteUpload() and TIdFTP.SiteToSiteDownload() methods to make the server transfer files to itself, eg:
IdFTP2.Connect;
...
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
try
IdFTP1.SiteToSiteUpload(IdFTP2, Item.FileName, '/Sauvegardes/' + Item.FileName);
except
try
IdFTP2.SiteToSiteDownload(IdFTP1, Item.FileName, '/Sauvegardes/' + Item.FileName);
except
// fallback to another transfer option, see further below...
end;
end;
end;
...
IdFTP2.Disconnect;
But, if using such commands is simply not an option, then you will have to resort to downloading each file locally and then re-uploading it. When copying a large file in this manner, you should use TFileStream (or similar) instead of TMemoryStream. Do not store large files in memory. Not only do you risk a memory error if the memory manager can't allocate enough memory to hold the entire file, but once that memory has been allocated and freed, the memory manager will hold on to it for later reuse, it does not get returned back to the OS. This is why you end up with such high memory usage when you transfer large files, even after all transfers are finished.
If you really want to use a TMemoryStream, use it for smaller files only. You can check each file's size on the server (either via TIdFTPListItem.Size if available, otherwise via TIdFTP.Size()) before downloading the file, and then choose an appropriate TStream-derived class to use for that transfer, eg:
const
MaxMemoryFileSize: Int64 = ...; // for you to choose...
var
...
FichierFTP : TStream;
LocalFileName: string;
RemoteFileSize: Int64;
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
LocalFileName := '';
if Item.SizeAvail then
RemoteFileSize := Item.Size
else
RemoteFileSize := IdFTP1.Size(Item.FileName);
if (RemoteFileSize >= 0) and (RemoteFileSize <= MaxMemoryFileSize) then
begin
FichierFTP := TMemoryStream.Create;
end else
begin
LocalFileName := MakeTempFilename;
FichierFTP := TFileStream.Create(LocalFileName, fmCreate);
end;
try
IdFTP1.Get(Item.FileName, FichierFTP, false);
IdFTP1.Put(FichierFTP, '/Sauvegardes/' + Item.FileName, False, 0);
finally
FichierFTP.Free;
if LocalFileName <> '' then
DeleteFile(LocalFileName);
end;
end;
There are other optimizations you can make to this, for instance creating a single TMemoryStream with a pre-sized Capacity and then reuse it for multiple transfers that will not exceed that Capacity.
So, putting this all together, you could end up with something like the following:
var
I: Integer;
Item: TIdFTPListItem;
SourceFile, DestFile: string;
IdFTP2: TIdFTP;
CanAttemptRemoteCopy: Boolean;
CanAttemptSiteToSite: Boolean;
function CopyFileRemotely: Boolean;
begin
Result := False;
if CanAttemptRemoteCopy then
begin
try
IdFTP1.Site('CPFR ' + SourceFile);
IdFTP1.Site('CPTO ' + DestFile);
except
CanAttemptRemoteCopy := False;
Exit;
end;
Result := True;
end;
end;
function CopyFileSiteToSite: Boolean;
begin
Result := False;
if CanAttemptSiteToSite then
begin
try
if IdFTP2 = nil then
begin
IdFTP2 := TIdFTP.Create(nil);
IdFTP.Host := IdFTP1.Host;
IdFTP.Port := IdFTP1.Port;
IdFTP.UserName := IdFTP1.UserName;
IdFTP.Password := IdFTP1.Password;
// copy other properties as needed...
IdFTP2.Connect;
end;
try
IdFTP1.SiteToSiteUpload(IdFTP2, SourceFile, DestFile);
except
IdFTP2.SiteToSiteDownload(IdFTP1, SourceFile, DestFile);
end;
except
CanAttemptSiteToSite := False;
Exit;
end;
Result := True;
end;
end;
function CopyFileManually: Boolean;
const
MaxMemoryFileSize: Int64 = ...;
var
FichierFTP: TStream;
LocalFileName: String;
RemoteFileSize: Int64;
begin
Result := False;
try
if Item.SizeAvail then
RemoteFileSize := Item.Size
else
RemoteFileSize := IdFTP1.Size(SourceFile);
if (RemoteFileSize >= 0) and (RemoteFileSize <= MaxMemoryFileSize) then
begin
LocalFileName := '';
FichierFTP := TMemoryStream.Create;
end else
begin
LocalFileName := MakeTempFilename;
FichierFTP := TFileStream.Create(LocalFileName, fmCreate);
end;
try
IdFTP1.Get(SourceFile, FichierFTP, false);
IdFTP1.Put(FichierFTP, DestFile, False, 0);
finally
FichierFTP.Free;
if LocalFileName <> '' then
DeleteFile(LocalFileName);
end;
except
Exit;
end;
Result := True;
end;
begin
CanAttemptRemoteCopy := True;
CanAttemptSiteToSite := True;
IdFTP2 := nil;
try
IdFTP1.Passive := True;
IdFTP1.ChangeDir('/Fichiers/');
IdFTP1.List;
for I := 0 to IdFTP1.DirectoryListing.Count-1 do
begin
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
SourceFile := Item.FileName;
DestFile := '/Sauvegardes/' + Item.FileName;
if CopyFileRemotely then
Continue;
if CopyFileSiteToSite then
Continue;
if CopyFileManually then
Continue;
// failed to copy file! Do something...
end;
end;
finally
IdFTP2.Free;
end;
IdFTP1.Disconnect;
end;
I need to send a Push notification out through Parse.com's API using Delphi.
I see there is a TParseApi but the documentation is, as usual, rather sparse on the subject.
How can I do this?
Drop a TParseProvider and a TBackendPush component onto a form or datamodule. Connect them and enter your credentials in the appropriate properties of the provider. Set the backend Message property to the message to send and call Push.
There are at least three ways of doing this:
1) A direct method would be to create your own HTTP request with custom headers and JSON
Procedure TForm1.ParseDotComPushNotification(pushMessage: string);
var
parseDotComUrl: string;
JSON: TStringStream;
webRequest: TIDHttp;
response: string;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
parseDotComUrl := 'https://api.parse.com/1/push';
// Modify the JSON as required to push to whomever you want to.
// This one is set up to push to EVERYONE.
// JSON := TStringStream.Create('{ "where": {}, ' + '"data" : {"alert":"'
// + pushMessage + '"}' + '}', TEncoding.UTF8);
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
JSON := TStringStream.Create(mainJsonObject.ToJSON);
mainJsonObject.Free; // free all the child objects.
webRequest := TIDHttp.Create(nil);
webRequest.Request.Connection := 'Keep-Alive';
webRequest.Request.CustomHeaders.Clear;
webRequest.Request.CustomHeaders.AddValue('X-Parse-Application-Id',
'YourApplicationID');
webRequest.Request.CustomHeaders.AddValue('X-Parse-REST-API-KEY',
'YourRestApiKey');
webRequest.Request.ContentType := 'application/json';
webRequest.Request.CharSet := 'utf-8';
webRequest.Request.ContentLength := JSON.Size;
try
try
response := webRequest.Post(parseDotComUrl, JSON);
except
on E: Exception do
begin
showmessage(response);
end;
end;
finally
webRequest.Free;
JSON.Free;
end;
end;
Thus bypassing the need for TParseApi
2) Based on UweRabbe's answer, you can also do it like this in code:
procedure TForm1.parseProviderCodeButtonClick(Sender: TObject);
var
myParseProvider: TParseProvider;
myBackendPush: TBackendPush;
myStrings: Tstrings;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
myParseProvider := TParseProvider.Create(nil);
myParseProvider.ApiVersion := '1';
myParseProvider.ApplicationID := 'YourApplicationID';
myParseProvider.MasterKey := 'YourMasterKey';
myParseProvider.RestApiKey := 'YourRestApiKey';
myBackendPush := TBackendPush.Create(nil);
myBackendPush.Provider := myParseProvider;
// myBackendPush.Message := 'Hello world';
myStrings := TStringList.Create;
myStrings.Clear;
// I like putting the message in when I generate the JSON for the Target
// (since it seems I have to do it anyways, my not do it all in one place).
// You could however us TBackendPush.Message as I've commented out above.
// myStrings.Add('{ "where": { }, "data" : {"alert":"goodbye world"}}');
myStrings.Add(mainJsonObject.ToJSON);
myBackendPush.Target := myStrings;
myStrings.Free;
mainJsonObject.Free; // free all the child objects.
myBackendPush.Push;
myBackendPush.Free;
myParseProvider.Free;
end;
3) And to round this out into one complete answer (again based on UweRabbe's answer)
On your form/datamodule:
Place a TParseProvider
Place a TBackendPush - this should automatically set its Provider filed to the name of the TParseProvider you created in the previous step.
Set the TBackendPush's ApplicationID, MasterKey, RestApiKey, and Message properties
Set the TBackendPush's Push method from code.
e.g.,
procedure TForm1.Button1(Sender: TObject);
begin
BackendPush1.Push;
end;
I have this function:
var
_WordApplicationExistsCache: Integer = -1; // Cache result
function WordApplicationExists: Boolean;
var
WordObj: OleVariant;
begin
if (_WordApplicationExistsCache = -1) then
begin
Result := False;
try
try
WordObj := CreateOleObject('Word.Application');
WordObj.Visible := False;
WordObj.Quit;
WordObj := Unassigned;
Result := True;
except
// error
end;
finally
_WordApplicationExistsCache := Ord(Result); // 0;1
end;
end
else
begin
Result := Boolean(_WordApplicationExistsCache);
end;
end;
I'm trying to call this function only once in the Application lifetime. I might not call this function at all.
Is this the correct pattern? Can this be done better?
EDIT:
Another way I can think of, in this case is to use 2 variables:
var
_WordApplicationExistsInitialized: Boolean = False; // Cache result
_WordApplicationExistsCacheResult: Boolean; // Undefined ?
function WordApplicationExists: Boolean;
var
WordObj: OleVariant;
begin
if not _WordApplicationExistsInitialized then
begin
_WordApplicationExistsInitialized := True;
Result := False;
try
try
WordObj := CreateOleObject('Word.Application');
WordObj.Visible := False;
WordObj.Quit;
WordObj := Unassigned;
Result := True;
except
// error
end;
finally
_WordApplicationExistsCacheResult := Result;
end;
end
else
begin
Result := _WordApplicationExistsCacheResult;
end;
end;
What bugs me a bit about the first version is the type casting Boolean<->Integer. If Boolean could be initialized to nil it would have been perfect (I think).
Use a TriState type for the cached result.
type
TTriState = ( tsUnknown, tsFalse, tsTrue );
var
_WordApplicationExists : TTriState = tsUnknown;
function WordApplicationExists : Boolean;
var
WordObj: OleVariant;
begin
if _WordApplicationExists = tsUnknown
then
try
WordObj := CreateOleObject('Word.Application');
WordObj.Visible := False;
WordObj.Quit;
WordObj := Unassigned;
_WordApplicationExists := tsTrue;
except
_WordApplicationExists := tsFalse;
end;
Result := _WordApplicationExists = tsTrue;
end;
This code will work fine, and is correctly implemented. A nullable boolean or a tristate enum will read better, but fundamentally the logic would be the same.
It's heavy handed and clunky approach though, invoking an instance of Word that is then thrown away. Personally I would read the registry to check whether or not the COM object is registered. I would not attempt to anticipate the case where the object is registered but cannot be created. In my view that is an exceptional case that should be handled when it occurs, but not before.
Another way to go is simply not to attempt to check ahead of time for the Word COM object being available. Just go ahead and attempt to create the object when you need to use it. If this fails, deal with that. If you wish to remember that it failed, do so. But you really should avoid creating the object twice when once will suffice.
This could be done also with a Variant type. Variants are set to Unassigned. (reference)
var
_WordApplicationCanCreate: Variant; // Unassigned (VType = varEmpty)
function WordApplicationCanCreate: Boolean;
var
WordObj: OleVariant;
begin
if VarIsEmpty(_WordApplicationCanCreate) then
try
WordObj := CreateOleObject('Word.Application');
WordObj.Visible := False;
WordObj.Quit;
WordObj := Unassigned;
_WordApplicationCanCreate := True;
except
_WordApplicationCanCreate := False;
end;
Result := _WordApplicationCanCreate = True;
end;
Please help to understand about routing and web-url of web-service below.
type
TAirportService = class(TInterfacedObject, IAirportService)
public
procedure GetAirportDefinition(const AirPortID: integer; out Definition: TDTOAirportDefinition);
end;
procedure TAirportService.GetAirportDefinition(const AirPortID: integer;
out Definition: TDTOAirportDefinition);
begin
// create an object from static data
// (real application may use database and complex code to retrieve the values)
with Definition.Airport.Add do begin
Location := 'LAX';
Terminal := TRawUTF8DynArrayFrom(['terminalA', 'terminalB', 'terminalC']);
Gate := TRawUTF8DynArrayFrom(['gate1', 'gate2', 'gate3', 'gate4', 'gate5']);
BHS := 'Siemens';
DCS := 'Altiea';
end;
with Definition.Airline.Add do begin
CX := TRawUTF8DynArrayFrom(['B777', 'B737', 'A380', 'A320']);
QR := TRawUTF8DynArrayFrom(['A319', 'A380', 'B787']);
ET := '380';
SQ := 'A320';
end;
Definition.GroundHandler := TRawUTF8DynArrayFrom(['Swissport','SATS','Wings','TollData']);
end;
procedure StartWebService();
var
aModel: TSQLModel;
aDB: TSQLRestServer;
aServer: TSQLHttpServer;
begin
// set the logs level to only important events (reduce .log size)
TSQLLog.Family.Level := LOG_STACKTRACE+[sllInfo,sllServer];
// initialize the ORM data model
aModel := TSQLModel.Create([]);
try
// create a fast in-memory ORM server
aDB := TSQLRestServerFullMemory.Create(aModel,'test.json',false,false);
try
// register our TAirportServer implementation
// aDB.ServiceRegister(TServiceCalculator,[TypeInfo(ICalculatorXML)],sicShared);
aDB.ServiceRegister(TAirportService,[TypeInfo(IAirportService)],sicShared);
// launch the HTTP server
aServer := TSQLHttpServer.Create('8092', [aDB], '+', useHttpApiRegisteringURI);
try
aServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
writeln('Background server is running'#10);
write('Press [Enter] to close the server.');
ConsoleWaitForEnterKey;
finally
aServer.Free;
end;
finally
aDB.Free;
end;
finally
aModel.Free;
end;
end;
I try to call follow web-urls:
http://localhost:8092/root/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService.GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService/GetAirportDefinition
http://localhost:8092/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/AirportService.GetAirportDefinition?AirPortID=1
http://localhost:8092/AirportService/GetAirportDefinition
but every time I get:
{
"errorCode":400,
"errorText":"Bad Request"
}
or Bad request
Where am I wrong?
A was wrong, really urls below works as needed:
http://localhost:8092/root/AirportService/GetAirportDefinition?AirPortID=1
http://localhost:8092/root/AirportService.GetAirportDefinition?AirPortID=1
What is good way to implement long running queries on IdHttpServer. I have written simple logic to do so, please advise suggest better way to achive the same as I'm struggling with its performance.
I am using D2010 and Indy 10.5.8 to achieve the goal, also suggest if we retrive values frequently from session will that be a resource intensive ?
procedure TForm1.ServerCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
SessionObj : TSessionData;
begin
if ARequestInfo.Document = 'EXECUTEQUERY' then
begin
if not Assigned(ARequestInfo.Session.Content) then
begin
SessionObj := TSessionData.Create;
ARequestInfo.Session.Content.AddObject('U_SESSION', SessionObj);
SessionObj.RunLongQuery;
end;
end;
if ARequestInfo.Document = 'GETDATA' then
begin
SessionObj := TSessionData(ARequestInfo.Session.Content.Objects[ ARequestInfo.Session.Content.IndexOf('U_SESSION')]);
if SessionObj.GetQueryStat = Done then
begin
AResponseInfo.ContentStream.CopyFrom(SessionObj.GetMemStream, SessionObj.GetMemStream.Size);
SessionObj.GetMemStream.Clear;
AResponseInfo.ResponseNo := 200;
end else if SessionObj.GetQueryStat = Error then
AResponseInfo.ResponseNo := 500
else AResponseInfo.ResponseNo := 102;
end;
end;
procedure TForm1.ServerSessionEnd(Sender: TIdHTTPSession);
begin
TSessionData(Sender.Content.Objects[ Sender.Content.IndexOf('U_SESSION')]).Free;
end;
{ TProcessQuery }
constructor TProcessQuery.Create;
begin
myConn := TMyConnection.Create(nil);
myConn.LoginPrompt := False;
myConn.UserName := 'UserName';
myConn.Password := 'Password';
myConn.Server := 'Host';
myConn.Database := 'DBName';
myConn.Connected := True;
myQuery := TMyQuery.Create(nil);
myQuery.Unidirectional := True;
myQuery.Options.CreateConnection := False;
myQuery.Connection := myConn;
Fstat := None;
Fstream := TMemoryStream.Create;
end;
destructor TProcessQuery.Destroy;
begin
if Assigned(myConn) then begin
myConn.Close;
myConn.Disconnect;
FreeAndNil(myConn);
end;
end;
procedure TProcessQuery.ExecuteQuery;
begin
Status := Started;
myQuery.SQL.Text := '<Some Query>';
myQuery.Open;
try
try
while not myQuery.Eof do
begin
Status := Inprogress;
//Add to FStream which would be returned to user.
end;
except
on Exception do
Status := Error;
end;
finally
myQuery.Close;
end;
end;
{ TSessionData }
constructor TSessionData.Create;
begin
FProcessQuery := TProcessQuery.Create;
end;
function TSessionData.GetMemStream: TMemoryStream;
begin
result := FProcessQuery.Fstream;
end;
function TSessionData.GetQueryStat: TStatus;
begin
result := FProcessQuery.Status;
end;
procedure TSessionData.RunLongQuery;
begin
FProcessQuery.ExecuteQuery
end;
You are running the actual query in the context of ServerCommandGet(), so the client will not receive a reply until the query has finished. For what you are attempting, you need to move the query to its own thread and let ServerCommandGet() exit so the client gets a reply and can move on, thus freeing it to send subsequent GETDATA requests. In ServerSessionEnd(), you will have to terminate the query thread if it is still running, and free the TSessionData object.
There are some other problems with your code as well.
ServerCommandGet() is checking for not Assigned(ARequestInfo.Session.Content) and then calling ARequestInfo.Session.Content.AddObject() when ARequestInfo.Session.Contentis nil. I don't see any code that is creating the ARequestInfo.Session.Content object.
If the client issues multiple EXECUTEQUERY requests, you are storing them all in AResponseInfo.Session.Content using the same name, 'U_SESSION'. GETDATA will return the results of only the first query it finds, and ServerSessionEnd() only frees the first query it finds. So either give each query a unique name and send that back to the client so it can include it in GETDATA and make ServerSessionEnd() loop through the entire Sender.Content, or else do not allow multiple queries in the same HTTP session. If the client issues a new EXECUTEQUERY while a previous query is still active, kill the previous query before starting the new one.
When the client issues GETDATA, the code needs to take into account that the requested query may not exist, such as if it previously expired and was freed by ServerSessionEnd(). Also, if a query does exist and has finished, you are calling AResponseInfo.ContentStream.CopyFrom() but AResponseInfo.ContentStream is nil when ServerCommandGet() is called. You are responsible for providing your own ContentStream object. So either take ownership of TSessionData's memory stream and assign it as the AResponseInfo.ContentStream object, or else create a new TMemoryStream to copy into and then assign that as the AResponseInfo.ContentStream object. Either way, TIdHTTPServer will free the AResponseInfo.ContentStream after sending it to the client.