Delphi TWebModule (IIS-ISAPI): get the current request - delphi

in a TWebModule procedure/function how get the current request?
I have tried:
procedure TWebModule1.DoSomething;
var
aRequest : TWebRequest;
begin
aRequest := Request;
end;
but it seems the first request produced on TWebModule creation.
I know i'm able to pass the request to subsequent Procedures/Functions from each TWebActionItem, but i want avoid to pass the request every where. Any tips?
Update
After digging into the code, i found WebContext and it seems the solution, eg.:
uses Web.WebCntxt;
procedure TWebModule1.DoSomething;
var
aRequest : TWebRequest;
begin
if WebContext <> nil then
aRequest := WebContext.Request;
end;
is it the right way? WebContext seems always nil.
I'm on Delphi Berlin update 2.

Every Request goes through a TWebActionItem defined in the TWebModule.Actions. The TWebActionItem has an event OnAction. There you will get the TWebRequest Object of the current Request.
Then you are able to pass it to subsequent Procedures/Functions.

Related

Delphi using TNetHTTPClient: how to give the final url after redirections?

I use, with Delphi 10.3.1, a TNetHTTPClient with GET command and I need to get the final URL after a page redirection(s).
Is there any property or function for it ?
Thx.
It seems there's no direct (public) access to request instance associated with response. A hacky solution relies on:
IHTTPResponse returned is implemented by THTTPResponse (implementation detail)
protected access to FRequest field of THTTPResponse
Then you can use following code to access request instance:
type
THTTPResponseAccess = class(THTTPResponse);
procedure TForm2.Button1Click(Sender: TObject);
var
Response: THTTPResponse;
Request: IURLRequest;
begin
Response := NetHTTPClient1.Get('http://google.com') as THTTPResponse;
Request := THTTPResponseAccess(Response).FRequest;
ShowMessage(Request.URL.ToString);
end;
Output is:
http://www.google.com/

Can TIndyHTTP's POST method be called multiple times very quickly?

