blob: a68667b556cda7feda4830fe02420645165305bb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep, isEmpty, omit, pick, unset } from 'lodash';
import { convertToFormData } from '@/components/Upstream/service';
import { transformLabelValueToKeyValue } from '@/helpers';
import { HOST_REWRITE_TYPE, SCHEME_REWRITE, URI_REWRITE_TYPE } from '@/pages/Route/constants';
export const transformProxyRewrite2Plugin = (
data: RouteModule.ProxyRewrite,
): RouteModule.ProxyRewrite => {
const omitFieldsList: string[] = ['kvHeaders'];
let headers: Record<string, string> = {};
if (data.scheme !== 'http' && data.scheme !== 'https') {
omitFieldsList.push('scheme');
}
if (data.method === '') {
omitFieldsList.push('method');
}
(data.kvHeaders || []).forEach((kvHeader) => {
if (kvHeader.key) {
// support value to be an empty string, which means remove a header
headers = {
...headers,
[kvHeader.key]: kvHeader.value || '',
};
}
});
if (!isEmpty(headers)) {
return omit(
{
...data,
headers,
},
omitFieldsList,
);
}
return omit(data, omitFieldsList);
};
const transformProxyRewrite2Formdata = (pluginsData: any) => {
const proxyRewriteData: RouteModule.ProxyRewrite = {
scheme: SCHEME_REWRITE.KEEP,
};
let URIRewriteType = URI_REWRITE_TYPE.KEEP;
let hostRewriteType = HOST_REWRITE_TYPE.KEEP;
if (pluginsData) {
if (pluginsData.regex_uri) {
URIRewriteType = URI_REWRITE_TYPE.REGEXP;
}
if (pluginsData.uri && !pluginsData.regex_uri) {
URIRewriteType = URI_REWRITE_TYPE.STATIC;
}
if (pluginsData.host) {
hostRewriteType = HOST_REWRITE_TYPE.REWRITE;
}
Object.keys(pluginsData).forEach((key) => {
switch (key) {
case 'scheme':
proxyRewriteData[key] =
pluginsData[key] === SCHEME_REWRITE.HTTP || pluginsData[key] === SCHEME_REWRITE.HTTPS
? pluginsData[key]
: SCHEME_REWRITE.KEEP;
break;
case 'uri':
case 'regex_uri':
case 'host':
case 'method':
proxyRewriteData[key] = pluginsData[key];
break;
case 'headers':
Object.keys(pluginsData[key]).forEach((headerKey) => {
proxyRewriteData.kvHeaders = [
...(proxyRewriteData.kvHeaders || []),
{
key: headerKey,
value: pluginsData[key][headerKey],
},
];
});
break;
default:
break;
}
});
}
return {
proxyRewriteData,
URIRewriteType,
hostRewriteType,
};
};
// Transform Route data then sent to API
export const transformStepData = ({
form1Data,
form2Data,
advancedMatchingRules,
step3Data,
}: RouteModule.RequestData) => {
const { custom_normal_labels, custom_version_label, service_id = '' } = form1Data;
let redirect: RouteModule.Redirect = {};
const proxyRewriteFormData: RouteModule.ProxyRewrite = form1Data.proxyRewrite;
const proxyRewriteConfig = transformProxyRewrite2Plugin(proxyRewriteFormData);
const step3DataCloned = cloneDeep(step3Data);
if (form1Data.redirectOption === 'disabled') {
step3DataCloned.plugins = omit(step3Data.plugins, ['redirect']);
} else if (form1Data.redirectOption === 'forceHttps') {
redirect = { http_to_https: true };
} else if (form1Data.redirectURI !== '') {
redirect = {
ret_code: form1Data.ret_code,
uri: form1Data.redirectURI,
};
}
const labels: Record<string, string> = {};
transformLabelValueToKeyValue(custom_normal_labels).forEach(({ labelKey, labelValue }) => {
labels[labelKey] = labelValue;
});
if (custom_version_label) {
labels.API_VERSION = custom_version_label;
}
const data: Partial<RouteModule.Body> = {
...form1Data,
labels,
...step3DataCloned,
vars: advancedMatchingRules.map((rule) => {
const { reverse, operator, position, name, value } = rule;
let key: string;
switch (position) {
case 'cookie':
key = `cookie_${name}`;
break;
case 'http':
key = `http_${name}`;
break;
case 'arg':
key = `arg_${name}`;
break;
case 'post_arg':
key = `post_arg_${name}`;
break;
default:
key = `${name}`;
}
let finalValue = value;
if (operator === 'IN') {
finalValue = JSON.parse(value as string);
}
return reverse ? [key, '!', operator, finalValue] : [key, operator, finalValue];
}),
// @ts-ignore
methods: form1Data.methods.includes('ALL') ? [] : form1Data.methods,
status: Number(form1Data.status),
};
if (!isEmpty(proxyRewriteConfig)) {
if (Object.keys(data.plugins || {}).length === 0) {
data.plugins = {};
}
data.plugins!['proxy-rewrite'] = proxyRewriteConfig;
} else {
unset(data.plugins, ['proxy-rewrite']);
}
if (data.uris && data.uris.filter(Boolean).length === 1) {
[data.uri] = data.uris;
delete data.uris;
}
if (data.hosts && data.hosts.filter(Boolean).length === 1) {
[data.host] = data.hosts;
delete data.hosts;
}
if (data.remote_addrs && data.remote_addrs.filter(Boolean).length === 1) {
[data.remote_addr] = data.remote_addrs;
delete data.remote_addrs;
}
if ((Object.keys(redirect).length === 0 || redirect.http_to_https) && form2Data) {
/**
* Due to convertToRequestData under the Upstream component,
* if upstream_id === Custom or None, it will be omitted.
* So upstream_id here mush be a valid Upstream ID from API.
*/
if (form2Data.upstream_id) {
data.upstream_id = form2Data.upstream_id;
} else {
data.upstream = form2Data;
}
if (redirect.http_to_https) {
if (Object.keys(data.plugins || {}).length === 0) {
data.plugins = {};
}
data.plugins!.redirect = redirect;
}
// Remove some of the frontend custom variables
return omit(data, [
'custom_version_label',
'custom_normal_labels',
'advancedMatchingRules',
'upstreamHostList',
'upstreamPath',
'timeout',
'redirectURI',
'ret_code',
'redirectOption',
'URIRewriteType',
'hostRewriteType',
'proxyRewrite',
service_id.length === 0 ? 'service_id' : '',
!Object.keys(data.plugins || {}).length ? 'plugins' : '',
!Object.keys(data.script || {}).length ? 'script' : '',
form1Data.hosts?.filter(Boolean).length === 0 ? 'hosts' : '',
form1Data.redirectOption === 'disabled' ? 'redirect' : '',
data.remote_addrs?.filter(Boolean).length === 0 ? 'remote_addrs' : '',
step3DataCloned.plugin_config_id === '' ? 'plugin_config_id' : '',
data.vars?.length ? '' : 'vars',
]);
}
if (Object.keys(redirect).length) {
data.plugins = {
...data.plugins,
redirect,
};
}
return pick(data, [
'name',
'desc',
'priority',
'methods',
'redirect',
'plugins',
'labels',
'enable_websocket',
data.uri ? 'uri' : 'uris',
data.vars?.length ? 'vars' : '',
service_id.length !== 0 ? 'service_id' : '',
data.hosts?.filter(Boolean).length !== 0 ? 'hosts' : '',
data.remote_addrs?.filter(Boolean).length !== 0 ? 'remote_addrs' : '',
data.host ? 'host' : '',
data.remote_addr ? 'remote_addr' : '',
]);
};
const transformVarsToRules = (data: RouteModule.VarTuple[] = []): RouteModule.MatchingRule[] =>
data.map((varTuple) => {
const key = varTuple[0];
const reverse = varTuple[1] === '!';
const operator = varTuple[1] === '!' ? varTuple[2] : varTuple[1];
const value = varTuple[varTuple.length - 1];
let position: string;
let name: string;
const regex = new RegExp('^(cookie|http|arg|post_arg)_.+');
if (regex.test(key)) {
[, position, name] = key.split(/^(cookie|http|arg|post_arg)_/);
} else {
position = 'buildin';
name = key;
}
return {
position: position as RouteModule.VarPosition,
name,
value: typeof value === 'object' ? JSON.stringify(value) : value,
reverse,
operator,
key: Math.random().toString(36).slice(2),
};
});
export const transformUpstreamNodes = (
nodes: Record<string, number> = {},
): RouteModule.UpstreamHost[] => {
const data: RouteModule.UpstreamHost[] = [];
Object.entries(nodes).forEach(([k, v]) => {
const [host, port] = k.split(':');
data.push({ host, port: Number(port), weight: Number(v) });
});
if (data.length === 0) {
data.push({} as RouteModule.UpstreamHost);
}
return data;
};
// Transform response's data
export const transformRouteData = (data: RouteModule.Body) => {
const {
name,
id,
desc,
labels = {},
methods = [],
uris,
uri,
hosts,
host,
remote_addrs,
remote_addr,
vars = [],
status,
upstream,
upstream_id,
service_id = '',
priority = 0,
enable_websocket,
} = data;
const form1Data: Partial<RouteModule.Form1Data> = {
name,
id,
desc,
status,
hosts: hosts || (host && [host]) || [''],
uris: uris || (uri && [uri]) || [],
remote_addrs: remote_addrs || (remote_addr && [remote_addr]) || [''],
// NOTE: API_VERSION is a system label
custom_version_label: labels.API_VERSION || '',
custom_normal_labels: Object.keys(labels)
.filter((item) => item !== 'API_VERSION')
.map((key) => `${key}:${labels[key]}`),
// @ts-ignore
methods: methods.length ? methods : ['ALL'],
priority,
enable_websocket,
service_id,
};
const redirect = data.plugins?.redirect || {};
if (redirect?.http_to_https) {
form1Data.redirectOption = 'forceHttps';
} else if (redirect?.uri) {
form1Data.redirectOption = 'customRedirect';
form1Data.ret_code = redirect?.ret_code;
form1Data.redirectURI = redirect?.uri;
} else {
form1Data.redirectOption = 'disabled';
}
const proxyRewrite = data.plugins ? data.plugins['proxy-rewrite'] : {};
const { proxyRewriteData, URIRewriteType, hostRewriteType } = transformProxyRewrite2Formdata(
proxyRewrite,
);
form1Data.proxyRewrite = proxyRewriteData;
form1Data.URIRewriteType = URIRewriteType;
form1Data.hostRewriteType = hostRewriteType;
const advancedMatchingRules: RouteModule.MatchingRule[] = transformVarsToRules(vars);
if (upstream && Object.keys(upstream).length) {
upstream.upstream_id = 'Custom';
}
const form2Data: UpstreamComponent.ResponseData = convertToFormData(upstream) || {
upstream_id: upstream_id || 'None',
};
const { plugins, script, plugin_config_id = '' } = data;
const step3Data: RouteModule.Step3Data = {
plugins,
script,
plugin_config_id,
};
return {
form1Data,
form2Data,
step3Data,
advancedMatchingRules,
};
};