@@ -74,6 +74,26 @@ component accessors="true" {
7474 */
7575 property name = " maximumRedirects" default = " *" ;
7676
77+ /**
78+ * An array describing how to retry failed requests.
79+ * Defaults to an empty array meaning no retries will be attempted.
80+ */
81+ property name = " retries" ;
82+
83+ /**
84+ * The current request count.
85+ * Used for determining if a retry should happen and for how long.
86+ */
87+ property name = " currentRequestCount" default = " 1" ;
88+
89+ /**
90+ * A predicate function to determine if the retry should be attempted.
91+ * The next request can also be modified in this predicate function.
92+ * Defaults to retrying if the response has an error status code,
93+ * as determined by `HyperResponse#isError`
94+ */
95+ property name = " retryPredicate" ;
96+
7797 /**
7898 * The body to send with the request.
7999 * How the body is serialized is
@@ -177,21 +197,28 @@ component accessors="true" {
177197 * @return s The HyperRequest instance.
178198 */
179199 function init ( httpClient = new CfhttpHttpClient () ) {
180- variables .requestID = createUUID ();
181- variables .httpClient = arguments .httpClient ;
182- variables .queryParams = [];
183- variables .headers = createObject ( " java" , " java.util.LinkedHashMap" ).init ();
184- variables .cookies = structNew ( " ordered" );
185- variables .files = [];
186- variables .requestCallbacks = [];
187- variables .responseCallbacks = [];
200+ variables .requestID = createUUID ();
201+ variables .httpClient = arguments .httpClient ;
202+ variables .queryParams = [];
203+ variables .headers = createObject ( " java" , " java.util.LinkedHashMap" ).init ();
204+ variables .cookies = structNew ( " ordered" );
205+ variables .files = [];
206+ variables .requestCallbacks = [];
207+ variables .responseCallbacks = [];
208+ variables .retries = [];
209+ variables .retryPredicate = function ( res , req , exception ) {
210+ return res .isError ();
211+ };
188212
189213 setUserAgent ( " HyperCFML/#getHyperVersion () #" );
214+
190215 // This is overwritten by the HyperBuilder if WireBox exists.
191216 variables .interceptorService = {
192217 " processState" : function () {
193218 }
194219 };
220+
221+ // This is overwritten by the HyperBuilder if WireBox exists.
195222 variables .asyncManager = {
196223 " newFuture" : function () {
197224 throw ( " No asyncManager set!" );
@@ -1044,18 +1071,53 @@ component accessors="true" {
10441071 }
10451072 variables .interceptorService .processState ( " onHyperRequest" , { " request" : this } );
10461073
1047- var res = shouldFake () ? generateFakeRequest () : variables .httpClient .send ( this );
1074+ try {
1075+ var res = shouldFake () ? generateFakeRequest () : variables .httpClient .send ( this );
10481076
1049- for ( var callback in variables .responseCallbacks ) {
1050- callback ( res );
1051- }
1052- variables .interceptorService .processState ( " onHyperResponse" , { " response" : res } );
1077+ for ( var callback in variables .responseCallbacks ) {
1078+ callback ( res );
1079+ }
1080+ variables .interceptorService .processState ( " onHyperResponse" , { " response" : res } );
1081+
1082+ if (
1083+ variables .currentRequestCount <= variables .retries .len () &&
1084+ variables .retryPredicate ( res , this )
1085+ ) {
1086+ sleep ( variables .retries [ variables .currentRequestCount ] );
1087+ variables .currentRequestCount ++ ;
1088+ return variables .send ();
1089+ }
10531090
1054- if ( res .isRedirect () && shouldFollowRedirect () ) {
1055- return followRedirect ( res );
1056- }
1091+ if ( res .isRedirect () && shouldFollowRedirect () ) {
1092+ return followRedirect ( res );
1093+ }
1094+
1095+ return res ;
1096+ } catch ( HyperRequestError e ) {
1097+ var resMemento = deserializeJSON ( e .extendedinfo ).response ;
1098+ var res = new Hyper .models .HyperResponse (
1099+ originalRequest = this ,
1100+ executionTime = resMemento .executionTime ,
1101+ charset = resMemento .charset ,
1102+ statusCode = resMemento .statusCode ,
1103+ statusText = resMemento .statusText ,
1104+ headers = resMemento .headers ,
1105+ data = resMemento .data ,
1106+ timestamp = resMemento .timestamp ,
1107+ responseID = resMemento .responseID
1108+ );
1109+
1110+ if (
1111+ variables .currentRequestCount <= variables .retries .len () &&
1112+ variables .retryPredicate ( res , this , e )
1113+ ) {
1114+ sleep ( variables .retries [ variables .currentRequestCount ] );
1115+ variables .currentRequestCount ++ ;
1116+ return variables .send ();
1117+ }
10571118
1058- return res ;
1119+ rethrow ;
1120+ }
10591121 }
10601122
10611123 /**
@@ -1129,6 +1191,34 @@ component accessors="true" {
11291191 return this ;
11301192 }
11311193
1194+ public HyperRequest function retry (
1195+ required any attempts ,
1196+ numeric delay ,
1197+ function predicate
1198+ ) {
1199+ // convert attempt counts into an array of identical backoff delays
1200+ if ( isSimpleValue ( arguments .attempts ) ) {
1201+ if ( isNull ( arguments .delay ) || ! isNumeric ( arguments .delay ) ) {
1202+ throw (
1203+ type = " HyperRetryMissingParameter" ,
1204+ message = " The `delay` parameter is required when using a numeric attempt count."
1205+ );
1206+ }
1207+ var attemptCount = arguments .attempts ;
1208+ arguments .attempts = [];
1209+ for ( var i = 1 ; i <= attemptCount ; i ++ ) {
1210+ arguments .attempts .append ( arguments .delay );
1211+ }
1212+ }
1213+
1214+ variables .retries = arguments .attempts ;
1215+ if ( ! isNull ( arguments .predicate ) ) {
1216+ variables .retryPredicate = arguments .predicate ;
1217+ }
1218+
1219+ return this ;
1220+ }
1221+
11321222 /**
11331223 * Clones the current request into a new HyperRequest.
11341224 *
@@ -1164,6 +1254,8 @@ component accessors="true" {
11641254 req .setAuthType ( variables .authType );
11651255 req .setRequestCallbacks ( duplicate ( variables .requestCallbacks ) );
11661256 req .setResponseCallbacks ( duplicate ( variables .responseCallbacks ) );
1257+ req .setRetries ( duplicate ( getRetries () ) );
1258+ req .setRetryPredicate ( getRetryPredicate () );
11671259 return req ;
11681260 }
11691261
@@ -1277,30 +1369,32 @@ component accessors="true" {
12771369 */
12781370 public struct function getMemento () {
12791371 return {
1280- " requestID" : getRequestID (),
1281- " baseUrl" : getBaseUrl (),
1282- " url" : getUrl (),
1283- " fullUrl" : getFullUrl (),
1284- " method" : getMethod (),
1285- " queryParams" : getQueryParams (),
1286- " headers" : getHeaders (),
1287- " cookies" : getCookies (),
1288- " files" : getFiles (),
1289- " bodyFormat" : getBodyFormat (),
1290- " body" : getBody (),
1291- " referrerId" : isNull ( variables .referrer ) ? " " : variables .referrer .getResponseID (),
1292- " throwOnError" : getThrowOnError (),
1293- " timeout" : getTimeout (),
1294- " maximumRedirects" : getMaximumRedirects (),
1295- " authType" : getAuthType (),
1296- " username" : getUsername (),
1297- " password" : getPassword (),
1298- " clientCert" : isNull ( variables .clientCert ) ? " " : variables .clientCert ,
1299- " clientCertPassword" : isNull ( variables .clientCertPassword ) ? " " : variables .clientCertPassword ,
1300- " domain" : getDomain (),
1301- " workstation" : getWorkstation (),
1302- " resolveUrls" : getResolveUrls (),
1303- " encodeUrl" : getEncodeUrl ()
1372+ " requestID" : getRequestID (),
1373+ " baseUrl" : getBaseUrl (),
1374+ " url" : getUrl (),
1375+ " fullUrl" : getFullUrl (),
1376+ " method" : getMethod (),
1377+ " queryParams" : getQueryParams (),
1378+ " headers" : getHeaders (),
1379+ " cookies" : getCookies (),
1380+ " files" : getFiles (),
1381+ " bodyFormat" : getBodyFormat (),
1382+ " body" : getBody (),
1383+ " referrerId" : isNull ( variables .referrer ) ? " " : variables .referrer .getResponseID (),
1384+ " throwOnError" : getThrowOnError (),
1385+ " timeout" : getTimeout (),
1386+ " maximumRedirects" : getMaximumRedirects (),
1387+ " authType" : getAuthType (),
1388+ " username" : getUsername (),
1389+ " password" : getPassword (),
1390+ " clientCert" : isNull ( variables .clientCert ) ? " " : variables .clientCert ,
1391+ " clientCertPassword" : isNull ( variables .clientCertPassword ) ? " " : variables .clientCertPassword ,
1392+ " domain" : getDomain (),
1393+ " workstation" : getWorkstation (),
1394+ " resolveUrls" : getResolveUrls (),
1395+ " encodeUrl" : getEncodeUrl (),
1396+ " retries" : getRetries (),
1397+ " currentRequestCount" : getCurrentRequestCount ()
13041398 };
13051399 }
13061400
0 commit comments