I'm using Indy ver 10.5498 to post some multipart/form data including an attached file to an api. My code is adapted from that supplied to my by Remy in this post, with protocol error handling taken from here.
The code I now have works well and I get a response back from the server with data about 2 seconds after making the post.
However on occasion I might need to do a post multiple times very quickly, for example by looping through the dataset returned from a database and doing a post for each record.
Is there anything I need to know or any special code I need to write in order to deal with the situation where I might be making a second POST before the first POST has completed sending (or at least before the server's response has been received? Or is the POST a blocking call that does not return control until the response is received?
At the moment the TIdHTTP component is placed on the form, not dynamically created. I could create a new TIdHTTP object for every post and destroy it afterwards if that's necessary.
The code I use to do the post at the moment is below
function TForm1.Upload(url: string; params, filenames: TStrings): string;
var
FormData : TIdMultiPartFormDataStream;
ResponseText : string;
i : integer;
begin
FormData := TIdMultiPartFormDataStream.Create;
try
for i := 0 to params.Count - 1 do
FormData.AddFormField(params.Names[i], params.ValueFromIndex[i]);
for i := 0 to filenames.Count - 1 do
FormData.AddFile('attachment', filenames[i]);
//add authorisation header
IdHTTP1.Request.CustomHeaders.Add('Authorization:Basic ' + U_generalStuff.base64encodeStr(ATHORISATION_STR));
//way to use just one try except yet get the full text server response whether its a 400 error or a success response
//see https://stackoverflow.com/questions/54475319/accessing-json-data-after-indy-post
// Make sure it uses HTTP 1.1, not 1.0, and disable EIdHTTPProtocolException on errors
IdHTTP1.ProtocolVersion := pv1_1;
IdHTTP1.HTTPOptions := IdHTTP1.HTTPOptions + [hoKeepOrigProtocol, hoNoProtocolErrorException, hoWantProtocolErrorContent];
try
ResponseText := IdHTTP1.Post(url, FormData); //post to the api
except
on E: Exception do
begin
ResponseText := E.ClassName + ': ' + E.message;
raise;
end;
end; //try-except
finally
result := ResponseText;
end; //try finally
end;
I've since seen this post that talks about threading and using the parallel library. Is that something I should be investigating to answer my question?
Like most things in Indy, TIdHTTP.Post() is a synchronous operation. It does not return back to the caller until the response has been received in full (or until an error occurs). So, if you call TIdHTTP.Post() in a simple loop, you CANT send a new POST request until the previous request has fully completed. If you need to do that, you will have to call TIdHTTP.Post() asynchronously by calling it in multiple worker threads (and give each thread its own TIdHTTP object, as you can't reuse a TIdHTTP object across multiple simultaneous requests).

What is the best way of detecting that a Delphi TWebBrowser web page has changed since I last displayed it?

I want to display a 'news' page in a form using Deplhi TWebBrowser. The news page is a simple HTML page which we upload to our website from time to time and may be output from various tools. The display is fine but I'd like to know in my app whether it has changed since I last displayed it, so ideally I'd like to get either its modified date/time or its size / checksum. Precision is not important and ideally should not rely on properties that might fail because 'simple' tools were used to edit the HTML file such as NotePad. Checking on the web there are several document modified java calls but I really dont know where to start with those. I've looked through the numerous calls in Delphi's Winapi.WinInet unit and I see I can fetch the file with HTTP to examine it but that seems like cracking a walnut with a sledgehammer. I also cannot see any file date time functionality which makes me think I'm missing something obvious. I'm using Delphi XE5. In which direction should I be looking please? Thanks for any pointers.
You could use Indy TIdHTTP to send a HEAD request and examine Last-Modified / Content-Length headers.
e.g.:
procedure TForm1.Button1Click(Sender: TObject);
var
Url: string;
Http: TIdHTTP;
LastModified: TDateTime;
ContentLength: Integer;
begin
Url := 'http://yoursite.com/newspage.html';
Http := TIdHTTP.Create(nil);
try
Http.Head(Url);
LastModified := Http.Response.LastModified;
ContentLength := Http.Response.ContentLength;
ShowMessage(Format('Last-Modified: %s ; Content-Length: %d', [DateTimeToStr(LastModified), ContentLength]));
finally
Http.Free;
end;
end;
When the TWebBrowser.DocumentComplete event is fired make a HEAD request and store LastModified and ContentLength variables.
Then periodically make HEAD requests to test for changes (via TTimer for example).
These Header parameters are dependent on the web server implementation, and may not return file system date-time on the server (dynamic pages for example). your server might not result back these parameters at all.
For example, with static HTML pages on IIS, Last-Modified returns the file system last modified date-time, which is what you want.
For dynamic content (e.g. php, asp, .NET etc..), if you control the web-server, you might as well add your own custom HTTP response header on the server side to indicate the file system date-time (e.g. X-Last-Modified) or set the response Last-Modified header to your needs and examine this header on the client side.
If you need to examine/hash the entire HTTP content, you need to issue a GET method: http.Get(URL)
Thanks to a mixture of suggestions and pointers from kobik, David and TLama, I realised that I actually did need a sledgehammer and I finally came up with this solution (and I'm probably not the first, or the last!). I had to read the file contents because this did seem a better way of detecting changes. The code below calls "CheckForWebNewsOnTimer" from a TTimer infrequently and uses Indy to read the news page, make an MD5 hash of its contents and compare that with a previous hash stored in the registry. If the contents change, or 120 days elapses, the page pops up. The code has wrinkles, for example a change to a linked image on the page might not trigger a change but hey, its only news, and text almost always changes too.
function StreamToMD5HashHex( AStream : TStream ) : string;
// Creates an MD5 hash hex of this stream
var
idmd5 : TIdHashMessageDigest5;
begin
idmd5 := TIdHashMessageDigest5.Create;
try
result := idmd5.HashStreamAsHex( AStream );
finally
idmd5.Free;
end;
end;
function HTTPToMD5HashHex( const AURL : string ) : string;
var
HTTP : TidHTTP;
ST : TMemoryStream;
begin
HTTP := TidHTTP.Create( nil );
try
ST := TMemoryStream.Create;
try
HTTP.Get( AURL, ST );
Result := StreamToMD5HashHex( ST );
finally
ST.Free;
end;
finally
HTTP.Free;
end;
end;
function ShouldShowNews( const ANewHash : string; AShowAfterDays : integer ) : boolean;
const
Section = 'NewsPrompt';
IDHash = 'LastHash';
IDLastDayNum = 'LastDayNum';
var
sLastHash : string;
iLastPromptDay : integer;
begin
// Check hash
sLastHash := ReadRegKeyUserStr( Section, IDHash, '' );
Result := not SameText( sLastHash, ANewHash );
if not Result then
begin
// Check elapsed days
iLastPromptDay := ReadRegKeyUserInt( Section, IDLastDayNum, 0 );
Result := Round( Now ) - iLastPromptDay > AShowAfterDays;
end;
if Result then
begin
// Save params for checking next time.
WriteRegKeyUserStr( Section, IDHash, ANewHash );
WriteRegKeyUserInt( Section, IDLastDayNum, Round(Now) );
end;
end;
procedure CheckForWebNewsOnTimer;
var
sHashHex, S : string;
begin
try
S := GetNewsURL; // < my news address
sHashHex := HTTPToMD5HashHex( S );
If ShouldShowNews( sHashHex, 120 {days default} ) then
begin
WebBrowserDlg( S );
end;
except
// .. ignore or save as info
end;
end;

Indy sends params with GET after POST redirect

Using Delphi XE5 + Indy 10.
I am sending POST with login and password to log in. Site responds with redirect (302) to target page. In browser, redirect is handled by GET and everything goes right, but Indy continues with POST.
I solve this by using this code inside my OnRedirect handler:
procedure TForm1.MyRedirect(Sender: TObject;
var dest: string;
var NumRedirect: Integer;
var Handled: Boolean;
var VMethod: string);
var
TempHttp: TIdHttp;
begin
TempHttp := (Sender as TIdHTTP);
if (TempHttp.ResponseCode = 302) then
VMethod := 'GET';
Handled := true;
end;
Request method is then changed to GET, but Indy still sends POST request params with GET. So I get 413 Request Entity Too Large response.
How can I make Indy NOT send params with GET after redirect? Solution inside OnRedirect would be ideal.
Thanks!
Client behavior for handling the HTTP 302 reply code is ambiguous, and often treated erroneously by various clients. This is well documented in various RFCs, including 2068 and 2616. The 303 reply code was created to resolve the ambiguity, but many clients still do not support 303 yet, and many servers still use 302 expecting clients to behave as if 303 was used.
TIdHTTP has jumped back and forth many times over the years trying to figure out what behavior should be used when 302 is received - should it redirect using GET, or should it redirect using POST? In 2012, an hoTreat302Like303 flag was added to the TIdHTTP.HTTPOptions property to let users decide what to do. So make sure you are using an up-to-date version of Indy.
If 303 is received, TIdHTTP will clear its Request.Source property (thus ignoring any previous POST params) and send a GET request, ignoring the method returned by the OnRedirect event handler, if assigned.
If 302 is received:
if hoTreat302Like303 is enabled, TIdHTTP will clear its Request.Source property (thus ignoring any previous POST params) and send a GET request, ignoring the method returned by the OnRedirect event handler, if assigned.
if hoTreat302Like303 is disabled (which it is by default), TIdHTTP will send a request using the method returned by the OnRedirect event handler if assigned, otherwise it will send a request using the same method as the previous request that was redirected. But in either case, it does not clear its Request.Source property (thus any previous POST params will be re-sent). So if you change the method in the OnRedirect handler, you will have to update the Request.Source property accordingly as well, eg:
procedure TForm1.MyRedirect(Sender: TObject;
var dest: string;
var NumRedirect: Integer;
var Handled: Boolean;
var VMethod: string);
var
TempHttp: TIdHttp;
begin
TempHttp := (Sender as TIdHTTP);
if (TempHttp.ResponseCode = 302) then
begin
VMethod := 'GET';
TempHttp.Request.Source := nil; // <-- add this
end;
Handled := true;
end;

Indy TIdImap4.UIDRetrieve method!

Here is my little code:
curMessage:TIdMessage;
tidImap: TIdIMAP4;
...
tidImap.UIDRetrieve('123', curMessage);
That works fine! Now when i try to read
curMessage.Body
Then it is empty sometimes. I've understand that it is empty when message IsMsgSinglePartMime is False. So then i can't read message's body from Body property.
I've searched in curMessage's every property, but nowhere could i found the body text. What makes it even more odd, is that when i save curMessage
curMessage.Savefile('...');
then i can see all the body there.
I don't want to make another request to fetch for the body (eg UIDRetrieveText(2)) because i understand that the body data is there somewhere, i just could not find it or is Savefile/SaveStream making some internal requests to server?
Thank you guys in advance!
You need to be checking TIdMessage.MessageParts.
var
Msg: TIdMessage;
i: Integer;
begin
// Code to retrieve message from server
for i := to Msg.MessageParts.Count - 1 do
begin
if (Msg.MessageParts.Items[i] is TIdAttachment) then
// Handle attachment
else
begin
if Msg.MessageParts.Items[i] is TIdText then
HandleText(TIdText(Msg.MessageParts.Items[i]).Body);
end;
end;
end;
In Indy 10, TIdMessageParts has been moved into it's own unit, so you may have to add IdMessageParts to your uses clause.

Resources