1.参考文档
以下两篇文档是我调试成功的基础,感激不尽。
2. 起因
需要使用云解析python sdk,同事已经写好了相关python脚本。我想借此机会了解一下restful api。
先是找到了postman这个工具,再者搜到了上面提到的两篇文章。当然还需要wireshark把python sdk的请求抓一下包,照猫画虎,构造postman中的结构。
3. 获取域名列表
3.1 Headers
3.2 Params
3.3 Pre-request Script
Date.prototype.format = function(format) {
var date = {
"M+": this.getUTCMonth() + 1,
"d+": this.getUTCDate(),
"h+": this.getUTCHours(),
"m+": this.getUTCMinutes(),
"s+": this.getUTCSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getUTCMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
//填写实际使用的key和secret
var appKey = "******";
var appSecret = "**********";
//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);
var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\n";
//textToSign += date + "\n";
//var headers = headersToSign();
//var signatureHeaders;
//var sortedKeys = Array.from(headers.keys()).sort()
//for (var headerName of sortedKeys) {
// textToSign += headerName + ":" + headers.get(headerName) + "\n";
// signatureHeaders = signatureHeaders ? signatureHeaders + "," + headerName : headerName;
//}
textToSign += urlToSign();
console.log("textToSign\n" + textToSign.replace(/\n/g, "#"));
//var hash = CryptoJS.HmacSHA256(textToSign, appSecret + "&" )
var hash = CryptoJS.HmacSHA1(textToSign, appSecret + "&" )
console.log("hash:" + hash)
var signature = hash.toString(CryptoJS.enc.Base64)
console.log("signature:" + signature)
pm.globals.set('AppKey', appKey);
//pm.globals.set('Md5', md5);
pm.globals.set("Date", date);
pm.globals.set("Signature", signature);
//pm.globals.set("SignatureHeaders", signatureHeaders);
pm.globals.set("Nonce", nonce);
//function headersToSign() {
// var headers = new Map();
// for (var name in request.headers) {
// name = name.toLowerCase();
// if (!name.startsWith('x-ca-')) {
// continue;
// }
// if (name === "x-ca-signature" || name === "x-ca-signature-headers" || name == "x-ca-key" || name === 'x-ca-nonce') {
// continue;
// }
// var value = request.headers[name];
// headers.set(name, value);
// }
// headers.set('x-ca-key', appKey);
// headers.set('x-ca-nonce', nonce);
// return headers;
//}
function urlToSign() {
var params = new Map();
// var contentType = request.headers["content-type"];
// if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
// const formParams = request.data.split("&");
// formParams.forEach((p) => {
// const ss = p.split('=');
// params.set(ss[0], ss[1]);
// })
// }
const ss = request.url.split('?');
if (ss.length > 1 && ss[1]) {
const queryParams = ss[1].split('&');
queryParams.forEach((p) => {
const ss = p.split('=');
if(ss[0]==="Signature") return;
if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
if(ss[1]==="{{Nonce}}") ss[1]=nonce;
params.set(ss[0], ss[1]);
})
}
var sortedKeys = Array.from(params.keys())
sortedKeys.sort();
// var l1 = ss[0].lastIndexOf('/');
// var url = ss[0].substring(l1);
var first = true;
var qs
for (var k of sortedKeys) {
var s = k + "=" + params.get(k);
qs = qs ? qs + "&" + s : s;
console.log("key=" + k + " value=" + params.get(k));
}
// return qs ? url + "?" + qs : url;
return encodeURIComponent(qs);
}
//function calcMd5() {
// var contentType = request.headers["content-type"];
// if (request.data && !contentType.startsWith('application/x-www-form-urlencoded')) {
// var data = request.data;
// var md5 = CryptoJS.MD5(data);
// var md5String = md5.toString(CryptoJS.enc.Base64);
// console.log("data:" + data + "\nmd5:" + md5String);
// return md5String;
// } else {
// return "";
// }
//}
function createUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
这里的大多数脚本都是来源于前面提到的文档。
pm.globals.set设置的全局变量可以在params、headers、body等中引用,用两个大括号括起来。比如{{tstamp}}
最主要是签名规则,本例是把params中所有参数按key排序,连在一起,再签名。
3.4 post
左侧下拉菜单选择POST。地址框中要写api的地址,后面的参数都是自动填写的。
点右侧的send按钮,运气好的话,正确的reponse就出现了。
4.获取A记录
4.1 Headers
4.2 Params
4.3 Body
这里Id的值是53,是之前请求域名列表时返回的DomainId。
4.4 Pre-request Script
Date.prototype.format = function(format) {
var date = {
"M+": this.getUTCMonth() + 1,
"d+": this.getUTCDate(),
"h+": this.getUTCHours(),
"m+": this.getUTCMinutes(),
"s+": this.getUTCSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getUTCMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
//填写自己的key和secret
var appKey = "******";
var appSecret = "**********";
//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);
var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\n";
//textToSign += date + "\n";
//var headers = headersToSign();
//var signatureHeaders;
//var sortedKeys = Array.from(headers.keys()).sort()
//for (var headerName of sortedKeys) {
// textToSign += headerName + ":" + headers.get(headerName) + "\n";
// signatureHeaders = signatureHeaders ? signatureHeaders + "," + headerName : headerName;
//}
textToSign += urlToSign();
console.log("textToSign\n" + textToSign.replace(/\n/g, "#"));
//var hash = CryptoJS.HmacSHA256(textToSign, appSecret + "&" )
var hash = CryptoJS.HmacSHA1(textToSign, appSecret + "&" )
console.log("hash:" + hash)
var signature = hash.toString(CryptoJS.enc.Base64)
console.log("signature:" + signature)
pm.globals.set('AppKey', appKey);
//pm.globals.set('Md5', md5);
pm.globals.set("Date", date);
pm.globals.set("Signature", signature);
//pm.globals.set("SignatureHeaders", signatureHeaders);
pm.globals.set("Nonce", nonce);
//function headersToSign() {
// var headers = new Map();
// for (var name in request.headers) {
// name = name.toLowerCase();
// if (!name.startsWith('x-ca-')) {
// continue;
// }
// if (name === "x-ca-signature" || name === "x-ca-signature-headers" || name == "x-ca-key" || name === 'x-ca-nonce') {
// continue;
// }
// var value = request.headers[name];
// headers.set(name, value);
// }
// headers.set('x-ca-key', appKey);
// headers.set('x-ca-nonce', nonce);
// return headers;
//}
function urlToSign() {
var params = new Map();
var contentType = request.headers["content-type"];
console.log("request");
console.log(request);
console.log("request.data");
console.log(request.data);
console.log("pm.request");
console.log(pm.request);
// if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
// const formParams = request.data.split("&");
// formParams.forEach((p) => {
// const ss = p.split('=');
// params.set(ss[0], ss[1]);
// })
// }
console.log("body form:");
for(var key in request.data) {
var value = request.data[key];
if(key==="RrSet") value=escape(value);
console.log("key = "+key+", value = "+value);
params.set(key,value);
}
const ss = request.url.split('?');
if (ss.length > 1 && ss[1]) {
const queryParams = ss[1].split('&');
queryParams.forEach((p) => {
const ss = p.split('=');
if(ss[0]==="Signature") return;
if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
if(ss[1]==="{{Nonce}}") ss[1]=nonce;
params.set(ss[0], ss[1]);
})
}
var sortedKeys = Array.from(params.keys())
sortedKeys.sort();
// var l1 = ss[0].lastIndexOf('/');
// var url = ss[0].substring(l1);
var first = true;
var qs
console.log("All params:");
for (var k of sortedKeys) {
var s = k + "=" + params.get(k);
qs = qs ? qs + "&" + s : s;
console.log("key=" + k + " value=" + params.get(k));
}
// return qs ? url + "?" + qs : url;
// return encodeURIComponent(qs);
return escape(qs);
}
//function calcMd5() {
// var contentType = request.headers["content-type"];
// if (request.data && !contentType.startsWith('application/x-www-form-urlencoded')) {
// var data = request.data;
// var md5 = CryptoJS.MD5(data);
// var md5String = md5.toString(CryptoJS.enc.Base64);
// console.log("data:" + data + "\nmd5:" + md5String);
// return md5String;
// } else {
// return "";
// }
//}
function createUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
5.更新A记录
5.1 Headers
5.2 Params
5.3 Body
5.4 Pre-request Script
Date.prototype.format = function(format) {
var date = {
"M+": this.getUTCMonth() + 1,
"d+": this.getUTCDate(),
"h+": this.getUTCHours(),
"m+": this.getUTCMinutes(),
"s+": this.getUTCSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getUTCMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
//填写自己的key和scret
var appKey = "*******";
var appSecret = "********";
//var md5 = calcMd5();
var nowDate = new Date();
var date = nowDate.toUTCString();
var nonce = createUuid();
var tstamp = nowDate.format('yyyy-MM-ddThh%3Amm%3AssZ');
pm.globals.set("tstamp", tstamp);
var textToSign = "";
textToSign += request.method + "&" + "%2F" + "&";
//textToSign += request.headers["accept"] + "\n";
//textToSign += md5 + "\n";
//textToSign += request.headers["content-type"] + "\n";
//textToSign += date + "\n";
//var headers = headersToSign();
//var signatureHeaders;
//var sortedKeys = Array.from(headers.keys()).sort()
//for (var headerName of sortedKeys) {
// textToSign += headerName + ":" + headers.get(headerName) + "\n";
// signatureHeaders = signatureHeaders ? signatureHeaders + "," + headerName : headerName;
//}
textToSign += urlToSign();
console.log("textToSign\n" + textToSign.replace(/\n/g, "#"));
//var hash = CryptoJS.HmacSHA256(textToSign, appSecret + "&" )
var hash = CryptoJS.HmacSHA1(textToSign, appSecret + "&" )
console.log("hash:" + hash)
var signature = hash.toString(CryptoJS.enc.Base64)
console.log("signature:" + signature)
pm.globals.set('AppKey', appKey);
//pm.globals.set('Md5', md5);
pm.globals.set("Date", date);
pm.globals.set("Signature", signature);
//pm.globals.set("SignatureHeaders", signatureHeaders);
pm.globals.set("Nonce", nonce);
//function headersToSign() {
// var headers = new Map();
// for (var name in request.headers) {
// name = name.toLowerCase();
// if (!name.startsWith('x-ca-')) {
// continue;
// }
// if (name === "x-ca-signature" || name === "x-ca-signature-headers" || name == "x-ca-key" || name === 'x-ca-nonce') {
// continue;
// }
// var value = request.headers[name];
// headers.set(name, value);
// }
// headers.set('x-ca-key', appKey);
// headers.set('x-ca-nonce', nonce);
// return headers;
//}
function urlToSign() {
var params = new Map();
var contentType = request.headers["content-type"];
console.log("request");
console.log(request);
console.log("request.data");
console.log(request.data);
console.log("pm.request");
console.log(pm.request);
// if (contentType && contentType.startsWith('application/x-www-form-urlencoded')) {
// const formParams = request.data.split("&");
// formParams.forEach((p) => {
// const ss = p.split('=');
// params.set(ss[0], ss[1]);
// })
// }
console.log("body form:");
for(var key in request.data) {
var value = request.data[key];
//根据response中提示的签名字符串,body中form中的数组多进行了一次转义,把每个百分号多转义了一次
if(key==="RrSet") value=escape(value);
console.log("key = "+key+", value = "+value);
params.set(key,value);
}
const ss = request.url.split('?');
if (ss.length > 1 && ss[1]) {
const queryParams = ss[1].split('&');
queryParams.forEach((p) => {
const ss = p.split('=');
if(ss[0]==="Signature") return;
if(ss[1]==="{{tstamp}}") ss[1]=tstamp;
if(ss[1]==="{{Nonce}}") ss[1]=nonce;
params.set(ss[0], ss[1]);
})
}
var sortedKeys = Array.from(params.keys())
sortedKeys.sort();
// var l1 = ss[0].lastIndexOf('/');
// var url = ss[0].substring(l1);
var first = true;
var qs
console.log("All params:");
for (var k of sortedKeys) {
var s = k + "=" + params.get(k);
qs = qs ? qs + "&" + s : s;
console.log("key=" + k + " value=" + params.get(k));
}
// return qs ? url + "?" + qs : url;
// return encodeURIComponent(qs);
return escape(qs);
}
//function calcMd5() {
// var contentType = request.headers["content-type"];
// if (request.data && !contentType.startsWith('application/x-www-form-urlencoded')) {
// var data = request.data;
// var md5 = CryptoJS.MD5(data);
// var md5String = md5.toString(CryptoJS.enc.Base64);
// console.log("data:" + data + "\nmd5:" + md5String);
// return md5String;
// } else {
// return "";
// }
//}
function createUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
6.调试
6.1 充分利用wireshark
把sdk成功执行的请求抓出来,看header、body、parameter部分,在postman中配置成一样的
6.2 充分利用postman的console
利用console.log打印出变量值,注意打印对象值的时候不要把对象用加号和字符串连接进来。
比如
console.log(request);
console.log("request.data");
console.log(request.data);
console.log("pm.request");
console.log(pm.request);
这些对象的结果非常清晰直观。
把textToSign打印出来,当签名有问题时,用于和服务器端返回的reponse中提示的签名字符串比较。
6.3 仔细看reponse中的提示
比如签名有问题时,提示消息中有"Message": "Specified signature is not matched with our calculation. server string to sign is:后面是签名字符串,看看和自己的testToSign是否一样
7.存在问题
偶尔会提示Specified signature is not matched,再点一下send就好了,没再找原因了。