I am implementing a REST server API in Delphi XE3 (first time using Delphi in about a decade so am a bit rusty). Currently it is using Indy server for debug purposes, but eventually it will be an ISAPI dll.
Now I have implemented a number of TDSServerClass classes and want to access the request header within the class methods. So for example when the user requests mysite.com/datasnap/rest/foo/bar I want to be able to read the header within the foo class method called bar. Is this possible?
If not, is it possible to create a global filter of incoming requests before they get to the REST class method? I need to check the API key and user authentication on incoming requests and not sure the best way to implement. Thanks.
I don't know if anything changed in XE3, but in XE2 you can do the following:
uses
Web.HTTPApp,
Datasnap.DSHTTPWebBroker;
function TServerMethods1.EchoString(Value: string): string;
var
Module: TWebModule;
begin
Module := GetDataSnapWebModule;
Result := Module.Request.RemoteIP + ': ' + Value;
end;
Related
I'm using Delphi 10.1 Berlin Update 2 Enterprise and the DataSnap client/server REST framework.
If I run the app without debugging and invoke a method the user isn't authorized to invoke, the code runs without any exception and the method returns a null response.
When interactively debugging a call on the client to a DataSnap server method, I get two popup exceptions regarding "unauthorized".
The first bubbles up and is replaced by the second.
The second exception gets "eaten" and the session/connection simply closed and then the method returns a blank result (e.g. a zero if the return type is integer, and an empty string for a string return type).
This is happening in the following section of code near the end of the ExecuteRequest method in the Datasnap.DSClientRest unit:
except
on E: TDSRestProtocolException do
LSessionExpired;
end;
Why are these exceptions (e.g. TDSRestProtocolException) not reaching my code?
I kind of think this is new to Update 2, and I remember seeing those exceptions bubble up to my code prior to Update 2.
Attached is a skeleton example (standard example generated by Delphi wizards) that demonstrates the issue - click the button and you get "" instead of "4321" because the user isn't authorized - but no runtime exception.
I'm new to DataSnap, so bear with me :-)
Thanks in advance for helpful responses =)
This is happening due to DSAuthenticationManager1 component added to webmodule of the server and client side is failing to authenticate.
Please go through this to check how to work with authentication
Adding Authentication and Authorization
Well..I'm not sure but try providing username and password to DSRestConnection1 component before the instance of server methods gets created
procedure TClientModule1.TestCon(aUsername, aPassword: string);
var
lServerMethodsClient : TServerMethodsClient;
begin
DSRestConnection1.UserName := aUsername;
DSRestConnection1.Password := aPassword;
lServerMethodsClient:=TServerMethodsClient.Create(DSRestConnection1);
end;
and try to call this functn from ur clientform
procedure TF_ClientForm.Button1Click(Sender: TObject);
begin
ClientModule1.TestCon(EdtUsername.Text, EdtPassword.Text);
end;
Maybe a little late but this morning I've had a deep dive into this because, after upgrading from Delphi XE6 to Tokyo 10.2, applications where I used the TDSRestConnection component got broken. Although I supplied the correct username and password, they did not appear in the TDSAuthenticationManager.OnUserAuthenticate event.
The 'problem' has to do with the new System.Net.HttpClient implementation.
To make a long story short (or a little bit less long):
The client component does not send the credentials until the receiving server demands one by sending a 401 response. After receiving this (properly formatted) response the client looks at de TDSConnection credentials en tries again. At the client side a complete list of urls with credential requirements is maintaned so repetitive calls to the same url go 'smoother'.
I added this code to the server's WebModule (where the TDSRESTWebDispatcher resides) which solved my problems:
procedure TwbmMain.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
LAuthorization: string;
begin
inherited;
if Request.PathInfo.StartsWith('/datasnap/') then
begin
LAuthorization := TNetEncoding.Base64.Decode(Request.Authorization.Replace('Basic ', ''));
if LAuthorization.IsEmpty then
begin
Response.StatusCode := 401;
Response.WWWAuthenticate := 'Basic';
Handled := True;
end;
end;
end;
Because my applications provides some downloadable items like a logo etc., I limited the check to just those URLs that have anything to do with datasnap.
Hope this is useful to others!
I am using Indy10 and have created a Web server using a class derived from TIdHttpServer. In my subclass I override the DoMaxConnectionsExceeded method. And this seems to be properly firing when MaxConnections is exceeded.
In earlier Indy versions, at least according to Remy Lebeau's comment here, there was MaxConnectionReply property on TIdHttpServer. This could be used to create custom messages if MaxConnections was exceeded. This doesn't seem to be the case with Indy 10.
Is there a way with Indy10 that you can create custom messages when MaxConnections is exceeded?
As I stated in the thread you linked to, MaxConnectionReply is implemented by TIdCmdTCPServer, which TIdHTTPServer does not derive from. Since you are overriding DoMaxConnectionsExceeded(), you will have to send your own reply to the client manually, and make sure it is properly HTTP-formatted, for example:
procedure TMyHttpServer.DoMaxConnectionsExceeded(AIOHandler: TIdIOHandler);
var
Html: TIdBytes;
begin
Html := ToBytes('<html><body>500 - Too many connections</body></html>');
AIOHandler.WriteLn('HTTP/1.0 500 Too many connections');
AIOHandler.WriteLn('Content-Type: text/html');
AIOHandler.WriteLn('Content-Length: ' + IntToStr(Html));
AIOHandler.WriteLn('Connection: close');
AIOHandler.WriteLn;
AIOHandler.Write(Html);
end;
I am including these in uses clause
IdAuthentication
,
IdAuthenticationDigest
,
IdAuthenticationNTLM
,
IdAuthenticationSSPI
Currently I have code that does this:
W.Request.BasicAuthentication := True;
W.Request.Username := AOptionsPtr^.AuthUsername;
W.Request.Password := AOptionsPtr^.AuthPassword;
And if I have access to OpenSSL:
TmpOpenSSL := TIdSSLIOHandlerSocketOpenSSL.Create;
TmpOpenSSL.SSLOptions.Method := sslvSSLv23;
TmpOpenSSL.SSLOptions.Mode := sslmClient;
TmpOpenSSL.SSLOptions.VerifyMode := [];
TmpOpenSSL.SSLOptions.VerifyDepth := 0;
//--
W.IOHandler := TmpOpenSSL;
From skimming the documentation for WinINet (yes, I know it is not Indy) it seems persistent connections is also required for authentication. I suppose this also goes for Indy? URL:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa384220(v=vs.85).aspx
I need to get this to work with SharePoint. The problem, however, is hat I have no intranet SharePoint server to test against. Thus I can not step through the code and see what works and what does not. However, I have a potential customer that can test it for me.
What more do I need to do to get above code working with SharePoint using Windows Authentication (NTML? SPPI?)
Will Indy automatically test and use proper auhentication?
do I need to set W.Request.BasicAuthentication := False; for auto authentication/detection to work?
If multiple requests are necessary (with first response being 401) I assume I need to add support for this in my own code when making a GET request? (To set authentication mode and make a new request?)
You can request a persistent connection by setting the Request.Connection property to 'keep-alive'.
TIdHTTP will check the server's WWW-Authorization header and compare it to the TIdAthentication classes you have included in your uses clause. The TIdHTTP.OnSelectAuthorization event will tell you which class was picked, and allow you to override it if needed. The TIdHTTP.OnAuthorization event will be triggered if authentication fails and different credentials are needed.
The BasicAuthrnication property simply allows TIdHTTP to fall back to TIdBasicAuthentication if no other TIdAuthentication class is assigned.
No, you do not need to handle multi-request authentications manually, like NTLM. TIdHTTP and TIdAuthentication handle those details for you.
I use Delphi7 and I need to use some reports I have made before in SSRS 2008 within delphi.Actually I want to call them within Delphi. I have Used a WSDl importer and imported reportservice2005.asmx and delphi gave me a PAS file with list of SSRS methods but when I try to create an instance of ReportingService2010Soap with GetReportingService2010Soap Function gives me some errors!. Is there anywhere to find a Document for using this PAS file?
thank you and excuse my bad English!
The Delphi 7 WSDL Importer (wsdlimp.exe) has an update that can be downloaded from Embarcadero ID: 24535, Delphi SOAP Runtime and Importer Update
Here are 3 informative articles. Consuming ASMX web services in Delphi is pretty simple whether it's Delphi 7 or a more recent version.
1. Consuming C# Web Services with Delphi 7 Professional
2. Delphi 2010 and WCF Clients
3. Introduction to WCF Programming in Delphi
Apart from that, during development you can enclose your web service calls in a try except block like this
uses
SysUtils,
ABCService; // .pas unit generated by WSDLIMP.EXE (WSDL Importer)
procedure PerformServiceCall;
var
MyService: IMyService;
MyServiceResponse: TMyServiceResponse; // the result returned from the service call
MyServiceRequest: TMyServiceRequest; // the parameter passed with the service call
Connected: boolean;
begin
MyService := nil;
try
try
MyService := IMyService.GetMyService;
Connected := (MyService <> nil);
if Connected then
MyServiceResponse := MyService.MethodName(MyServiceRequest);
else
raise Exception.Create('Could Not Connect');
except
on E: Exception do
ShowMessage(E.ClassName + #13#10 + E.Message);
end;
finally
MyService := nil;
end;
end;
At this stage we investigate issues according to the ClassName and Message in the Exception raised, until we get no exceptions... then there are other things that we could check (like whether the service is actually up at the moment, addressing, timeouts, performance, security, etc.).
I want to load a url directly into a string without any data stream,what is the best way, internet open url works but it seems not clear.
I don't want to use any component for reading some short messages
Delphi 6 and later ship with Indy, which has a TIdHTTP client component, eg:
uses
..., IdHTTP;
var
Reply: String;
begin
Reply := IdHTTP1.Get('http://test.com/postaccepter?=msg1=3444&msg2=test');
...
end;
Or:
uses
..., IdHTTP;
var
Reply: TStream;
begin
Reply := TMemoryStream.Create;
try
IdHTTP1.Get('http://test.com/postaccepter?=msg1=3444&msg2=test', Reply);
Reply.Position := 0;
...
finally
Reply.Free;
end;
end;
Depending on your needs.
You can use Synapse, a very light weight library that has a simple function call to get just what your asking for:
uses
classes, httpsend;
var
Response : TStringlist;
begin
if HttpGetText(URL,Response) then
DoSomethingWithResponse(Response.Text);
end;
I would suggest getting the latest copy from SVN, which is more current and contains support for the latest versions of Delphi. There are also simple functions for posting form data, or retrieving binary resources. These are implemented as simple functions and are a great template if you want to do something extra, or that is not directly supported.
You can use our SynCrtSock unit, which is even lighter than Synapse.
See http://synopse.info/fossil/finfo?name=SynCrtSock.pas
It is a self-contained unit (only one dependency with the WinSock unit), and it works from Delphi 6 up to Delphi XE.
You have these two functions available to get your data in one line of code:
/// retrieve the content of a web page, using the HTTP/1.1 protocol and GET method
function HttpGet(const server, port: AnsiString; const url: TSockData): TSockData;
/// send some data to a remote web server, using the HTTP/1.1 protocol and POST method
function HttpPost(const server, port: AnsiString; const url, Data, DataType: TSockData): boolean;
Or you can use textfile-based commands (like readln or writeln) to receive or save data.
TSockData is just a wrapper of RawByteString (under Delphi 2009/2010/XE) or AnsiString (up to Delphi 2007).
If you need also to write a server, you have dedicates classes at hand, resulting in fast processing and low resource consummation (it uses a Thread pool, and is implemented over I/O Completion Ports).
If I'm already using XML in an application (and the MSXML2_TLB), I generally use IXmlHttpRequest to perform http operations. If you open and send the request, you can either use the response data as XML DOM using the ResponseXML, as text using ResponseText or as a data-stream using ResponseStream, see here for an example how to use this in Delphi: http://yoy.be/item.asp?i142