alibabacloud-gateway-sls/main.tea (392 lines of code) (raw):
import SPI;
import Credential;
import Util;
import OpenApiUtil;
import String;
import Map;
import Array;
import EncodeUtil;
import SignatureUtil;
import SLS_Util;
extends SPI;
type @respBodyDecompressType = map[string][string];
type @reqBodyCompressType = map[string][string];
init(){
super();
@respBodyDecompressType = {
PullLogs = ['zstd', 'lz4', 'gzip'],
GetLogsV2 = ['zstd', 'lz4', 'gzip'],
PreviewSPL = ['lz4']
};
@reqBodyCompressType = {
PutLogs = ['zstd', 'lz4', 'deflate'],
PreviewSPL = ['lz4']
};
}
async function modifyConfiguration(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
var config = context.configuration;
config.endpoint = getEndpoint(config.regionId, config.network, config.endpoint);
}
async function modifyRequest(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
var request = context.request;
var hostMap : map[string]string = {};
if (!Util.isUnset(request.hostMap)) {
hostMap = request.hostMap;
}
var project = hostMap.project;
var config = context.configuration;
var credential : Credential = request.credential;
var credentialModel = credential.getCredential();
var accessKeyId = credentialModel.accessKeyId;
var accessKeySecret = credentialModel.accessKeySecret;
var securityToken = credentialModel.securityToken;
if (!Util.empty(securityToken)) {
request.headers['x-acs-security-token'] = securityToken;
}
var signatureVersion = Util.defaultString(request.signatureVersion, 'v1');
var finalCompressType = getFinalRequestCompressType(request.action, request.headers);
var contentHash = '';
// get body bytes
var bodyBytes : bytes = null;
if (!Util.isUnset(request.body)) {
if (String.equals(request.reqBodyType, 'json') || String.equals(request.reqBodyType, 'formData')) {
request.headers['content-type'] = 'application/json';
var bodyStr = Util.toJSONString(request.body);
bodyBytes = Util.toBytes(bodyStr);
} else if (String.equals(request.reqBodyType, 'binary')) {
// content-type: application/octet-stream
bodyBytes = Util.assertAsBytes(request.body);
}
}
// get body raw size
var bodyRawSize : string = '0';
var rawSizeRef = request.headers['x-log-bodyrawsize']; // for php bug, Argument #1 ($value) could not be passed by reference
if (!Util.isUnset(rawSizeRef)) {
bodyRawSize = rawSizeRef;
} else if (!Util.isUnset(request.body)) {
bodyRawSize = `${SLS_Util.bytesLength(bodyBytes)}`;
}
// compress if needed, and set body and hash
if (!Util.isUnset(request.body)) {
if (!Util.empty(finalCompressType)) {
var compressed = SLS_Util.compress(bodyBytes, finalCompressType);
bodyBytes = compressed;
}
contentHash = makeContentHash(bodyBytes, signatureVersion);
request.stream = bodyBytes;
}
var host = getHost(config.network, project, config.endpoint);
request.headers = {
accept = 'application/json',
host = host,
user-agent = request.userAgent,
x-log-apiversion = '0.6.0',
...request.headers
};
request.headers['x-log-bodyrawsize'] = bodyRawSize;
setDefaultAcceptEncoding(request.action, request.headers);
buildRequest(context); // move param in path to query
if (String.equals(signatureVersion, 'v4')) {
if (Util.empty(contentHash)) {
contentHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
}
var date = getDateISO8601();
request.headers['x-log-date'] = date;
request.headers['x-log-content-sha256'] = contentHash;
request.headers['authorization'] = getAuthorizationV4(context, date, contentHash, accessKeyId, accessKeySecret);
return;
}
if (!Util.empty(accessKeyId)) {
request.headers['x-log-signaturemethod'] = 'hmac-sha256';
}
request.headers['date'] = Util.getDateUTCString();
request.headers['content-md5'] = contentHash;
request.headers['authorization'] = getAuthorization(request.pathname, request.method, request.query, request.headers, accessKeyId, accessKeySecret);
}
async function getFinalRequestCompressType(action: string, headers: map[string]string): string {
var compressType = headers['x-log-compresstype'];
var rawSize = headers['x-log-bodyrawsize']; // for php bug, Argument #1 ($value) could not be passed by reference
// 1. already compressed, has x-log-compresstype and x-log-bodyrawsize in header, we dont need compress here
if (!Util.isUnset(compressType) && !Util.isUnset(rawSize)) {
return '';
}
// 2. not compressed, but has x-log-compresstype in header, we need compress here
if (!Util.isUnset(compressType)) {
return compressType;
}
// 3. not compressed, in pre-defined api list, try use default supported compress type in order
var encodings = @reqBodyCompressType[action];
if (!Util.isUnset(encodings)) {
for (var encoding : encodings) {
if (SLS_Util.isCompressorAvailable(encoding)) {
headers['x-log-compresstype'] = encoding; // set header x-log-compresstype
return encoding;
}
}
}
// 4. otherwise we dont need compress here
return '';
}
async function setDefaultAcceptEncoding(action: string, headers: map[string]string): void {
var acceptEncoding = headers['Accept-Encoding']; // for php warning, dont rm this line
if (!Util.isUnset(acceptEncoding)) {
return;
}
var encodings = @respBodyDecompressType[action];
if (!Util.isUnset(encodings)) {
for (var c : encodings) {
if (SLS_Util.isDecompressorAvailable(c)) {
headers['Accept-Encoding'] = c;
return;
}
}
}
}
async function makeContentHash(content: bytes, signatureVersion: string) : string {
if (String.equals(signatureVersion, 'v4')) {
if (Util.isUnset(content) || Util.equalString(`${SLS_Util.bytesLength(content)}`, "0")) {
return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
}
return String.toLower(EncodeUtil.hexEncode(EncodeUtil.hash(content, 'SLS4-HMAC-SHA256')));
}
return String.toUpper(EncodeUtil.hexEncode(SignatureUtil.MD5SignForBytes(content)));
}
async function modifyResponse(context: SPI.InterceptorContext, attributeMap: SPI.AttributeMap): void {
var request = context.request;
var response = context.response;
if (Util.is4xx(response.statusCode) || Util.is5xx(response.statusCode)) {
var error = Util.readAsJSON(response.body);
var resMap = Util.assertAsMap(error);
throw {
code = resMap['errorCode'],
message = resMap['errorMessage'],
accessDeniedDetail = resMap['accessDeniedDetail'],
data = {
httpCode = response.statusCode,
requestId = response.headers['x-log-requestid'],
statusCode = response.statusCode,
}
};
}
if (!Util.isUnset(response.body)) {
var bodyrawSize = response.headers['x-log-bodyrawsize'];
var compressType = response.headers['x-log-compresstype'];
var uncompressedData : readable = response.body;
if (!Util.isUnset(bodyrawSize) && !Util.isUnset(compressType)) {
uncompressedData = SLS_Util.readAndUncompressBlock(response.body, compressType, bodyrawSize);
}
if (Util.equalString(request.bodyType, 'binary')) {
response.deserializedBody = uncompressedData;
} else if (Util.equalString(request.bodyType, 'byte')) {
var byt = Util.readAsBytes(uncompressedData);
response.deserializedBody = byt;
} else if (Util.equalString(request.bodyType, 'string')) {
response.deserializedBody = Util.readAsString(uncompressedData);
} else if (Util.equalString(request.bodyType, 'json')) {
var obj = Util.readAsJSON(uncompressedData);
// var res = Util.assertAsMap(obj);
response.deserializedBody = obj;
} else if (Util.equalString(request.bodyType, 'array')) {
response.deserializedBody = Util.readAsJSON(uncompressedData);
} else {
response.deserializedBody = Util.readAsString(uncompressedData);
}
}
}
async function getEndpoint(regionId: string, network: string, endpoint: string) : string{
if (!Util.empty(endpoint)) {
return endpoint;
}
if (Util.empty(regionId)) {
regionId = 'cn-hangzhou';
}
if (!Util.empty(network)) {
if (String.equals(network, 'intranet')) {
return `${regionId}-intranet.log.aliyuncs.com`;
} else if (String.equals(network, 'accelerate')) {
return `log-global.aliyuncs.com`;
} else if (String.equals(network, 'share')) {
if (String.equals(regionId, 'cn-hangzhou-corp') || String.equals(regionId, 'cn-shanghai-corp')) {
return `${regionId}.sls.aliyuncs.com`;
} else if (String.equals(regionId, 'cn-zhangbei-corp')) {
return `zhangbei-corp-share.log.aliyuncs.com`;
}
return `${regionId}-share.log.aliyuncs.com`;
}
}
return `${regionId}.log.aliyuncs.com`;
}
async function getHost(network: string, project: string, endpoint: string): string {
if (Util.isUnset(project)) {
return endpoint;
}
return `${project}.${endpoint}`;
}
async function getAuthorization(pathname: string, method: string, query: map[string]string, headers: map[string]string, ak: string, secret: string): string {
var sign = getSignature(pathname, method, query, headers, secret);
return `LOG ${ak}:${sign}`;
}
async function getSignature(pathname: string, method: string, query: map[string]string, headers: map[string]string, secret: string): string {
var resource : string = pathname;
var stringToSign : string = '';
var canonicalizedResource = buildCanonicalizedResource(resource, query);
var canonicalizedHeaders = buildCanonicalizedHeaders(headers);
stringToSign = `${method}\n${canonicalizedHeaders}${canonicalizedResource}`;
return EncodeUtil.base64EncodeToString(SignatureUtil.HmacSHA256Sign(stringToSign, secret));
}
async function buildCanonicalizedResource(pathname: string, query: map[string]string): string {
var canonicalizedResource : string = pathname;
if (!Util.isUnset(query)) {
var queryList : [string] = Map.keySet(query);
var sortedParams = Array.ascSort(queryList);
var separator : string = '?';
for(var paramName : sortedParams) {
canonicalizedResource = `${canonicalizedResource}${separator}${paramName}`;
var paramValue = query[paramName];
if (!Util.isUnset(paramValue)) {
canonicalizedResource = `${canonicalizedResource}=${paramValue}`;
}
separator = '&';
}
}
return canonicalizedResource;
}
async function buildCanonicalizedHeaders(headers: map[string]string): string {
var canonicalizedHeaders : string = '';
var contentType = headers.content-type;
if (Util.isUnset(contentType)) {
contentType = '';
}
var contentMd5 = headers.content-md5;
if (Util.isUnset(contentMd5)) {
contentMd5 = '';
}
canonicalizedHeaders = `${canonicalizedHeaders}${contentMd5}\n${contentType}\n${headers.date}\n`;
var keys = Map.keySet(headers);
var sortedHeaders = Array.ascSort(keys);
for(var header : sortedHeaders) {
if (String.contains(String.toLower(header), 'x-log-') || String.contains(String.toLower(header), 'x-acs-')) {
canonicalizedHeaders = `${canonicalizedHeaders}${header}:${headers[header]}\n`;
}
}
return canonicalizedHeaders;
}
async function buildRequest(context: SPI.InterceptorContext): void {
var request = context.request;
var resource : string = request.pathname;
if (!Util.empty(resource)) {
var paths : [string] = String.split(resource, `?`, 2);
resource = paths[0];
if (Util.equalNumber(Array.size(paths), 2)) {
var params : [string] = String.split(paths[1], '&', null);
for (var sub : params) {
var item : [string] = String.split(sub, '=', null);
var key : string = item[0];
var value : string = null;
if (Util.equalNumber(Array.size(item), 2)) {
value = item[1];
}
request.query[key] = value;
}
}
}
request.pathname = resource;
}
async function getAuthorizationV4(context: SPI.InterceptorContext, date: string, contentHash: string, accessKeyId: string, accessKeySecret: string): string {
var region = getRegion(context);
var headerStr = getSignedHeaderStrV4(context.request.headers);
var dateShort = String.subString(date, 0, 8);
dateShort = String.replace(dateShort, 'T', '', null); // for fix php sdk bug
var scope = `${dateShort}/${region}/sls/aliyun_v4_request`;
var signingkey = getSigningkeyV4('SLS4-HMAC-SHA256', accessKeySecret, region, dateShort);
var signature = getSignatureV4(context, 'SLS4-HMAC-SHA256', headerStr, date, scope, contentHash, signingkey);
return `${'SLS4-HMAC-SHA256'} Credential=${accessKeyId}/${scope},Signature=${signature}`;
}
async function getSigningkeyV4(signatureAlgorithm: string, secret: string, region: string, date: string): bytes {
var sc1 = `aliyun_v4${secret}`;
var sc2 = SignatureUtil.HmacSHA256Sign(date, sc1);
var sc3 = SignatureUtil.HmacSHA256SignByBytes(region, sc2);
var sc4 = SignatureUtil.HmacSHA256SignByBytes('sls', sc3);
return SignatureUtil.HmacSHA256SignByBytes('aliyun_v4_request', sc4);
}
async function getSignatureV4(context: SPI.InterceptorContext, signatureAlgorithm: string, signedHeaderStr: string, date: string, scope: string, contentSha256: string, signingkey: bytes): string {
var request = context.request;
var canonicalURI = '/';
if (!Util.empty(request.pathname)) {
canonicalURI = request.pathname;
}
var resources = buildCanonicalizedResourceV4(request.query);
var headers = buildCanonicalizedHeadersV4(request.headers, signedHeaderStr);
var stringToHash = `${request.method}\n${canonicalURI}\n${resources}\n${headers}\n${signedHeaderStr}\n${contentSha256}`;
var hex = EncodeUtil.hexEncode(EncodeUtil.hash(Util.toBytes(stringToHash), signatureAlgorithm));
var stringToSign = `${signatureAlgorithm}\n${date}\n${scope}\n${hex}`;
var signature = SignatureUtil.HmacSHA256SignByBytes(stringToSign, signingkey);
return EncodeUtil.hexEncode(signature);
}
async function buildCanonicalizedResourceV4(query: map[string]string): string {
var canonicalizedResource : string = '';
if (!Util.isUnset(query)) {
var queryArray : [string] = Map.keySet(query);
var sortedQueryArray = Array.ascSort(queryArray);
var separator : string = '';
for(var key : sortedQueryArray) {
canonicalizedResource = `${canonicalizedResource}${separator}${key}`;
if (!Util.empty(query[key])) {
canonicalizedResource = `${canonicalizedResource}=${EncodeUtil.percentEncode(query[key])}`;
}
separator = '&';
}
}
return canonicalizedResource;
}
async function buildCanonicalizedHeadersV4(headers: map[string]string, signedHeaderStr: string): string {
var canonicalizedHeaders : string = '';
var signedHeaders: [string] = String.split(signedHeaderStr, ';', null);
for (var header : signedHeaders) {
canonicalizedHeaders = `${canonicalizedHeaders}${header}:${String.trim(headers[header])}\n`;
}
return canonicalizedHeaders;
}
async function getSignedHeaderStrV4(headers: map[string]string): string {
var headersArray : [string] = Map.keySet(headers);
var sortedHeadersArray = Array.ascSort(headersArray);
var tmp : string = '';
var separator : string = '';
for(var key : sortedHeadersArray) {
var lowerKey = String.toLower(key);
if (String.hasPrefix(lowerKey, 'x-log-') ||
String.hasPrefix(lowerKey, 'x-acs-') ||
String.equals(lowerKey, 'host') ||
String.equals(lowerKey, 'content-type')) {
tmp = `${tmp}${separator}${lowerKey}`;
separator = ';';
}
}
return tmp;
}
async function getRegion(context: SPI.InterceptorContext): string {
var config = context.configuration;
if (!Util.isUnset(config.regionId) && !Util.empty(config.regionId)) {
return config.regionId;
}
// try parse region from endpoint
// do not use String.subString, subString has bug in php implementation
var region: string = String.replace(config.endpoint, '.log.aliyuncs.com', '', null);
region = String.replace(region, '.sls.aliyuncs.com', '', null);
if (String.equals(region, config.endpoint)) {
throw {
code = 'ClientConfigError',
message = 'The regionId configuration of sls client is missing.'
};
}
region = String.replace(region, '-share', '', null);
region = String.replace(region, '-intranet', '', null);
region = String.replace(region, '-vpc', '', null);
return region;
}
// format: YYYYMMDDTHHMMSSZ
async function getDateISO8601() : string {
var date = OpenApiUtil.getTimestamp(); // 2024-02-04T11:31:58Z
date = String.replace(date, '-', '', null);
return String.replace(date, ':', '', null);
}