I used to use Indy back in the Delphi 6 days, and I am playing with Indy 10 now. What I want to do is incredibly simple, but I don't see a simple way of doing it, so I must be missing something.
What I want to do is something like this:
Here is the actual code I am using:
procedure TForm1.btnGetURLClick(Sender: TObject);
begin
moHeader.Lines.Clear;
moBody.Lines.Clear;
try
moBody.text := IdHttp1.Get(edURL.text);
finally
end;
end;
When the request is complete, the http_result should contain the HTML from the URL specified. This doesn't seem to work however, so I get the feeling I should perhaps be using the IOHandler property or the OnWork event of the component - however the usage doesn't seem obvious to me, and I couldn't find any working examples with google. I am sure this is something that has been done before, so any help would be appreciated.
Additional Information:
In the spirit of being more specific, I want to know:
1. Am I doing this right to begin with (or did I miss something?).
2. If so, why might it not be working.
3. It is always possible that there is a bug in the combination of compiler/os/Indy I am using. (Although it should be working).
I should mention, I always get a popup "Connection Closed Gracefully". This seems to be an exception, and it could be interfering with the result of the function. I attempted to trap this with a TRY...FINALLY, but it doesn't work. Probably because Indy is triggering the exception in the background after the Get method runs I suppose.
Finally, here is a screencast of the program running to clear up any confusion:
http://screencast.com/t/NDMzNTQ5
I expect the HTML to fill the second memo box.
i think you have the TIdHTTP.HandleRedirects property set to false, if you get the error "HTTP/1.1 302 Found" you can try this
var
http_result:string;
Begin
IdHTTP1.HandleRedirects:=True;
http_result := IdHTTP1.Get('http://www.google.com');
End;
Another option, would be to use synapse. This is all that is needed to retrieve a webpage using this library:
uses
...,HTTPSEND;
var
Result : TStrings;
if HTTPGetText('http://www.google.com',Result) then
// do something with result
Synapse is a lightweight TCPIP library. The library is being actively maintained and the current version runs fine in Delphi 2009/2010. It is NOT a component based framework, so it is very easy to use with other threading techniques (OmniThreadLibrary or AsyncCalls for example).
You have to set the property HandleRedirects to true.
There's no need for a form, using GExperts components to code I got this:
var
IdHTTP: TIdHTTP;
IdHTTP := TIdHTTP.Create(Self);
with IdHTTP do
begin
Name := 'IdHTTP';
AllowCookies := True;
HandleRedirects := True;
HTTPOptions := [hoForceEncodeParams];
end;
Just paste this in your unit, it should be all you need.
Iirc if the website redirects, you also need to override some handler (onredirect or so). But this was also the case in indy9 iirc.
This question has lingered open for quite some time, so I am closing it out. My solution was to just use Synapse, as one of the posters suggested. It works on windows/Linux/Mac OS with minimal modifications, and works fine in libraries/threads.
Related
I am currently trying to connect to a database using an ODBC Alias to SQL Server. The problem I'm having is that when I use my TQuery object to get the information it always requests login details (nevermind whether I've specified them in the ODBC creation). I don't mind manually setting them in the code, but I can't find how to do that.
The most common solution I've found is to use the database component and go through that. However that comes with its own issues. Due to my dataset being so large and the database component converting the dataset to a Paradox table I keep getting a BDE error of 'Temporary Table Resource Limit'.
I don't get this error if I ignore the database component (which is fine) however this leaves me with the login prompt issue. Has anyone found a way to bypass this for TQuerys without swapping to other connection paths such as ADO?
I'm a bit rusty with the BDE but I don't think there's an easy way to avoid the login prompt if what you're saying is that you're not using a TDatabase component in your project.
The reason is that when you attempt to open your TQuery without a TDatabase (or TSession) component in your project, the default Session object in your app will call the routine below from within your TQuery's OpenCursor:
{ from DBTables.Pas }
function TSession.DoOpenDatabase(const DatabaseName: string; AOwner: TComponent): TDatabase;
var
TempDatabase: TDatabase;
begin
Result := nil;
LockSession;
try
TempDatabase := nil;
try
Result := DoFindDatabase(DatabaseName, AOwner);
if Result = nil then
begin
TempDatabase := TDatabase.Create(Self);
TempDatabase.DatabaseName := DatabaseName;
TempDatabase.KeepConnection := FKeepConnections;
TempDatabase.Temporary := True;
Result := TempDatabase;
end;
Result.Open;
Inc(Result.FRefCount);
except
TempDatabase.Free;
raise;
end;
finally
UnLockSession;
end;
end;
As you can see, if the session can't find an existing TDatabase component with the right name, it creates a temporary one, and it's the call to Result.Open that pops up the login prompt, without, so far as I can see, giving you any opportunity to supply the password + user name before the pop-up (the Session's OnPassword doesn't seem to get called in the course of this).
Obviously you need to check using the debugger that that's what's happening in your app, a temporary TDatabase being created, I mean.
If what I've suggested in the Update below didn't work and I were desperate to avoid using a TDatabase component, I would look into the possibility of maybe deriving a TQuery descendant, and trying to override its OpenCursor to see if I could jam in the user name/password.
Anyway, seeing as you say you're not using an explicit TDatabase, if I understand you correctly, because of the "Temporary Table ..." issue, and seeing as the Session will create a temporary one anyway, I suppose it might be worth your while investigating why the temporary one doesn't provoke the "Temporary Table" error, whereas using a TDatabase component in your app evidently does. Idapi32.Cfg configuration issue, maybe? At the moment, I can't help you with that because I can't reproduce your "Temporary Table" error, despite using my TQuery to do a SELECT on a SqlServer table to return 250,000+ rows.
Oh, that's a point: Does your table contain any BLOBs? I seem to recall there's an Idapi config parameter that lets you reduce the temporary storage space the BDE uses for BLOBs (to zero, maybe, but it's been a long time since I used the BDE "for real").
Update: The thought just occurred to me that since your query seems to work with Session dynamically creating a TDatabase object, maybe it would also work with a TDatabase which you dynamically create yourself. I just tried the following, and it works for me:
procedure TForm1.DatabaseLogin(Database: TDatabase;
LoginParams: TStrings);
begin
LoginParams.Add('user name=sa');
LoginParams.Add('password=1234');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ADatabase : TDatabase;
begin
ADatabase := TDatabase.Create(Self);
ADatabase.AliasName := 'MAT41032';
ADatabase.DatabaseName := 'MAT41032';
ADatabase.SessionName := 'Default';
ADatabase.OnLogin := DatabaseLogin;
Query1.Open;
end;
+1 for an interesting question, btw.
I tried to employ Indy 10.5.5 (shipped with Delphi 2010) for:
connecting to telnet server
performing username/password authentication (gaining access to the command shell)
executing a command with returning resulting data back to application
and had no success, additionally i'm completely lost in spaghetti logic of Indy's internals and now have no idea why it didnt work or how i supposed to send strings to the server and grab the results. Need some sample code to study.
Formal form of the question: Where can i get 3-rd party contributed demo covering TIdTelnet component? (indyproject.org demos webpage do not have one)
The main problem with Telnet is that it DOES NOT utilize a command/response model like most other Internet protocols do. Either party can send data at any time, and each direction of data is independant from the other direction. This is reflected in TIdTelnet by the fact that it runs an internal reading thread to receive data. Because of this, you cannot simply connect, send a command, and wait for a response in a single block of code like you can with other Indy components. You have to write the command, then wait for the OnDataAvailable event to fire, and then parse the data to determine what it actually is (and be prepared to handle situations where partial data may be received, since that is just how TCP/IP works).
If you are connecting to a server that actually implements a command/response model, then you are better off using TIdTCPClient directly instead of TIdTelnet (and then implement any Telnet sequence decoding manually if the server really is using Telnet, which is rare nowadays but not impossible). For Indy 11, we might refactor TIdTelnet's logic to support a non-threaded version, but that is undecided yet.
done with indy.
no comments.. just som old code :-)
telnet don't like the send string kommand.. use sendch.
telnetdude.Host := 1.1.1.1;
try
telnetdude.connect;
except
on E: Exception do begin
E.CleanupInstance;
end; {except}
if telnetdude.Connected then begin
for i := 1 to length(StringToSend) do telnetdude.sendch(StringToSend[i]);
telnetdude.sendch(#13);
end;
end; {while}
end; {if}
if telnetdude.Connected then telnetdude.Disconnect;
end;
I hope this helps anyone looking for answers to a similar question.
Firstly, It would seem the typical command/response model (as mentioned above, does indeed NOT apply).
So I just got it working for some very simple application (rebooting my router).
Specific additions to above code from Johnny Lanewood (and perhaps some clarification)
a) You have to send #13 to confirm the command
b) I got "hangs" on every command I sent / response I requested UNTIL I enabled ThreadedEvent. (this was my big issue)
c) the OnDataAvailable event tells you when new data is available from the Telnet Server - however there are no guarantees as to what this data is - i.e. it's pretty what you get in the command line / what ever is appended to the previous responses. But is is NOT a specific response line to your command - it's whatever the telnet server returns (could be welcome info, ASCII drawings etc etc.)
Given (c) above, one would rather check the OnDataAvailable event and parse the data (knowing what you'd expect). When the output stops (i.e. you need build a mechanism for this), you can parse the data and determine whether the server is ready for something new from the client. For the purpose of my code below, I set a read timemout and I just used Sleep(2000) - ignorantly expecting no errors and that the server would be ready after the sleep for the next command.
My biggest stumbling block was ThreadedEvent := True (see above in b)
Thus, my working solution (for specific application, and possibly horrible to some).
lIDTelnet := TIdTelnet.Create(nil);
try
lIdTelnet.ReadTimeout := 30000;
lIDTelnet.OnDataAvailable := TDummy.Response;
lIDTelnet.OnStatus := TDummy.Status;
lIdTelnet.ThreadedEvent := True;
try
lIDTelnet.Connect('192.168.0.1', 23);
if not lIDTelnet.Connected then
Raise Exception.Create('192.168.0.1 TELNET Connection Failed');
Sleep(2000);
lIdtelnet.SendString(cst_user + #13);
Sleep(2000);
lIdtelnet.SendString(cst_pass + #13);
Sleep(2000);
lIdtelnet.SendString(cst_reboot + #13);
Sleep(2000);
if lIDTelnet.Connected then
lIDTelnet.Disconnect;
except
//Do some handling
end;
finally
FreeAndNil(lIdTelnet);
end;
and then
class procedure TDummy.Response(Sender: TIdTelnet; const Buffer: TIdBytes);
begin
Write(TDummy.ByteToString(Buffer));
end;
class function TDummy.ByteToString(
const aBytes: TIdBytes): String;
var
i : integer;
begin
result := '';
for i := 0 to Length(aBytes) -1 do
begin
result := result + Char(aBytes[i]);
end;
end;
I have a Com Object, setup/create/working from a DataModule.
creating/running/freeing the Datamodule from an Application works with out an issue.
but putting the datamodule into a DLL works fine the first time, runing the com object etc.. but after a few calls with out restarting the application, this error appears.
Error Message image http://darkaxi0m.name/so/errormessage.GIF
There is a fare bit of code in the App, so i cant post it all,
I have tried MadExcept in both the Application and Dll, with no luck. The IDE Breaks at a point that does not seem much help...
alt text http://darkaxi0m.name/so/cpubreak.gif
this is the code that handles the DataModule, the same function is used in the Application and the Dll in both tests
function GetAmount( Amount : integer; var Info: PChar): integer; stdcall;
var
tempInfo: string;
workerDM : TworkerDM;
begin
Result := 0;
workerDM := TworkerDM.Create(nil);
try
tempInfo:= Info;
Result := workerDM.GetAmount(Amount, tempInfo);
StrPCopy(Info, tempInfo);
finally
workerDM.Free;
end;
end;
i would like to blame the Ole Object, but it works fine out of the Dll
I'm at a loss to even think where to start looking.
In the finally, you are calling Free, but should call workerDM.Free.
I don't believe this question can be answered any more.
The project has be scraped, and the object that produce the error no longer used.
My Delete Requests have gone unanswered.
So this is now my answer.
I have a Delphi form inside a DLL (I know that this restricts the use of the DLL to Delphi but this is not a problem in this case).
The DLL exports a function ShowForm that looks roughly like this:
procedure ShowForm (App : TApplication);
begin
OldApp := Application;
try
Application := App;
MyForm := TMyForm.Create (nil);
try
MyForm.ShowModal;
finally
FreeAndNil (MyForm);
end;
finally
Application := OldApp;
end;
end;
Now on the form I use a TAdvOfficeHint (from the TMS component pack). Unfortunately the hints do not show up.
Am I missing something here? How can I make the form behave exactly as it would if I showed it from the main application?
Thanks!
I don't know TAdvOfficeHint but I guess it hooks Application.OnShowHint to set its own THintWindowClass, and even if both the main executable and the DLL are linking in the TMS unit, they each have their own copy of the class which is where things go wrong.
Assigning Application is not enough: there are other global variables, like Screen, Mouse, etc. Others are even hidden in the implementation so I'd say your chances to make the form behave exactly as from the main application are slim.
Just found the reason why it does not work. As TOndrej states, TAdvOfficeHinthooks Application.OnShowHint and internally executes the following line of code:
FHintInfo.Assign (AHintInfo);
Assign internally uses a dynamic type check
if (Source is TAddvHintInfo) then ...
which fails due to the separate type registries of the DLL and the main application.
I have run into this problem a few times now and maybe I really have to switch to runtime packages to avoid all this stuff.
Anyway, if there's anything I can do to prevent this, please comment.
Wrong setting of Application.
Try this and see if it solves your problem:
procedure ShowForm (AppHandle : THandle);
begin
OldAppHandle := Application.Handle;
try
Application.Handle := AppHandle;
........
finally
Application.Handle := OldAppHandle;
end;
end;
I guess in Delphi 2006 and later versions you can call System.ShareMemoryManager method in the EXE code, so that its memory manager is shared with other modules loaded in the process memory space.
I get an EInOutError with message 'Too many open files' when executing this code block repeatedly for some time from a number of client threads:
var InputFile : Text;
...
Assign (InputFile, FileName);
Reset (InputFile)
try
// do some stuff
finally
CloseFile (InputFile);
end;
The number of client threads is approximately 10, so only 10 files can be open at any time. Is there any possibility that Delphi refuses to close files right away? Can I ensure that it does? Or am I making a mistake here? This is the only place where I open files and the try..finally block should guarantee that opened files get closed, shouldn't it?
REEDIT: forget the edit
I can only advise you to use the more "modern" facilities for dealing with files. I don't know whether there is a limit of open files using the Windows API, but I just tested and could easily open 1000 streams in parallel:
procedure TForm1.Button1Click(Sender: TObject);
var
Strs: TList;
i: integer;
begin
Strs := TList.Create;
try
for i := 1 to 1000 do begin
Strs.Add(TFileStream.Create('D:\foo.txt', fmOpenRead or fmShareDenyWrite));
end;
finally
FreeObjectList(Strs);
end;
end;
I have never understood why people still use untyped files instead of TStream and its descendants in new code.
Edit: In your comment you write that you only want to read plain text files - if so just create a TStringList and use its LoadFromFile() method.
You aren't running this on an older Windows 9x based computer, are you? If so, you might be running into a DOS filehandle problem.
Delphi closes immidiately in the CloseFile. Your example code seems to be correct.
Try again without anything between try and finally.
There IS a thread safety issue here although I can't see how it could cause the problem.
The problem is Reset uses the global FileMode variable.
As for client threads--are you sure they aren't leaking away on broken connections or something?
Might be useful to put some debug output alongside the Reset and the Close so you can see how long each thread has the file open for.
Do you really need threads? It sounds like they are causing you problems. Your code would be easier to debug without them.
This code should work just fine. There are no known problems related to using files from threaded code (as far as I know). We use such idioms fairly regularly and everything works fine.
I would suggest adding some logging code (before Assign and CloseFile) to see if a) close is executed and b) you really have only 10 threads running. Maybe your thread terminating logic is faulty and CloseFile never executes.