1521 lines
69 KiB
Plaintext
1521 lines
69 KiB
Plaintext
<cfcomponent hint="Base class for taffy REST application's Application.cfc">
|
|
|
|
<!--- this method is meant to be (optionally) overrided in your application.cfc --->
|
|
<cffunction name="getEnvironment" output="false" hint="override this function to define the current API environment"><cfreturn "" /></cffunction>
|
|
|
|
<!---
|
|
onTaffyRequest gives you the opportunity to inspect the request before it is sent to the service.
|
|
If you override this function, you MUST either return TRUE or a representation object
|
|
(eg either taffy.core.nativeJsonSerializer or your default representation class)
|
|
--->
|
|
<cffunction name="onTaffyRequest" output="false">
|
|
<cfargument name="verb" />
|
|
<cfargument name="cfc" />
|
|
<cfargument name="requestArguments" />
|
|
<cfargument name="mimeExt" />
|
|
<cfargument name="headers" />
|
|
<cfargument name="methodMetadata" />
|
|
<cfargument name="matchedURI" />
|
|
<cfreturn true />
|
|
</cffunction>
|
|
|
|
<!---
|
|
onTaffyRequestEnd gives you the opportunity to access the request after it has been processed by the service.
|
|
If you override this function, you MUST either return TRUE or a representation object
|
|
(eg either taffy.core.nativeJsonSerializer or your default representation class)
|
|
--->
|
|
<cffunction name="onTaffyRequestEnd" output="false">
|
|
<cfargument name="verb" />
|
|
<cfargument name="cfc" />
|
|
<cfargument name="requestArguments" />
|
|
<cfargument name="mimeExt" />
|
|
<cfargument name="headers" />
|
|
<cfargument name="methodMetadata" />
|
|
<cfargument name="matchedURI" />
|
|
<cfargument name="parsedResponse" />
|
|
<cfargument name="originalResponse" />
|
|
<cfargument name="statusCode" />
|
|
<cfreturn true />
|
|
</cffunction>
|
|
|
|
<!--- override these functions to implement caching hooks --->
|
|
<cffunction name="validCacheExists" output="false">
|
|
<cfargument name="cacheKey" />
|
|
<cfreturn false />
|
|
</cffunction>
|
|
<cffunction name="setCachedResponse" output="false">
|
|
<cfargument name="cacheKey" />
|
|
<cfargument name="data" />
|
|
</cffunction>
|
|
<cffunction name="getCachedResponse" output="false">
|
|
<cfargument name="cacheKey" />
|
|
</cffunction>
|
|
<cffunction name="getCacheKey" output="false">
|
|
<cfargument name="cfc" />
|
|
<cfargument name="requestArguments" />
|
|
<cfargument name="matchedURI" />
|
|
|
|
<cfreturn arguments.matchedURI & "_" & arguments.requestArguments.hashCode() />
|
|
</cffunction>
|
|
|
|
<!--- Your Application.cfc should override this method AND call super.onApplicationStart() --->
|
|
<cffunction name="onApplicationStart">
|
|
<cfset var before = getTickCount() />
|
|
<cfset var after = 0 />
|
|
<cfset setupFramework() />
|
|
<cfset after = getTickCount() />
|
|
<cfheader name="X-TIME-TO-RELOAD" value="#(after-before)#" />
|
|
<cfreturn true />
|
|
</cffunction>
|
|
|
|
<!--- Your Application.cfc should override this method AND call super.onRequestStart(targetpath) --->
|
|
<cffunction name="onRequestStart">
|
|
<cfargument name="targetPath" default="" />
|
|
<cfset var local = structNew() />
|
|
<cfset request.unhandled = false />
|
|
<cfset local.reloadedInThisRequest = false />
|
|
<cfset request.taffyReloaded = false />
|
|
<!--- this will probably happen if taffy is sharing an app name with an existing application so that you can use its application context --->
|
|
<cfif not structKeyExists(application, "_taffy")>
|
|
<cfset onApplicationStart() />
|
|
<cfset local.reloadedInThisRequest = true />
|
|
</cfif>
|
|
<!--- allow reloading --->
|
|
<cfif
|
|
(
|
|
structKeyExists(url, application._taffy.settings.reloadKey)
|
|
AND
|
|
url[application._taffy.settings.reloadKey] eq application._taffy.settings.reloadPassword
|
|
)
|
|
OR
|
|
(
|
|
application._taffy.settings.reloadOnEveryRequest eq true
|
|
)>
|
|
<cfif !local.reloadedInThisRequest and !isUnhandledPathRequest(arguments.targetPath)><!--- prevent double reloads --->
|
|
<cfset onApplicationStart() />
|
|
</cfif>
|
|
</cfif>
|
|
<cfif !isUnhandledPathRequest(arguments.targetPath)>
|
|
<!--- if browsing to root of api, show dashboard --->
|
|
<cfset local.path = replaceNoCase(cgi.path_info, cgi.script_name, "") />
|
|
<cfif
|
|
NOT structKeyExists(url,application._taffy.settings.endpointURLParam)
|
|
AND NOT structKeyExists(form,application._taffy.settings.endpointURLParam)
|
|
AND len(local.path) lte 1
|
|
AND listFindNoCase(cgi.script_name, "index.cfm", "/") EQ listLen(cgi.script_name, "/")>
|
|
<cfif NOT application._taffy.settings.disableDashboard>
|
|
<cfif StructKeyExists( URL, "docs" )>
|
|
<cfinclude template="#application._taffy.settings.docsPath#" />
|
|
<cfelse>
|
|
<cfinclude template="../dashboard/dashboard.cfm" />
|
|
</cfif>
|
|
<cfabort />
|
|
<cfelse>
|
|
<cfif len(application._taffy.settings.disabledDashboardRedirect)>
|
|
<cflocation url="#application._taffy.settings.disabledDashboardRedirect#" addtoken="false" />
|
|
<cfabort />
|
|
<cfelseif application._taffy.settings.showDocsWhenDashboardDisabled IS False>
|
|
<cfset throwError(403, "Forbidden") />
|
|
</cfif>
|
|
</cfif>
|
|
</cfif>
|
|
<cfelse>
|
|
<!--- allow pass-thru for selected paths --->
|
|
<cfset structDelete(this, 'onRequest') />
|
|
<cfset structDelete(variables, 'onRequest') />
|
|
<cfset request.unhandled = true />
|
|
</cfif>
|
|
<cfreturn true />
|
|
</cffunction>
|
|
|
|
<!--- If you choose to override this function, consider calling super.onError(exception) --->
|
|
<cffunction name="onError">
|
|
<cfargument name="exception" />
|
|
<cfset var data = {} />
|
|
<cfset var root = '' />
|
|
<cfset var logger = '' />
|
|
<cftry>
|
|
<cfif structKeyExists(request, 'unhandled') and request.unhandled eq true>
|
|
<cfreturn super.onError( arguments.exception ) />
|
|
</cfif>
|
|
|
|
<cfset logger = createObject("component", application._taffy.settings.exceptionLogAdapter).init(
|
|
application._taffy.settings.exceptionLogAdapterConfig
|
|
) />
|
|
<cfset logger.saveLog(exception) />
|
|
|
|
<!--- return 500 no matter what --->
|
|
<cfheader statuscode="500" statustext="Error" />
|
|
<cfcontent reset="true" />
|
|
|
|
<cfif structKeyExists(exception, "rootCause")>
|
|
<cfset root = exception.rootCause />
|
|
<cfelse>
|
|
<cfset root = exception />
|
|
</cfif>
|
|
|
|
<cfif structKeyExists(root, "TagContext")>
|
|
<cfset data.stacktrace = root.tagContext />
|
|
</cfif>
|
|
|
|
<cfif application._taffy.settings.returnExceptionsAsJson eq true>
|
|
<!--- try to find the relevant details --->
|
|
<cfif structKeyExists(root, "message")>
|
|
<cfset data.error = root.message />
|
|
</cfif>
|
|
<cfif structKeyExists(root, "detail")>
|
|
<cfset data.detail = root.detail />
|
|
</cfif>
|
|
<cfif structKeyExists(root,"tagContext")>
|
|
<cfset data.tagContext = root.tagContext[1].template & " [Line #root.tagContext[1].line#]" />
|
|
</cfif>
|
|
<!--- MAKE IT LOOK GOOD! --->
|
|
<cfsetting enablecfoutputonly="true" showdebugoutput="false" />
|
|
<cfcontent type="application/json; charset=utf-8" />
|
|
<cfoutput>#serializeJson(data)#</cfoutput>
|
|
</cfif>
|
|
<cfcatch>
|
|
<cfcontent reset="true" type="text/plain; charset=utf-8" />
|
|
<cfheader statuscode="500" statustext="Error" />
|
|
<cfoutput>An unhandled exception occurred: <cfif isStruct(root) and structKeyExists(root,"message")>#root.message#<cfelse>#root#</cfif> <cfif isStruct(root) and structKeyExists(root,"detail")>-- #root.detail#</cfif></cfoutput>
|
|
<cfdump var="#cfcatch#" format="text" label="ERROR WHEN LOGGING EXCEPTION" />
|
|
<cfdump var="#exception#" format="text" label="ORIGINAL EXCEPTION" />
|
|
</cfcatch>
|
|
</cftry>
|
|
</cffunction>
|
|
|
|
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
|
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
|
|
|
<!--- short-circuit logic --->
|
|
<cffunction name="onRequest" output="true" returntype="boolean">
|
|
<cfargument name="targetPage" type="string" required="true" />
|
|
|
|
<cfset var _taffyRequest = {} />
|
|
<cfset var local = {} />
|
|
<cfset var m = '' />
|
|
<cfset request._taffyRequest = _taffyRequest />
|
|
<cfset local.debug = false />
|
|
|
|
<cfset _taffyRequest.metrics = {} />
|
|
<cfset m = _taffyRequest.metrics />
|
|
<cfset m.init = getTickCount() />
|
|
|
|
<!--- enable/disable debug output per settings --->
|
|
<cfif not structKeyExists(url, application._taffy.settings.debugKey)>
|
|
<cfsetting showdebugoutput="false" />
|
|
</cfif>
|
|
|
|
<!--- display api dashboard if requested --->
|
|
<cfif
|
|
NOT structKeyExists(url,application._taffy.settings.endpointURLParam)
|
|
AND NOT structKeyExists(form,application._taffy.settings.endpointURLParam)
|
|
AND len(cgi.path_info) lte 1
|
|
AND listFindNoCase(cgi.script_name, "index.cfm", "/") EQ listLen(cgi.script_name, "/")>
|
|
<cfif NOT application._taffy.settings.disableDashboard>
|
|
<cfif StructKeyExists( URL, "docs" )>
|
|
<cfinclude template="#application._taffy.settings.docsPath#" />
|
|
<cfelse>
|
|
<cfinclude template="../dashboard/dashboard.cfm" />
|
|
</cfif>
|
|
<cfabort />
|
|
<cfelse>
|
|
<cfif len(application._taffy.settings.disabledDashboardRedirect)>
|
|
<cflocation url="#application._taffy.settings.disabledDashboardRedirect#" addtoken="false" />
|
|
<cfabort />
|
|
<cfelseif application._taffy.settings.showDocsWhenDashboardDisabled>
|
|
<cfinclude template="#application._taffy.settings.docsPath#" />
|
|
<cfabort />
|
|
<cfelse>
|
|
<cfset throwError(403, "Forbidden") />
|
|
</cfif>
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<!--- get request details --->
|
|
<cfset m.beforeParse = getTickCount() />
|
|
<cfset local.parsed = parseRequest() />
|
|
<cfset m.afterParse = getTickCount() />
|
|
<cfset structAppend(_taffyRequest, local.parsed) />
|
|
<cfset m.parseTime = m.afterParse - m.beforeParse />
|
|
|
|
<!--- CORS headers (so that CORS can pass even if the resource throws an exception) --->
|
|
<cfset local.allowVerbs = uCase(structKeyList(_taffyRequest.matchDetails.methods)) />
|
|
<cfif (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)
|
|
AND listFindNoCase('PUT,PATCH,DELETE,OPTIONS',_taffyRequest.verb)
|
|
AND NOT listFind(local.allowVerbs,'OPTIONS')>
|
|
<cfset local.allowVerbs = listAppend(local.allowVerbs,'OPTIONS') />
|
|
</cfif>
|
|
<cfif structKeyExists(_taffyRequest.headers, "origin") AND (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)>
|
|
<cfif application._taffy.settings.allowCrossDomain eq true>
|
|
<cfheader name="Access-Control-Allow-Origin" value="*" />
|
|
<cfelse>
|
|
<!---
|
|
The Access-Control-Allow-Origin header can only have 1 value so we check to see if the Origin header is
|
|
in the list of origins specified in the config setting and parrot back the Origin header if so.
|
|
We also need to add the Access-Control-Allow-Credentials header and set it to true for those type requests
|
|
--->
|
|
<cfset local.domains = listToArray( application._taffy.settings.allowCrossDomain, ', ;' )>
|
|
<cfif structKeyExists(_taffyRequest.headers, "origin")>
|
|
<cfloop from="1" to="#arrayLen( local.domains )#" index="local.i">
|
|
<cfif lcase( rereplace( _taffyRequest.headers.origin, "(http|https):\/\/", "", "all" ) ) EQ lcase( rereplace( local.domains[ local.i ], "(http|https):\/\/", "", "all" ) ) >
|
|
<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
|
|
<cfheader name="Access-Control-Allow-Credentials" value="true" />
|
|
<cfbreak>
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
</cfif>
|
|
<cfheader name="Access-Control-Allow-Methods" value="#local.allowVerbs#" />
|
|
<!--- Why do we parrot back these headers? See: https://github.com/atuttle/Taffy/issues/144 --->
|
|
<cfif not structKeyExists(_taffyRequest.headers, "Access-Control-Request-Headers")>
|
|
<cfheader name="Access-Control-Allow-Headers" value="Origin, Authorization, X-CSRF-Token, X-Requested-With, Content-Type, X-HTTP-Method-Override, Accept, Referrer, User-Agent" />
|
|
<cfelse>
|
|
<!--- parrot back all of the request headers to allow the request to continue (can we improve on this?) --->
|
|
<cfset local.allowedHeaders = {} />
|
|
<cfloop list="Origin,Authorization,X-CSRF-Token,X-Requested-With,Content-Type,X-HTTP-Method-Override,Accept,Referrer,User-Agent" index="local.h">
|
|
<cfset local.allowedHeaders[local.h] = 1 />
|
|
</cfloop>
|
|
<cfset local.requestedHeaders = _taffyRequest.headers['Access-Control-Request-Headers'] />
|
|
<cfloop list="#local.requestedHeaders#" index="local.i">
|
|
<cfset local.allowedHeaders[ local.i ] = 1 />
|
|
</cfloop>
|
|
<cfheader name="Access-Control-Allow-Headers" value="#structKeyList(local.allowedHeaders)#" />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<!--- global headers --->
|
|
<cfset addHeaders(getGlobalHeaders()) />
|
|
|
|
<!---
|
|
Now we know everything we need to know to service the request. let's service it!
|
|
--->
|
|
|
|
<!--- ...after we let the api developer know all of the request details first... --->
|
|
<cfset m.beforeOnTaffyRequest = getTickCount() />
|
|
<cfset _taffyRequest.continue = onTaffyRequest(
|
|
_taffyRequest.verb
|
|
,_taffyRequest.matchDetails.beanName
|
|
,_taffyRequest.requestArguments
|
|
,_taffyRequest.returnMimeExt
|
|
,_taffyRequest.headers
|
|
,_taffyRequest.methodMetadata
|
|
,local.parsed.matchDetails.srcUri
|
|
) />
|
|
<cfset m.afterOnTaffyRequest = getTickCount() />
|
|
<cfset m.otrTime = m.afterOnTaffyRequest - m.beforeOnTaffyRequest />
|
|
|
|
<cfif not structKeyExists(_taffyRequest, "continue")>
|
|
<!--- developer forgot to return true --->
|
|
<cfthrow
|
|
message="Error in your onTaffyRequest method"
|
|
detail="Your onTaffyRequest method returned no value. Expected: Return TRUE or call noData()/representationOf()."
|
|
errorcode="400"
|
|
/>
|
|
</cfif>
|
|
|
|
<cfif isObject(_taffyRequest.continue)>
|
|
<!--- inspection complete but request has been aborted by developer; return custom response --->
|
|
<cfset _taffyRequest.result = duplicate(_taffyRequest.continue) />
|
|
<cfset structDelete(_taffyRequest, "continue")/>
|
|
<cfset m.resourceTime = 0 />
|
|
<cfelse>
|
|
<!--- inspection complete and request allowed by developer --->
|
|
|
|
<!--- handle requests for simulated responses --->
|
|
<cfif structKeyExists(_taffyRequest.requestArguments, application._taffy.settings.simulateKey) and _taffyRequest.requestArguments[application._taffy.settings.simulateKey] eq application._taffy.settings.simulatePassword>
|
|
<!--- is there a simulated response? --->
|
|
<cfset sampler = 'sample#_taffyRequest.method#Response' />
|
|
<cfif structKeyExists(_taffyRequest.matchDetails.metadata, sampler)>
|
|
<!--- get simulated response --->
|
|
<cfinvoke
|
|
component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
|
|
method="#sampler#"
|
|
returnvariable="_taffyRequest.result"
|
|
/>
|
|
<cfset _taffyRequest.result = rep(_taffyRequest.result) />
|
|
<cfelse>
|
|
<!--- no method for simulated response, so return 400 --->
|
|
<cfset _taffyRequest.result = noData().withStatus(400, "No Sample Response Available") />
|
|
</cfif>
|
|
<cfelse>
|
|
<!--- send request to service --->
|
|
<cfif structKeyExists(_taffyRequest.matchDetails.methods, _taffyRequest.verb)>
|
|
<!--- check the cache before we call the resource --->
|
|
<cfset m.cacheCheckTime = getTickCount() />
|
|
<cfset local.cacheKey = getCacheKey(
|
|
_taffyRequest.matchDetails.beanName
|
|
,_taffyRequest.requestArguments
|
|
,local.parsed.matchDetails.srcUri
|
|
) />
|
|
<cfif ucase(_taffyRequest.verb) eq "GET" and validCacheExists(local.cacheKey)>
|
|
<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
|
|
<cfset m.cacheGetTime = getTickCount() />
|
|
<cfset _taffyRequest.result = getCachedResponse(local.cacheKey) />
|
|
<cfset m.cacheGetTime = m.cacheGetTime - getTickCount() />
|
|
<cfelse>
|
|
<cfif ucase(_taffyRequest.verb) eq "GET">
|
|
<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
|
|
<cfelse>
|
|
<cfset structDelete(m, "cacheCheckTime") />
|
|
</cfif>
|
|
<!--- returns a representation-object --->
|
|
<cfset m.beforeResource = getTickCount() />
|
|
<cfinvoke
|
|
component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
|
|
method="#_taffyRequest.method#"
|
|
argumentcollection="#_taffyRequest.requestArguments#"
|
|
returnvariable="_taffyRequest.result"
|
|
/>
|
|
<cfset m.afterResource = getTickCount() />
|
|
<cfset m.resourceTime = m.afterResource - m.beforeResource />
|
|
<cfif !isDefined("_taffyRequest.result")>
|
|
<cfthrow
|
|
message="Resource did not return a value"
|
|
detail="The resource is expected to return a call to rep()/representationOf() or noData(). It appears there was no return at all."
|
|
errorcode="taffy.resources.ResourceReturnsNothing"
|
|
/>
|
|
</cfif>
|
|
<!--- If the type returned is not an instance of baseSerializer, wrap it with a call to rep().
|
|
This way we can directly return the object instead of a serializer from resource actions. --->
|
|
<cfif !isInstanceOf(_taffyRequest.result, "taffy.core.baseSerializer")>
|
|
<cfset _taffyRequest.result = rep(_taffyRequest.result) />
|
|
</cfif>
|
|
<cfif ucase(_taffyRequest.verb) eq "GET" and structKeyExists(local, "cacheKey")>
|
|
<cfset m.cacheSaveStart = getTickCount() />
|
|
<cfset setCachedResponse(local.cacheKey, _taffyRequest.result) />
|
|
<cfset m.cacheSaveTime = getTickCount() - m.cacheSaveStart />
|
|
</cfif>
|
|
</cfif>
|
|
<cfelseif NOT listFind(local.allowVerbs,_taffyRequest.verb)>
|
|
<!--- if the verb is not implemented, refuse the request --->
|
|
<cfheader name="ALLOW" value="#local.allowVerbs#" />
|
|
<cfset throwError(405, "Method Not Allowed") />
|
|
<cfelse>
|
|
<!--- create dummy response for cross domain OPTIONS request --->
|
|
<cfset _taffyRequest.resultHeaders = structNew() />
|
|
<cfset _taffyRequest.statusArgs = structNew() />
|
|
<cfset _taffyRequest.statusArgs.statusCode = 200 />
|
|
<cfset _taffyRequest.statusArgs.statusText = 'OK' />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
</cfif>
|
|
<!--- make sure the requested mime type is available --->
|
|
<cfif not mimeSupported(_taffyRequest.returnMimeExt)>
|
|
<cfset throwError(400, "Requested format not available (#_taffyRequest.returnMimeExt#)") />
|
|
</cfif>
|
|
|
|
<cfif structKeyExists(_taffyRequest,'result')>
|
|
<!--- get status code --->
|
|
<cfset _taffyRequest.statusArgs = structNew() />
|
|
<cfset _taffyRequest.statusArgs.statusCode = _taffyRequest.result.getStatus() />
|
|
<cfset _taffyRequest.statusArgs.statusText = _taffyRequest.result.getStatusText() />
|
|
<!--- get custom headers --->
|
|
<cfinvoke
|
|
component="#_taffyRequest.result#"
|
|
method="getHeaders"
|
|
returnvariable="_taffyRequest.resultHeaders"
|
|
/>
|
|
</cfif>
|
|
|
|
<cfsetting enablecfoutputonly="true" />
|
|
<cfcontent reset="true" type="#getReturnMimeAsHeader(_taffyRequest.returnMimeExt)#; charset=utf-8" />
|
|
<cfheader statuscode="#_taffyRequest.statusArgs.statusCode#" statustext="#_taffyRequest.statusArgs.statusText#" />
|
|
|
|
<!--- headers --->
|
|
<cfset addHeaders(_taffyRequest.resultHeaders) />
|
|
|
|
<!--- add ALLOW header for current resource, which describes available verbs --->
|
|
<cfheader name="ALLOW" value="#local.allowVerbs#" />
|
|
|
|
<!--- metrics headers that should always apply --->
|
|
<cfheader name="X-TIME-IN-PARSE" value="#m.parseTime#" />
|
|
<cfheader name="X-TIME-IN-ONTAFFYREQUEST" value="#m.otrTime#" />
|
|
<cfif structKeyExists(m, "resourceTime")>
|
|
<cfheader name="X-TIME-IN-RESOURCE" value="#m.resourceTime#" />
|
|
</cfif>
|
|
<cfif structKeyExists(m, "cacheCheckTime")>
|
|
<cfheader name="X-TIME-IN-CACHE-CHECK" value="#m.cacheCheckTime#" />
|
|
</cfif>
|
|
<cfif structKeyExists(m, "cacheGetTime")>
|
|
<cfheader name="X-TIME-IN-CACHE-GET" value="#m.cacheGetTime#" />
|
|
</cfif>
|
|
<cfif structKeyExists(m, "cacheSaveTime")>
|
|
<cfheader name="X-TIME-IN-CACHE-SAVE" value="#m.cacheSaveTime#" />
|
|
</cfif>
|
|
|
|
<cfif application._taffy.settings.exposeHeaders>
|
|
<cfset local.exposeHeaderList = structKeyList(_taffyRequest.resultHeaders) />
|
|
<cfset local.exposeHeaderValue = "" />
|
|
<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET" and _taffyRequest.result.getType() eq "textual">
|
|
<cfset local.exposeHeaderList = listAppend(local.exposeHeaderList, "Etag") />
|
|
</cfif>
|
|
<cfloop list="#local.exposeHeaderList#" index="local.exposeHeader">
|
|
<!--- filter out default simple response headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers --->
|
|
<cfif not listFindNoCase("Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma", local.exposeHeader)>
|
|
<cfset local.exposeHeaderValue = listAppend(local.exposeHeaderValue, local.exposeHeader) />
|
|
</cfif>
|
|
</cfloop>
|
|
<cfif listLen(local.exposeHeaderValue) gt 0>
|
|
<cfheader name="Access-Control-Expose-Headers" value="#local.exposeHeaderValue#" />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<!--- result data --->
|
|
<cfif structKeyExists(_taffyRequest,'result')>
|
|
<cfset _taffyRequest.resultType = _taffyRequest.result.getType() />
|
|
<cfset local.resultSerialized = '' />
|
|
|
|
<cfif _taffyRequest.resultType eq "textual">
|
|
<!--- serialize the representation's data into the requested mime type --->
|
|
<cfset _taffyRequest.metrics.beforeSerialize = getTickCount() />
|
|
<cfinvoke
|
|
component="#_taffyRequest.result#"
|
|
method="getAs#_taffyRequest.returnMimeExt#"
|
|
returnvariable="_taffyRequest.resultSerialized"
|
|
/>
|
|
<cfset _taffyRequest.metrics.afterSerialize = getTickCount() />
|
|
<cfset m.serializeTime = m.afterSerialize - m.beforeSerialize />
|
|
<cfheader name="X-TIME-IN-SERIALIZE" value="#m.serializeTime#" />
|
|
|
|
<!--- apply jsonp wrapper if requested --->
|
|
<cfif structKeyExists(_taffyRequest, "jsonpCallback")>
|
|
<cfset _taffyRequest.resultSerialized = _taffyRequest.jsonpCallback & "(" & _taffyRequest.resultSerialized & ");" />
|
|
</cfif>
|
|
|
|
<!--- don't return data if etags are enabled and the data hasn't changed --->
|
|
<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET">
|
|
<!--- etag values are quoted per: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag --->
|
|
<cfif structKeyExists(server, "lucee")>
|
|
<!--- hashCode() will not work for lucee, see issue #354 --->
|
|
<cfset _taffyRequest.serverEtag = '"' & hash(_taffyRequest.resultSerialized) & '"' />
|
|
<cfelse>
|
|
<cfset _taffyRequest.serverEtag = '"' & _taffyRequest.result.getData().hashCode() & '"' />
|
|
</cfif>
|
|
<cfif structKeyExists(_taffyRequest.headers, "If-None-Match")>
|
|
<cfset _taffyRequest.clientEtag = _taffyRequest.headers['If-None-Match'] />
|
|
|
|
<cfif len(_taffyRequest.clientEtag) gt 0 and _taffyRequest.clientEtag eq _taffyRequest.serverEtag>
|
|
<cfheader statuscode="304" statustext="Not Modified" />
|
|
<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
|
|
<cfreturn true />
|
|
<cfelse>
|
|
<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
|
|
</cfif>
|
|
<cfelse>
|
|
<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<cfset m.done = getTickCount() />
|
|
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.serializeTime />
|
|
<cfif structKeyExists(m, "resourceTime")>
|
|
<cfset m.taffyTime -= m.resourceTime />
|
|
</cfif>
|
|
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
|
|
|
<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
|
|
<cfif _taffyRequest.resultSerialized neq ('"' & '"')>
|
|
<cfset local.resultSerialized = _taffyRequest.resultSerialized />
|
|
</cfif>
|
|
<!--- debug output --->
|
|
<cfif structKeyExists(url, application._taffy.settings.debugKey)>
|
|
<cfset local.debug = true />
|
|
</cfif>
|
|
|
|
<cfelseif _taffyRequest.resultType eq "filename">
|
|
<cfset m.done = getTickCount() />
|
|
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
|
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
|
<cfcontent reset="true" file="#_taffyRequest.result.getFileName()#" type="#_taffyRequest.result.getFileMime()#" deletefile="#_taffyRequest.result.getDeleteFile()#" />
|
|
|
|
<cfelseif _taffyRequest.resultType eq "filedata">
|
|
<cfset m.done = getTickCount() />
|
|
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
|
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
|
<cfcontent reset="true" variable="#_taffyRequest.result.getFileData()#" type="#_taffyRequest.result.getFileMime()#" />
|
|
|
|
<cfelseif _taffyRequest.resultType eq "imagedata">
|
|
<cfset m.done = getTickCount() />
|
|
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
|
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
|
<cfcontent reset="true" variable="#_taffyRequest.result.getImageData()#" type="#_taffyRequest.result.getFileMime()#" />
|
|
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<cfset local.resultSerialized = "" />
|
|
<cfif structKeyExists( _taffyRequest, "resultSerialized" )>
|
|
<cfset local.resultSerialized = _taffyRequest.resultSerialized />
|
|
</cfif>
|
|
|
|
<cfset local.result = StructNew() />
|
|
<cfif structKeyExists( _taffyRequest, "result" )>
|
|
<cfset local.result = _taffyRequest.result.getData() />
|
|
</cfif>
|
|
|
|
<!--- ...after the service has finished... --->
|
|
<cfset m.beforeOnTaffyRequestEnd = getTickCount() />
|
|
<cfset onTaffyRequestEnd(
|
|
_taffyRequest.verb
|
|
,_taffyRequest.matchDetails.beanName
|
|
,_taffyRequest.requestArguments
|
|
,_taffyRequest.returnMimeExt
|
|
,_taffyRequest.headers
|
|
,_taffyRequest.methodMetadata
|
|
,local.parsed.matchDetails.srcUri
|
|
,local.resultSerialized
|
|
,local.result
|
|
,_taffyRequest.statusArgs.statusCode
|
|
) />
|
|
<cfset m.otreTime = getTickCount() - m.beforeOnTaffyRequestEnd />
|
|
<cfheader name="X-TIME-IN-ONTAFFYREQUESTEND" value="#m.otreTime#" />
|
|
|
|
<cfif len(trim(local.resultSerialized))>
|
|
<cfoutput>#local.resultSerialized#</cfoutput>
|
|
</cfif>
|
|
<!--- debug output --->
|
|
<cfif local.debug>
|
|
<cfoutput><h3>Request Details:</h3><cfdump var="#_taffyRequest#"></cfoutput>
|
|
</cfif>
|
|
|
|
<cfreturn true />
|
|
</cffunction>
|
|
|
|
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
|
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
|
|
|
<!--- internal methods --->
|
|
<cffunction name="setupFramework" access="private" output="false" returntype="void">
|
|
<cfset var local = structNew() />
|
|
<cfparam name="variables.framework" default="#structNew()#" />
|
|
<cfheader name="X-TAFFY-RELOADED" value="true" />
|
|
<cfset request.taffyReloaded = true />
|
|
<cfset local._taffy = structNew() />
|
|
<cfset local._taffy.version = "3.6.0" />
|
|
<cfset local._taffy.endpoints = structNew() />
|
|
<!--- default settings --->
|
|
<cfset local.defaultConfig = structNew() />
|
|
<cfset local.defaultConfig.docs = structNew() />
|
|
<cfset local.defaultConfig.docs.APIName = "Your API Name (variables.framework.docs.APIName)" />
|
|
<cfset local.defaultConfig.docs.APIVersion = "0.0.0 (variables.framework.docs.APIVersion)" />
|
|
<cfset local.defaultConfig.docsPath = "../dashboard/docs.cfm" />
|
|
<cfset local.defaultConfig.defaultMime = "" />
|
|
<cfset local.defaultConfig.debugKey = "debug" />
|
|
<cfset local.defaultConfig.reloadKey = "reload" />
|
|
<cfset local.defaultConfig.reloadPassword = "true" />
|
|
<cfset local.defaultConfig.reloadOnEveryRequest = false />
|
|
<cfset local.defaultConfig.simulateKey = "sampleResponse" />
|
|
<cfset local.defaultConfig.simulatePassword = "true" />
|
|
<cfset local.defaultConfig.endpointURLParam = 'endpoint' />
|
|
<cfset local.defaultConfig.serializer = "taffy.core.nativeJsonSerializer" />
|
|
<cfset local.defaultConfig.deserializer = "taffy.core.nativeJsonDeserializer" />
|
|
<cfset local.defaultConfig.disableDashboard = false />
|
|
<cfset local.defaultConfig.disabledDashboardRedirect = "" />
|
|
<cfset local.defaultConfig.dashboardHeaders = {} />
|
|
<cfset local.defaultConfig.showDocsWhenDashboardDisabled = false />
|
|
<cfset local.defaultConfig.unhandledPaths = "/flex2gateway" />
|
|
<cfset local.defaultConfig.allowCrossDomain = false />
|
|
<cfset local.defaultConfig.useEtags = false />
|
|
<cfset local.defaultConfig.exposeHeaders = false />
|
|
<cfset local.defaultConfig.jsonp = false />
|
|
<cfset local.defaultConfig.noDataSends204NoContent = false />
|
|
<cfset local.defaultConfig.globalHeaders = structNew() />
|
|
<cfset local.defaultConfig.mimeTypes = structNew() />
|
|
<cfset local.defaultConfig.returnExceptionsAsJson = true />
|
|
<cfset local.defaultConfig.exceptionLogAdapter = "taffy.bonus.LogToDevNull" />
|
|
<cfset local.defaultConfig.exceptionLogAdapterConfig = StructNew() />
|
|
<cfset local.defaultConfig.csrfToken = structNew() />
|
|
<cfset local.defaultConfig.csrfToken.cookieName = "" />
|
|
<cfset local.defaultConfig.csrfToken.headerName = "" />
|
|
<!--- status --->
|
|
<cfset local._taffy.status = structNew() />
|
|
<cfset local._taffy.status.internalBeanFactoryUsed = false />
|
|
<cfset local._taffy.status.externalBeanFactoryUsed = false />
|
|
<cfset local._taffy.uriMatchOrder = [] />
|
|
<!--- allow setting overrides --->
|
|
<cfset local._taffy.settings = structNew() />
|
|
<cfset structAppend(local._taffy.settings, local.defaultConfig, true) /><!--- initialize to default values --->
|
|
<cfset structAppend(local._taffy.settings, variables.framework, true) /><!--- update with user values --->
|
|
<!--- allow environment-specific config --->
|
|
<cfset local.env = getEnvironment() />
|
|
<cfif len(local.env) gt 0>
|
|
<cfparam name="variables.framework" default="#structNew()#" />
|
|
<cfparam name="variables.framework.environments" default="#structNew()#" />
|
|
<cfif structKeyExists(variables.framework.environments, local.env) and isStruct(variables.framework.environments[local.env])>
|
|
<cfset structAppend(local._taffy.settings, variables.framework.environments[local.env]) />
|
|
</cfif>
|
|
</cfif>
|
|
<!--- bean factory definition --->
|
|
<cfif structKeyExists(variables.framework, "beanFactory")>
|
|
<cfif isSimpleValue(variables.framework.beanFactory) and len(variables.framework.beanFactory) eq 0>
|
|
<!--- if the BF value is "" doing nothing with it --->
|
|
<cfelse>
|
|
<cfset local._taffy.externalBeanFactory = variables.framework.beanFactory />
|
|
<cfset local._taffy.status.externalBeanFactoryUsed = true />
|
|
</cfif>
|
|
</cfif>
|
|
<!--- translate unhandledPaths config to regex for easier matching (This is ripped off from FW/1. Thanks, Sean!) --->
|
|
<cfset local._taffy.settings.unhandledPathsRegex = replaceNoCase(
|
|
REReplace(local._taffy.settings.unhandledPaths, '(\+|\*|\?|\.|\[|\^|\$|\(|\)|\{|\||\\)', '\\\1', 'all' ),
|
|
',', '|', 'all' )
|
|
/>
|
|
<!--- if resources folder exists, use internal bean factory --->
|
|
<cfset local.resourcePath = guessResourcesFullPath() />
|
|
<cfset local.noResources = false />
|
|
<cfif directoryExists(local.resourcePath)>
|
|
<!--- setup internal bean factory --->
|
|
<cfset local._taffy.factory = createObject("component", "taffy.core.factory") />
|
|
<cfif structKeyExists(local._taffy, "externalBeanFactory")>
|
|
<cfset local._taffy.factory.init(local._taffy.externalBeanFactory) />
|
|
<cfelse>
|
|
<cfset local._taffy.factory.init() />
|
|
</cfif>
|
|
<cfset local._taffy.factory.loadBeansFromPath(local.resourcePath, guessResourcesCFCPath(), guessResourcesFullPath(), true, local._taffy) />
|
|
<cfset local._taffy.beanList = local._taffy.factory.getBeanList() />
|
|
<cfset local._taffy.endpoints = cacheBeanMetaData(local._taffy.factory, local._taffy.beanList, local._taffy) />
|
|
<cfset local._taffy.status.internalBeanFactoryUsed = true />
|
|
<cfelseif local._taffy.status.externalBeanFactoryUsed>
|
|
<!--- only using external factory, so create a pointer to it --->
|
|
<cfset local._taffy.factory = local._taffy.externalBeanFactory />
|
|
<!--- since external factory is only factory, check it for taffy resources --->
|
|
<cfset local.beanList = getBeanListFromExternalFactory( local._taffy.externalBeanFactory ) />
|
|
<cfset local._taffy.endpoints = cacheBeanMetaData(local._taffy.externalBeanFactory, local.beanList, local._taffy) />
|
|
<cfelse>
|
|
<cfset local.noResources = true />
|
|
</cfif>
|
|
<cfif not local.noResources>
|
|
<!--- sort URIs --->
|
|
<cfset local._taffy.URIMatchOrder = sortURIMatchOrder(local._taffy.endpoints) />
|
|
<!--- automatically introspect mime types from cfc metadata of default representation class --->
|
|
<cfset local.mimetypes = inspectMimeTypes(local._taffy.settings.serializer, local._taffy.factory) />
|
|
<cfset structAppend(local._taffy.settings, local.mimeTypes) />
|
|
<!--- check to make sure a default mime type is set --->
|
|
<cfif local._taffy.settings.defaultMime eq "">
|
|
<cfset throwError(400, "You have not specified a default mime type!") />
|
|
</cfif>
|
|
</cfif>
|
|
<!--- inspect the deserializer to find out what contentTypes are supported --->
|
|
<cfset local._taffy.contentTypes = getSupportedContentTypes(local._taffy.settings.deserializer) />
|
|
<!--- hot-swap! --->
|
|
<cfset application._taffy = local._taffy />
|
|
</cffunction>
|
|
|
|
<cffunction name="parseRequest" access="private" output="false" returnType="struct">
|
|
<cfset var requestObj = {} />
|
|
<cfset var tmp = 0 />
|
|
<cfset var local = {} />
|
|
|
|
<!--- Check for method tunnelling by clients unable to send PUT/DELETE requests (e.g. Flash Player);
|
|
Actual desired method will be contained in a special header --->
|
|
<cfset var httpMethodOverride = GetPageContext().getRequest().getHeader("X-HTTP-Method-Override") />
|
|
|
|
<cfset requestObj.uri = getPath() />
|
|
<cfif NOT len(requestObj.uri)>
|
|
<cfif structKeyExists(url,application._taffy.settings.endpointURLParam)>
|
|
<cfset requestObj.uri = urlDecode( url[application._taffy.settings.endpointURLParam] ) />
|
|
|
|
<cfelseif structKeyExists(form,application._taffy.settings.endpointURLParam)>
|
|
<cfset requestObj.uri = urlDecode( form[application._taffy.settings.endpointURLParam] ) />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<!--- check for format in the URI --->
|
|
<cfset requestObj.uriFormat = formatFromURI(requestObj.uri) />
|
|
|
|
<!--- attempt to find the cfc for the requested uri --->
|
|
<cfset requestObj.matchingRegex = matchURI(requestObj.uri) />
|
|
|
|
<!--- uri doesn't map to any known resources --->
|
|
<cfif not len(requestObj.matchingRegex)>
|
|
<cfset throwError(404, "Not Found") />
|
|
</cfif>
|
|
|
|
<!--- get the cfc name and token array for the matching regex --->
|
|
<cfset requestObj.matchDetails = application._taffy.endpoints[requestObj.matchingRegex] />
|
|
|
|
<!--- which verb is requested? --->
|
|
<cfset requestObj.verb = cgi.request_method />
|
|
|
|
<!--- Should we override the actual method based on method tunnelling? --->
|
|
<cfif isDefined("httpMethodOverride") AND not isNull(httpMethodOverride)>
|
|
<cfset requestObj.verb = httpMethodOverride />
|
|
</cfif>
|
|
|
|
<cfif structKeyExists(application._taffy.endpoints[requestObj.matchingRegex].methods, requestObj.verb)>
|
|
<cfset requestObj.method = application._taffy.endpoints[requestObj.matchingRegex].methods[requestObj.verb] />
|
|
<cfelse>
|
|
<cfset requestObj.method = "" />
|
|
</cfif>
|
|
|
|
<cfset requestObj.body = getRequestBody() />
|
|
<cfset requestObj.contentType = cgi.content_type />
|
|
<cfif len(requestObj.body) AND requestObj.body neq "null">
|
|
<cfif findNoCase("multipart/form-data", requestObj.contentType)>
|
|
<!--- do nothing, to support the way railo handles multipart requests (just avoids the error condition below) --->
|
|
<cfset requestObj.queryString = cgi.query_string />
|
|
<cfelse>
|
|
<cfif contentTypeIsSupported(requestObj.contentType)>
|
|
<cfset requestObj.bodyArgs = getDeserialized(requestObj.body, requestObj.contentType) />
|
|
<cfset requestObj.queryString = cgi.query_string />
|
|
<cfelse>
|
|
<cfif isJson(requestObj.body)>
|
|
<cfset throwError(400, "Looks like you're sending JSON data, but you haven't specified a content type. Aborting request.") />
|
|
<cfelse>
|
|
<cfset throwError(400, "You must specify a content-type. Aborting request.") />
|
|
</cfif>
|
|
</cfif>
|
|
</cfif>
|
|
<cfelse>
|
|
<!--- actual query parameters --->
|
|
<cfset requestObj.queryString = cgi.query_string />
|
|
</cfif>
|
|
|
|
<!--- grab request headers --->
|
|
<cfset requestObj.headers = getHTTPRequestData().headers />
|
|
|
|
<!--- build the argumentCollection to pass to the cfc --->
|
|
<cfset requestObj.requestArguments = buildRequestArguments(
|
|
requestObj.matchingRegex,
|
|
requestObj.matchDetails.tokens,
|
|
requestObj.uri,
|
|
requestObj.queryString,
|
|
requestObj.headers
|
|
) />
|
|
<!--- include any deserialized body params --->
|
|
<cfif structKeyExists(requestObj, "bodyArgs")>
|
|
<cfset structAppend(requestObj.requestArguments, requestObj.bodyArgs) />
|
|
</cfif>
|
|
<!--- also capture form POST data (higher priority that url variables of same name) --->
|
|
<cfset structAppend(requestObj.requestArguments, form) />
|
|
|
|
<!--- if JSONP is enabled, capture the requested callback name --->
|
|
<cfif application._taffy.settings.jsonp neq false>
|
|
<cfif structKeyExists(requestObj.requestArguments, application._taffy.settings.jsonp)>
|
|
<!--- variables.framework.jsonp contains the callback parameter name --->
|
|
<cfset requestObj.jsonpCallback = requestObj.requestArguments[application._taffy.settings.jsonp] />
|
|
</cfif>
|
|
</cfif>
|
|
|
|
<!--- use requested mime type or the default --->
|
|
<cfset requestObj.returnMimeExt = "" />
|
|
<cfif structKeyExists(requestObj.requestArguments, "_taffy_mime")>
|
|
<cfset requestObj.returnMimeExt = requestObj.requestArguments._taffy_mime />
|
|
<cfif left(requestObj.returnMimeExt, 1) eq ".">
|
|
<cfset requestObj.returnMimeExt = right(requestObj.returnMimeExt, len(requestObj.returnMimeExt)-1) />
|
|
</cfif>
|
|
<cfif requestObj.returnMimeExt eq "*/*">
|
|
<cfset requestObj.returnMimeExt = application._taffy.settings.defaultMime />
|
|
</cfif>
|
|
<cfif not structKeyExists(application._taffy.settings.mimeExtensions, requestObj.returnMimeExt)>
|
|
<cfset throwError(400, "Requested mime type is not supported (#requestObj.returnMimeExt#)") />
|
|
</cfif>
|
|
<cfelseif requestObj.uriFormat neq "">
|
|
<cfset requestObj.returnMimeExt = requestObj.uriFormat />
|
|
<cfelse>
|
|
<!--- run some checks on the default --->
|
|
<cfif application._taffy.settings.defaultMime eq "">
|
|
<cfset throwError(400, "You have not specified a default mime type") />
|
|
<cfelseif not structKeyExists(application._taffy.settings.mimeExtensions, application._taffy.settings.defaultMime)>
|
|
<cfset throwError(400, "Your default mime type (#application._taffy.settings.defaultMime#) is not implemented") />
|
|
</cfif>
|
|
<cfset requestObj.returnMimeExt = application._taffy.settings.defaultMime />
|
|
</cfif>
|
|
<cfset structDelete(requestObj.requestArguments, "_taffy_mime") />
|
|
<!--- get the method metadata (if any) for onTaffyRequest --->
|
|
<cfif requestObj.method neq "">
|
|
<cfset requestObj.methodMetadata = application._taffy.endpoints[requestObj.matchingRegex].metadata[requestObj.method] />
|
|
<cfelse>
|
|
<!--- method will be "" when un-implemented method is requested (e.g. OPTIONS) --->
|
|
<cfset requestObj.methodMetadata = StructNew() />
|
|
</cfif>
|
|
<cfreturn requestObj />
|
|
</cffunction>
|
|
|
|
<cffunction name="formatFromURI" access="private" output="false">
|
|
<cfargument name="uri" />
|
|
<cfset var local = structNew() />
|
|
<cfloop collection="#application._taffy.settings.mimeExtensions#" item="local.mime">
|
|
<cfif right(arguments.uri, len(local.mime)+1) eq "." & local.mime>
|
|
<cfreturn local.mime />
|
|
</cfif>
|
|
</cfloop>
|
|
<cfreturn "" />
|
|
</cffunction>
|
|
|
|
<cffunction name="convertURItoRegex" access="private" output="false">
|
|
<cfargument name="uri" type="string" required="true" hint="wants the uri mapping defined by the cfc endpoint" />
|
|
<cfset var local = StructNew() />
|
|
|
|
<cfset local.uriChunks = listToArray(arguments.uri, '/') />
|
|
<cfset local.returnData = StructNew() />
|
|
<cfset local.returnData.tokens = ArrayNew(1) />
|
|
<cfset local.uriMatcher = "" />
|
|
|
|
<cfset local.regexes.segment = "([^\/]+)" /> <!--- anything but a slash --->
|
|
<cfset local.regexes.segmentWithOptFormat = "(?:(?:([^\/\.]+)(?:\.)([a-zA-Z0-9]+))\/?|([^\/\.]+))" />
|
|
<cfset local.regexes.optFormatWithOptSlash = "((?:\.)[^\.\?\/]+)?\/?" /><!--- for ".json[/]" support --->
|
|
<!---
|
|
above regex explained:
|
|
(?:
|
|
(?:
|
|
([^\/]+)(?:\.)([a-zA-Z0-9]+) --foo.json
|
|
)
|
|
\/? --optional trailing slash
|
|
|( --or
|
|
([^\/]+) --foo
|
|
)
|
|
)
|
|
|
|
we make it this complicated so that we can capture the ".json" separately from the "foo"
|
|
... fucking regex, man!
|
|
--->
|
|
|
|
<cfloop array="#local.uriChunks#" index="local.chunk">
|
|
<cfif left(local.chunk, 1) neq "{" or right(local.chunk, 1) neq "}">
|
|
<!--- not a token --->
|
|
<cfset local.uriMatcher = local.uriMatcher & '/' & local.chunk />
|
|
<cfelse>
|
|
<!--- strip {curly braces} --->
|
|
<cfset local.chunk = left(right(local.chunk, len(local.chunk)-1), len(local.chunk)-2) />
|
|
<!--- it's a token... but which kind? --->
|
|
<cfif find(':', local.chunk) neq 0>
|
|
<cfset local.pattern = '(' & listRest(local.chunk, ':') & ')' /><!--- make sure we capture the value --->
|
|
<cfset local.tokenName = listFirst(local.chunk, ':') />
|
|
<cfelse>
|
|
<cfset local.pattern = local.regexes.segment />
|
|
<cfset local.tokenName = local.chunk />
|
|
</cfif>
|
|
<cfset local.uriMatcher = local.uriMatcher & '/' & local.pattern />
|
|
<cfset arrayAppend(local.returnData.tokens, local.tokenName) />
|
|
</cfif>
|
|
</cfloop>
|
|
|
|
<!--- if uriRegex ends with a token, slip the format piece in there too... --->
|
|
<cfset local.uriRegex = "^" & local.uriMatcher />
|
|
<cfif right(local.uriRegex, 8) eq local.regexes.segment>
|
|
<cfset local.uriRegex = left(local.uriRegex, len(local.uriRegex)-8) & local.regexes.segmentWithOptFormat />
|
|
</cfif>
|
|
|
|
<!--- require the uri to terminate after specified content --->
|
|
<cfset local.uriRegex = local.uriRegex & local.regexes.optFormatWithOptSlash & "$" />
|
|
|
|
<cfset local.returnData.uriRegex = local.uriRegex />
|
|
<cfreturn local.returnData />
|
|
</cffunction>
|
|
|
|
<cffunction name="sortURIMatchOrder" access="private" output="false">
|
|
<cfargument name="endpoints" />
|
|
<cfset var URIMatchOrder = listToArray( structKeyList(arguments.endpoints, chr(10)), chr(10) ) />
|
|
<cfset arraySort(URIMatchOrder, "textnocase", "desc") />
|
|
<cfreturn URIMatchOrder />
|
|
</cffunction>
|
|
|
|
<cffunction name="matchURI" access="private" output="false" returnType="string">
|
|
<cfargument name="requestedURI" type="string" required="true" hint="probably just pass in cgi.path_info" />
|
|
<cfset var local = {} />
|
|
<cfset local.uriCount = arrayLen(application._taffy.URIMatchOrder) />
|
|
<cfloop from="1" to="#local.uriCount#" index="local.i">
|
|
<cfset local.attempt = reMatchNoCase(application._taffy.URIMatchOrder[local.i], arguments.requestedURI) />
|
|
<cfif arrayLen(local.attempt) gt 0>
|
|
<!--- found our mapping --->
|
|
<cfreturn application._taffy.URIMatchOrder[local.i] />
|
|
</cfif>
|
|
</cfloop>
|
|
<!--- nothing found --->
|
|
<cfreturn "" />
|
|
</cffunction>
|
|
|
|
<cffunction name="getRequestBody" access="private" output="false" hint="Gets request body data, which CF doesn't do automatically for some verbs">
|
|
<!--- Special thanks to Jason Dean (@JasonPDean) and Ray Camden (@ColdFusionJedi) who helped me figure out how to do this --->
|
|
<cfset var body = getHTTPRequestData().content />
|
|
<!--- on input with content-type "application/json" CF seems to expose it as binary data. Here we convert it back to plain text --->
|
|
<cfif isBinary(body)>
|
|
<cfset body = charsetEncode(body, "UTF-8") />
|
|
</cfif>
|
|
<cfreturn body />
|
|
</cffunction>
|
|
|
|
<cffunction name="contentTypeIsSupported" access="private" output="false">
|
|
<cfargument name="contentType" type="string" />
|
|
<cfset var ct = listFirst(arguments.contentType, ";") />
|
|
<cfreturn structKeyExists(application._taffy.contentTypes, ct) />
|
|
</cffunction>
|
|
|
|
<cffunction name="getDeserialized" access="private" output="false">
|
|
<cfargument name="body" required="true" />
|
|
<cfargument name="contentType" required="true" />
|
|
<cfset var ct = listFirst(arguments.contentType,';') />
|
|
<cfset var fn = application._taffy.contentTypes[ct] />
|
|
<cfset var args = {} />
|
|
<cfset var result = {} />
|
|
<cfset args.body = arguments.body />
|
|
|
|
<cfinvoke
|
|
component="#application._taffy.settings.deserializer#"
|
|
method="#fn#"
|
|
argumentcollection="#args#"
|
|
returnvariable="result"
|
|
/>
|
|
<cfreturn result />
|
|
</cffunction>
|
|
|
|
<cffunction name="getSupportedContentTypes" access="private" output="false">
|
|
<cfargument name="deserializer" hint="must be the full dot-notation path to the component" />
|
|
<cfreturn _recurse_getSupportedContentTypes(getComponentMetadata(arguments.deserializer)) />
|
|
</cffunction>
|
|
|
|
<cffunction name="_recurse_getSupportedContentTypes" access="private" output="false">
|
|
<cfargument name="objMetaData" />
|
|
<cfset var local = StructNew() />
|
|
<cfset local.response = {} />
|
|
<!--- recurse into parents first so that children override parents --->
|
|
<cfif structKeyExists(arguments.objMetaData, "extends")>
|
|
<cfset local.response = _recurse_getSupportedContentTypes(arguments.objMetaData.extends) />
|
|
</cfif>
|
|
<!--- then handle child settings --->
|
|
<cfif structKeyExists(arguments.objMetaData, "functions") and isArray(arguments.objMetaData.functions)>
|
|
<cfset local.funcs = arguments.objMetaData.functions />
|
|
<cfloop from="1" to="#arrayLen(local.funcs)#" index="local.f">
|
|
<!--- for every function whose name starts with "getFrom" *and* has a taffy_mime metadata attribute, count it --->
|
|
<cfset local.mime = '' />
|
|
<cfif structKeyExists(local.funcs[local.f], "taffy_mime")>
|
|
<cfset local.mime = local.funcs[local.f].taffy_mime />
|
|
<cfelseif structKeyExists(local.funcs[local.f], "taffy:mime")>
|
|
<cfset local.mime = local.funcs[local.f]["taffy:mime"] />
|
|
</cfif>
|
|
<cfif ucase(left(local.funcs[local.f].name, 7)) eq "GETFROM" and len(local.mime)>
|
|
<cfloop list="#local.mime#" delimiters=",;" index="local.m">
|
|
<cfset local.response[local.m] = local.funcs[local.f].name />
|
|
</cfloop>
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
<cfreturn local.response />
|
|
</cffunction>
|
|
|
|
<cffunction name="buildRequestArguments" access="private" output="false" returnType="struct">
|
|
<cfargument name="regex" type="string" required="true" hint="regex that describes the request (including uri and query string parameters)" />
|
|
<cfargument name="tokenNamesArray" type="array" required="true" hint="array of token names associated with the matched uri" />
|
|
<cfargument name="uri" type="string" required="true" hint="the requested uri" />
|
|
<cfargument name="queryString" type="string" required="true" hint="any query string parameters included in the request" />
|
|
<cfargument name="headers" type="struct" required="true" hint="any headers included in the request" />
|
|
|
|
<cfset var local = StructNew() />
|
|
<cfset var tmp = "" />
|
|
<cfset local.returnData = StructNew() /><!--- this will be used as an argumentCollection for the method that ultimately gets called --->
|
|
|
|
<!--- parse path_info data into key-value pairs --->
|
|
<cfset local.tokenValues = reFindNoSuck(arguments.regex, arguments.uri) />
|
|
<cfset local.numTokenValues = arrayLen(local.tokenValues) />
|
|
<cfset local.numTokenNames = arrayLen(arguments.tokenNamesArray) />
|
|
<cfif local.numTokenNames gt 0>
|
|
<cfloop from="1" to="#local.numTokenNames#" index="local.t">
|
|
<cfset local.returnData[arguments.tokenNamesArray[local.t]] = local.tokenValues[local.t] />
|
|
</cfloop>
|
|
</cfif>
|
|
<!--- query_string input is also key-value pairs --->
|
|
<cfloop list="#arguments.queryString#" delimiters="&" index="local.t">
|
|
<cfset local.qsKey = urlDecode(listFirst(local.t,'=')) />
|
|
<cfset local.qsValue = "" />
|
|
<cfif listLen(local.t,'=') eq 2>
|
|
<cfset local.qsValue = urlDecode(listLast(local.t,'=')) />
|
|
</cfif>
|
|
<cfif (len(local.qsKey) gt 2) and (right(local.qsKey, 2) eq "[]")>
|
|
<cfset local.qsKey = left(local.qsKey, len(local.qsKey) - 2) />
|
|
<cfif not structKeyExists(local.returnData, local.qsKey)>
|
|
<cfset local.returnData[local.qsKey] = arrayNew(1) />
|
|
</cfif>
|
|
<cfset arrayAppend(local.returnData[local.qsKey], local.qsValue) />
|
|
<cfelse>
|
|
<cfset local.returnData[local.qsKey] = local.qsValue />
|
|
</cfif>
|
|
</cfloop>
|
|
<!--- if a mime type is requested as part of the url ("whatever.json"), then extract that so taffy can use it --->
|
|
<cfif local.numTokenValues gt local.numTokenNames><!--- when there is 1 more token value than name, that value (regex capture group) is the format --->
|
|
<cfset local.mime = local.tokenValues[local.numTokenValues] />
|
|
<cfset local.returnData["_taffy_mime"] = local.mime />
|
|
<cfelseif structKeyExists(arguments.headers, "Accept")>
|
|
<cfset local.headerMatch = false />
|
|
<cfloop list="#arguments.headers.accept#" index="tmp">
|
|
<!--- deal with that q=0 stuff (just ignore it) --->
|
|
<cfif listLen(tmp, ";") gt 1>
|
|
<cfset tmp = listFirst(tmp, ";") />
|
|
</cfif>
|
|
<cfif structKeyExists(application._taffy.settings.mimeTypes, tmp)>
|
|
<cfset local.returnData["_taffy_mime"] = application._taffy.settings.mimeTypes[tmp] />
|
|
<cfset local.headerMatch = true />
|
|
<cfbreak /><!--- exit loop --->
|
|
<cfelseif trim(tmp) eq "*/*">
|
|
<cfset local.returnData["_taffy_mime"] = application._taffy.settings.defaultMime />
|
|
<cfset local.headerMatch = true />
|
|
<cfbreak /><!--- exit loop --->
|
|
</cfif>
|
|
</cfloop>
|
|
<!--- if a header is passed, but it didn't match any known mimes, and no mime was found via extension, just use whatever's in the header --->
|
|
<cfif local.headerMatch eq false>
|
|
<cfset local.returnData["_taffy_mime"] = listFirst(listFirst(arguments.headers.accept, ","), ";") />
|
|
</cfif>
|
|
</cfif>
|
|
<cfreturn local.returnData />
|
|
</cffunction>
|
|
|
|
<cffunction name="guessResourcesPath" access="private" output="false" returntype="string" hint="used to try and figure out the absolute path of the /resources folder even though this file may not be in the web root">
|
|
<!--- if /resources has been explicitly defined in an server/application mapping, it should take precedence --->
|
|
<cfif directoryExists(expandPath("resources"))>
|
|
<cfreturn "resources" />
|
|
<cfelseif directoryExists(expandPath("/resources"))>
|
|
<cfreturn "/resources" />
|
|
</cfif>
|
|
|
|
<!--- if all else fails, fall through to guessing where /resources lives --->
|
|
<cfset local.indexcfmpath = cgi.script_name />
|
|
<cfset local.resourcesPath = listDeleteAt(local.indexcfmpath, listLen(local.indexcfmpath, "/"), "/") & "/resources" />
|
|
|
|
<cfif GetContextRoot() NEQ "">
|
|
<cfset local.resourcesPath = ReReplace(local.resourcesPath,"^#GetContextRoot()#","")>
|
|
</cfif>
|
|
|
|
<cfreturn local.resourcesPath />
|
|
</cffunction>
|
|
|
|
<cffunction name="guessResourcesFullPath" access="private" output="false" returntype="string">
|
|
<cfreturn expandPath(guessResourcesPath()) />
|
|
</cffunction>
|
|
|
|
<cffunction name="guessResourcesCFCPath" access="private" output="false" returntype="string">
|
|
<cfset var path = guessResourcesPath() />
|
|
<cfif left(path, 1) eq "/"><cfset path = right(path, len(path)-1) /></cfif>
|
|
<cfreturn reReplace(path, "\/", ".", "all") />
|
|
</cffunction>
|
|
|
|
<cffunction name="throwError" access="private" output="false" returntype="void">
|
|
<cfargument name="statusCode" type="numeric" default="500" />
|
|
<cfargument name="msg" type="string" required="true" hint="message to return to api consumer" />
|
|
<cfargument name="headers" type="struct" required="false" default="#structNew()#" />
|
|
<cfcontent reset="true" />
|
|
<cfset addHeaders(arguments.headers) />
|
|
<cfheader statuscode="#arguments.statusCode#" statustext="#arguments.msg#" />
|
|
<cfabort />
|
|
</cffunction>
|
|
|
|
<cffunction name="splitURIs" access="private" returntype="array" output="false">
|
|
<cfargument name="input" type="string" required="true" />
|
|
|
|
<cfset var inBracketCount = 0 />
|
|
<cfset var output = [] />
|
|
<cfset var s = createObject("java", "java.lang.StringBuffer").init("") />
|
|
<cfset var c = "" />
|
|
<cfloop array="#trim(input).toCharArray()#" index="c">
|
|
<cfif c EQ "{">
|
|
<cfset inBracketCount += 1 />
|
|
<cfelseif c EQ "}" AND inBracketCount GT 0>
|
|
<cfset inBracketCount -= 1 />
|
|
</cfif>
|
|
<cfif c EQ "," AND inBracketCount EQ 0>
|
|
<cfset arrayAppend(output, s.toString()) />
|
|
<cfset s.setLength(0) />
|
|
<cfelse>
|
|
<cfset s.append(c) />
|
|
</cfif>
|
|
</cfloop>
|
|
<cfif s.length() GT 0>
|
|
<cfset arrayAppend(output, s.toString()) />
|
|
</cfif>
|
|
<cfreturn output />
|
|
</cffunction>
|
|
|
|
<cffunction name="throwExceptionIfURIDoesntBeginWithForwardSlash" output="false" access="private" returntype="void">
|
|
<cfargument name="uri" type="string" required="true">
|
|
<cfargument name="beanName" type="string" required="true">
|
|
|
|
<cfset var uriDoesntBeginWithForwardSlash = left(arguments.uri,1) neq "/">
|
|
|
|
<cfif uriDoesntBeginWithForwardSlash>
|
|
<cfthrow
|
|
message="URI doesn't begin with a forward slash."
|
|
detail="The URI (#arguments.uri#) for `#arguments.beanName#` should begin with a forward slash."
|
|
errorcode="taffy.resources.URIDoesntBeginWithForwardSlash"
|
|
/>
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="cacheBeanMetaData" access="private" output="false">
|
|
<cfargument name="factory" required="true" />
|
|
<cfargument name="beanList" type="string" required="true" />
|
|
<cfargument name="taffyRef" type="any" required="True" />
|
|
<cfset var local = StructNew() />
|
|
<cfset local.endpoints = StructNew() />
|
|
<cfloop list="#arguments.beanList#" index="local.beanName">
|
|
<!--- get the cfc metadata that defines the uri for that cfc --->
|
|
<cfset local.cfcMetadata = getMetaData(arguments.factory.getBean(local.beanName)) />
|
|
<cfset local.uriAttr = '' />
|
|
<cfif structKeyExists(local.cfcMetadata, "taffy_uri")>
|
|
<cfset local.uriAttr = local.cfcMetadata["taffy_uri"] />
|
|
<cfelseif structKeyExists(local.cfcMetadata, "taffy:uri")>
|
|
<cfset local.uriAttr = local.cfcMetadata["taffy:uri"] />
|
|
</cfif>
|
|
|
|
<cfset local.uris = splitURIs(local.uriAttr) />
|
|
|
|
<cfif structKeyExists(local.cfcMetaData, "taffy:aopbean")>
|
|
<cfset local.cachedBeanName = local.cfcMetaData["taffy:aopbean"] />
|
|
<cfelseif structKeyExists(local.cfcMetaData, "taffy_aopbean")>
|
|
<cfset local.cachedBeanName = local.cfcMetaData["taffy_aopbean"] />
|
|
<cfelse>
|
|
<cfset local.cachedBeanName = local.beanName />
|
|
</cfif>
|
|
|
|
<!--- if it doesn't have any uris, then it's not a resource --->
|
|
<cfif arrayLen(local.uris)>
|
|
<cfloop array="#local.uris#" index="local.uri">
|
|
<cftry>
|
|
<cfset local.uri = trim(local.uri) />
|
|
<cfset local.metaInfo = convertURItoRegex(local.uri) />
|
|
<cfif structKeyExists(local.endpoints, local.metaInfo.uriRegex)>
|
|
<cfthrow
|
|
message="Duplicate URI scheme detected. All URIs must be unique (excluding tokens)."
|
|
detail="The URI (#local.uri#) for `#local.beanName#` conflicts with the existing URI definition of `#local.endpoints[local.metaInfo.uriRegex].beanName#`"
|
|
errorcode="taffy.resources.DuplicateUriPattern"
|
|
/>
|
|
</cfif>
|
|
|
|
<cfset throwExceptionIfURIDoesntBeginWithForwardSlash(local.uri, local.beanName)>
|
|
|
|
<cfset local.endpoints[local.metaInfo.uriRegex] = { beanName = local.cachedBeanName, tokens = local.metaInfo.tokens, methods = structNew(), srcURI = local.uri } />
|
|
<cfif structKeyExists(local.cfcMetadata, "functions")>
|
|
<cfloop array="#local.cfcMetadata.functions#" index="local.f">
|
|
<cfif local.f.name eq "get" or local.f.name eq "post" or local.f.name eq "put" or local.f.name eq "patch" or local.f.name eq "delete" or local.f.name eq "head" or local.f.name eq "options">
|
|
<cfset local.endpoints[local.metaInfo.uriRegex].methods[local.f.name] = local.f.name />
|
|
|
|
<!--- also support future/misc verbs via metadata --->
|
|
<cfelseif structKeyExists(local.f,"taffy:verb")>
|
|
<cfset local.endpoints[local.metaInfo.uriRegex].methods[local.f["taffy:verb"]] = local.f.name />
|
|
<cfelseif structKeyExists(local.f,"taffy_verb")>
|
|
<cfset local.endpoints[local.metaInfo.uriRegex].methods[local.f["taffy_verb"]] = local.f.name />
|
|
</cfif>
|
|
|
|
<!--- cache any extra function metadata for use in onTaffyRequest --->
|
|
<cfset local.extraMetadata = duplicate(local.f) />
|
|
<cfset structDelete(local.extraMetadata, "name") />
|
|
<cfset structDelete(local.extraMetadata, "parameters") />
|
|
<cfset structDelete(local.extraMetadata, "hint") />
|
|
<cfparam name="local.endpoints['#local.metaInfo.uriRegex#'].metadata" default="#structNew()#" />
|
|
<cfif not structIsEmpty(local.extraMetadata)>
|
|
<cfset local.endpoints[local.metaInfo.uriRegex].metadata[local.f.name] = local.extraMetadata />
|
|
<cfelse>
|
|
<cfset local.endpoints[local.metaInfo.uriRegex].metadata[local.f.name] = StructNew() />
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
<cfcatch>
|
|
<!--- skip cfc's with errors, but save info about them for display in the dashboard --->
|
|
<cfset local.err = structNew() />
|
|
<cfset local.err.resource = local.beanName />
|
|
<cfset local.err.exception = cfcatch />
|
|
<cfset arrayAppend(arguments.taffyRef.status.skippedResources, local.err) />
|
|
</cfcatch>
|
|
</cftry>
|
|
</cfloop>
|
|
</cfif>
|
|
</cfloop>
|
|
<cfreturn local.endpoints />
|
|
</cffunction>
|
|
|
|
<cffunction name="getBeanListFromExternalFactory" output="false" access="private" returntype="String">
|
|
<cfargument name="bf" required="true" />
|
|
<cfset var beanFactoryMeta = getMetadata(arguments.bf) />
|
|
<cfif lcase(left(beanFactoryMeta.name, 10)) eq "coldspring">
|
|
<cfreturn getBeanListFromColdSpring( arguments.bf ) />
|
|
<cfelseif beanFactoryMeta.name contains "ioc">
|
|
<!--- this isn't a perfect test (contains "ioc") but it's all we can do for now... --->
|
|
<cfset local.beanInfo = arguments.bf.getBeanInfo().beanInfo />
|
|
<cfset local.beanList = "" />
|
|
<cfloop collection="#local.beanInfo#" item="local.beanName">
|
|
<cfif structKeyExists(local.beanInfo[local.beanName],'name')
|
|
AND local.beanName NEQ local.beanInfo[local.beanName].name
|
|
AND isInstanceOf(arguments.bf.getBean(local.beanName),'taffy.core.resource')>
|
|
<cfset local.beanList = listAppend(local.beanList,local.beanName) />
|
|
</cfif>
|
|
</cfloop>
|
|
<cfreturn local.beanList />
|
|
</cfif>
|
|
<cfreturn "" />
|
|
</cffunction>
|
|
|
|
<cffunction name="getBeanListFromColdSpring" access="private" output="false" returntype="string">
|
|
<cfargument name="bf" required="true" />
|
|
<cfset var local = StructNew() />
|
|
<cfset local.beans = arguments.bf.getBeanDefinitionList() />
|
|
<cfset local.beanList = "" />
|
|
<cfloop collection="#local.beans#" item="local.beanName">
|
|
<!---
|
|
Can't call instanceOf() method on beans generated by factories; there is no
|
|
class property on factory-generated beans, and it throws an error in ColdSpring 1.2.
|
|
Temporary patch is to skip those beans, but we'd really either want to examine the
|
|
return type metadata on the factory method or find some other way to determine if
|
|
this is a Taffy bean.
|
|
--->
|
|
<cfif (local.beans[local.beanName].getBeanClass() NEQ "")>
|
|
<cfif (local.beans[local.beanName].instanceOf('taffy.core.resource'))>
|
|
<cfset local.beanList = listAppend(local.beanList, local.beanName) />
|
|
</cfif>
|
|
</cfif>
|
|
</cfloop>
|
|
<cfreturn local.beanList />
|
|
</cffunction>
|
|
|
|
<cffunction name="inspectMimeTypes" access="private" output="false">
|
|
<cfargument name="customClassDotPath" type="string" required="true" hint="dot-notation path of representation class" />
|
|
<cfargument name="factory" required="true" />
|
|
<cfif arguments.factory.containsBean(arguments.customClassDotPath)>
|
|
<cfreturn _recurse_inspectMimeTypes(getMetadata(arguments.factory.getBean(arguments.customClassDotPath))) />
|
|
<cfelse>
|
|
<cfreturn _recurse_inspectMimeTypes(getComponentMetadata(arguments.customClassDotPath)) />
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="_recurse_inspectMimeTypes" output="false" access="private">
|
|
<cfargument name="objMetaData" type="struct" required="true" />
|
|
<cfargument name="data" type="struct" default="#StructNew()#" />
|
|
<cfset var local = StructNew() />
|
|
<cfset local.ext = '' />
|
|
<cfparam name="arguments.data.mimeTypes" default="#StructNew()#" />
|
|
<cfparam name="arguments.data.mimeExtensions" default="#StructNew()#" />
|
|
<cfparam name="arguments.data.defaultMime" default="" />
|
|
<!--- recurse into parents first so that child defaults override parent defaults --->
|
|
<cfif structKeyExists(arguments.objMetaData, "extends")>
|
|
<cfset arguments.data = _recurse_inspectMimeTypes(arguments.objMetaData.extends, arguments.data) />
|
|
</cfif>
|
|
<!--- then handle child settings --->
|
|
<cfif structKeyExists(arguments.objMetaData, "functions") and isArray(arguments.objMetaData.functions)>
|
|
<cfset local.funcs = arguments.objMetaData.functions />
|
|
<cfloop from="1" to="#arrayLen(local.funcs)#" index="local.f">
|
|
<!--- for every function whose name starts with "getAs" *and* has a taffy_mime metadata attribute, register the mime type --->
|
|
<cfset local.mime = '' />
|
|
<cfif structKeyExists(local.funcs[local.f], "taffy_mime")>
|
|
<cfset local.mime = local.funcs[local.f].taffy_mime />
|
|
<cfelseif structKeyExists(local.funcs[local.f], "taffy:mime")>
|
|
<cfset local.mime = local.funcs[local.f]["taffy:mime"] />
|
|
</cfif>
|
|
<cfif ucase(left(local.funcs[local.f].name, 5)) eq "GETAS" and len(local.mime)>
|
|
<cfset local.ext = lcase(right(local.funcs[local.f].name, len(local.funcs[local.f].name)-5)) />
|
|
<cfset local.mime = lcase(local.mime) />
|
|
<cfloop list="#local.mime#" delimiters=",;" index="local.thisMime">
|
|
<cfparam name="arguments.data.mimeExtensions['#local.ext#']" default="#local.thisMime#" />
|
|
<cfset arguments.data.mimeTypes[local.thisMime] = local.ext />
|
|
</cfloop>
|
|
<!--- check for taffy_default metadata to set the current mime as the default --->
|
|
<cfif structKeyExists(local.funcs[local.f], "taffy_default") and local.funcs[local.f].taffy_default>
|
|
<cfset arguments.data.defaultMime = local.ext />
|
|
<cfelseif structKeyExists(local.funcs[local.f], "taffy:default") and local.funcs[local.f]["taffy:default"] eq true>
|
|
<cfset arguments.data.defaultMime = local.ext />
|
|
</cfif>
|
|
</cfif>
|
|
</cfloop>
|
|
</cfif>
|
|
<cfreturn arguments.data />
|
|
</cffunction>
|
|
|
|
<cffunction name="mimeSupported" output="false" access="private" returntype="boolean">
|
|
<cfargument name="mimeExt" type="string" required="true" />
|
|
<cfif structKeyExists(application._taffy.settings.mimeExtensions, arguments.mimeExt)>
|
|
<cfreturn true />
|
|
</cfif>
|
|
<cfif structKeyExists(application._taffy.settings.mimeTypes, arguments.mimeExt)>
|
|
<cfreturn true />
|
|
</cfif>
|
|
<cfreturn false />
|
|
</cffunction>
|
|
|
|
<cffunction name="getReturnMimeAsHeader" output="false" access="private">
|
|
<cfargument name="mimeExt" type="string" required="true" />
|
|
<cfif structKeyExists(application._taffy.settings.mimeExtensions, arguments.mimeExt)>
|
|
<cfreturn application._taffy.settings.mimeExtensions[arguments.mimeExt] />
|
|
</cfif>
|
|
<cfif structKeyExists(application._taffy.settings.mimeTypes, arguments.mimeExt)>
|
|
<cfreturn arguments.mimeExt />
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="getReturnMimeAsExt" output="false" access="private">
|
|
<cfargument name="mimeExt" type="string" required="true" />
|
|
<cfif structKeyExists(application._taffy.settings.mimeExtensions, arguments.mimeExt)>
|
|
<cfreturn arguments.mimeExt />
|
|
</cfif>
|
|
<cfif structKeyExists(application._taffy.settings.mimeTypes, arguments.mimeExt)>
|
|
<cfreturn application._taffy.settings.mimeTypes[arguments.mimeExt] />
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="isUnhandledPathRequest" access="private" returntype="boolean">
|
|
<cfargument name="targetPath" />
|
|
<cfreturn REFindNoCase( "^(" & application._taffy.settings.unhandledPathsRegex & ")", arguments.targetPath ) />
|
|
</cffunction>
|
|
|
|
<cffunction name="reFindNoSuck" output="false" access="private" hint="I wrote this wrapper for reFindNoCase because the way it returns matches is god awful.">
|
|
<cfargument name="pattern" required="true" type="string" />
|
|
<cfargument name="data" required="true" type="string" />
|
|
<cfargument name="startPos" required="false" default="1" />
|
|
<cfscript>
|
|
var local = StructNew();
|
|
local.awesome = arrayNew(1);
|
|
local.sucky = refindNoCase(arguments.pattern, arguments.data, arguments.startPos, true);
|
|
if (not isArray(local.sucky.len) or arrayLen(local.sucky.len) eq 0){return arrayNew(1);} //handle no match at all
|
|
for (local.i=1; local.i<= arrayLen(local.sucky.len); local.i++){
|
|
//if there's a match with pos 0 & length 0, that means the mime type was not specified
|
|
if (local.sucky.len[local.i] gt 0 && local.sucky.pos[local.i] gt 0){
|
|
//don't include the group that matches the entire pattern
|
|
local.matchBody = mid(arguments.data, local.sucky.pos[local.i], local.sucky.len[local.i]);
|
|
if (local.matchBody neq arguments.data){
|
|
arrayAppend( local.awesome, local.matchBody );
|
|
}
|
|
}
|
|
}
|
|
return local.awesome;
|
|
</cfscript>
|
|
</cffunction>
|
|
|
|
<!---
|
|
helper methods: stuff used in Application.cfc
|
|
--->
|
|
<cffunction name="getBeanFactory" access="private" output="false">
|
|
<cfreturn application._taffy.factory />
|
|
</cffunction>
|
|
|
|
<cffunction name="getExternalBeanFactory" access="private" output="false">
|
|
<cfreturn application._taffy.externalBeanFactory />
|
|
</cffunction>
|
|
|
|
<cffunction name="newRepresentation" access="private" output="false" hint="private as of 3.0">
|
|
<cfset var repClass = application._taffy.settings.serializer />
|
|
<cfif application._taffy.factory.containsBean(repClass)>
|
|
<cfreturn application._taffy.factory.getBean(repClass) />
|
|
<cfelse>
|
|
<cfreturn createObject("component", repClass) />
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="noData" access="public" output="false">
|
|
<cfreturn newRepresentation().noData() />
|
|
</cffunction>
|
|
|
|
<cffunction name="noContent" access="public" output="false">
|
|
<cfreturn newRepresentation().noContent() />
|
|
</cffunction>
|
|
|
|
<cffunction name="representationOf" access="public" output="false">
|
|
<cfargument name="data" required="true" />
|
|
<cfreturn newRepresentation().setData( arguments.data ) />
|
|
</cffunction>
|
|
|
|
<cffunction name="rep" access="public" output="false" hint="alias for representationOf">
|
|
<cfargument name="data" required="true" />
|
|
<cfreturn representationOf(arguments.data) />
|
|
</cffunction>
|
|
|
|
<cffunction name="getGlobalHeaders" access="private" output="false" returntype="Struct">
|
|
<cfreturn application._taffy.settings.globalHeaders />
|
|
</cffunction>
|
|
|
|
<cfif NOT isDefined("getComponentMetadata")>
|
|
<!--- workaround for platforms where getComponentMetadata doesn't exist --->
|
|
<cffunction name="tmp">
|
|
<cfreturn getMetaData(createObject("component",arguments[1])) />
|
|
</cffunction>
|
|
<cfset this.getComponentMetadata = tmp />
|
|
</cfif>
|
|
|
|
<cffunction name="getPath" output="false" access="public" returntype="String"
|
|
hint="This method returns just the URI portion of the URL, and makes it easier to port Taffy to other
|
|
platforms by subclassing this method to match the way the platform works. The default behavior is
|
|
tested and works on Adobe ColdFusion 9.0.1.">
|
|
<cfif cgi.path_info eq cgi.script_name>
|
|
<!--- WTF! I've only seen this on Win+IIS, seems fine on OSX+Apache... --->
|
|
<cfreturn "" />
|
|
</cfif>
|
|
<cfreturn cgi.path_info />
|
|
</cffunction>
|
|
|
|
<cffunction name="addHeaders" access="public" output="false" returntype="void">
|
|
<cfargument name="headers" type="struct" required="true" />
|
|
<cfset var h = '' />
|
|
<cfif !structIsEmpty(arguments.headers)>
|
|
<cfloop list="#structKeyList(arguments.headers)#" index="h">
|
|
<cfheader name="#h#" value="#arguments.headers[h]#" />
|
|
</cfloop>
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
<cffunction name="getBasicAuthCredentials" access="public" output="false" returntype="Struct">
|
|
<cfset var local = {} />
|
|
<cfset local.credentials = {} />
|
|
<cfset local.credentials.username = "" />
|
|
<cfset local.credentials.password = "" />
|
|
<cftry>
|
|
<cfset local.encodedCredentials = ListLast( GetPageContext().getRequest().getHeader("Authorization"), " " ) />
|
|
<cfset local.decodedCredentials = toString( toBinary( local.EncodedCredentials ), "iso-8859-1" ) />
|
|
<cfset local.credentials.username = listFirst( local.decodedCredentials, ":" ) />
|
|
<cfset local.credentials.password = listRest( local.decodedCredentials, ":" ) />
|
|
<cfcatch></cfcatch>
|
|
</cftry>
|
|
<cfreturn local.credentials />
|
|
</cffunction>
|
|
|
|
<cffunction name="getHostname"><!--- unceremoniously stolen from FW/1 --->
|
|
<cfreturn createObject( "java", "java.net.InetAddress" ).getLocalHost().getHostName() />
|
|
</cffunction>
|
|
|
|
<cffunction name="getHintsFromMetaData" output="false">
|
|
<cfargument name="metadata" type="struct" required="true" />
|
|
<cfset var result = StructNew() />
|
|
<cfset var func = '' />
|
|
<cfset var f = 0 />
|
|
<cfset var g = 0 />
|
|
<cfset var foundFunc = false />
|
|
<cfset result.functions = arrayNew(1) />
|
|
<!--- don't recurse if we've reached the base component --->
|
|
<cfif structKeyExists(metadata, "extends") and not metadata.extends.fullname eq "taffy.core.resource">
|
|
<cfset result = getHintsFromMetaData(metadata.extends) />
|
|
</cfif>
|
|
<!--- component attributes --->
|
|
<cfif structKeyExists(arguments.metadata, "hint")>
|
|
<!--- intentionally overwrite hint from any parent cfc's --->
|
|
<cfset result.hint = arguments.metadata.hint />
|
|
</cfif>
|
|
<!--- get uri --->
|
|
<cfif structKeyExists(arguments.metadata, "taffy_uri")>
|
|
<cfset result.uri = arguments.metadata.taffy_uri />
|
|
</cfif>
|
|
<cfif structKeyExists(arguments.metadata, "taffy:uri")>
|
|
<cfset result.uri = arguments.metadata['taffy:uri'] />
|
|
</cfif>
|
|
<!--- if there aren't any functions here to grab, return what we already have --->
|
|
<cfif not structKeyExists (metadata,'functions') or not isArray(metadata.functions) or not arrayLen(metadata.functions)>
|
|
<cfreturn result />
|
|
</cfif>
|
|
<cfloop from="1" to="#arrayLen(metadata.functions)#" index="f">
|
|
<cfset func = metadata.functions[f] />
|
|
<!--- ignore hidden methods, if access is not set, assume public --->
|
|
<cfif not structKeyExists(func, "access") or (func.access neq "private" and func.access neq "package")>
|
|
<!--- check to see if this function is already in the list. If so, overwrite, otherwise append --->
|
|
<cfset foundFunc = False />
|
|
<cfloop from="1" to="#arrayLen( result.functions )#" index="g">
|
|
<cfif result.functions[g].NAME EQ func.NAME>
|
|
<cfset result.functions[g] = func />
|
|
<cfset foundFunc = True />
|
|
<cfbreak />
|
|
</cfif>
|
|
</cfloop>
|
|
<cfif NOT foundFunc>
|
|
<cfset arrayAppend(result.functions, func) />
|
|
</cfif>
|
|
</cfif>
|
|
</cfloop>
|
|
<cfreturn result />
|
|
|
|
</cffunction>
|
|
|
|
</cfcomponent>
|