diff --git a/pom.xml b/pom.xml
index 8baac6f4..8a68a013 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.qcloud
cos_api
- 5.6.237
+ 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 42223084..7f058159 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..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();
@@ -373,7 +375,7 @@ public void setRequestTimeOutEnable(boolean requestTimeOutEnable) {
}
public boolean getRequestTimeOutEnable() {
- return isRequestTimeOutEnable;
+ return isRequestTimeOutEnable && (requestTimeout > 0);
}
public void setShutdownTimeout(int shutdownTimeout) {
@@ -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/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..421e966b 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();
}
@@ -201,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);
@@ -224,6 +230,7 @@ public void shutdown() {
}
log.info(trace.toString());
}
+ cosHttpClientTimer.shutdown();
this.idleConnectionMonitor.shutdown();
}
@@ -596,7 +603,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 +748,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() {
+ }
+}