Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add limit-conn plugin form #1728

Merged
merged 10 commits into from
Apr 12, 2021
109 changes: 109 additions & 0 deletions web/cypress/integration/consumer/create-with-limit-conn-form.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.
*/
/* eslint-disable no-undef */

context('Create and delete consumer with limit-conn plugin form', () => {
beforeEach(() => {
cy.login();

cy.fixture('selector.json').as('domSelector');
cy.fixture('data.json').as('data');
});

const selector = {
conn: '#conn',
burst: '#burst',
default_conn_delay: '#default_conn_delay',
key: '#key',
rejected_code: '#rejected_code',
title: '[title="remote_addr"]'
}

const data = {
conn: 1,
burst: 0,
default_conn_delay: 1,
key: 'remote_addr',
}

it('creates consumer with limit-conn form', function () {
cy.visit('/');
cy.contains('Consumer').click();
cy.get(this.domSelector.empty).should('be.visible');
cy.contains('Create').click();
// basic information
cy.get(this.domSelector.username).type(this.data.consumerName);
cy.get(this.domSelector.description).type(this.data.description);
cy.contains('Next').click();

// config auth plugin
cy.contains(this.domSelector.pluginCard, 'key-auth').within(() => {
cy.contains('Enable').click({
force: true,
});
});
cy.focused(this.domSelector.drawer).should('exist');
cy.get(this.domSelector.disabledSwitcher).click();
// edit codemirror
cy.get(this.domSelector.codeMirror)
.first()
.then((editor) => {
editor[0].CodeMirror.setValue(
JSON.stringify({
key: 'test',
}),
);
cy.contains('button', 'Submit').click();
});

cy.contains(this.domSelector.pluginCard, 'limit-conn').within(() => {
cy.contains('Enable').click({
force: true,
});
});

cy.focused(this.domSelector.drawer).should('exist');

// config limit-conn form
cy.get(selector.conn).type(data.conn);
cy.get(selector.burst).type(data.burst);
cy.get(selector.default_conn_delay).type(data.default_conn_delay);
cy.get(selector.key).click();
cy.get(this.domSelector.selectDropdown).should('be.visible');
cy.get(selector.title).click({
timeout: 5000,
});
cy.get(this.domSelector.disabledSwitcher).click();
cy.get(this.domSelector.drawer).within(() => {
cy.contains('Submit').click({
force: true,
});
});
cy.get(this.domSelector.drawer).should('not.exist');

cy.contains('button', 'Next').click();
cy.contains('button', 'Submit').click();
cy.get(this.domSelector.notification).should('contain', this.data.createConsumerSuccess);
});

it('delete the consumer', function () {
cy.visit('/consumer/list');
cy.contains(this.data.consumerName).should('be.visible').siblings().contains('Delete').click();
cy.contains('button', 'Confirm').click();
cy.get(this.domSelector.notification).should('contain', this.data.deleteConsumerSuccess);
});
});
8 changes: 0 additions & 8 deletions web/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,6 @@ Cypress.Commands.add('configurePlugins', (cases) => {
}
cy.get(domSelector.drawer).should('exist');

cy.get(domSelector.codeMirrorMode).invoke('text').then(text => {
if (text === 'Form') {
cy.get(domSelector.codeMirrorMode).click();
cy.get(domSelector.selectDropdown).should('be.visible');
cy.get(domSelector.selectJSON).click();
}
});

cy.get(domSelector.drawer, { timeout }).within(() => {
cy.contains('Submit').click({
force: true,
Expand Down
93 changes: 93 additions & 0 deletions web/src/components/Plugin/UI/limit-conn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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 React from 'react';
import type { FormInstance } from 'antd/es/form';
import { Form, InputNumber, Select } from 'antd';
import { useIntl } from 'umi';

type Props = {
form: FormInstance;
ref?: any;
};

const FORM_ITEM_LAYOUT = {
labelCol: {
span: 6,
},
wrapperCol: {
span: 8
},
};

const LimitConn: React.FC<Props> = ({ form }) => {
const { formatMessage } = useIntl();
return (
<Form
form={form}
{...FORM_ITEM_LAYOUT}
>
<Form.Item
label="conn"
required
name="conn"
tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.conn.tooltip' })}
>
<InputNumber min={1} required />
</Form.Item>
<Form.Item
label="burst"
required
name="burst"
tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.burst.tooltip' })}
>
<InputNumber min={0} required />
</Form.Item>
<Form.Item
label="default_conn_delay"
required
name="default_conn_delay"
tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.default_conn_delay.tooltip' })}
>
<InputNumber step={0.001} min={0.001} required />
</Form.Item>

<Form.Item
label="key"
required
name="key"
tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.key.tooltip' })}
>
<Select>
{["remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for", "consumer_name"].map(item => {
return <Select.Option value={item} key={item}>{item}</Select.Option>
})}
</Select>
</Form.Item>

<Form.Item
label="rejected_code"
name="rejected_code"
initialValue={503}
tooltip={formatMessage({ id: 'component.pluginForm.limit-conn.rejected_code.tooltip' })}
>
<InputNumber min={200} max={599} required />
</Form.Item>
</Form>
);
}

export default LimitConn;
7 changes: 5 additions & 2 deletions web/src/components/Plugin/UI/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import type { FormInstance } from 'antd/es/form';
import { Empty } from 'antd';
import { useIntl } from 'umi';

import BasicAuth from './basic-auth'
import BasicAuth from './basic-auth';
import LimitConn from './limit-conn';

type Props = {
name: string,
form: FormInstance,
renderForm: boolean
}

export const PLUGIN_UI_LIST = ['basic-auth',];
export const PLUGIN_UI_LIST = ['basic-auth', 'limit-conn'];

export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {

Expand All @@ -38,6 +39,8 @@ export const PluginForm: React.FC<Props> = ({ name, renderForm, form }) => {
switch (name) {
case 'basic-auth':
return <BasicAuth form={form} />
case 'limit-conn':
return <LimitConn form={form} />
default:
return null;
}
Expand Down
8 changes: 8 additions & 0 deletions web/src/components/Plugin/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export default {
'component.step.select.pluginTemplate.select.option': 'Custom',
'component.plugin.pluginTemplate.tip1': '1. When a route already have plugins field configured, the plugins in the plugin template will be merged into it.',
'component.plugin.pluginTemplate.tip2': '2. The same plugin in the plugin template will override one in the plugins',

// limit-conn
'component.pluginForm.limit-conn.conn.tooltip': 'the maximum number of concurrent requests allowed. Requests exceeding this ratio (and below conn + burst) will get delayed(the latency seconds is configured by default_conn_delay) to conform to this threshold.',
'component.pluginForm.limit-conn.burst.tooltip': 'the number of excessive concurrent requests (or connections) allowed to be delayed.',
'component.pluginForm.limit-conn.default_conn_delay.tooltip': 'the latency seconds of request when concurrent requests exceeding conn but below (conn + burst).',
'component.pluginForm.limit-conn.key.tooltip': 'to limit the concurrency level.For example, one can use the host name (or server zone) as the key so that we limit concurrency per host name. Otherwise, we can also use the client address as the key so that we can avoid a single client from flooding our service with too many parallel connections or requests.Now accept those as key: "remote_addr"(client\'s IP), "server_addr"(server\'s IP), "X-Forwarded-For/X-Real-IP" in request header, "consumer_name"(consumer\'s username).',
'component.pluginForm.limit-conn.rejected_code.tooltip': 'returned when the request exceeds conn + burst will be rejected.',

'component.plugin.form': 'Form',
'component.plugin.format-codes.disable': 'Format JSON or YAML data',
'component.plugin.editor': 'Plugin Editor',
Expand Down
8 changes: 8 additions & 0 deletions web/src/components/Plugin/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export default {
'component.step.select.pluginTemplate.select.option': '手动配置',
'component.plugin.pluginTemplate.tip1': '1. 若路由已配置插件,则插件模板数据将与已配置的插件数据合并。',
'component.plugin.pluginTemplate.tip2': '2. 插件模板相同的插件会覆盖掉原有的插件。',

// limit-conn
'component.pluginForm.limit-conn.conn.tooltip': '允许的最大并发请求数。超过 conn 的限制、但是低于 conn + burst 的请求,将被延迟处理。',
'component.pluginForm.limit-conn.burst.tooltip': '允许被延迟处理的并发请求数。',
'component.pluginForm.limit-conn.default_conn_delay.tooltip': '默认的典型连接(或请求)的处理延迟时间。',
'component.pluginForm.limit-conn.key.tooltip': '用户指定的限制并发级别的关键字,可以是客户端 IP 或服务端 IP。例如,可以使用主机名(或服务器区域)作为关键字,以便限制每个主机名的并发性。 否则,我们也可以使用客户端地址作为关键字,这样我们就可以避免单个客户端用太多的并行连接或请求淹没我们的服务。当前接受的 key 有:"remote_addr"(客户端 IP 地址), "server_addr"(服务端 IP 地址), 请求头中的"X-Forwarded-For" 或 "X-Real-IP", "consumer_name"(consumer 的 username)。',
'component.pluginForm.limit-conn.rejected_code.tooltip': '当请求超过 conn + burst 这个阈值时,返回的 HTTP 状态码。',

'component.plugin.form': '表单',
'component.plugin.format-codes.disable': '用于格式化 JSON 或 YAML 内容',
'component.plugin.editor': '插件配置',
Expand Down