When you are calling a method of a webservice and want to omit an unrequired numeric variable that has a default value set coldfusion will throw the following error:
The fault returned when invoking the web service operation is:<br>
<pre>'' java.lang.IllegalArgumentException</pre>
Example:
<cfinvoke
webservice = "http://*.cfc?WSDL"
method="getFriendlyDay"
returnvariable="response"
refreshWSDL="true"
>
<cfinvokeargument name="dayNumber" omit="true"/>
</cfinvoke>
webservice component:
<cffunction name="getFriendlyDay" access="remote" returntype="any" output="no" description="get a friendly date from a number">
<cfargument name="dayNumber" type="numeric" required="no" default="0">
...
</cffunction>
My solution to this is to just not omit the argument. Pass in the default value. I just wanted to record this in case someone else gets the same error. Thus far it has only occurred on numeric values.
<cfinvoke
webservice = "http://*.cfc?WSDL"
method="getFriendlyDay"
returnvariable="response"
refreshWSDL="true"
>
<cfinvokeargument name="dayNumber" value="0" >
</cfinvoke>
Update:
I believe this probably relates to the bug outlined here:
The way Coldfusion handles optional arguments as a remote service is
that it allows the calling client to pass in a null value. In a
document/literal or rpc/encoded WSDL description, an element can
accept null unless it specifies "nillable='false'". The generated
WSDLs from Coldfusion do not use "nillable='false'" or "minOccurs='0'"
which instructs the client that they must include the parameter and
that it is ok to pass in a null value.
The problem however is that "numeric" or "boolean" argument types that
are optional will throw an "Illegal Argument Exception" when being
called by a client who is trying to explicitly pass in null ...
Related
I was writing a REST component but having difficulties when I do not want to pass argument for a parameter. My code and URL (PATH style) look like this.
Component definition:
<cfcomponent rest="true" restPath="SearchRestAPI">
<cffunction name="restaurantResults" access="remote" httpMethod="get"
output="false" returntype="Query" produces="application/json"
restPath="Search/{city : ([a-zA-Z])*}/{address}/{type}">
<cfargument name="city" default="" type="string" restargsource="path">
<cfargument name="address" default="" type="string" restargsource="path">
<cfargument name="type" default="" type="string" restargsource="path">
Calling URL/Path to consume the REST service. Notice I do not want to send any value for type parameter so there is double slash at the end of the path:
http://localhost:8502/backend/section/SearchResAPI/Search/Calcutta/Lindsay Street//
But this is throwing a "Not Found" error. Any suggestion would be highly appreciated.
I am not sure about this ColdFusion functionality, but looks like "restPath" works with regular expressions (which you are already using when capturing the city parameter
([a-zA-Z])* // your city parameter matches the specified character sets zero or more times.
Similarly to the above, you could try:
restPath="Search/{city : ([a-zA-Z])*}/{address}/{type: ([a-zA-Z])*}"
Here is a ColdFusion page that has some examples as well (search the page for "restPath"):
https://wikidocs.adobe.com/wiki/display/coldfusionen/cfcomponent
I have a CFC (call it proxy.cfc) that I'm using as a proxy for a simple API that I've written. Everything has been going along just fine and a few partners are starting to use the API effectively.
However, one site that is attempting to post data isn't sending valid JSON and I can't seem tho figure how to gracefully handle this error.
A valid JSON string that is posted as a URL param may look like this:
{"apicomponent":"proxyRemoteAdd","apimethod":"add","apiarguments":{"ph_num":1212,"rbpid":999,"ph_exch":555,"state":"HI","address_1":"123 Main Street","address_2":"","rmtid":"PON83","last_name":"Smith","test":1,"zip":999999,"first_name":"Joe","email":"test#test.com","city":"Honolulu","type":"SP","ph_area":995},"apiauthkey":"abc123"}
And that works just fine.
However, if that string is truncated for any reason:
{"apicomponent":"proxyRemoteAdd","apimethod":"add","apiarguments":{"ph_num":1212,"rbpid":999,"ph_exch":555,"state":"HI"
I catch an exception as follows: Exception: JSON parsing failure: Unexpected end of JSON string
This is coming from my onError in Application.cfc. I did add some code to isolate it in Application.cfc as follows:
<cfif ARGUMENTS.EXCEPTION.MESSAGE IS "JSON parsing failure: Unexpected end of JSON string">
<!--- do some stuff here --->
</cfif>
Is that the best way to handle that sort of error? Is there anyway to pass it off to the proxy.cfc file so I can return an error message back to the posting client? As it is now the Application.cfc is catching it and not even letting me get to the proxy.cfc file.
UPDATE - here's some specific code samples. This is how I've been testing and will illustrate how the data gets posted:
<cfhttp url="https://www.domain.com/api/proxy.cfc" method="post" result="httpResult" charset="UTF-8">
<cfhttpparam type="url" name="method" value="apiauth"/>
<cfhttpparam type="url" name="argumentCollection" value="#jsData#"/>
</cfhttp>
"apiauth" is the method in the CFC that acts as the authorization and proxy.
The argument collection is a JSON string as shown below. It lists a component (a different CFC), the method in that component, the authkey of the user accessing the AP, and then a JSON string called apiarguments which contains the arguments and data that get passed to the apicomponent listed.
The proxy CFC file looks like this:
<cffunction name="apiauth" access="remote" returntype="any" output="false" returnFormat="JSON">
<cfargument name="apicomponent" required="yes" type="string"/>
<cfargument name="apimethod" required="yes" type="string"/>
<cfargument name="apiauthkey" required="yes" type="string"/>
<cfargument name="apiarguments" required="yes" type="struct"/>
<cfset var LOCAL = {}/>
<cfif not isDefined("ARGUMENTS.apiauthkey")>
<cfreturn THIS.NewErrorResponse("Error 401 Malformed Request.") />
</cfif>
<cfif not isDefined("ARGUMENTS.apicomponent")>
<cfreturn THIS.NewErrorResponse("Error 402 Malformed Request.") />
</cfif>
<cfif not isDefined("ARGUMENTS.apimethod")>
<cfreturn THIS.NewErrorResponse("Error 403 Malformed Request.") />
</cfif>
<cfset LOCAL.checkpwResult = FALSE/>
<cfset LOCAL.apicomponent = ARGUMENTS.apicomponent />
<cfset LOCAL.apimethod = ARGUMENTS.apimethod />
At this point some other CFCs are access to check the API key and user ID # to make sure they can access the API. If everything checks out there the data gets passed off to the correct component/method:
<cfinvoke component="#LOCAL.apicomponent#" method="#LOCAL.apimethod#" argumentcollection="#apiarguments#" returnvariable="LOCAL.Response.Data"/>
However the JSON exception is being thrown before this CFC can even be accessed by the Application.cfc file. If I drop a quick cfmail in proxy.cfc to just dump and mail the arguments it doesn't even hit that.
Here's a chunk of the stack trace:
coldfusion.runtime.JSONUtils$JSONParseOverflowException: JSON parsing failure: Unexpected end of JSON string at
coldfusion.runtime.JSONUtils$ParserState.incrementOffset(JSONUtils.java:1999) at coldfusion.runtime.JSONUtils$ParserState.incrementOffset(JSONUtils.java:1980) at coldfusion.runtime.JSONUtils.parseString(JSONUtils.java:1385) at coldfusion.runtime.JSONUtils.parseObject(JSONUtils.java:1074) at coldfusion.runtime.JSONUtils.parseStruct(JSONUtils.java:1178) at coldfusion.runtime.JSONUtils.parseObject(JSONUtils.java:1059) at coldfusion.runtime.JSONUtils.parseStruct(JSONUtils.java:1178) at coldfusion.runtime.JSONUtils.parseObject(JSONUtils.java:1059) at coldfusion.runtime.JSONUtils.parseJSON(JSONUtils.java:1028) at coldfusion.runtime.JSONUtils.deserializeJSON(JSONUtils.java:168) at coldfusion.runtime.JSONUtils.deserializeJSON(JSONUtils.java:128) at coldfusion.filter.FilterUtils.GetArgumentCollection(FilterUtils.java:50) at coldfusion.filter.ComponentFilter.invoke(ComponentFilter.java:193) at coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:442) at coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:48) at coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40)
coldfusion.filter.PathFilter.invoke(PathFilter.java:112) at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:94) at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28) at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38) at coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:58) at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38) at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22) at coldfusion.xml.rpc.CFCServlet.invoke(CFCServlet.java:155) at coldfusion.xml.rpc.CFCServlet.doPost(CFCServlet.java:331) at javax.servlet.http.HttpServlet.service(HttpServlet.java:641) at javax.servlet.http.HttpServlet.service(HttpServlet.java:722) at
If your argument is always a JSON string, you should be able to change the argument type to string, and pass it in that way (it will make it through Application.cfc) and then validate it within your apiAuth function using isJSON() - you should be validating it against malicious JSON anyways, so it won't be much code to initially check against isJSON(). This way, your API will give meaningful feedback and keep all the code where it should be.
However if you have other users passing an actual struct into your method, that won't work.
And you will definitely want to find out why the JSON string is being truncated. At a guess I would suggest there might be some un-escaped characters being passed in which is truncating the string early...
I'm trying to refactor all of my CFCs to avoid using SESSION and APPLICATION variables (not an easy task).
However, in this application, SESSION variables are used in every database call, since different logged in users may be accessing different databases and schemas:
<cfquery name="qEmployees" datasource="#SESSION.DataSourceName#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
I don't want to go through the trouble of passing these two SESSION variables to every method call that accesses the database. This is especially the case since I don't want to pass DSNs and Schema Names in remote AJAX calls.
What is best practice for doing this - for all Scopes that shouldn't be used in CFCs?
I think that since the datasource truly is variable I'd pass it into every function as an optional parameter and set the default value to a variables scoped dsn attribute. I'd set the variables scoped DSN in the CFC's constructor. That way you only have to pass in the DSN for the AJAX calls.
<cffunction name="doFoo" access="remote"...>
<cfargument name="dsn" type="String" required="false" default="#variables.datasource#" />
</cffunction>
I'd use the session scope of your app to store the users dsn name and use that var to pass to the AJAX call.
You should create an "init" method that will serve as a constructor for your CFC. You can then instantiate the CFCs and store them in a shared scope, most likely the application scope. From here, to use this CFC via AJAX, I typically will create a remote facade. Basically this is another CFC that will directly access the CFC instance in the application scope. It will implement the methods you need to access via Ajax, expose them using access="remote" giving your application access to the access="public" methods from the actual CFC. In this case it is generally accepted that the remote facade can access the application scope directly as part of the design pattern.
A simple example:
example.cfc:
<cfcomponent output="false">
<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="dsn" type="string" required="true" />
<cfset variables.dsn = arguments.dsn />
<cfreturn this />
</cffunction>
<cffunction name="doStuff" access="public" output="false" returntype="query">
<cfset var q = "" />
<cfquery name="q" datasource="#variables.dsn#">
select stuff from tblStuff
</cfquery>
<cfreturn q />
</cffunction>
</cfcomponent>
In your Application.cfc onApplicationStart() method:
<cfset application.example = createObject("component","example").init(dsn = "somedsn") />
remote.cfc:
<cfcomponent output="false">
<cffunction name="doStuff" access="remote" returntype="query">
<cfreturn application.example.doStuff() />
</cffunction>
</cfcomponent>
Can you set your datasource variables in the onRequest or onRequestStart functions in your Application.cfc
<cffunction name="onSessionStart">
<cfset session.dsn = _users_personal_dsn_ />
</cffunction>
<cffunction name="onRequestStart" >
<cfset dsn = "#session.dsn#" />
</cffunction>
<cfquery name="qEmployees" datasource="#dsn#">
SELECT *
FROM #SESSION.DatabaseSchema#.Employees
</cfquery>
etc.
not sure if that will work [not tested - actually feels a bit sloppy]
-sean
The scope you choose (for any variation of this question, not just for DSNs) should be based on whether the lifetime of the value is the same as the lifetime of the scope.
In our application, the DSN is just set once in the lifetime of the application, so we have an application.config struct that gets created (parsed from a file) in onApplicationStart, and within it is application.config.dsn
If your value really does change between sessions, but not over the life of a session, go ahead and use the session scope.
If your value could change for any given request, but not in the middle of a request, put it in the request scope.
That said, still heed ryan's advice and add optional arguments that only default to this value: being flexible is always the best.
My suggestion for this is to create a base class and then have your components that need database access extend that component. It doesn't have to be in the immediate parent hierarchy but somewhere down the line.
They goal is to do two things, keep the cfc abstracted from the main program and keep it easily configurable. This accomplishes both.
So your CFC that queries the database would look something like this :
<cfcomponent extends="DataAccessBase">
<cffunction name="myFunction" access="public" returntype="string">
<cfquery datasource="#getDSN()#" name="qStuff">select * from table</cfquery>
</cffunction>
The key above is the extends="DataAccessBase" portion. This adds the layer of abstraction where you can control the data access at one configurable point, but it's not tied to the application itself, leaving the component abstracted from where it's implemented.
Your DataAccessBase.cfc could look something like this:
<cfcomponent>
<cffunction name="loadSettings">
<cfparam name="request.settings" default="#structNew()#">
<cfparam name="request.settigns.loaded" default="false">
<cfif request.settings.loaded eq false>
<!--- load settings from resource bundle etc --->
<cfset request.settings.dsn = 'myDSN'>
<cfset request.settings.loaded = true>
</cfif>
</cffunction>
<cffunction name="getDsn" access="public" returntype="string">
<cfset loadSettings()>
<cfreturn request.settings.dsn>
</cffunction>
You can of course get more intricate with how you configure and store the settings etc, but that's out of scope of the question I think. :)
I don't see any reason to pass the DSN with every method call. Yes, it works, but it's not necessary. The components are developed with a built-in assumption of the datastructure so you know that it is not going to change from a addItem() call to a updateItem() call, thus its duplication of work which means additional points of failure. :P
Make sense?
I have read many posts by people who have problems with onSessionEnd. This is my first conversion of application.cfm to application.cfc and the onSessionEnd is not working with the CFFunction I am trying to invoke.
I guess what's hanging this up is how to properly call the component from the /lib/components/ folder where it resides.
When a user logs in I am creating a session array that tracks a jobNumber and the last_completed_step in that job. There are multiple jobs in a users session. At the end of the session I want to write the updated array data back to the DB.
I should make it clear that at present I look into my log file and see that the session is started - as coded in the onSessionStart shown below. Furthermore, the onSessionEnd also writes to the log file when I take out the invocation of the component. In other words if I just tell it to write "Session ended." to the log file I will see it in the log file. I have set current session timeout in CF Administrator and my app.cfc for 3 minutes for testing.
If I call the "giveMeAnswer" method in the jobState.cfc from a separate file (also at the root level) the giveMeAnswer method works properly and returns the value "I am a CFC."
If I move the jobState.cfc to the root level and set the component attribute to "jobState" I am also getting a return from the component.
<!--- Runs when your session starts --->
<cffunction name="onSessionStart" returnType="void" output="false">
<!--- :: invoke all session variables | moved out of on session start :: --->
<cfinvoke component="#application.virtualPaths.cfcPath#system/sessionVars" method="init" />
<cflog file="#This.Name#" type="Information" text="Session started.">
</cffunction>
<!--- Runs when session times out --->
<cffunction name="onSessionEnd" returntype="void">
<cfargument name="SessionScope" type="struct" required="true" />
<cfargument name="ApplicationScope" type="struct" required="true" />
<cfinvoke component="/lib/components/jobState" method="giveMeAnswer" returnvariable="returnFromCfc">
</cfinvoke>
<cflog file="#This.Name#" type="Information" text="Session ended. #returnFromCfc#">
<cfreturn />
</cffunction>
So, is it just not finding the component? Any other ideas?
Thanks much, Jerry
I know I've seen folks use / in component calls before, but I do not believe it is officially supported. You want to use a dot notation path instead, ala
component="lib.components.jobstate"
and assure that lib is either a subdirectory or a known CF mapping that points to the lib folder.
Problem: When requesting the WSDL for a CFC, I get the following error: Variable FORM is undefined. It happens in this line of code, in the OnRequestStart method in application.cfc
<cfif structKeyExists(form,'resetappvars')>
<cfset OnApplicationStart() />
</cfif>
If I request a specific method, it works fine. I have considered using cfparam to create a default form struct if none exists, but that seems like an ugly hack and I worry it will actually create the form struct in the variables or this scope of the CFC. Maybe this is a legitimate bug as well?
Note: This only happens when I request the WSDL, if I invoke a method directly - the code executes as expected without problems.
Update: Application.cfc code sample - just add any CFC to your app and request it with ?wsdl to see the issue. This has been tested (and failed) on ColdFusion 7 and ColdFusion 8.
<cfcomponent output="false">
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<cfset application.dsn = "my_dsn" />
<cfreturn true />
</cffunction>
<cffunction name="OnRequestStart" access="public" returntype="boolean" output="false" hint="Fires at first part of page processing.">
<cfargument name="TargetPage" type="string" required="true" />
<cfif structKeyExists(form,'resetappvars')>
<cfset OnApplicationStart() />
</cfif>
<cfreturn true />
</cffunction>
</cfcomponent>
Maybe try adding a:
<cfif IsDefined("form")>...</cfif>
around the above code?
You could also cfparam the variable you're looking for then just change your logic a little (assuming resetAppVars is a boolean:
<cfparam name="form.resetAppVars" default="false" />
...
<cfif form.resetAppVars>
<cfset OnApplicationStart() />
</cfif>
Edit: I'm not sure if the above code could be considered a hack, but it seems pretty standard CF, to me.
This post of Ben Nadel gives detailed list of scopes available for different types of requests.
By reading it you can easily find out that form scope is not available in given context, but url is.
I've heard it's just a matter of opinion, but it seems to me that it is improper to reference your form scope within a CFC, as there is no guarantee that the form scope will be available when your cfc is invoked and when your method is called. It is better to ensure that any data that needs to be available to the method is provided explicitly to your object. This can be done either by including an argument:
<cfargument name="resetAppVars" type="boolean" required="false" default="false" />
Then you check arguments.resetAppVars, and it is always defined, but defaulted to false.
Or by creating an attribute on your object and creating an explicit set method:
(at the top of your cfc)
<cfset this.resetAppVars = false />
<cffunction name="setResetAppVars" access="public" returnType="void" output="false">
<cfargument name="flagValue" type="boolean" required="true" />
<cfset this.resetAppVars = arguments.flagValue />
</cffunction>
In which case you will check against this.resetAppVars. You can also scope this locally using <cfset var resetAppVars = false /> as the declaration, which makes it a private attribute of your object, and is probably proper, so code that invokes the object cannot improperly overwrite this variable with a non-boolean type. In that case, you would simply refer directly to resetAppvars in your test, instead of using this scope.
You could also do this:
<cfif NOT isSoapRequest()>...
and stick your remaining logic inside that chunk.