

مقدمه: در این مقاله به توسعهدهندگان Java نشان میدهیم چگونه با استفاده از Apache HttpClient درخواستهای HTTP را هنگام خطا دوباره ارسال (Retry) کنید تا سیستم اسکریپ شما پایدارتر شود. دو رویکرد رایج را بررسی میکنیم: استفاده از کتابخانهٔ retry4j و نوشتن یک wrapper سفارشی برای منطق ریتری. در پایانِ هر بخش، نکات عملی، محدودیتها و نمونهٔ کد قابلاجرا ارائه میشود.
ایدهٔ کلی: از یک کتابخانهٔ آماده برای مدیریت تعداد تلاشها، تاخیر بین تلاشها و backoff استفاده میکنیم تا به جای مدیریت دستی خطاها، رفتار Retry بهصورت قابلپیکربندی و قابلنظارت اجرا شود.
مراحل کلی:
نمونهٔ سادهٔ بازنویسیشده:
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.time.temporal.ChronoUnit;
import com.evanlennick.retry4j.CallExecutor;
import com.evanlennick.retry4j.CallExecutorBuilder;
import com.evanlennick.retry4j.config.RetryConfig;
import com.evanlennick.retry4j.config.RetryConfigBuilder;
public class RetryExample {
public static List badStatusCodes = Arrays.asList(429,500,502,503,504);
public static void main(String[] args) throws Exception {
CloseableHttpAsyncClient client = HttpAsyncClients.createDefault();
client.start();
Callable<Void> makeRequest = () -> {
var request = SimpleRequestBuilder.get("https://quotes.toscrape.com").build();
SimpleHttpResponse response = client.execute(request, null).get();
int status = response.getCode();
if (badStatusCodes.contains(status)) {
throw new Exception("Bad status code: " + status);
}
System.out.println("Response body: " + response.getBodyText());
return null;
};
RetryConfig config = new RetryConfigBuilder()
.retryOnAnyException()
.withMaxNumberOfTries(5)
.withDelayBetweenTries(10, ChronoUnit.SECONDS)
.withExponentialBackoff()
.build();
CallExecutor callExecutor = new CallExecutorBuilder()
.config(config)
.build();
callExecutor.execute(makeRequest);
client.close();
}
}
توضیح کد:
ایدهٔ کلی: وقتی کنترل کامل میخواهید (مثلاً شرایط ناموفق سفارشی، بررسی محتوای HTML، یا رفتارهای متفاوت بر اساس نوع خطا)، نوشتن منطق Retry دستی مناسبتر است.
مزایا و معایب:
نمونهٔ ساده:
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.http.ConnectionClosedException;
import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
public class CustomRetry {
final public static int NUM_RETRIES = 3;
public static void main(String[] args) throws Exception {
CloseableHttpAsyncClient client = HttpAsyncClients.createDefault();
client.start();
SimpleHttpResponse response = null;
for (int i=0; i<NUM_RETRIES; i++) {
try {
var request = SimpleRequestBuilder.get("https://quotes.toscrape.com").build();
response = client.execute(request, null).get();
int status = response.getCode();
if (status == 200 || status == 404) {
break; // موفق یا صفحه پیدا نشد؛ نیازی به تکرار نیست
}
} catch (Exception e) {
boolean connectionError = e instanceof ConnectionClosedException
|| e instanceof ConnectionRequestTimeoutException;
if (connectionError) {
// لاگ کنید و اجازه دهید حلقه تکرار شود
} else {
// در موارد دیگر ممکن است بخواهید exception را بازپرتاب کنید
throw e;
}
} finally {
System.out.println("Total tries: " + (i+1));
}
}
if (response != null && response.getCode() == 200) {
System.out.println("Response body: " + response.getBodyText());
} else {
System.out.println("No valid response after maximum number of tries");
}
client.close();
}
}
نکات اجرایی:
فقط بررسی کد وضعیت (status code) کافی نیست: گاهی سرویس پاسخ 200 میدهد اما صفحهٔ پاسخ حاوی پیام بلاک یا CAPTCHA است. در اینجا از Jsoup برای بررسی عنوان صفحه یا وجود الگوهای مشخص استفاده میکنیم.
// پس از دریافت response
String body = response.getBodyText();
String pageTitle = org.jsoup.Jsoup.parse(body).title();
boolean valid = response.getCode() == 200 && !pageTitle.contains("Robot or human?");
if (!valid) {
// علامتدهی به منطق retry برای تکرار درخواست
}
توضیح: این روش به شما امکان میدهد شرط موفقیت را فراتر از کد وضعیت گسترش دهید—مثلاً بررسی وجود المانهای مشخص، طول محتوا، یا مقایسهٔ hash با صفحهٔ نمونه.
دو رویکرد اصلی برای اضافه کردن retry به اسکریپهای Java با Apache HttpClient وجود دارد: استفاده از کتابخانههای آماده مانند retry4j برای راهاندازی سریع و قابلپیکربندی، یا نوشتن wrapper سفارشی برای کنترل دقیقتر روی شرایط شکست و اعتبارسنجی محتوا. هر دو روش مزایا و محدودیتهای خود را دارند؛ انتخاب بین آنها بستگی به نیازهای قابلیت اطمینان، پیچیدگی منطق و میزان کنترلی دارد که میخواهید روی رفتار درخواستها داشته باشید. در عمل ترکیب یک کتابخانهٔ Retry با اعتبارسنجی محتوایی (مثل Jsoup) و رعایت best practiceهایی همچون backoff، jitter و مدیریت زمانها بهترین نتیجه را میدهد.


