If the application.cfc contains onRequest() and it is not deleted during onRequestStart() for WSDL requests then no output is rendered to the screen. This is because onRequest() expects a file to be included and none is. For displaying the generated WSDL I do not wish to include a template so I delete the method for requests for WSDL. If the application.cfc does not contain onRequest then the delete is not needed.
The locks are present because the application scope is not single threaded in onRequestStart().
The code does not do anything when the web service is actually invoked. It is only when the WSDL is requested that the code is processed. Since the documentation and the implementation of the web service are separate I don't think changing the way the WSDL is returned on the fly is a breach of the web service call contract.
The WSDL should change very rarely, I agree. Which is another reason for having the WSDL generated automatically as any manual processing would be needed so rarely that it could be forgotten about even though it could be well documented.
The issue with writing the WSDL to the file is that <cfcomponent> will only auto generate the WSDL if the wsdlFile attribute isn't present.
In order to have the <cfcomponent> auto generate the WSDL i'd need to deploy the <cfcomponent> without the wsdlFile attribute and then request it via cfhttp and then i'd need to write the WSDL, with CF version removed, to the server using cffile and then i'd need to alter the <cfcomponent> containing the remote function so that it contained the wsdlFile attribute. Since the production code is overwritten regularly with the code from our version control system this would mean adding a scheduled job to generate the wsdlFile. Also after any scheduled release the job would need to be run too as the WSDL file would be deleted during the release. Either that or the manual process would be needed across multiple CF servers and instances.
Having the WSDL modified when it is requested means the production code matches what's in version control and means I won't have to manually maintain any files. So, if the CFC changes and the auto generated WSDL changes those changes are available immediately and with no developer/admin effort.
Thank you for taking the time to look at this problem and look at the code I wrote as it has made me think about the details a lot more closely even though our solutions to the problem are ultimately quite different.
Cheers
Tom
There was an issue with the original solution where if you hit the page with enough simultaneous requests you would have application.InterceptWSDL defined even though you weren't the request that was calling CFHTTP. Whilst an unlikely problem it kind of defeats the purpose of not wanting to show the ColdFusion version and patch number in the WSDL for penetration testing purposes. I've bypassed that problem by ditching the application.interceptWSDL and using the userAgent attribute on CFHTTP. I normally avoid the CGI scope since it can vary by web server but in this case it seems the best solution. Here's the new code:
<cffunction name="onRequestStart" returnType="boolean" output="no">
<cfargument name="targetPage" type="string" required="true">
<cfif listLast(arguments.targetPage,".") EQ "cfc" AND structkeyexists(url, "wsdl")>>
<cfif cgi.User_Agent NEQ "UniqueWSDLIdentiferAgent">
<!--- this will stop the cfhttp request calling itself since cgi.User_Agent will be changed for this request only --->
<cfhttp useragent="UniqueWSDLIdentiferAgent" url="http://mysite#arguments.thePage#?wsdl" method="get" charset="utf-8" timeout="20" result="GeneratedWSDL" />
</cfif>
<cfif structKeyExists(variables, "GeneratedWSDL") and len(GeneratedWSDL.fileContent)>
<cfprocessingdirective suppresswhitespace="Yes">
<cfcontent type="text/xml; charset=utf-8">
<cfoutput>
#REReplaceNoCase(GeneratedWSDL.filecontent, "<!--WSDL created by ColdFusion [A-Z0-9 ,-->]*\r\n", "", "ALL")#
</cfoutput>
</cfprocessingdirective>
<!--- without aborting here the auto generated WSDL is displayed --->
<Cfabort>
</cfif>
<!--- if we don't delete onRequest then it will fire and no output will be rendered to the screen as no file will be included by onRequest(). This is because OnRequest() is being fired for the cfhttp request and this then stops further processing. --->
<cfset structDelete(this, "onRequest")>
<cfset structDelete(variables, "onRequest")>
</cfif>
<cfreturn true>
</cffunction>
Hope that helps anyone doing this in the future.
Cheers
Tom