I have imported a WSDL and use it to send a SOAP request. It looks like this:
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
<ContractdocumentIn>
<AL>
...More XML...
The problem is the xmlns="urn:xx.WSDL.xxxxxWebService" part in the Calculate element. The web service cannot accept this. The web service doesn't like namespaces like this...
Using SoapUI I found this request to work just fine:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:col="http://example.com.service.xxx/">
<SOAP-ENV:Body>
<col:Calculate>
<ContractdocumentIn>
<AL>
...More XML...
So, how do I change the request from the first to the second version? (Without using dirty tricks!) (Re-importing is not a problem if this would result in the proper request format.)
Again: no dirty tricks allowed, like hacking the request stream to modify it!
And while I haven't completely tested it, it seems that C#/VS2010 and Delphi 2010 are also unable to use the web service that I'm trying to call. A web service that seems to be written in Java. SoapUI happens to be written in Java, thus we have a Java client talking to a Java service, which seems to work just fine. But any other client?
Anyways, time to add two more tags: "Java", since it's a Java service, and "vs2010" because .NET also dislikes this service.And I was about to write a wrapper around this service in .NET, hoping that would work... It doesn't. So this is a very serious flaw, possibly a Java flaw...
If a Service expects:
<col:Calculate>
<ContractdocumentIn>
<AL>
and Delphi SOAP is sending...
<Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
<ContractdocumentIn>
<AL>
... the problem is that ContractdocumentIn is an unqualified element and (until Delphi XE) Delphi SOAP did not support unqualified elements that are top level elements of an operation. Top level elements are parameters of the function and there is nowhere to store the fact that the underlying element must be unqualified; for elements that map to properties, we use the Index of the property to store away the IS_UNQL flag.
BTW, it's not necessary to use a prefix. The Service will (should) also accept:
<Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
<ContractdocumentIn xmlns="">
<AL>
The latter is more verbose but it's equivalent to the prefix case.
In Delphi XE the importer stores away the fact that a particular parameter maps to an unqualified element and the runtime acts on this information. I've posted patches based on the XE implementation for D2010 and D2007 in the newsgroup when it came up in a thread recently:
https://forums.embarcadero.com/thread.jspa?threadID=43057
If someone needs access to them (they were in the attachments area but might have scrolled off), please email me and I'll make them available. [bbabet at embarcadero dot com]
Cheers,
Bruneau
OMG! It took lots of coffee and plenty of sleep depravation but I managed to solve my problem! It's reasonable simple too...First I import the WSDL, as expected. This will generate several TRemotable classes. Then, for each TRemotable which needs a different namespace, I override the ObjectToSOAP() method! (And include XMLIntf to the WSDL source.) In my case with code like this for several of the remotable types:
function AL2.ObjectToSOAP( RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const NodeName, NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString ): IXMLNode;
begin
Result := inherited ObjectToSOAP( RootNode, ParentNode, ObjConverter, NodeName, '', '', ObjConvOpts, RefID );
end;
Which worked in Delphi XE. In Delphi 2007 I had to use units XMLIntf and XMLDoc plus this code on the input type:
function ContractdocumentInType.ObjectToSOAP(RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const Name, URI: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString): IXMLNode;
procedure AlterChildren(Child: IXMLNode);
var
I: Integer;
begin
if (Child.NodeType = ntElement) then Child.SetAttributeNS('xmlns', '', '');
for I := 0 to Pred(Child.ChildNodes.Count) do
AlterChildren(Child.ChildNodes[I]);
end;
begin
Result := inherited ObjectToSOAP(RootNode, ParentNode, ObjConverter, Name, '', ObjConvOpts, RefID);
AlterChildren(Result);
end;
It is a hack, in my opinion. But it's not a very dirty one. It's a bit of experimenting, capturing the SOAP requests and responses to check their content and to see if it uses the proper namespaces. Unfortunately, Delphi XE does a far better job at this than Delphi 2007.
Still, I keep this Q open for any better solutions...
Btw, to add the col: to the output, I also had to change this line in the WSDL RemClassRegistry.RegisterXSClass(Calculate, 'http://colan.ogconnect.service.wzp/', 'Calculate'); to this: RemClassRegistry.RegisterXSClass(Calculate, 'http://colan.ogconnect.service.wzp/', 'cal:Calculate');. The result then becomes <cal:Calculate xmlns:cal="http://example.webservice/">. Ont more thing needs to be done, though: moving that xmlns:cal to the xml header. But as it is now, it works for me.
Another note: for the WSDL I used the following settings: 'One Outparam is return', 'Unwind literal params', 'Generate destructors', 'Warning comments', 'emit literal types', 'Map string to widestring'. Other options are: 'Generate verbose information about types and interfaces', 'Ignore porttypes with HTTP Bindings', 'Validate enumeration members', 'Import fault types', 'Import header types', 'Process included and imported schemas', 'Generate class alias as class types', 'Allow Out parameters' and 'Process nillable and optional elements'. The emit literal types was practical because it generates a class around the single method that I was calling. Unfortunately, this won't help much either, although the class would help you to modify the SOAP request on the upper level within the envelope by overriding the ObjectToSOAP() method.Creation of the envelope itself is in the SOAPEnv unit and it's used in the OPToSOAPDomConv unit. Unfortunately, I haven't found an easy method to access the envelope itself to alter the header to add this additional namespace. Then again, I could override the TSOAPDomConv class with my own version that does add the additional namespace. But the code is working for me now, and as my father told me, when he learned me to program: never fix anything that isn't broken.
Related
I'm using Delphi XE4 and i usually use Indy with IdHttp.POST to POST request to websites,
This time, whenever i try to POST the request i get Error: Your browser is not sending the correct data.
I'm very sure that I'm POSTing the right data, and i'm using the IOHandler and CookieManager.
Been dealing with this for days(literally)
Here is the code(the site in the code):
procedure TForm1.Button1Click(Sender: TObject);
var s, lge, Kf1, Kf2, Kf3, Kf4 : String;
lParam : TStringList;
begin
S := http.Get('https://www.neobux.com/m/l/');
Memo1.Lines.Add(S);
getParamLge(s,lge,'lge');
GetInput(s,Kf1,'id="Kf1"');
GetInput(s,Kf2,'id="Kf2"');
GetInput(s,Kf3,'id="Kf3"');
GetInput(s,Kf4,'id="Kf4"');
lParam := TStringList.Create;
lParam.Add('lge='+lge);
lParam.Add(Kf1+'=USERNAME');
lParam.Add(Kf2+'=PASSWORD');
lParam.Add(Kf3+'=');
lParam.Add(Kf4+'=');
lParam.Add('login=1');
memo1.Lines.Add(http.Post('https://www.neobux.com/m/l/', lParam));
end;
(the getParamLge and GetInput function, are just simple copy and pos functions to extract value from the GET respone).
I thought maybe it needed cookies so i've added this in the beginning:
Cookie.CookieCollection.Clear;
Cookie.CookieCollection.AddClientCookies('CFID=21531887; CFTOKEN=20369251; dh=20130709111845,1920x1080,{ts ''2013-07-09 06:18:58''}; __utma=90161412.436822896.1373368451.1373368451.1373368451.1; __utmb=90161412.11.10.1373368451; __utmc=90161412; __utmz=90161412.1373368451.1.1.'+'utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __asc=06ff77ad13fc32381fd1f5d6405; __auc=06ff77ad13fc32381fd1f5d6405; __atuvc=4%7C28; MS=flat');
But all in vain.
I'm very sure that I'm POSTing the right data
Since it does not work - obviously you do not (or Delphi does not - that makes no difference for server).
You should start usual debugging loop:
Observe reference working behaviour.
Observe your program behavior
Spot the difference
Eliminate the difference
Check if the program works now
If not - go to step 2.
Reference implementation would be some WWW browser working with site: Opera, Chrome, Firefox, MS IE, etc.
Observing tool would be some HTTP Sniffer like WireShark or OmniPacket or Microsoft Net Monitor or else, however this tinkers with OS work on rather deep level.
Or it can be local proxy with GUI, like Proxomitron or Membrane Monitor - but that would require special setup for both the program and the browser, to route their traffic through that local proxy.
Then you should read about HTTP, starting with shallow observation at Wikipedia and then opening related RFC documents (specifications of different part of HTTP protocol) so that you would understand what do the observed differences mean and how to fix them. For example many people use POST request when they actually should use GET request or such.
You want to debug HTTP program but for this HTTP logs, workign and borken, are required and your question lacks them. More so, most probably you can fix it your self, just bring your program's HTTP log to accordance with both RFCs theory and working browsers practice.
One of my users at a large university (with, I imagine, the aggressive security settings that university IT departments general have on their computers) is getting an empty string returned by Windows XP for CSIDL_COMMON_APPDATA or CSIDL_PERSONAL. (I'm not sure which of these is returning the empty string, because I haven't yet examined his computer to see how he's installed the software, but I'm pretty sure it's the COMMON_APPDATA...)
Has anyone encountered this or have suggestions on how to deal with this?
Here's the Delphi code I'm using to retrieve the value:
Function GetSpecialFolder( FolderID: Integer):String;
var
PIDL: PItemIDList;
Path: array[0..MAX_PATH] of Char;
begin
SHGetSpecialFolderLocation(Application.Handle, FolderID, PIDL);
SHGetPathFromIDList(PIDL, Path);
Result := Path;
end; { GetSpecialFolder }
ShowMessage(GetSpecialFolder(CSIDL_COMMON_APPDATA)); <--- This is an empty string
Edit:
Figuring out this API made me feel like I was chasing my tail - I went in circles trying to find the right call. This method and others similar to it are said to be deprecated by Microsoft (as well as by a earlier poster to this question (#TLama?) who subsequently deleted the post.) But, it seems like most of us, including me, regularly and safely ignore that status.
In my searches, I found a good answer here on SO from some time ago, including sample code for the non-deprecated way of doing this: what causes this error 'Unable to write to application file.ini'.
If you want to find out why an API call is failing you need to check the return values. That's what is missing in this code.
You need to treat each function on its own merits. Read the documentation on MSDN. In the case of SHGetSpecialFolderLocation, the return value is an HRESULT. For SHGetPathFromIDList you get back a BOOL. If that is FALSE then the call failed.
The likely culprit here is SHGetSpecialFolderLocation, the code that receives the CSIDL, but you must check for errors whenever you call Windows API functions.
Taking a look at the documentation for CSIDL we see this:
CSIDL_COMMON_APPDATA
Version 5.0. The file system directory that contains application data for all users. A typical path is C:\Documents and Settings\All
Users\Application Data. This folder is used for application data that
is not user specific. For example, an application can store a
spell-check dictionary, a database of clip art, or a log file in the
CSIDL_COMMON_APPDATA folder. This information will not roam and is
available to anyone using the computer.
If the machine has a shell version lower than 5.0, then this CSIDL value is not supported. That's the only documented failure mode for this CSIDL value. I don't think that applies to your situation, so you'll just have to see what the HRESULT status code has to say.
Delphi XE2, so Indy 10.
My client sends a command which is processed by a TIdCommandHandler of my TIdCmdTCPServer.
I want to be able to perform some logic and return either a success or fail response and check for that back at the client.
Can someone please point me at a few lines of code as an example? Thanks in advance.
Well, here's the simplest demo.
Add an IdCmdTCPServer to your form, and add one command, set its name in the Command property, I originally thought I should handle Response in OnCommand event like this:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
begin
//ASender.Response.Add('Hello'); // wrong way
ASender.Reply.SetReply(0,'HELLO');
end;
Update Remy pointed out I shouldn't be using Response.
So you want to return success or failure, it's traditional to use a numeric result followed by the string value. Each string in the response strings list has an implied end-of-line transmitted back to the client:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
begin
if DoSomething then
ASender.Reply.SetReply(0,'OK')
else
ASender.Reply.SetReply(999,'ERROR');
end;
The idea with the IdCommandHandler and a CmdTCPServer/Client is that you follow the "RFC" style of protocols, which are ANSI/ASCII text-based. An RFC-style internet protocol's reply is typically encoded over the wire as text with both a numeric and string value. ASender.Response could be used if you needed to take the content of a string list and return that as the response.
As for the client, a question here suggests that TIdCmdTcpClient is not the most natural way to build the client for this server. From their names, you'd have thought they were made for each other, but it's not exactly. For most simple TIdCMDTCPServers that you could build, you would find that a plain-vanilla TIdTCPClient is the simplest building block to start your client with.
I have the following declaration for DNSServiceRegister:
function DNSServiceRegister
(
var sdRef: TDNSServiceRef;
const flags: TDNSServiceFlags;
const interfaceIndex: uint32_t;
const name: PUTF8String; //* may be NULL */
const regType: PUTF8String;
const domain: PUTF8String; //* may be NULL */
const host: PUTF8String; //* may be NULL */
const port: uint16_t;
const txtLen: uint16_t;
const txtRecord: Pointer; //* may be NULL */
const callBack: TDNSServiceRegisterReply; //* may be NULL */
const context: Pointer //* may be NULL */
): TDNSServiceErrorType; stdcall; external DNSSD_DLL;
In my Bonjour framework I have the following response to an announced service being made active (i.e. to actually start announcing itself, via Bonjour):
procedure TAnnouncedService.Activate;
var
flags: Cardinal;
name: UTF8String;
svc: UTF8String;
pn: PUTF8String;
ps: PUTF8String;
begin
fPreAnnouncedServiceName := ServiceName;
inherited;
if AutoRename then
flags := 0
else
flags := kDNSServiceFlagsNoAutoRename; { - do not auto-rename }
if (ServiceName <> '') then
begin
name := ServiceName;
pn := PUTF8String(name);
end
else
pn := NIL;
svc := ServiceType;
ps := PUTF8String(svc);
CheckAPIResult(DNSServiceRegister(fHandle,
flags,
0 { interfaceID - register on all interfaces },
pn,
ps,
NIL { domain - register in all available },
NIL { hostname - use default },
ReverseBytes(Port),
0 { txtLen },
NIL { txtRecord },
DNSServiceRegisterReply,
self));
TBonjourEventHandler.Create(fHandle);
end;
This is more verbose than I think it strictly needs to be, certainly it was working perfectly well in Delphi 7 in a much less verbose form. I have expanded a lot of operations into explicit steps to facilitate debugging, e.g. to be able to identify any implicit transforms of string payloads that may be occuring "under the hood" in Delphi 2009.
Even in this untidy expanded form this code compiles and works perfectly well in Delphi 7, but if I compile and run with Delphi 2009 I get no announcement of my service.
For example, if I run this code as part of a Delphi 7 application to register a _daap._tcp service (an iTunes shared library) I see it pop-up in a running instance of iTunes. If I recompile the exact same application without modification in Delphi 2009 and run it, I do not see my service appearing in iTunes.
I get the same behaviour when monitoring with the dns-sd command line utility. That is, service code compiled with Delphi 7 behaves as I expect, compiled in Delphi 2009 - nothing.
I am not getting any errors from the Bonjour API - the DNSServiceRegisterReply callback is being called with an ErrorCode of 0 (zero), i.e. success, and if I supply a NIL name parameter with AutoRename specified in the flags then my service is allocated the correct default name. But still the service does not show up in iTunes.
I am at a loss as to what is going on.
As you might be able to tell from the expansion of the code, I have been chasing potential errors being introduced by the Unicode implementation in Delphi 2009, but this seems to be leading me nowhere.
The code was originally developed against version 1.0.3 of the Bonjour API/SDK. I've since updated to 1.0.6 in case that was somehow involved, without any success. afaict 1.0.6 merely added a new function for obtaining "properties", which currently supports only a "DaemonVersion" property for obtaining the Bonjour version - this is working perfectly.
NOTE: I'm aware that the code as it stands is not technically UTF8-safe in Delphi 7 - I have eliminated explicit conversions as far as possible so as to keep things as simple as possible for the automatic conversions that Delphi 2009 applies. My aim now is to get this working in Delphi 2009 then work backward from that solution to hopefully find a compatible approach for earlier versions of Delphi.
NOTE ALSO: I originally also had problems with browsing for advertised services, i.e. identifying an actual iTunes shared library on the network. Those issues were caused by the Unicode handling in Delphi 2009 and have been resolved. My Delphi 2009 code is just as capable of identifying an actual iTunes shared library and querying it's TXT records. It's only this service registration that isn't working.
I must be missing something stupid and obvious.
Does anyone have any ideas?!
UPDATE
Having returned to this problem I have now discovered the following:
If I have a pre-D2009 and a D2009+ IDE open (e.g D2006 and D2010) with the same project loaded into both IDE's concurrently:
Build and run under 2006: It works - my service announcement is picked up by iTunes
Switch to D2010 and run (without building): It does a minimal compile, runs and works.
Do a full build in D2010: It stops working
Switch back to D2006 and run (without building): It doesn't work
Do a full build in D2006: It works again
Does this give anyone any other ideas?
The answer to this is mind boggling. On the one hand I made a completely stupid, very simple mistake, but on the other hand it should never - as far as I can see - have worked in ANY version of Delphi!
The problem was nothing what-so-ever to do with the Unicode/non-unicodeness of any strings, but was actually due to a type mismatch in the PORT parameter.
I was passing in the result of ReverseBytes(Port) - that parameter expected a uint16_t, i.e. a Word value. My Port property was however declared (lazily) as an Integer!!
Once I fixed this and had Port declared as a Word, it now works on both D2007- and D2009+ versions of Delphi.
Very weird.
I can only think that some other edge-case behaviour of the compiler that might have somehow affected this was changed when Unicode support was introduced.
Based on the information that we have available here, the situation is this:
When calling the DLL with your code in Delphi 2007, it gives one result.
When calling the same DLL with your code in Delphi 2009, it gives another result.
The suspicion is, that it is related to the Delphi 2009 compiler.
Logically, the difference must therefore be, that Delphi 2009 sends different values as parameters. In order to make the debugging truly Delphi-independent, you therefore need to create a dummy DLL, which reports the values it gets. Other Delphi-dependent methods may be applied, like looking at the disassembly of the function-call into the DLL, and debugging it so that we know exactly what values are passed, and how, to the DLL, in both compilers.
I can't find the declaration instruction for the vars "ServiceName" and "ServiceType" in your code sample.
Assuming a String type (thus a unicode string), I guess (yes... no D2009 available to test this) lazy typecasting could be an issue:
name := ServiceName;
Why not use the following?
name := PAnsiChar(AnsiString(ServiceName))
Anyhow... just my 2 cts.
BTW:
I always use the pre defined "EmptyStr", "EmptyWideStr" ... so the test would look like:
if (ServiceName <> EmptyStr) then
which should be safe and avoid the confusion of types.
On the other side, Delphi may interpret the '' as an ANSIChar like the following declaration do:
const
MyParagraphChar = 'ยง';
Not sure... I'm confused - should go home now ;)
If the DLL is not written using Delphi 2009, you may want to use something else than PUTF8String. The Delphi 2009 Utf8String type is different from Delphi 2007's UTF8String type.
If the DLL was written using C/C++, I strongly suggest to use PAnsiChar() instead of PUtf8String.
I'm quite familiar with PHP dockblocks since it's been my job for the last 15+ years.
/**
* Description
*
* #tag bla bla
* #tag more bla bla
*/
What I'm trying to understand is if there is a standard like that for Delphi and/or FreePascal.
From my analysis on an awful lot of code I never seen any, but I could be dead wrong.
Delphi documentation tools
Using the XMLDoc tool for API documentation and HelpInsight with Delphi 2005
http://edn.embarcadero.com/article/32770
XML Documentation in Delphi 2006
http://tondrej.blogspot.com/2006/03/xml-documentation-in-delphi-2006.html
DelphiCodeToDoc
http://dephicodetodoc.sourceforge.net/
Doc-O-Matic
http://www.doc-o-matic.com/examplesourcecode.html
PasDoc
http://pasdoc.sipsolutions.net/
Pascal Browser
http://www.peganza.com/
Doxygen
http://www.doxygen.nl/
Pas2Dox
http://sourceforge.net/projects/pas2dox/
JADD - Just Another DelphiDoc
http://delphidoc.sourceforge.net/
Stackoverflow discussion
Is there a Delphi code documentor that supports current Delphi syntax?
https://stackoverflow.com/questions/673248/is-there-a-delphi-code-documentor-that-supports-current-delphi-syntax
Code documentation for delphi similar to javadoc or c# xml doc
Code documentation for delphi similar to javadoc or c# xml doc
Documenting Delphi
https://stackoverflow.com/questions/33336/documenting-delphi
Latest Delphis support parsing of XML documentation. They also use this information in hints (for example if you move the mouse cursor over a method name).
I'm using this template for method documentation:
///<summary></summary>
///<param name=''></param>
///<returns></returns>
///<exception cref=""></exception>
///<since>2009-04-15</since>
I prefer docs outside of the source (it always gets messy), and use the excellent fpdoc that comes with FPC. (FPC's own docs are written in it).
On a project I'm currently working on, we're using DelphiCodeToDoc, which works reasonably well. Its syntax looks like this:
type
{* This is an example class }
TMyClass = class
private
protected
public
{* Does some twiddling with AParam, and returns the result as String
#param AParam Input value
#return AParam incremented by 2, as String
#throws Exception 'Boo' if it's full moon }
function MyFunction(AParam: Integer): String;
end;
It looks like doxygen has a tool which can be used in conjunction to document Pascal and Delphi code. Maybe that will help you.
There are several standards, usually depending on the documentation tool being used. We use PasDoc, so we adhere mostly to its format http://pasdoc.sipsolutions.net/ which is based on JavaDoc.
Alternatively, there is XMLDoc as gabr pointed out and there are quite a few other tools, most having similar syntax with subtle differences.