我正在尝试调用产品广告API,但是仍然出现相同的错误。
InvalidParameterValue
参数Timestamp的值2018-12-02T16%3A37%3A08.174%2B0000无效。原因:必须为ISO8601格式。7c46e224-8e98-4909-986b-cbedb9dd4d15
我已经阅读了与发现的错误相关的各种帖子,但似乎没有一种解决方案有效。下面是我使用的代码:
public class SignedRequestsHelper {
private static final String UTF8_CHARSET = "UTF-8";
/**
* The HMAC algorithm required by Amazon
*/
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
/**
* This is the URI for the service, don't change unless you really know
* what you're doing.
*/
private static final String REQUEST_URI = "/onca/xml";
/**
* The sample uses HTTP GET to fetch the response. If you changed the sample
* to use HTTP POST instead, change the value below to POST.
*/
private static final String REQUEST_METHOD = "GET";
private String endpoint = null;
private String awsAccessKeyId = null;
private String awsSecretKey = null;
private SecretKeySpec secretKeySpec = null;
private Mac mac = null;
/**
* You must provide the three values below to initialize the helper.
*
* @param endpoint Destination for the requests.
* @param awsAccessKeyId Your AWS Access Key ID
* @param awsSecretKey Your AWS Secret Key
*/
public SignedRequestsHelper(
String endpoint,
String awsAccessKeyId,
String awsSecretKey
) throws IllegalArgumentException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException
{
if (null == endpoint || endpoint.length() == 0)
{ throw new IllegalArgumentException("endpoint is null or empty"); }
if (null == awsAccessKeyId || awsAccessKeyId.length() == 0)
{ throw new IllegalArgumentException("awsAccessKeyId is null or empty"); }
if (null == awsSecretKey || awsSecretKey.length() == 0)
{ throw new IllegalArgumentException("awsSecretKey is null or empty"); }
this.endpoint = endpoint.toLowerCase();
this.awsAccessKeyId = awsAccessKeyId;
this.awsSecretKey = awsSecretKey;
byte[] secretyKeyBytes = this.awsSecretKey.getBytes(UTF8_CHARSET);
this.secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
this.mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
this.mac.init(this.secretKeySpec);
}
/**
* The construct is private since we'd rather use getInstance()
*/
private SignedRequestsHelper() {}
/**
* This method signs requests in hashmap form. It returns a URL that should
* be used to fetch the response. The URL returned should not be modified in
* any way, doing so will invalidate the signature and Amazon will reject
* the request.
*/
public String sign(Map<String, String> params) {
// Let's add the AWSAccessKeyId and Timestamp parameters to the request.
params.put("AWSAccessKeyId", this.awsAccessKeyId);
params.put("Timestamp", this.timestamp());
// The parameters need to be processed in lexicographical order, so we'll
// use a TreeMap implementation for that.
SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(params);
// get the canonical form the query string
String canonicalQS = this.canonicalize(sortedParamMap);
// create the string upon which the signature is calculated
String toSign =
REQUEST_METHOD + "\n"
+ this.endpoint + "\n"
+ REQUEST_URI + "\n"
+ canonicalQS;
// get the signature
String hmac = this.hmac(toSign);
String sig = this.percentEncodeRfc3986(hmac);
// construct the URL
String url =
"http://" + this.endpoint + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig;
return url;
}
/**
* This method signs requests in query-string form. It returns a URL that
* should be used to fetch the response. The URL returned should not be
* modified in any way, doing so will invalidate the signature and Amazon
* will reject the request.
*/
public String sign(String queryString) {
// let's break the query string into it's constituent name-value pairs
Map<String, String> params = this.createParameterMap(queryString);
// then we can sign the request as before
return this.sign(params);
}
/**
* Compute the HMAC.
*
* @param stringToSign String to compute the HMAC over.
* @return base64-encoded hmac value.
*/
private String hmac(String stringToSign) {
String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = stringToSign.getBytes(UTF8_CHARSET);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
}
return signature;
}
/**
* Generate a ISO-8601 format timestamp as required by Amazon.
*
* @return ISO-8601 format timestamp.
*/
private String timestamp() {
String timestamp = null;
Calendar cal = Calendar.getInstance();
DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
timestamp = dfm.format(cal.getTime());
return timestamp;
}
/**
* Canonicalize the query string as required by Amazon.
*
* @param sortedParamMap Parameter name-value pairs in lexicographical order.
* @return Canonical form of query string.
*/
private String canonicalize(SortedMap<String, String> sortedParamMap) {
if (sortedParamMap.isEmpty()) {
return "";
}
StringBuffer buffer = new StringBuffer();
Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> kvpair = iter.next();
buffer.append(percentEncodeRfc3986(kvpair.getKey()));
buffer.append("=");
buffer.append(percentEncodeRfc3986(kvpair.getValue()));
if (iter.hasNext()) {
buffer.append("&");
}
}
String cannoical = buffer.toString();
return cannoical;
}
/**
* Percent-encode values according the RFC 3986. The built-in Java
* URLEncoder does not encode according to the RFC, so we make the
* extra replacements.
*
* @param s decoded string
* @return encoded string per RFC 3986
*/
private String percentEncodeRfc3986(String s) {
String out;
try {
out = URLEncoder.encode(s, UTF8_CHARSET)
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
out = s;
}
return out;
}
/**
* Takes a query string, separates the constituent name-value pairs
* and stores them in a hashmap.
*
* @param queryString
* @return
*/
private Map<String, String> createParameterMap(String queryString) {
Map<String, String> map = new HashMap<String, String>();
String[] pairs = queryString.split("&");
for (String pair: pairs) {
if (pair.length() < 1) {
continue;
}
String[] tokens = pair.split("=",2);
for(int j=0; j<tokens.length; j++)
{
try {
tokens[j] = URLDecoder.decode(tokens[j], UTF8_CHARSET);
} catch (UnsupportedEncodingException e) {
}
}
switch (tokens.length) {
case 1: {
if (pair.charAt(0) == '=') {
map.put("", tokens[0]);
} else {
map.put(tokens[0], "");
}
break;
}
case 2: {
map.put(tokens[0], tokens[1]);
break;
}
}
}
return map;
}
}
@服务 公共类ProductService {
private static final String ASSOCIATE_TAG = "TAG";
private static final String BASE_URL = "";
private final SignedRequestsHelper signedRequestsHelper;
private final WebClient webClient;
public ProductService(SignedRequestsHelper signedRequestsHelper,
WebClient.Builder webClientBuilder) {
this.signedRequestsHelper = signedRequestsHelper;
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(tcpClient ->
tcpClient.proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP).host("localhost").port(8888)));
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
this.webClient = webClientBuilder.baseUrl("http://" + PAApiConfig.ENDPOINT).clientConnector(connector).build();
}
public String getRequest() {
String requestUrl = null;
Map<String, String> params = new HashMap<>();
params.put("AssociateTag", "lucaorlando00-21");
params.put("BrowseNode", "1480106031");
params.put("Operation", "ItemSearch");
params.put("ResponseGroup", "Images,ItemAttributes,OfferFull");
params.put("SearchIndex", "Beauty");
params.put("Service", "AWSECommerceService");
params.put("Condition", "New");
params.put("Availability", "Available");
params.put("IncludeReviewsSummary", "true");
return signedRequestsHelper.sign(params);
}
@PostConstruct
public void tt() {
this.webClient
.get()
.uri(getRequest())
// .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML_VALUE)
.header(HttpHeaders.ACCEPT, "text/plain, */*; q=0.01")
//.header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.header(HttpHeaders.ACCEPT_LANGUAGE, "it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7")
.retrieve()
.bodyToMono(String.class)
.doOnError(throwable -> ((WebClientResponseException)throwable).getResponseBodyAsString())
.subscribe();
}
}