[ad_1]
The day gone by, I used to be taking into account the ergonomics of Tags and Gadgets in ColdFusion. The object that were given me began down that trail used to be the need so as to add retry good judgment to a CFHttp
workflow. And, the truth that extending the bottom capability of positive ColdFusion sides slightly calls for transferring to an object-based / method-based interplay. As a fast-follow to that put up, I sought after to discover the perception of a “retry coverage” in ColdFusion.
I contemporary releases of Lucee CFML, you’ll upload “listeners” to positive options of the language. Those listeners act slightly like interceptors, permitting you to look at and alter the inputs and outputs of an underlying workflow. For instance, the Lucee CFML Question Listener means that you can supply .sooner than()
and .after()
strategies which are invoked sooner than and after SQL execution, respectively.
Alongside the similar traces, it may well be great to offer a “listener” to the CFHttp
tag that drives retry mechanics. To discover this concept, I will borrow closely from the Amazon Internet Products and services (AWS) SDK which encodes the idea that of a RetryPolicy
. This retry coverage composes good judgment that determines which requests may also be retried; and, how lengthy the thread must wait sooner than making an attempt the retry.
To translate this idea into ColdFusion, I will create a ColdFusion element that has two public strategies:
shouldRetry( request, reaction, depend )
delayBeforeNextRetry( request, reaction, depend )
Those components names are borrowed without delay from the Java SDK. The shouldRetry()
components returns a Boolean indicating whether or not or no longer the HTTP request must be retried. And, if that is so, the delayBeforeNextRetry()
components returns the choice of milliseconds that the ColdFusion thread must pause sooner than making the following HTTP request try.
For example this element API, here is my HttpRetryPolicy.cfc
, which applies some fairly simplistic good judgment round retries. Necessarily, it is simply having a look on the underlying HTTP standing code and is looking for a retry when positive standing codes are provide:
element
output = false
trace = "I supply hooks for managing the retry habits of a ColdFusion HTTP request."
{
/**
* I initialize the retry coverage with the given houses.
*/
public void serve as init() {
backoffDurations = [
200, // After 1st attempt.
700, // After 2nd attempt.
1000, // After 3rd attempt.
2000, // After 4th attempt.
4000 // After 5th attempt.
];
maxRetryAttempts = backoffDurations.len();
}
// ---
// PUBLIC METHODS.
// ---
/**
* I resolve if the HTTP request must be retried.
*/
public boolean serve as shouldRetry(
required any httpRequest,
required any httpResponse,
required numeric requestsAttempted
) {
if ( requestsAttempted > maxRetryAttempts ) {
go back( false );
}
if ( httpRequest.getAttributes().components != "get" ) {
go back( false );
}
transfer ( val( httpResponse.statusCode ) ) {
case 408: // Request timeout.
case 429: // Too many requests.
case 500: // Server error.
case 503: // Provider unavailable.
case 504: // Gateway timeout.
go back( true );
destroy;
default:
go back( false );
destroy;
}
}
/**
* I resolve how lengthy (in milliseconds) the thread must sleep sooner than retrying the
* HTTP request.
*/
public numeric serve as delayBeforeNextRetry(
required any httpRequest,
required any httpResponse,
required numeric requestsAttempted
) {
go back( backoffDurations[ requestsAttempted ] );
}
}
Now, in principle, it’s good to move a ColdFusion element example that implements this API to the CFHttp
tag as a “listener”. However, since Lucee does not in fact put into effect a listener for CFHttp
, I will pretend it through the use of an element that proxies the local Http.cfc
element.
My proxy element will settle for an not obligatory example of the HttpRetryPolicy.cfc
element after which disclose a .ship()
components with wraps the underlying .ship()
name in retry mechanics. The next ColdFusion element isn’t an entire proxy – it is simply sufficient to display the idea that:
element
output = false
trace = "PROOF OF CONCEPT: I supply a proxy to the local HTTP object that provides retry capability."
{
/**
* I initialize the retryable HTTP request with the given houses.
*/
public void serve as init() {
variables.httpRequest = new org.lucee.cfml.Http( argumentCollection = arguments );
variables.retryPolicy = nullValue();
// For demo debugging and representation.
this.lines = [];
}
// ---
// PUBLIC METHODS.
// ---
/**
* I PROXY the underlying HTTP ship and wrap it with retry mechanics the use of the given
* retry coverage (if one has been outlined).
*/
public any serve as ship() {
if ( isNull( retryPolicy ) ) {
go back( httpRequest.ship() );
}
// If the retry coverage is malformed (developer error), we wish to save you this
// thread from falling into an unending loop. As such, we're going to simplest permit a most
// of 10 retries without reference to what the retry coverage supplies.
var infiniteLoopCutoff = 10;
var requestsAttempted = 0;
var startedAt = getTickCount();
do {
requestsAttempted++;
// For debugging output within the demo in order that we will be able to see how time and again the
// underlying HTTP request used to be achieved.
this.lines.append( "Appearing HTTP request (#requestsAttempted# at #( getTickCount() - startedAt )#ms)." );
var httpResponseWrapper = httpRequest.ship();
var httpResponse = httpResponseWrapper.getPrefix();
if ( --infiniteLoopCutoff <= 0 ) {
destroy;
}
if ( ! retryPolicy.shouldRetry( httpRequest, httpResponse, requestsAttempted ) ) {
destroy;
}
sleep( retryPolicy.delayBeforeNextRetry( httpRequest, httpResponse, requestsAttempted ) );
} whilst( true );
go back( httpResponseWrapper );
}
/**
* For the PROOF OF CONCEPT, I will require the retry coverage to be set one at a time
* in order that the `arguments` scope can be utilized to initialize the underlying HTTP request
* with no need to pluck-out the retry coverage.
*/
public any serve as setRetryPolicy( required any retryPolicy ) {
variables.retryPolicy = arguments.retryPolicy;
go back( this );
}
}
As you’ll see, the proxy good judgment is in fact somewhat easy:
-
We make the HTTP request the use of the local
Http.cfc
example. -
We ask the retry coverage if the given HTTP reaction must be retried – understand that we do not take a look at the standing code within the proxy; all responses are handed to the retry coverage.
-
If the retry is licensed, we ask the retry coverage how lengthy the thread must
sleep()
. -
Repeat.
To check this interaction between the local Http.cfc
and my HttpWithRetry.cfc
, I created a easy ColdFusion web page that may randomly fail 2/3 of the time:
<cfscript>
if ( randRange( 1, 3 ) == 1 ) {
header
statusCode = 200
statusText = "OK"
;
echo( "Luck!" );
} else {
header
statusCode = 500
statusText = "ServerError"
;
echo( "Oh no!" );
}
</cfscript>
After which, a easy CFML web page that draws this all in combination:
<cfscript>
// NOTE: We all know that "goal.cfm" will randomly fail 2/3 of the time.
httpRequest = new HttpWithRetry(
components = "GET",
url = "http://127.0.0.1:62625/retry-policy/goal.cfm"
);
httpRequest.setRetryPolicy( new HttpRetryPolicy() );
unload( httpRequest.ship().getPrefix() );
unload( httpRequest.lines );
</cfscript>
On this case, the place I am calling the .setRetryPolicy()
components, it’s possible you’ll consider that being changed with a listener
characteristic at the CFHttp
tag (to stick with the present development that Lucee has established with question and listeners). And, if we run this ColdFusion web page, we get the next output:

As you’ll see, because of our randomly failing goal web page, our HttpWithRetry.cfc
ColdFusion element ended up executing 5 consecutive HTTP requests till the HttpRetryPolicy.cfc
rejected the request for any other retry.
This implementation isn’t supposed to be feature-complete – it is only a evidence of idea. However, I do like the concept that the way is not looking to be a one-size-fits-all resolution. As an alternative, the HTTP capability can be extensible in some way that other API purchasers in user-land may provide other retry insurance policies.
Need to use code from this put up?
Take a look at the license.
[ad_2]