From 4246921399a6f54110bd98f0d4cde429159d54a1 Mon Sep 17 00:00:00 2001 From: jeffreykzli Date: Fri, 22 Nov 2024 15:32:45 +0800 Subject: [PATCH 1/2] update request timeout --- pom.xml | 2 +- src/main/java/com/qcloud/cos/COSClient.java | 6 +- .../java/com/qcloud/cos/ClientConfig.java | 2 +- .../qcloud/cos/http/CosHttpClientTimer.java | 70 +++++++++++++++++++ .../com/qcloud/cos/http/CosHttpRequest.java | 12 ++++ .../qcloud/cos/http/DefaultCosHttpClient.java | 68 +++++++++++++++++- .../cos/internal/CosClientAbortTask.java | 13 ++++ .../cos/internal/CosClientAbortTaskImpl.java | 62 ++++++++++++++++ .../internal/CosClientAbortTaskMonitor.java | 13 ++++ .../CosClientAbortTaskMonitorImpl.java | 43 ++++++++++++ .../internal/DefaultClientAbortTaskImpl.java | 27 +++++++ 11 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/qcloud/cos/http/CosHttpClientTimer.java create mode 100644 src/main/java/com/qcloud/cos/internal/CosClientAbortTask.java create mode 100644 src/main/java/com/qcloud/cos/internal/CosClientAbortTaskImpl.java create mode 100644 src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitor.java create mode 100644 src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitorImpl.java create mode 100644 src/main/java/com/qcloud/cos/internal/DefaultClientAbortTaskImpl.java diff --git a/pom.xml b/pom.xml index eee1c7e3..8a68a013 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.qcloud cos_api - 5.6.234 + 5.6.238 jar cos-java-sdk java sdk for qcloud cos diff --git a/src/main/java/com/qcloud/cos/COSClient.java b/src/main/java/com/qcloud/cos/COSClient.java index 5dbaf3ab..156640ea 100644 --- a/src/main/java/com/qcloud/cos/COSClient.java +++ b/src/main/java/com/qcloud/cos/COSClient.java @@ -176,11 +176,7 @@ public COSClient(COSCredentialsProvider credProvider, ClientConfig clientConfig) super(); this.credProvider = credProvider; this.clientConfig = clientConfig; - if (clientConfig.getRequestTimeOutEnable()) { - this.cosHttpClient = new TimeOutCosHttpClient(clientConfig); - } else { - this.cosHttpClient = new DefaultCosHttpClient(clientConfig); - } + this.cosHttpClient = new DefaultCosHttpClient(clientConfig); } public void shutdown() { diff --git a/src/main/java/com/qcloud/cos/ClientConfig.java b/src/main/java/com/qcloud/cos/ClientConfig.java index 160b17e6..50e0b271 100644 --- a/src/main/java/com/qcloud/cos/ClientConfig.java +++ b/src/main/java/com/qcloud/cos/ClientConfig.java @@ -373,7 +373,7 @@ public void setRequestTimeOutEnable(boolean requestTimeOutEnable) { } public boolean getRequestTimeOutEnable() { - return isRequestTimeOutEnable; + return isRequestTimeOutEnable && (requestTimeout > 0); } public void setShutdownTimeout(int shutdownTimeout) { diff --git a/src/main/java/com/qcloud/cos/http/CosHttpClientTimer.java b/src/main/java/com/qcloud/cos/http/CosHttpClientTimer.java new file mode 100644 index 00000000..832ba4c9 --- /dev/null +++ b/src/main/java/com/qcloud/cos/http/CosHttpClientTimer.java @@ -0,0 +1,70 @@ +package com.qcloud.cos.http; + +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.internal.CosClientAbortTask; +import com.qcloud.cos.internal.CosClientAbortTaskMonitor; +import com.qcloud.cos.internal.DefaultClientAbortTaskImpl; +import com.qcloud.cos.internal.CosClientAbortTaskImpl; +import com.qcloud.cos.internal.CosClientAbortTaskMonitorImpl; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +public class CosHttpClientTimer { + private volatile ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; + + private static ThreadFactory getThreadFactory(final String name) { + return new ThreadFactory() { + private int threadCount = 1; + + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + if (name != null) { + thread.setName(name + "-" + threadCount++); + } + thread.setPriority(Thread.MAX_PRIORITY); + return thread; + } + }; + } + + private synchronized void initializeExecutor() { + if (scheduledThreadPoolExecutor == null) { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5, getThreadFactory("COSClientRequestTimeOutThread")); + try { + executor.getClass().getMethod("setRemoveOnCancelPolicy", boolean.class).invoke(executor, Boolean.TRUE); + } catch (NoSuchMethodException e) { + throw new CosClientException("The request timeout feature is only available for Java 1.7 and above."); + } catch (SecurityException e) { + throw new CosClientException("The request timeout feature needs additional permissions to function.", e); + } catch (Exception e) { + throw new CosClientException(e); + } + + executor.setKeepAliveTime(5, TimeUnit.SECONDS); + executor.allowCoreThreadTimeOut(true); + + scheduledThreadPoolExecutor = executor; + } + } + + public CosClientAbortTaskMonitor startTimer(int requestTimeout) { + if (requestTimeout <= 0) { + return DefaultClientAbortTaskImpl.INSTANCE; + } else if (scheduledThreadPoolExecutor == null) { + initializeExecutor(); + } + + CosClientAbortTask task = new CosClientAbortTaskImpl(Thread.currentThread()); + ScheduledFuture timerTaskFuture = scheduledThreadPoolExecutor.schedule(task, requestTimeout, TimeUnit.MILLISECONDS); + return new CosClientAbortTaskMonitorImpl(task, timerTaskFuture); + } + + public synchronized void shutdown() { + if (scheduledThreadPoolExecutor != null) { + scheduledThreadPoolExecutor.shutdown(); + } + } +} diff --git a/src/main/java/com/qcloud/cos/http/CosHttpRequest.java b/src/main/java/com/qcloud/cos/http/CosHttpRequest.java index 149d794c..f859c14e 100644 --- a/src/main/java/com/qcloud/cos/http/CosHttpRequest.java +++ b/src/main/java/com/qcloud/cos/http/CosHttpRequest.java @@ -27,7 +27,9 @@ import com.qcloud.cos.auth.COSCredentials; import com.qcloud.cos.event.ProgressListener; import com.qcloud.cos.exception.ExceptionLogDetail; +import com.qcloud.cos.internal.CosClientAbortTaskMonitor; import com.qcloud.cos.internal.CosServiceRequest; +import com.qcloud.cos.internal.DefaultClientAbortTaskImpl; public class CosHttpRequest { @@ -62,6 +64,8 @@ public class CosHttpRequest { private List logDetails = new ArrayList(); + private CosClientAbortTaskMonitor clientAbortTaskMonitor = DefaultClientAbortTaskImpl.INSTANCE; + public CosHttpRequest(T originRequest) { this.originRequest = originRequest; this.ciSpecialEndParameter = originRequest.getCiSpecialEndParameter(); @@ -182,6 +186,14 @@ public List getExceptionsLogDetails() { return logDetails; } + public CosClientAbortTaskMonitor getClientAbortTaskMonitor() { + return clientAbortTaskMonitor; + } + + public void setClientAbortTaskMonitor(CosClientAbortTaskMonitor clientAbortTaskMonitor) { + this.clientAbortTaskMonitor = clientAbortTaskMonitor; + } + @Override public String toString() { StringBuilder strBuilder = new StringBuilder(); diff --git a/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java b/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java index 20f31bfe..109eebef 100644 --- a/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java +++ b/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java @@ -56,6 +56,7 @@ import com.qcloud.cos.region.Region; import com.qcloud.cos.event.ProgressInputStream; import com.qcloud.cos.event.ProgressListener; +import com.qcloud.cos.exception.AbortedException; import com.qcloud.cos.exception.ClientExceptionConstants; import com.qcloud.cos.exception.CosClientException; import com.qcloud.cos.exception.CosServiceException; @@ -70,6 +71,7 @@ import com.qcloud.cos.internal.SdkBufferedInputStream; import com.qcloud.cos.internal.CIWorkflowServiceRequest; import com.qcloud.cos.internal.CIServiceRequest; +import com.qcloud.cos.internal.CosClientAbortTaskMonitor; import com.qcloud.cos.retry.BackoffStrategy; import com.qcloud.cos.retry.RetryPolicy; import com.qcloud.cos.utils.CodecUtils; @@ -124,6 +126,7 @@ public class DefaultCosHttpClient implements CosHttpClient { private CosErrorResponseHandler errorResponseHandler; private HandlerAfterProcess handlerAfterProcess; + private final CosHttpClientTimer cosHttpClientTimer; private static final Logger log = LoggerFactory.getLogger(DefaultCosHttpClient.class); public DefaultCosHttpClient(ClientConfig clientConfig) { @@ -179,6 +182,7 @@ public InetAddress[] resolve(String host) throws UnknownHostException { this.maxErrorRetry = clientConfig.getMaxErrorRetry(); this.retryPolicy = ValidationUtils.assertNotNull(clientConfig.getRetryPolicy(), "retry policy"); this.backoffStrategy = ValidationUtils.assertNotNull(clientConfig.getBackoffStrategy(), "backoff strategy"); + this.cosHttpClientTimer = new CosHttpClientTimer(); initHttpClient(); } @@ -224,6 +228,7 @@ public void shutdown() { } log.info(trace.toString()); } + cosHttpClientTimer.shutdown(); this.idleConnectionMonitor.shutdown(); } @@ -596,7 +601,11 @@ public X exeute(CosHttpRequest request, httpRequest = buildHttpRequest(request); httpResponse = null; startTime = System.currentTimeMillis(); - httpResponse = executeRequest(context, httpRequest); + if (clientConfig.getRequestTimeOutEnable()) { + httpResponse = executeRequestWithTimeout(context, httpRequest, request); + } else { + httpResponse = executeRequest(context, httpRequest); + } checkResponse(request, httpRequest, httpResponse); break; } catch (CosServiceException cse) { @@ -737,6 +746,63 @@ private HttpResponse executeRequest(HttpContext context, HttpRequestBase httpReq return httpResponse; } + private HttpResponse executeRequestWithTimer(HttpContext context, HttpRequestBase httpRequest, CosHttpRequest originRequest) throws Exception { + CosClientAbortTaskMonitor abortTaskMonitor = cosHttpClientTimer.startTimer(clientConfig.getRequestTimeout()); + abortTaskMonitor.setCurrentHttpRequest(httpRequest); + HttpResponse httpResponse = null; + try { + originRequest.setClientAbortTaskMonitor(abortTaskMonitor); + httpResponse = executeOneRequest(context, httpRequest); + } catch (IOException ie) { + if (originRequest.getClientAbortTaskMonitor().hasTimeoutExpired()) { + Thread.interrupted(); + String errorMsg = String.format("catch IOException when executing http request[%s], and execution aborted task has been done, exp:", originRequest); + log.error(errorMsg, ie); + throw new InterruptedException(); + } + throw ie; + } finally { + originRequest.getClientAbortTaskMonitor().cancelTask(); + } + + return httpResponse; + } + + private HttpResponse executeRequestWithTimeout(HttpContext context, HttpRequestBase httpRequest, CosHttpRequest originRequest) throws Exception { + try { + return executeRequestWithTimer(context, httpRequest, originRequest); + } catch (InterruptedException ie) { + if (originRequest.getClientAbortTaskMonitor().hasTimeoutExpired()) { + Thread.interrupted(); + String errorMsg = "InterruptedException: time out after waiting " + this.clientConfig.getRequestTimeout()/1000 + " seconds"; + throw new CosClientException(errorMsg, ClientExceptionConstants.REQUEST_TIMEOUT, ie); + } + if (!httpRequest.isAborted()) { + httpRequest.abort(); + } + throw ie; + } catch (AbortedException ae) { + if (originRequest.getClientAbortTaskMonitor().hasTimeoutExpired()) { + Thread.interrupted(); + String errorMsg = "AbortedException: time out after waiting " + this.clientConfig.getRequestTimeout()/1000 + " seconds"; + throw new CosClientException(errorMsg, ClientExceptionConstants.REQUEST_TIMEOUT, ae); + } + if (!httpRequest.isAborted()) { + httpRequest.abort(); + } + throw ae; + } catch (IOException ie) { + if (!httpRequest.isAborted()) { + httpRequest.abort(); + } + throw ExceptionUtils.createClientException(ie); + } finally { + if (originRequest.getClientAbortTaskMonitor().hasTimeoutExpired()) { + Thread.interrupted(); + } + } + } + private void handleLog(CosHttpRequest request) { for (ExceptionLogDetail logDetail : request.getExceptionsLogDetails()) { log.error(logDetail.getErrMsg(), logDetail.getException()); diff --git a/src/main/java/com/qcloud/cos/internal/CosClientAbortTask.java b/src/main/java/com/qcloud/cos/internal/CosClientAbortTask.java new file mode 100644 index 00000000..cab69481 --- /dev/null +++ b/src/main/java/com/qcloud/cos/internal/CosClientAbortTask.java @@ -0,0 +1,13 @@ +package com.qcloud.cos.internal; + +import org.apache.http.client.methods.HttpRequestBase; + +public interface CosClientAbortTask extends Runnable { + void setCurrentHttpRequest(HttpRequestBase newRequest); + + boolean hasClientExecutionAborted(); + + boolean isEnabled(); + + void cancel(); +} diff --git a/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskImpl.java b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskImpl.java new file mode 100644 index 00000000..409b6f0b --- /dev/null +++ b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskImpl.java @@ -0,0 +1,62 @@ +package com.qcloud.cos.internal; + +import org.apache.http.client.methods.HttpRequestBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CosClientAbortTaskImpl implements CosClientAbortTask { + private volatile boolean hasTaskExecuted; + private HttpRequestBase currentHttpRequest; + private final Thread thread; + private volatile boolean isCancelled; + + private static final Logger log = LoggerFactory.getLogger(CosClientAbortTaskImpl.class); + + private final Object lock = new Object(); + + public CosClientAbortTaskImpl(Thread thread) { + this.thread = thread; + } + + @Override + public void run() { + synchronized (this.lock) { + if (isCancelled) { + return; + } + hasTaskExecuted = true; + if (!thread.isInterrupted()) { + log.debug("request timeout and current thread will be interrupted"); + thread.interrupt(); + } + if (!currentHttpRequest.isAborted()) { + log.debug("request timeout and current http request will be aborted"); + currentHttpRequest.abort(); + } + } + } + + @Override + public void setCurrentHttpRequest(HttpRequestBase newRequest) { + this.currentHttpRequest = newRequest; + } + + @Override + public boolean hasClientExecutionAborted() { + synchronized (this.lock) { + return hasTaskExecuted; + } + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void cancel() { + synchronized (this.lock) { + isCancelled = true; + } + } +} diff --git a/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitor.java b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitor.java new file mode 100644 index 00000000..8ef96cca --- /dev/null +++ b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitor.java @@ -0,0 +1,13 @@ +package com.qcloud.cos.internal; + +import org.apache.http.client.methods.HttpRequestBase; + +public interface CosClientAbortTaskMonitor { + void setCurrentHttpRequest(HttpRequestBase newRequest); + + boolean hasTimeoutExpired(); + + boolean isEnabled(); + + void cancelTask(); +} diff --git a/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitorImpl.java b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitorImpl.java new file mode 100644 index 00000000..2aaaf9b3 --- /dev/null +++ b/src/main/java/com/qcloud/cos/internal/CosClientAbortTaskMonitorImpl.java @@ -0,0 +1,43 @@ +package com.qcloud.cos.internal; + +import org.apache.http.client.methods.HttpRequestBase; + +import java.util.concurrent.ScheduledFuture; + +public class CosClientAbortTaskMonitorImpl implements CosClientAbortTaskMonitor { + private final CosClientAbortTask abortTask; + private final ScheduledFuture scheduledFuture; + + public CosClientAbortTaskMonitorImpl(final CosClientAbortTask task, final ScheduledFuture future) { + if (task == null) { + throw new IllegalArgumentException("CosClientAbortTask should not be null"); + } + abortTask = task; + + if (future == null) { + throw new IllegalArgumentException("ScheduledFuture should not be null"); + } + scheduledFuture = future; + } + + @Override + public void setCurrentHttpRequest(HttpRequestBase newRequest) { + abortTask.setCurrentHttpRequest(newRequest); + } + + @Override + public boolean hasTimeoutExpired() { + return abortTask.hasClientExecutionAborted(); + } + + @Override + public boolean isEnabled() { + return abortTask.isEnabled(); + } + + @Override + public void cancelTask() { + scheduledFuture.cancel(false); + abortTask.cancel(); + } +} diff --git a/src/main/java/com/qcloud/cos/internal/DefaultClientAbortTaskImpl.java b/src/main/java/com/qcloud/cos/internal/DefaultClientAbortTaskImpl.java new file mode 100644 index 00000000..82e3f2a3 --- /dev/null +++ b/src/main/java/com/qcloud/cos/internal/DefaultClientAbortTaskImpl.java @@ -0,0 +1,27 @@ +package com.qcloud.cos.internal; + +import org.apache.http.client.methods.HttpRequestBase; + +public class DefaultClientAbortTaskImpl implements CosClientAbortTaskMonitor { + public static final DefaultClientAbortTaskImpl INSTANCE = new DefaultClientAbortTaskImpl(); + + private DefaultClientAbortTaskImpl() {} + + @Override + public void setCurrentHttpRequest(HttpRequestBase newRequest) { + } + + @Override + public boolean hasTimeoutExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public void cancelTask() { + } +} From 1c4f894a7cf094d7880f9977c0855408a68a9abd Mon Sep 17 00:00:00 2001 From: jeffreykzli Date: Tue, 26 Nov 2024 14:33:07 +0800 Subject: [PATCH 2/2] do not follow 302 by default --- src/main/java/com/qcloud/cos/ClientConfig.java | 10 ++++++++++ .../java/com/qcloud/cos/http/DefaultCosHttpClient.java | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/qcloud/cos/ClientConfig.java b/src/main/java/com/qcloud/cos/ClientConfig.java index 50e0b271..a3f47859 100644 --- a/src/main/java/com/qcloud/cos/ClientConfig.java +++ b/src/main/java/com/qcloud/cos/ClientConfig.java @@ -131,6 +131,8 @@ public class ClientConfig { private long preflightStatusUpdateInterval = 10 * 1000L; + private boolean isRedirectsEnabled = false; + // 不传入region 用于后续调用List Buckets(获取所有的bucket信息) public ClientConfig() { super(); @@ -467,4 +469,12 @@ public void setCheckPreflightStatus(boolean checkPreflightStatus) { public long getPreflightStatusUpdateInterval() { return preflightStatusUpdateInterval; } + + public boolean isRedirectsEnabled() { + return isRedirectsEnabled; + } + + public void setRedirectsEnabled(boolean redirectsEnabled) { + isRedirectsEnabled = redirectsEnabled; + } } diff --git a/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java b/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java index 109eebef..421e966b 100644 --- a/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java +++ b/src/main/java/com/qcloud/cos/http/DefaultCosHttpClient.java @@ -205,7 +205,9 @@ private void initHttpClient() { .setConnectionRequestTimeout( this.clientConfig.getConnectionRequestTimeout()) .setConnectTimeout(this.clientConfig.getConnectionTimeout()) - .setSocketTimeout(this.clientConfig.getSocketTimeout()).build(); + .setSocketTimeout(this.clientConfig.getSocketTimeout()) + .setRedirectsEnabled(this.clientConfig.isRedirectsEnabled()) + .build(); this.idleConnectionMonitor = new IdleConnectionMonitorThread(this.connectionManager); this.idleConnectionMonitor.setIdleAliveMS(this.clientConfig.getIdleConnectionAlive()); this.idleConnectionMonitor.setDaemon(true);