Skip to content

add enableAutoCorrectClockSkew #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/main/java/com/aliyun/oss/ClientConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ public class ClientConfiguration {

private boolean tracerEnabled = false;

private boolean enableAutoCorrectClockSkew = false;

public ClientConfiguration() {
super();
AppendDefaultExcludeList(this.cnameExcludeList);
Expand Down Expand Up @@ -991,4 +993,28 @@ public boolean isTracerEnabled() {
public void setTracerEnabled(boolean enabled) {
this.tracerEnabled = enabled;
}

/**
* Checks whether the automatic clock skew correction feature is enabled.
*
* @return
* {@code true} if the client is configured to automatically adjust its clock
* based on server time when a significant time discrepancy is detected;
* {@code false} otherwise.
*/
public boolean isEnableAutoCorrectClockSkew() {
return enableAutoCorrectClockSkew;
}

/**
* Enables or disables the automatic clock skew correction feature.
*
* @param enableAutoCorrectClockSkew
* {@code true} to enable automatic clock adjustment (client will correct time offsets
* detected during requests based on server time);
* {@code false} to disable this feature.
*/
public void setEnableAutoCorrectClockSkew(boolean enableAutoCorrectClockSkew) {
this.enableAutoCorrectClockSkew = enableAutoCorrectClockSkew;
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/aliyun/oss/OSSException.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package com.aliyun.oss;

import java.util.Map;

/**
* The OSSException is thrown upon error when accessing OSS.
*/
Expand Down Expand Up @@ -70,6 +72,15 @@ public OSSException(String errorMessage, String errorCode, String requestId, Str
this.method = method;
}

public OSSException(String errorMessage, String errorCode, String requestId, String hostId, String header,
String resourceType, String method, String rawResponseError, Throwable cause, String ec,
Map<String, String> headers, Map<String, Object> errorFields) {
super(errorMessage, errorCode, requestId, hostId, rawResponseError, cause, ec, headers, errorFields);
this.resourceType = resourceType;
this.header = header;
this.method = method;
}

public String getResourceType() {
return resourceType;
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/aliyun/oss/ServiceException.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@

package com.aliyun.oss;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
* <p>
* This is the base exception class to represent any expected or unexpected OSS
Expand Down Expand Up @@ -61,6 +70,9 @@ public class ServiceException extends RuntimeException {

private String rawResponseError;

private Map<String, String> headers = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
private Map<String, Object> errorFields = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);

/**
* Creates a default instance.
*/
Expand Down Expand Up @@ -187,6 +199,18 @@ public ServiceException(String errorMessage, String errorCode, String requestId,
this.ec = ec;
}

public ServiceException(String errorMessage, String errorCode, String requestId, String hostId,
String rawResponseError, Throwable cause, String ec, Map<String, String> headers, Map<String, Object> errorFields) {
this(errorMessage, cause);
this.errorCode = errorCode;
this.requestId = requestId;
this.hostId = hostId;
this.rawResponseError = rawResponseError;
this.ec = ec;
this.headers = headers;
this.errorFields = errorFields;
}

/**
* Gets error message.
*
Expand Down Expand Up @@ -258,6 +282,22 @@ private String formatRawResponseError() {
return String.format("\n[ResponseError]:\n%s", this.rawResponseError);
}

public Map<String, String> getHeaders() {
return headers;
}

public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}

public Map<String, Object> getErrorFields() {
return errorFields;
}

public void setErrorFields(Map<String, Object> errorFields) {
this.errorFields = errorFields;
}

@Override
public String getMessage() {
String msg = getErrorMessage() + "\n[ErrorCode]: " + getErrorCode() + "\n[RequestId]: " + getRequestId()
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/aliyun/oss/common/comm/ServiceClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;

import com.aliyun.oss.common.utils.DateUtil;
import org.apache.http.HttpMessage;

import com.aliyun.oss.ClientConfiguration;
Expand Down Expand Up @@ -161,6 +163,17 @@ private ResponseMessage sendRequestImpl(RequestMessage request, ExecutionContext
logException("[Server]Unable to execute HTTP request: ", sex,
request.getOriginalRequest().isLogEnabled());

if (response != null && response.getStatusCode() == 403) {
if ("RequestTimeTooSkewed".equals(sex.getErrorCode()) && this.config.isEnableAutoCorrectClockSkew()) {
try {
Date serverTime = DateUtil.parseIso8601Date(sex.getErrorFields().get("ServerTime").toString());
this.config.setTickOffset(serverTime.getTime());
} catch (Exception e) {
throw new ClientException(e.getMessage(), e);
}
}
}

// Notice that the response should not be closed in the
// finally block because if the request is successful,
// the response should be returned to the callers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public static OSSException createOSSException(OSSErrorResult errorResult) {

public static OSSException createOSSException(OSSErrorResult errorResult, String rawResponseError) {
return new OSSException(errorResult.Message, errorResult.Code, errorResult.RequestId, errorResult.HostId,
errorResult.Header, errorResult.ResourceType, errorResult.Method, rawResponseError, null, errorResult.EC);
errorResult.Header, errorResult.ResourceType, errorResult.Method, rawResponseError, null, errorResult.EC,
errorResult.Headers, errorResult.ErrorFields);
}

public static OSSException createOSSException(String requestId, String errorCode, String message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void handle(ResponseMessage response) throws OSSException, ClientExceptio
try {
if (response.getHeaders().containsKey(OSSHeaders.OSS_ERROR)) {
byte[] data = BinaryUtil.fromBase64String(response.getHeaders().get(OSSHeaders.OSS_ERROR));
result = errorResponseParser.parseErrorResponse(new ByteArrayInputStream(data));
result = errorResponseParser.parseErrorResponse(new ByteArrayInputStream(data), response.getHeaders());
if (result.Code == null) {
result = null;
}
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/com/aliyun/oss/internal/ResponseParsers.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,13 @@ public static final class ErrorResponseParser implements ResponseParser<OSSError
@Override
public OSSErrorResult parse(ResponseMessage response) throws ResponseParseException {
try {
return parseErrorResponse(response.getContent());
return parseErrorResponse(response.getContent(), response.getHeaders());
} finally {
safeCloseResponse(response);
}
}

OSSErrorResult parseErrorResponse(InputStream inputStream) throws ResponseParseException {
OSSErrorResult parseErrorResponse(InputStream inputStream, Map<String, String> headers) throws ResponseParseException {
OSSErrorResult ossErrorResult = new OSSErrorResult();
if (inputStream == null) {
return ossErrorResult;
Expand All @@ -199,13 +199,32 @@ OSSErrorResult parseErrorResponse(InputStream inputStream) throws ResponseParseE
ossErrorResult.Method = root.getChildText("Method");
ossErrorResult.Header = root.getChildText("Header");
ossErrorResult.EC = root.getChildText("EC");

Map<String, Object> map = new TreeMap<String, Object>();
parseElement(root, map);

ossErrorResult.Headers = headers;
ossErrorResult.ErrorFields = map;
return ossErrorResult;
} catch (JDOMParseException e) {
throw new ResponseParseException(e.getPartialDocument() + ": " + e.getMessage(), e);
} catch (Exception e) {
throw new ResponseParseException(e.getMessage(), e);
}
}

private static void parseElement(Element element, Map<String, Object> map) {
List<Element> children = element.getChildren();
for (Element child : children) {
if (child.getChildren().isEmpty()) {
map.put(child.getName(), child.getTextTrim());
} else {
Map<String, Object> subMap = new TreeMap<>();
parseElement(child, subMap);
map.put(child.getName(), subMap);
}
}
}
}

public static void setResultParameter(GenericResult result, ResponseMessage response){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Map;

@XmlRootElement(name = "Error")
public class OSSErrorResult {
Expand Down Expand Up @@ -48,4 +49,9 @@ public class OSSErrorResult {
@XmlElement(name = "EC")
public String EC;

@XmlElement(name = "Headers")
public Map<String, String> Headers;

@XmlElement(name = "ErrorFields")
public Map<String, Object> ErrorFields;
}
119 changes: 119 additions & 0 deletions src/test/java/com/aliyun/oss/common/parser/ResponseParsersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5580,6 +5580,125 @@ public void testOSSErrorResponseHandler() {
}
}

@Test
public void testParseErrorResponseWithMap() {
InputStream instream = null;
String respBody;

respBody = "" +
"<Error>\n" +
" <Code>RequestTimeTooSkewed</Code>\n" +
" <Message>The difference between the request time and the current time is too large.</Message>\n" +
" <RequestId>5CAC0CF8DE0170*****</RequestId>\n" +
" <HostId>versioning-get.oss-cn-hangzhou.aliyunc*****</HostId>\n" +
" <MaxAllowedSkewMilliseconds>900000</MaxAllowedSkewMilliseconds>\n" +
" <RequestTime>2025-04-10T11:00:57.000Z</RequestTime>\n" +
" <ServerTime>2025-04-10T10:00:57.000Z</ServerTime>\n" +
" <EC>0002-00000504</EC>\n" +
" <RecommendDoc>https://api.aliyun.com/troubleshoot?q=0002-00000504</RecommendDoc>\n" +
"</Error>";

try {
instream = new ByteArrayInputStream(respBody.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
Assert.fail("UnsupportedEncodingException");
}

OSSErrorResult result = null;
try {
ResponseMessage response = new ResponseMessage(null);
response.setContent(instream);
ResponseParsers.ErrorResponseParser parser = new ResponseParsers.ErrorResponseParser();
result = parser.parse(response);
} catch (ResponseParseException e) {
Assert.fail("parse delete directory response body fail!");
}

Assert.assertEquals("RequestTimeTooSkewed", result.Code);
Assert.assertEquals("The difference between the request time and the current time is too large.", result.Message);
Assert.assertEquals("5CAC0CF8DE0170*****", result.RequestId);
Assert.assertEquals("versioning-get.oss-cn-hangzhou.aliyunc*****", result.HostId);
Assert.assertEquals("0002-00000504", result.EC);
Assert.assertEquals("2025-04-10T11:00:57.000Z", result.ErrorFields.get("RequestTime").toString());
Assert.assertEquals("2025-04-10T10:00:57.000Z", result.ErrorFields.get("ServerTime").toString());


respBody = "" +
"<Error>\n" +
" <Code>RequestTimeTooSkewed</Code>\n" +
" <Message>The difference between the request time and the current time is too large.</Message>\n" +
" <RequestId>5CAC0CF8DE0170*****</RequestId>\n" +
" <HostId>versioning-get.oss-cn-hangzhou.aliyunc*****</HostId>\n" +
" <MaxAllowedSkewMilliseconds>900000</MaxAllowedSkewMilliseconds>\n" +
" <Detail>\n" +
" <RequestTime>2025-04-10T11:00:57.000Z</RequestTime>\n" +
" <ServerTime>2025-04-10T10:00:57.000Z</ServerTime>\n" +
" </Detail>\n" +
" <EC>0002-00000504</EC>\n" +
" <RecommendDoc>https://api.aliyun.com/troubleshoot?q=0002-00000504</RecommendDoc>\n" +
"</Error>";

try {
instream = new ByteArrayInputStream(respBody.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
Assert.fail("UnsupportedEncodingException");
}

try {
ResponseMessage response = new ResponseMessage(null);
response.setContent(instream);
ResponseParsers.ErrorResponseParser parser = new ResponseParsers.ErrorResponseParser();
result = parser.parse(response);
} catch (ResponseParseException e) {
Assert.fail("parse delete directory response body fail!");
}

Assert.assertEquals("RequestTimeTooSkewed", result.Code);
Assert.assertEquals("The difference between the request time and the current time is too large.", result.Message);
Assert.assertEquals("5CAC0CF8DE0170*****", result.RequestId);
Assert.assertEquals("versioning-get.oss-cn-hangzhou.aliyunc*****", result.HostId);
Assert.assertEquals("0002-00000504", result.EC);


Object value = result.ErrorFields.get("Detail");
if (value instanceof Map) {
Map<String, Object> nestedMap = (Map<String, Object>) value;
Assert.assertEquals("2025-04-10T11:00:57.000Z", nestedMap.get("RequestTime").toString());
Assert.assertEquals("2025-04-10T10:00:57.000Z", nestedMap.get("ServerTime").toString());
}


respBody = "" +
"<Error>\n" +
" <Code></Code>\n" +
" <Message></Message>\n" +
" <RequestId></RequestId>\n" +
" <HostId></HostId>\n" +
" <ResourceType></ResourceType>\n" +
" <Method></Method>\n" +
" <Header></Header>\n" +
" <RequestTime></RequestTime>\n" +
" <ServerTime></ServerTime>\n" +
"</Error>";

try {
instream = new ByteArrayInputStream(respBody.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
Assert.fail("UnsupportedEncodingException");
}

try {
ResponseMessage response = new ResponseMessage(null);
response.setContent(instream);
ResponseParsers.ErrorResponseParser parser = new ResponseParsers.ErrorResponseParser();
result = parser.parse(response);
Assert.assertTrue(true);
} catch (Exception e) {
e.printStackTrace();
Assert.assertTrue(false);
}
}


@Test
public void testGetBucketCallbackPolicyParser() {
Expand Down
Loading