| /* |
| * 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, |
| }; |
| }; |