fphttpclient works fine with simple examples like
procedure ReadFromURL(theURL: string);
var
httpClient: TFPHTTPClient;
FileContents: String;
theStatusCode: integer;
begin
httpClient := TFPHTTPClient.Create(nil);
try
FileContents := httpClient.Get(theURL);
theStatusCode := httpClient.ResponseStatusCode;
if theStatusCode = 200 then
begin
; // do something
end
else
ShowMessage(IntToStr(theStatusCode));
finally
httpClient.Free;
end;
end;
but only, if the URL exists, so that the status code is 200. In other cases the code crashes at httpClient.Get with an exception of class EHTTPClient, ESocketError or EXC_BAD_ACCESS, although the procedure uses a try ... finally section (formulating it as try ... except doesn't change anything). Unfortunately, the exception is raised before the status code can be processed.
What is the recommended way to handle errors with fphttpclient? Is there any method to check for the existence of a resource (and, possibly, the correctness of an URL, too), before invoking the Get method?
Make sure the various events are assigned, so that the class knows what to do on password prompts, redirects etc.
Standard examples init the class like
With TFPHTTPClient.Create(Nil) do
try
AllowRedirect:=True;
OnRedirect:=#ShowRedirect;
OnPassword:=#DoPassword;
OnDataReceived:=#DoProgress;
OnHeaders:=#DoHeaders;
{ Set this if you want to try a proxy.
Proxy.Host:='ahostname.net.domain';
Proxy.Port:=80;
etc.
Please study examples and try to sort through your problems till you have reproducible cases. If then there is still a problem, please submit it to the bugtracker
In rare cases, checking the existences of headers with HEAD() can speed up e.g. sifting through lists of old urls
Related
The good (and bad) old Delphi taught us the "classic" way of building application because of the way we write code "behind" the IDE.
Based on this paradigm, I built some time ago a library that allows me to save/load the GUI to INI file with a single line of code.
LoadForm(FormName)
BAM! That's it! No more INI files!
The library only saves "relevant" properties. For example, for a TCheckBox it saves only its Checked property not also its Color or Top/Left. But it is more than enough.
Saving the form has no issues. However, I have "problems" during app initialization. They are not quite problems, but the initialization code is not that nice/elegant.
For example when I assign Checkbox1.Checked := True it will trigger the OnClick event (supposing that the checkbox was unchecked at design time). But assigning a False value (naturally) will not trigger the OnClick event.
Therefore, I have to manually call CheckBox1Click(Sender) after SaveForm(FormName) to make sure that whatever code is in CheckBox1Click, it gets initialized. But this raises another problem. The CheckBox1Click(Sender) might be called twice during application start up (once by SaveForm and once my me, manually).
Of course, in theory the logic of the program should be put in individual objects and separated from the GUI. But even if we do this, the initialization problem remains. You load the properties of the object from disk and you assign them to the GUI. When you set whatever value you have in your object to Checkbox1, it will (or not) call CheckBox1Click(Sender) which will set the value back into the object.
On app startup:
procedure TForm.FormCreate (Sender: TObject);
begin
LogicObject.Load(File); // load app logic
Checkbox1.Checked := LogicObject.Checked; // assign object to GUI
end;
procedure TForm.CheckBox1Click(Sender: TObject);
begin
LogicObject.Checked := Checkbox1.Checked;
end;
Probably the solution involves writing stuff like this for EVERY control on the form:
OldEvent := CheckBox1.OnClick;
CheckBox1.OnClick := Nil;
CheckBox1.Checked := something;
CheckBox1.OnClick := OldEvent;
Not elegant.
Question:
How do you solve this specific problem OR what is your approach when saving/restoring your GUI to/from disk?
This is one of the things which botthered me in some components from the beginning. What I know the are 3 options, except separating GUI and the business logic as #David said, which is not always an option.
As you wrote above, always unassign the events so they don't get triggered
Use non-triggered events such as OnMouseDown or OnMouseUp
Or a similar solution that I use and I think is the most elegant
Create a global variable FormPreparing which is set during initialization and check its value at the beginning of the events like below.
procedure TForm.FormCreate (Sender: TObject);
begin
FormPreparing := True;
try
LogicObject.Load(File); // load app logic
Checkbox1.Checked := LogicObject.Checked; // assign object to GUI
finally
FormPreparing := False;
end;
end;
procedure TForm.CheckBox1Click(Sender: TObject);
begin
if FormPreparing then
Exit;
LogicObject.Checked := Checkbox1.Checked;
end;
I am currently using this code to retrieve the content as string after it is has been rendered by TWebBrowser and you visually can see it shows the content loaded dynamicly through AJAX:
function TMyViewerIE.GetDocumentAsStr: string;
begin
if Assigned(FWebBrowser.Document) then
begin
iall := (FWebBrowser.Document AS IHTMLDocument2).body;
while iall.parentElement <> nil do
begin
iall := iall.parentElement;
end;
Result := iall.outerHTML;
end
;
end;
However, this code does not appear to include changes performed by AJAX.
Note: For those who believe above code is "out of context", please also see this SO where a variation of this code was suggested as a solution:
Delphi TWebBrowser get HTML source after AJAX load
Problem is that it does not work for me. I had thought above would work since (again I thought) AJAX changes the internal document structure and that was what was saved.
Maybe I am missing something - if anyone knows the answer, I will appreciate it. I will also continue my own research and post results.
Note: I may try construct an AJAX page (or public / goverment page) so I can share my test cases more easily. I believe that will satisfy suggestions about a working example.
I currently am in the process of porting several Windows desktop applications to a single web site.
The current setup includes several SQL server backend databases, configured with Windows Authentication (SSPI) only, and every user/group/role has specific rights on specific objects. Which is convenient, because the application layer doesn't have to implement any access control.
I'd like to keep it the same way with the web server, an Apache on a Windows machine. But every connection to the databases is being made using Apache's account. That's understandable and expected, in fact Apache is deliberately given access to public data, to be able to deliver public content.
But in case a domain user logs in (the login process is already implemented) I'd like the Apache process that handles the request to impersonate that user, and thus act as them during the whole request.
At first, I tried php's fastcgi.impersonate trick, using IIS as the web server. But I eventually gave up on that, mainly because (1) we had to port to Apache anyway and (2) it was php-specific, and it turned out we should be targeting the web server as a whole...
So I redirected my search to Apache modules. Months of research gave no fruits, other than mod_auth_sspi and the like, which apparently isn't what I'm looking for (authentication and impersonation are two different things).
Finally I decided to make my own module. Most of the "101" examples I could find are written in C, but I managed to find 2-3 ones in Lazarus/FPC, which is what I've been using for quite a while now, but never for such a task.
I know I have to build a .dll project, I know (more or less) what units to use and I know functions like LogonUser() and ImpersonateLoggedOnUser() should be in my toolbox.
Has anyone done anything similar? Can anyone point me to the right direction?
An example would be appreciated, even if it's a simple proof of concept. This question is far from asking for a final, definitive solution.
I eventually came up with the following:
library mod_winimpersonate;
{$mode objfpc}{$H+}
uses SysUtils, Windows, httpd, apr, Classes;
function DefaultHandler(r: Prequest_rec): Integer; cdecl;
Var
cookies:TStringList;
logindata,username,password:String;
p:Integer;
begin
RevertToSelf;
cookies:=TStringList.Create;
cookies.Delimiter:=';';
cookies.DelimitedText:=apr_table_get(r^.headers_in,'COOKIE');
logindata:=URLDecode(cookies.Values['WinImpersonate']);
If Length(logindata)>0 then
Begin
p:=Pos(':',logindata);
username:=LeftStr(logindata,p-1);
password:=RightStr(logindata,Length(logindata)-p);
ChangeLoggedInUser(username,password,'');
End;
Result:=DECLINED;
end;
procedure RegisterHooks(p: Papr_pool_t); cdecl;
begin
ap_hook_handler(#DefaultHandler, nil, nil, APR_HOOK_REALLY_FIRST);
end;
var
TheModule: module;
exports TheModule name 'winimpersonate_module';
begin
FillChar(TheModule, sizeof(TheModule), 0);
STANDARD20_MODULE_STUFF(TheModule);
with TheModule do
begin
name := 'mod_winimpersonate.dll';
register_hooks := #RegisterHooks;
end;
end.
This is by no means a final solution, but it's a start. The logic is the following:
Revert to the Apache account. This is a must, in case we are using a recycled Apache thread that previously impersonate someone else.
Retrieve the user's credentials from a cookie named 'WinImpersonate', in the form of username:password. This needs more work (maybe encrypt the credentials, or store them in a safe(?) place up on the server or something even more secure)
Impersonate the user, with the help of the windows unit.
Return DECLINED, so Apache knows we didn't handle the request, and it should continue asking modules for the right handler.
There are many concerns to be addressed, in order for this to achieve a decent security level. Among others, the protection of the credentials, and the browser cache. But as I said, it's a start, as well as a proof of concept.
You'll notice that there are two utility functions missing from the above listing:
URLDecode decodes a url-encoded string:
// Convert URLEncoded string to utf8 string
function URLDecode(const s: String): String;
var
sAnsi: String;
sUtf8: String;
sWide: WideString;
i, len: Cardinal;
ESC: string[2];
CharCode: integer;
c: char;
begin
sAnsi := PChar(s);
SetLength(sUtf8, Length(sAnsi));
i := 1;
len := 1;
while (i <= Cardinal(Length(sAnsi))) do
begin
if (sAnsi[i] <> '%') then
begin
if (sAnsi[i] = '+') then c := ' ' else c := sAnsi[i];
sUtf8[len] := c;
Inc(len);
end
else
begin
Inc(i);
ESC := Copy(sAnsi, i, 2);
Inc(i, 1);
try
CharCode := StrToInt('$' + ESC);
c := Char(CharCode);
sUtf8[len] := c;
Inc(len);
except
end;
end;
Inc(i);
end;
Dec(len);
SetLength(sUtf8, len);
sWide := UTF8Decode(sUtf8);
len := Length(sWide);
Result := sWide;
end;
ChangeLoggedInUser tries to login the user using the credentials supplied, and upon success it tries to impersonate him:
Function ChangeLoggedInUser(username, password, domain: string):Boolean;
var
creds: Cardinal;
begin
Result:=False;
try
if LogonUser(PChar(username)
,PChar(domain)
,PChar(password)
,LOGON32_LOGON_NETWORK_CLEARTEXT
,LOGON32_PROVIDER_DEFAULT
,creds
) then
begin
ImpersonateLoggedOnUser(creds);
Result:=True;
end;
finally
//wipe the memory for security
FillChar(username,SizeOf(username),#0);
FillChar(password,SizeOf(username),#0);
FillChar(domain,SizeOf(username),#0);
end; //try-finally
end;
Hope someone finds this useful. Comments are more than welcome.
I added a few custom pages to my setup. In one of this custom pages I do some checks. If this checks failed, I want switch to finish page. How can I do this?
I can not do this with ShouldSkipPage event function because:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
// this will NEVER happened - see documentation below
if (PageID = wpInstalling) or (PageID = wpPreparing) or (PageID = wpWelcome) then
begin
// skip install - simply for example
result := True;
exit;
end;
resutl := false;
end;
From Inno Setup documentation:
The wizard calls this event function
to determine whether or not a
particular page (specified by PageID)
should be shown at all. If you return
True, the page will be skipped; if you
return False, the page may be shown.
Note: This event function isn't called
for the wpWelcome, wpPreparing, and
wpInstalling pages, nor for pages that
Setup has already determined should be
skipped (for example,
wpSelectComponents in an install
containing no components).
I'm sorry, i did not understand why you cannot use ShouldSkipPage.
The usual way of doing it is in ShouldSkipPage:
function ShouldSkipPage(curPageId : Integer) : Boolean;
begin
{For a certain condition, skip to wpFinished}
if (SomeCondition and (curPageId <> wpFinished)) then
Result := True
{Probably more checks}
else
Result := False
end;
If I understand you correctly, you are performing a check, and if it fails, you want to skip wpWelcome, wpPreparing, and wpInstalling (and perhaps more custom pages).
I assume that you also want to skip the actions performed when these pages are shown, specifically, you don't want to perform the installation step.
If that is the case, you should cancel the installation, not continue it without performing the actual installation steps. You can do it in InitializeSetup, for example:
Procedure InitializeSetup();
VAR
Check: Integer;
BEGIN
// perform you check here, set the variable according to it's result
IF (Check <> 0) THEN
// abort installation
Return False;
ELSE
Return True;
END;
Edit
In response to your comment: The easiest solution would be to show a message box with the result of your check, instead of a complete wizard page. If that is not enough for your purposes I would suggest the following approach:
Perform your check in InitializeSetup and store the result in a global variable.
Create a new wizard page (lets call it CheckResult) to display the results of your check, it should be displayed directly after wpWelcome.
In that page's OnNextButtonClick just call CancelButtonClick, that way the installation is always aborted when this page is displayed
Now is the time to modify ShouldSkipPage ;-) If the global check variable indicates that everything is ok, skip your CheckResult page, so that the installation is not aborted automatically
This should work, but if you somehow can, follow the KISS principle and go with the message box approach.
Has anyone seen this error when trying to call an external C function from an Oracle query? I'm using Oracle 10g and get this error every time I try to call one of the two functions in the library. A call to the other function returns fine every time, though the function that works is all self-contained, no calls to any OCI* functions.
Here's the stored procedure that is used to call the failing C code:
CREATE OR REPLACE PROCEDURE index_procedure(text in clob, tokens in out nocopy clob, location_needed in boolean)
as language c
name "c_index_proc"
library lexer_lib
with context
parameters
(
context,
text,
tokens,
location_needed
);
Any help would be appreciated. Everything I've found on this error message says that the action to take is: Contact Oracle customer support.
Edit: I've narrowed it down to the point that I know that there is a segfault deep in libclntsh after I call OCILobTrim (to truncate it down to 0 length) on the tokens clob. Here is the code I've been using to call this procedure.
declare text CLOB; tokens CLOB;
begin
dbms_lob.createtemporary(tokens, TRUE);
dbms_lob.append(tokens, 'token');
dbms_lob.createtemporary(text, TRUE);
dbms_lob.append(text, '<BODY>Test Document</BODY>');
index_procedure(text, tokens, FALSE);
dbms_output.put_line(tokens);
end;
/
Is there something wrong with this setup that might be causing OCILobTrim problems?
It looks like this is one of those errors that essentially means any number of things could have gone wrong with the external procedure.
There is a known bug in 10.2.0.3, no idea if it's relevant:
ORA-28579 occurs when trying to select
data from a pipelined table function
implemented in "C" using the
ODCITable/ANYDATASET interface.
ODCITableDescribe works fine but
ODCITableFetch generates an ORA-28579
error.
I would suggest:
Look in the database server
trace directories, and the directory
where the external proc is located,
for any log or trace files generated
when the error occurs.
Instrument your external proc in
some way so that you can try to
trace its execution yourself.
Contact Oracle support
Well, an upgrade to 10.2.0.4 (was using 10.2.0.1) at least gave me an understandable error instead of a fairly useless core file and the ORA-28579.
It turns out that the code I was debugging was assuming that calling OCILobRead would return all of the data in one pass. This is the case for any client using a fixed width character set.
For clients using a variable width character set, this isn't the case, OCILobRead was actually reading part of the data and returning OCI_NEED_DATA and future calls to OCILobTrim and OCILobWrite were failing because of the still pending call to OCILobRead. The solution was to loop OCILobRead calls until OCI_NEED_DATA was no longer returned and we had all of the needed data in our buffer.
A call to OCIBreak also would have allowed the OCILobTrim and OCILobWrite functions to continue, though we wouldn't have had all of the needed input data.