Skip to content
← Back to Functions
Code

HTTP Retry Request Planner

Builds retry timing and stop rules for outbound HTTP requests.

Function signature

ogPlanHttpRetryRequest(request_state = array(), policy = array())

Categories

  • Performance

Parameters

request_stateRequest state with attempt, status_code, method, and retry_after keys. Recognized keys: `attempt`, `idempotent`, `method`, `retry_after`, `status_code`.policyRetry policy overrides. Recognized keys: `base_delay_seconds`, `max_attempts`, `max_delay_seconds`, `retryable_statuses`.

Return value

Short public-safe status message.

  • should_retry
  • delay_seconds
  • attempt
  • max_attempts
  • status_code
  • method
  • reason

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, policy arrays, URLs, signatures, and caller-owned allowlists before use; keep secrets and internal paths out of public output.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Builds a safe retry decision for outbound HTTP requests.
 *
 * The helper respects idempotency, Retry-After values, retryable status
 * codes, capped exponential backoff, and a maximum attempt limit.
 *
 * @param array $request_state Request state with attempt, status_code, method, and retry_after keys.
 * @param array $policy Retry policy overrides.
 * @return array Retry decision with delay and reason data.
 */
function ogPlanHttpRetryRequest($request_state = array(), $policy = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($request_state)) {
		$result['message'] = 'Request state must be an array.';
		return $result;
	}

	if (!is_array($policy)) {
		$policy = array();
	}

	$attempt = 0;
	if (!empty($request_state['attempt'])) {
		$attempt = (int)$request_state['attempt'];
	}
	if ($attempt < 0) {
		$attempt = 0;
	}

	$status_code = 0;
	if (!empty($request_state['status_code'])) {
		$status_code = (int)$request_state['status_code'];
	}

	$method = 'GET';
	if (!empty($request_state['method'])) {
		$method = strtoupper(trim((string)$request_state['method']));
	}

	$max_attempts = 3;
	if (!empty($policy['max_attempts'])) {
		$max_attempts = (int)$policy['max_attempts'];
	}
	if ($max_attempts < 1) {
		$max_attempts = 1;
	}

	$base_delay = 2;
	if (!empty($policy['base_delay_seconds'])) {
		$base_delay = (int)$policy['base_delay_seconds'];
	}
	if ($base_delay < 1) {
		$base_delay = 1;
	}

	$max_delay = 300;
	if (!empty($policy['max_delay_seconds'])) {
		$max_delay = (int)$policy['max_delay_seconds'];
	}
	if ($max_delay < $base_delay) {
		$max_delay = $base_delay;
	}

	$retryable_statuses = array(408, 425, 429, 500, 502, 503, 504);
	if (!empty($policy['retryable_statuses']) && is_array($policy['retryable_statuses'])) {
		$retryable_statuses = array();
		foreach ($policy['retryable_statuses'] as $status) {
			$retryable_statuses[] = (int)$status;
		}
	}

	$idempotent_methods = array('GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS');
	$is_idempotent = in_array($method, $idempotent_methods, true);
	if (!empty($request_state['idempotent'])) {
		$is_idempotent = true;
	}

	$should_retry = false;
	$reason = 'Status is not retryable.';
	if (in_array($status_code, $retryable_statuses, true)) {
		$should_retry = true;
		$reason = 'Status is retryable.';
	}

	if (!$is_idempotent) {
		$should_retry = false;
		$reason = 'Request method is not idempotent.';
	}

	if ($attempt >= $max_attempts) {
		$should_retry = false;
		$reason = 'Maximum attempts reached.';
	}

	$delay = 0;
	if ($should_retry) {
		if (!empty($request_state['retry_after'])) {
			$retry_after = trim((string)$request_state['retry_after']);
			if (ctype_digit($retry_after)) {
				$delay = (int)$retry_after;
			} else {
				$retry_time = strtotime($retry_after);
				if (!empty($retry_time)) {
					$delay = $retry_time - time();
				}
			}
		}

		if ($delay <= 0) {
			$delay = $base_delay * (int)pow(2, $attempt);
		}
		if ($delay > $max_delay) {
			$delay = $max_delay;
		}
	}

	$result['success'] = true;
	$result['message'] = 'HTTP retry decision built.';
	$result['data'] = array(
		'should_retry' => $should_retry,
		'delay_seconds' => $delay,
		'attempt' => $attempt,
		'max_attempts' => $max_attempts,
		'status_code' => $status_code,
		'method' => $method,
		'reason' => $reason
	);

	return $result;
}