diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index e96b1b22..4e2bfd25 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,133 +1,94 @@
+## 贡献者契约行为准则
-# Contributor Covenant Code of Conduct
+### 我们的承诺
-## Our Pledge
+---
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, caste, color, religion, or sexual
-identity and orientation.
+作为成员、贡献者和领导者,我们承诺让每个人参与我们的社区成为一种无骚扰的体验,无论年龄、体型、可见或不可见的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度如何、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性身份和性取向。
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
+我们承诺通过建立一个开有助于开放、热情、多元化、包容和健康的社区的方式采取行动和互动。
-## Our Standards
+### 我们的标准
-Examples of behavior that contributes to a positive environment for our
-community include:
+---
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
- community
+为我们的社区营造积极环境的行为示例包括:
-Examples of unacceptable behavior include:
+* 表现出对他人的同理心和善意
+* 尊重不同的意见、观点和经验
+* 给予并优雅地接受建设性的反馈
+* 承担责任并向受我们错误影响的人道歉,并从经验中学习
+* 关注那些对我们个人和社区都最好的事情
-* The use of sexualized language or imagery, and sexual attention or advances of
- any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
- without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-## Enforcement Responsibilities
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
+不可接受的行为示例包括:
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
+* 使用色情语言或图像,以及任何形式的性关注或性推动
+* 拖钓、侮辱或贬损评论,以及人身或政治攻击
+* 公共或私人骚扰
+* 未经他人明确许可,发布他人的私人信息,例如物理地址或电子邮件地址
+* 在专业环境中可能被合理认为不适当的其他行为
-## Scope
+### 执法责任
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
+---
-## Enforcement
+社区领袖有责任澄清和执行我们可接受的行为标准,并将针对他们认为不恰当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-[apinto@eolink.com].
-All complaints will be reviewed and investigated promptly and fairly.
+社区领袖有权利和责任删除、编辑或拒绝与本行为准则不一致的评论、commits、代码、wiki 编辑、issues和其他贡献,并会在适当时传达审核决定的原因。
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
+### 范围
-## Enforcement Guidelines
+---
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
+本行为准则适用于所有社区空间,包括个人在公共场所正式代表社区的时候。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在在线或离线活动中担任指定代表。
-### 1. Correction
+### 执法
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
+---
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
+辱骂、骚扰或其他不可接受的行为可通过 [apinto@eolink.com] 向负责执法的社区领袖报告。所有投诉都将得到及时和公平的审查和调查。
-### 2. Warning
+所有社区领袖都有义务尊重任何事件报告者的隐私和安全。
-**Community Impact**: A violation through a single incident or series of
-actions.
+### 执法指南
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or permanent
-ban.
+---
-### 3. Temporary Ban
+社区领袖将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果:
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
+#### 1. 更正
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
+**社区影响**:使用不恰当的语言或其他在社区中被认为不专业或不受欢迎的行为。
-### 4. Permanent Ban
+**后果**:来自社区领袖的私下书面警告,清楚说明违规的性质,并解释行为不恰当的原因。可能会要求公开道歉。
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
+#### 2. 警告
-**Consequence**: A permanent ban from any sort of public interaction within the
-community.
+**社区影响**:通过单一事件或一系列行动的违规行为。
-## Attribution
+**后果**:对持续行为产生后果的警告。在指定的时间段内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区空间以及社交媒体等外部渠道中进行互动。违反这些条款可能会导致临时或永久禁令。
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.1, available at
-[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+#### 2. 临时禁令
-Community Impact Guidelines were inspired by
-[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+**社区影响**:严重违反社区标准,包括持续的不当行为。
-For answers to common questions about this code of conduct, see the FAQ at
-[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
-[https://www.contributor-covenant.org/translations][translations].
+**后果**:在指定时间段内暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与《行为准则》执行者互动。违反这些条款可能会导致永久禁令。
+
+#### 4. 永久禁令
+
+**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、骚扰个人,或攻击或贬低某一类个人。
+
+**后果**:永久禁止在社区内进行任何形式的公共互动。
+
+### 归属
+
+本行为准则改编自贡献者公约2.1 版,可在 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html获得。
+
+社区影响指南的灵感来自 Mozilla 的行为准则执行阶梯。
+
+有关此行为准则的常见问题的答案,请参阅 https://www.contributor-covenant.org/faq上的常见问题解答。
+
+翻译可在 https://www.contributor-covenant.org/translations获得。
-[homepage]: https://www.contributor-covenant.org
-[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
-[Mozilla CoC]: https://github.com/mozilla/diversity
-[FAQ]: https://www.contributor-covenant.org/faq
-[translations]: https://www.contributor-covenant.org/translations
diff --git a/CODE_OF_CONDUCT_CN.md b/CODE_OF_CONDUCT_CN.md
deleted file mode 100644
index 4e2bfd25..00000000
--- a/CODE_OF_CONDUCT_CN.md
+++ /dev/null
@@ -1,94 +0,0 @@
-## 贡献者契约行为准则
-
-### 我们的承诺
-
----
-
-作为成员、贡献者和领导者,我们承诺让每个人参与我们的社区成为一种无骚扰的体验,无论年龄、体型、可见或不可见的残疾、种族、性别特征、性别认同和表达、经验水平、教育程度如何、社会经济地位、国籍、个人外貌、种族、种姓、肤色、宗教或性身份和性取向。
-
-我们承诺通过建立一个开有助于开放、热情、多元化、包容和健康的社区的方式采取行动和互动。
-
-### 我们的标准
-
----
-
-为我们的社区营造积极环境的行为示例包括:
-
-* 表现出对他人的同理心和善意
-* 尊重不同的意见、观点和经验
-* 给予并优雅地接受建设性的反馈
-* 承担责任并向受我们错误影响的人道歉,并从经验中学习
-* 关注那些对我们个人和社区都最好的事情
-
-
-
-不可接受的行为示例包括:
-
-* 使用色情语言或图像,以及任何形式的性关注或性推动
-* 拖钓、侮辱或贬损评论,以及人身或政治攻击
-* 公共或私人骚扰
-* 未经他人明确许可,发布他人的私人信息,例如物理地址或电子邮件地址
-* 在专业环境中可能被合理认为不适当的其他行为
-
-### 执法责任
-
----
-
-社区领袖有责任澄清和执行我们可接受的行为标准,并将针对他们认为不恰当、威胁、冒犯或有害的任何行为采取适当和公平的纠正措施。
-
-社区领袖有权利和责任删除、编辑或拒绝与本行为准则不一致的评论、commits、代码、wiki 编辑、issues和其他贡献,并会在适当时传达审核决定的原因。
-
-### 范围
-
----
-
-本行为准则适用于所有社区空间,包括个人在公共场所正式代表社区的时候。代表我们社区的示例包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在在线或离线活动中担任指定代表。
-
-### 执法
-
----
-
-辱骂、骚扰或其他不可接受的行为可通过 [apinto@eolink.com] 向负责执法的社区领袖报告。所有投诉都将得到及时和公平的审查和调查。
-
-所有社区领袖都有义务尊重任何事件报告者的隐私和安全。
-
-### 执法指南
-
----
-
-社区领袖将遵循这些社区影响指南来确定他们认为违反本行为准则的任何行为的后果:
-
-#### 1. 更正
-
-**社区影响**:使用不恰当的语言或其他在社区中被认为不专业或不受欢迎的行为。
-
-**后果**:来自社区领袖的私下书面警告,清楚说明违规的性质,并解释行为不恰当的原因。可能会要求公开道歉。
-
-#### 2. 警告
-
-**社区影响**:通过单一事件或一系列行动的违规行为。
-
-**后果**:对持续行为产生后果的警告。在指定的时间段内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区空间以及社交媒体等外部渠道中进行互动。违反这些条款可能会导致临时或永久禁令。
-
-#### 2. 临时禁令
-
-**社区影响**:严重违反社区标准,包括持续的不当行为。
-
-**后果**:在指定时间段内暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与《行为准则》执行者互动。违反这些条款可能会导致永久禁令。
-
-#### 4. 永久禁令
-
-**社区影响**:表现出违反社区标准的模式,包括持续的不当行为、骚扰个人,或攻击或贬低某一类个人。
-
-**后果**:永久禁止在社区内进行任何形式的公共互动。
-
-### 归属
-
-本行为准则改编自贡献者公约2.1 版,可在 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html获得。
-
-社区影响指南的灵感来自 Mozilla 的行为准则执行阶梯。
-
-有关此行为准则的常见问题的答案,请参阅 https://www.contributor-covenant.org/faq上的常见问题解答。
-
-翻译可在 https://www.contributor-covenant.org/translations获得。
-
diff --git a/CODE_OF_CONDUCT_EN.md b/CODE_OF_CONDUCT_EN.md
new file mode 100644
index 00000000..e96b1b22
--- /dev/null
+++ b/CODE_OF_CONDUCT_EN.md
@@ -0,0 +1,133 @@
+
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+[apinto@eolink.com].
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/README.md b/README.md
index 1f17d3c1..0484788f 100644
--- a/README.md
+++ b/README.md
@@ -1,126 +1,136 @@
## Apinto
-[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/Apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total)
-[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
-![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png)
+[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total)
+[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT_CN.md)
-Apinto is a microservice gateway developed based on golang. It can achieve the purposes of high-performance HTTP API forwarding, multi tenant management, API access control, etc. it has a powerful user-defined plug-in system, which can be expanded by itself, and can quickly help enterprises manage API services and improve the stability and security of API services. In the future, we will provide the plug-in market. Through the strong plug-in expansion ability of **Apinto**, users can expand **Apinto** plug-ins as needed like Lego blocks to enrich **Apinto** capabilities.
+------------
+![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png)
-**Note**:The **main** branch is the main development branch. Frequent updates may lead to unstable use. If you need to use a stable version, please look [release](https://github.com/eolinker/apinto/releases)
+Apinto是一个基于 Golang 开发的微服务网关,能够实现高性能 HTTP API 转发、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。未来我们将提供插件市场,通过**Apinto**强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展**Apinto**的插件,丰富**Apinto**的能力。
-**Apinto** integrates configuration and forwarding functions. Users can configure it through OpenAPI or through visual UI items [apinto dashboard](https://github.com/eolinker/apinto-dashboard) for configuration, click [apinto dashboard deployment document](https://help.apinto.com/docs/dashboard/quick/arrange) for relevant documents
+注意:**main**分支为开发主要分支,频繁更新可能导致使用不稳定,若需要使用稳定版本,请查看[release](https://github.com/eolinker/apinto/releases)
-### Summary / [中文介绍](https://github.com/eolinker/apinto/blob/main/README_CN.md)
+**Apinto** 集合了配置和转发功能,使用者可以通过openAPI进行配置,也可通过可视化UI项目[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)进行配置,相关文档可点击[Apinto Dashboard部署文档](https://help.apinto.com/docs/dashboard/quick/arrange)
+### 概况 | [English Introduction](https://github.com/eolinker/apinto/blob/main/README_EN.md)
-- [Why Apinto](#WhyApinto "Why Apinto")
-- [Feature](#Feature)
-- [Benchmark](#Benchmark)
-- [Deployment](#Deployment)
-- [GetStart](#GetStart "Get Start")
-- [Contact](#Contact)
-- [About](#About)
+- [为什么要使用Apinto](#为什么要使用Apinto "Apinto")
+- [产品特性](#产品特性 "产品特性")
+- [基准测试](#基准测试 "基准测试")
+- [部署](#部署 "部署")
+- [启动](#启动 "启动")
+- [联系我们](#联系我们 "联系我们")
+- [关于我们](#关于我们 "关于我们")
+- [授权协议](#授权协议 "授权协议")
-### Why Apinto
+### 为什么要使用Apinto
-Apinto API gateway is a microservice gateway running on the service boundary of enterprise system. When you build websites, apps, iots and even open API transactions, Apinto API gateway can help you extract duplicate components from your internal system and run them on Apinto gateway, such as user authorization, access control, firewall, data conversion, etc; Moreover, Apinto provides the function of service arrangement, so that enterprises can quickly obtain the required data from various services and realize rapid response to business.
+**Apinto**是运行在企业系统服务边界上的API网关。当您构建网站、App、IOT甚至是开放API交易时,Apinto 能够帮你将内部系统中重复的组件抽取出来并放置在Apinto网关上运行,如进行用户授权、访问控制、防火墙、数据转换等;并且Apinto 提供服务编排的功能,让企业可以快速从各类服务上获取需要的数据,对业务实现快速响应。
-Apinto API gateway has the following advantages:
+**Apinto**具有以下优势:
-- Completely open source: the Apinto project is initiated and maintained by eolinker for a long time. We hope to work with global developers to build the infrastructure of micro service ecology.
-- Excellent performance: under the same environment, Apinto is about 50% faster than nginx, Kong and other products, and its stability is also optimized.
-- Rich functions: Apinto provides all the functions of a standard gateway, and you can quickly connect your micro services and manage network traffic.
-- Extremely low use and maintenance cost: Apinto is an open source gateway developed in pure go language. It has no cumbersome deployment and no external product dependence. It only needs to download and run, which is extremely simple.
-- Good scalability: most of Apinto's functions are modular, so you can easily expand its capabilities.
+- 完全开源:Apinto 项目由 Eolinker 发起并长期维护,我们希望与全球开发者共同打造微服务生态的基础设施。
+- 优异的性能表现:相同环境下,Apinto比Nginx、Kong等产品快约50%,并且在稳定性上也有所优化。
+- 丰富的功能:Apinto 提供了一个标准网关应有的所有功能,并且你可以快速连接你的各个微服务以及管理网络流量。
+- 极低的使用和维护成本:Apinto 是纯 Go 语言开发的开源网关,没有繁琐的部署,没有外部产品依赖,只需要下载并运行即可,极为简单。
+- 良好的扩展性:Apinto 的绝大部分功能都是模块化的,因此你可以很容易扩展它的能力。
-In a word, Apinto API gateway enables the business development team to focus more on business implementation.
+总而言之,Apinto 能让业务开发团队更加专注地实现业务。
-### Star History
+### Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date)
-### Feture
-
-| Feture | Description |
-| --------------------- | ------------------------------------------------------------ |
-| Dynamic router | Match the corresponding service by setting parameters such as location, query, header, host and method |
-| Service discovery | Support such as Eureka, Nacos and Consul |
-| Load Balance | Support polling weight algorithm |
-| Authentication | Anonymous, basic, apikey, JWT, AK / SK authentication |
-| SSL certificate | Manage multiple certificates |
-| Access Domain | The access domain can be set for the gateway |
-| Health check | Support health check of load nodes to ensure service robustness |
-| Protocol | HTTP/HTTPS、Webservice |
-| Plugin | The process is plug-in, and the required modules are loaded on demand |
-| OPEN API | Gateway configuration using open API is supported |
-| Log | Provide the operation log of the node, and set the level output of the log |
-| Multiple log output | The node's request log can be output to different log receivers, such as file, NSQ, Kafka,etc |
-| Cli | The gateway is controlled by cli command. The plug-in installation, download, opening and closing of the gateway can be controlled by one click command |
-| Black and white list | Support setting black-and-white list IP to intercept illegal IP |
-| Parameter mapping | Mapping the request parameters of the client to the forwarding request, you can change the location and name of the parameters as needed |
-| Additional parameters | When forwarding the request, add back-end verification parameters, such as apikey, etc |
-| Proxy rewrite | It supports rewriting of 'scheme', 'URI', 'host', and adding or deleting the value of the request header of the forwarding request |
-| flow control | Intercept abnormal traffic |
-
-#### RoadMap
-
-- **UI**: The gateway configuration can be operated through the UI interface, and different UI interfaces (Themes) can be customized by loading as required
-- **Multi protocol**:Support a variety of protocols, including but not limited to grpc, websocket, TCP / UDP and Dubbo
-- **Plugin Market**:Because Apinto mainly loads the required modules through plug-in loading, users can compile the required functions into plug-ins, or download and update the plug-ins developed by contributors from the plug-in market for one click installation
-- **Service Orchestration**:An orchestration API corresponds to multiple backends. The input parameters of backends support client input and parameter transfer between backends; The returned data of backend supports filtering, deleting, moving, renaming, unpacking and packaging of fields; The orchestration API can set the exception return when the orchestration call fails
-- **Monitor**:Capture the gateway request data and export it to Promethus and graphite for analysis
-- .....
+### 产品特性
-#### RoadMap for 2022
+| 功能 | 描述 |
+| ------------ | ------------------------------------------------------------ |
+| 动态路由 | 可通过设置location、query、header、host、method等参数匹配对应的服务 |
+| 服务发现 | 支持对接Eureka、Nacos、Consul |
+| 负载均衡 | 支持轮询权重算法 |
+| 用户鉴权 | 匿名、Basic、Apikey、JWT、AK/SK认证 |
+| SSL证书 | 管理多个证书 |
+| 访问域名 | 可为网关设置访问域名 |
+| 健康检查 | 支持对负载的节点进行健康检查,确保服务健壮性 |
+| 协议 | HTTP/HTTPS、Webservice、Restful、gRPC、Dubbo2、SOAP |
+| 插件化 | 流程插件化,按需加载所需模块 |
+| OPEN API | 支持使用open api配置网关 |
+| 日志 | 提供节点的运行日志,可根据日志设置的等级输出 |
+| 多种日志输出 | 可将节点的请求日志输出到不同的日志接收器,如file、nsq、kafka等 |
+| Cli命令支持 | 通过Cli命令操控网关,插件安装、下载和网关的开启、关闭等操作均可使用一键命令操控 |
+| 黑白名单 | 支持设置黑白名单IP,拦截非法IP |
+| 参数映射 | 将客户端的请求参数映射到转发请求中,可按需改变参数的位置及名称 |
+| 额外参数 | 转发请求时,额外加上后端验证参数,如apikey等 |
+| 转发重写 | 支持对 `scheme`、`uri`、`host` 的重写,同时支持对转发请求的请求头部header的值进行新增或者删除 |
+| 流量控制 | 拦截异常流量 |
-![roadmap_en](https://user-images.githubusercontent.com/14105999/170408557-478830d5-3725-4fbe-a6f6-0ff0dd91d90e.jpeg)
+#### 迭代计划
+- **UI界面支持**: 通过UI界面操作网关配置,可以通过需要加载定制不同的UI界面(主题)
-### Benchmark
+- **多协议支持**:支持多种协议,包括但不限于:gRPC、Websocket、tcp/udp、Dubbo
-![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png)
+- **插件市场**:由于apinto主要是通过插件加载的方式加载所需模块,用户可将所需功能编译成插件,也可从插件市场中下载更新贡献者开发的插件,一键安装使用
+- **服务编排**:一个编排API对应多个backend,backend的入参支持客户端传入,也支持backend间的参数传递;backend的返回数据支持字段的过滤、删除、移动、重命名、拆包和封包;编排API能够设定编排调用失败时的异常返回
+- **监控**:捕获网关请求数据,并可将其导出到promethus、Graphite中进行分析
+- .....
-### Deployment
+#### 2022年迭代计划
+![roadmap_cn](https://user-images.githubusercontent.com/14105999/170409057-407055ef-2d30-4272-ae8c-3c46b95af8d1.jpeg)
+
+### 基准测试
+
+
+![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png)
-* Direct Deployment:[Deployment Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html)
-* [Quick Start Tutorial](https://help.apinto.com/docs/apinto/quick/quick_course.html)
-* [Source Code Compilation Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html)
-* [Docker](https://hub.docker.com/r/eolinker/apinto-gateway)
-* Kubernetes:Follow up support
+### 部署
-### Get start
+* 直接部署:[部署教程](https://help.apinto.com/docs/apinto/quick/arrange)
+* [快速入门教程](https://help.apinto.com/docs/apinto/quick/quick_course)
+* [源码编译教程](https://help.apinto.com/docs/apinto/quick/arrange)
+* [Docker部署](https://hub.docker.com/r/eolinker/apinto-gateway)
+* Kubernetes部署:后续支持
-1. Download and unzip the installation package (here is an example of the installation package of version v0.11.1)
+### 启动
+
+1.下载安装包并解压(此处以v0.12.1版本的安装包示例)
```
-wget https://github.com/eolinker/apinto/releases/download/v0.11.1/apinto_v0.11.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.11.1_linux_amd64.tar.gz && cd apinto
+wget https://github.com/eolinker/apinto/releases/download/v0.12.1/apinto_v0.12.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.12.1_linux_amd64.tar.gz && cd apinto
```
-Apinto supports running on the arm64 and amd64 architectures.
-Please download the installation package of the corresponding architecture and system as required. [Click](https://github.com/eolinker/apinto/releases/) to jump to download the installation package.
+Apinto支持在arm64、amd64架构上运行。
+
+请根据需要下载对应架构及系统的安装包,安装包下载请[点击](https://github.com/eolinker/apinto/releases/)跳转
-2. Start gateway:
+2.启动网关:
```
./apinto start
```
-3.To configure the gateway through the visual interface, click [apinto dashboard](https://github.com/eolinker/apinto-dashboard)
+3.如需可视化界面请点击[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)
+
+
+- ### **联系我们**
+
+
+* **帮助文档**:[https://help.apinto.com](https://help.apinto.com/docs)
+
+- **QQ群**: 725853895
+
+- **Slack**:[加入我们](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ)
-### Contact
-- **Help documentation**:[https://help.apinto.com](https://help.apinto.com/docs)
-- **QQ group**: 725853895
-- **Slack**:[Join us](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ)
-- **Official website**:[https://www.apinto.com](https://www.apinto.com)
-- **Forum**:[https://community.apinto.com](https://community.apinto.com)
-- **Wechat**:
+- **官网**:[https://www.apinto.com](https://www.apinto.com/)
+- **论坛**:[https://community.apinto.com](https://community.apinto.com/)
+- **微信群**:
+### 关于我们
-### About
+EOLINK 是领先的 API 管理服务供应商,为全球超过3000家企业提供专业的 API 研发管理、API自动化测试、API监控、API网关等服务。是首家为ITSS(中国电子工业标准化技术协会)制定API研发管理行业规范的企业。
-Eolink is a leading API management service provider, providing professional API R & D management, API automation testing, API monitoring, API gateway and other services to more than 3000 enterprises around the world. It is the first enterprise to formulate API R & D management industry specifications for ITSS (China Electronics Industry Standardization Technology Association).
+官方网站:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站")
-Official website:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站")
-Download PC desktop for free:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端")
+免费下载PC桌面端:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端")
diff --git a/README_CN.md b/README_CN.md
deleted file mode 100644
index 64228caf..00000000
--- a/README_CN.md
+++ /dev/null
@@ -1,136 +0,0 @@
-## Apinto
-
-[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total)
-[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT_CN.md)
-
-------------
-![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png)
-
-Apinto是一个基于 Golang 开发的微服务网关,能够实现高性能 HTTP API 转发、多租户管理、API 访问权限控制等目的,拥有强大的自定义插件系统可以自行扩展,能够快速帮助企业进行 API 服务治理、提高 API 服务的稳定性和安全性。未来我们将提供插件市场,通过**Apinto**强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展**Apinto**的插件,丰富**Apinto**的能力。
-
-注意:**main**分支为开发主要分支,频繁更新可能导致使用不稳定,若需要使用稳定版本,请查看[release](https://github.com/eolinker/apinto/releases)
-
-**Apinto** 集合了配置和转发功能,使用者可以通过openAPI进行配置,也可通过可视化UI项目[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)进行配置,相关文档可点击[Apinto Dashboard部署文档](https://help.apinto.com/docs/dashboard/quick/arrange)
-### 概况 | [English Introduction](https://github.com/eolinker/apinto/blob/main/README.md)
-
-- [为什么要使用Apinto](#为什么要使用Apinto "Apinto")
-- [产品特性](#产品特性 "产品特性")
-- [基准测试](#基准测试 "基准测试")
-- [部署](#部署 "部署")
-- [启动](#启动 "启动")
-- [联系我们](#联系我们 "联系我们")
-- [关于我们](#关于我们 "关于我们")
-- [授权协议](#授权协议 "授权协议")
-
-### 为什么要使用Apinto
-
-**Apinto**是运行在企业系统服务边界上的API网关。当您构建网站、App、IOT甚至是开放API交易时,Apinto 能够帮你将内部系统中重复的组件抽取出来并放置在Apinto网关上运行,如进行用户授权、访问控制、防火墙、数据转换等;并且Apinto 提供服务编排的功能,让企业可以快速从各类服务上获取需要的数据,对业务实现快速响应。
-
-**Apinto**具有以下优势:
-
-- 完全开源:Apinto 项目由 Eolinker 发起并长期维护,我们希望与全球开发者共同打造微服务生态的基础设施。
-- 优异的性能表现:相同环境下,Apinto比Nginx、Kong等产品快约50%,并且在稳定性上也有所优化。
-- 丰富的功能:Apinto 提供了一个标准网关应有的所有功能,并且你可以快速连接你的各个微服务以及管理网络流量。
-- 极低的使用和维护成本:Apinto 是纯 Go 语言开发的开源网关,没有繁琐的部署,没有外部产品依赖,只需要下载并运行即可,极为简单。
-- 良好的扩展性:Apinto 的绝大部分功能都是模块化的,因此你可以很容易扩展它的能力。
-
-总而言之,Apinto 能让业务开发团队更加专注地实现业务。
-
-### Star 历史
-
-[![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date)
-
-
-### 产品特性
-
-| 功能 | 描述 |
-| ------------ | ------------------------------------------------------------ |
-| 动态路由 | 可通过设置location、query、header、host、method等参数匹配对应的服务 |
-| 服务发现 | 支持对接Eureka、Nacos、Consul |
-| 负载均衡 | 支持轮询权重算法 |
-| 用户鉴权 | 匿名、Basic、Apikey、JWT、AK/SK认证 |
-| SSL证书 | 管理多个证书 |
-| 访问域名 | 可为网关设置访问域名 |
-| 健康检查 | 支持对负载的节点进行健康检查,确保服务健壮性 |
-| 协议 | HTTP/HTTPS、Webservice、Restful、gRPC、Dubbo2、SOAP |
-| 插件化 | 流程插件化,按需加载所需模块 |
-| OPEN API | 支持使用open api配置网关 |
-| 日志 | 提供节点的运行日志,可根据日志设置的等级输出 |
-| 多种日志输出 | 可将节点的请求日志输出到不同的日志接收器,如file、nsq、kafka等 |
-| Cli命令支持 | 通过Cli命令操控网关,插件安装、下载和网关的开启、关闭等操作均可使用一键命令操控 |
-| 黑白名单 | 支持设置黑白名单IP,拦截非法IP |
-| 参数映射 | 将客户端的请求参数映射到转发请求中,可按需改变参数的位置及名称 |
-| 额外参数 | 转发请求时,额外加上后端验证参数,如apikey等 |
-| 转发重写 | 支持对 `scheme`、`uri`、`host` 的重写,同时支持对转发请求的请求头部header的值进行新增或者删除 |
-| 流量控制 | 拦截异常流量 |
-
-#### 迭代计划
-
-- **UI界面支持**: 通过UI界面操作网关配置,可以通过需要加载定制不同的UI界面(主题)
-
-- **多协议支持**:支持多种协议,包括但不限于:gRPC、Websocket、tcp/udp、Dubbo
-
-- **插件市场**:由于apinto主要是通过插件加载的方式加载所需模块,用户可将所需功能编译成插件,也可从插件市场中下载更新贡献者开发的插件,一键安装使用
-
-- **服务编排**:一个编排API对应多个backend,backend的入参支持客户端传入,也支持backend间的参数传递;backend的返回数据支持字段的过滤、删除、移动、重命名、拆包和封包;编排API能够设定编排调用失败时的异常返回
-
-- **监控**:捕获网关请求数据,并可将其导出到promethus、Graphite中进行分析
-- .....
-
-#### 2022年迭代计划
-![roadmap_cn](https://user-images.githubusercontent.com/14105999/170409057-407055ef-2d30-4272-ae8c-3c46b95af8d1.jpeg)
-
-### 基准测试
-
-
-![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png)
-
-### 部署
-
-* 直接部署:[部署教程](https://help.apinto.com/docs/apinto/quick/arrange)
-* [快速入门教程](https://help.apinto.com/docs/apinto/quick/quick_course)
-* [源码编译教程](https://help.apinto.com/docs/apinto/quick/arrange)
-* [Docker部署](https://hub.docker.com/r/eolinker/apinto-gateway)
-* Kubernetes部署:后续支持
-
-### 启动
-
-1.下载安装包并解压(此处以v0.11.1版本的安装包示例)
-
-```
-wget https://github.com/eolinker/apinto/releases/download/v0.11.1/apinto_v0.11.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.11.1_linux_amd64.tar.gz && cd apinto
-```
-
-Apinto支持在arm64、amd64架构上运行。
-
-请根据需要下载对应架构及系统的安装包,安装包下载请[点击](https://github.com/eolinker/apinto/releases/)跳转
-
-2.启动网关:
-
-```
-./apinto start
-```
-
-3.如需可视化界面请点击[Apinto Dashboard](https://github.com/eolinker/apinto-dashboard)
-
-
-- ### **联系我们**
-
-
-* **帮助文档**:[https://help.apinto.com](https://help.apinto.com/docs)
-
-- **QQ群**: 725853895
-
-- **Slack**:[加入我们](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ)
-
-- **官网**:[https://www.apinto.com](https://www.apinto.com/)
-- **论坛**:[https://community.apinto.com](https://community.apinto.com/)
-- **微信群**:
-
-### 关于我们
-
-EOLINK 是领先的 API 管理服务供应商,为全球超过3000家企业提供专业的 API 研发管理、API自动化测试、API监控、API网关等服务。是首家为ITSS(中国电子工业标准化技术协会)制定API研发管理行业规范的企业。
-
-官方网站:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站")
-
-免费下载PC桌面端:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端")
diff --git a/README_EN.md b/README_EN.md
new file mode 100644
index 00000000..3cf92e10
--- /dev/null
+++ b/README_EN.md
@@ -0,0 +1,126 @@
+## Apinto
+
+[![Go Report Card](https://goreportcard.com/badge/github.com/eolinker/apinto)](https://goreportcard.com/report/github.com/eolinker/apinto) [![Releases](https://img.shields.io/github/release/eolinker/apinto/all.svg?style=flat-square)](https://github.com/eolinker/apinto/releases) [![LICENSE](https://img.shields.io/github/license/eolinker/Apinto.svg?style=flat-square)](https://github.com/eolinker/apinto/blob/main/LICENSE)![](https://shields.io/github/downloads/eolinker/apinto/total)
+[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
+![](http://data.eolinker.com/course/eaC48Js3400ffd03c21e36b3eea434dce22d7877a3194f6.png)
+
+Apinto is a microservice gateway developed based on golang. It can achieve the purposes of high-performance HTTP API forwarding, multi tenant management, API access control, etc. it has a powerful user-defined plug-in system, which can be expanded by itself, and can quickly help enterprises manage API services and improve the stability and security of API services. In the future, we will provide the plug-in market. Through the strong plug-in expansion ability of **Apinto**, users can expand **Apinto** plug-ins as needed like Lego blocks to enrich **Apinto** capabilities.
+
+**Note**:The **main** branch is the main development branch. Frequent updates may lead to unstable use. If you need to use a stable version, please look [release](https://github.com/eolinker/apinto/releases)
+
+**Apinto** integrates configuration and forwarding functions. Users can configure it through OpenAPI or through visual UI items [apinto dashboard](https://github.com/eolinker/apinto-dashboard) for configuration, click [apinto dashboard deployment document](https://help.apinto.com/docs/dashboard/quick/arrange) for relevant documents
+
+### Summary / [中文介绍](https://github.com/eolinker/apinto/blob/main/README.md)
+
+- [Why Apinto](#WhyApinto "Why Apinto")
+- [Feature](#Feature)
+- [Benchmark](#Benchmark)
+- [Deployment](#Deployment)
+- [GetStart](#GetStart "Get Start")
+- [Contact](#Contact)
+- [About](#About)
+
+### Why Apinto
+
+Apinto API gateway is a microservice gateway running on the service boundary of enterprise system. When you build websites, apps, iots and even open API transactions, Apinto API gateway can help you extract duplicate components from your internal system and run them on Apinto gateway, such as user authorization, access control, firewall, data conversion, etc; Moreover, Apinto provides the function of service arrangement, so that enterprises can quickly obtain the required data from various services and realize rapid response to business.
+
+Apinto API gateway has the following advantages:
+
+- Completely open source: the Apinto project is initiated and maintained by eolinker for a long time. We hope to work with global developers to build the infrastructure of micro service ecology.
+- Excellent performance: under the same environment, Apinto is about 50% faster than nginx, Kong and other products, and its stability is also optimized.
+- Rich functions: Apinto provides all the functions of a standard gateway, and you can quickly connect your micro services and manage network traffic.
+- Extremely low use and maintenance cost: Apinto is an open source gateway developed in pure go language. It has no cumbersome deployment and no external product dependence. It only needs to download and run, which is extremely simple.
+- Good scalability: most of Apinto's functions are modular, so you can easily expand its capabilities.
+
+In a word, Apinto API gateway enables the business development team to focus more on business implementation.
+
+### Star History
+
+[![Star History Chart](https://api.star-history.com/svg?repos=eolinker/apinto&type=Date)](https://star-history.com/#eolinker/apinto&Date)
+
+
+### Feture
+
+| Feture | Description |
+| --------------------- | ------------------------------------------------------------ |
+| Dynamic router | Match the corresponding service by setting parameters such as location, query, header, host and method |
+| Service discovery | Support such as Eureka, Nacos and Consul |
+| Load Balance | Support polling weight algorithm |
+| Authentication | Anonymous, basic, apikey, JWT, AK / SK authentication |
+| SSL certificate | Manage multiple certificates |
+| Access Domain | The access domain can be set for the gateway |
+| Health check | Support health check of load nodes to ensure service robustness |
+| Protocol | HTTP/HTTPS、Webservice |
+| Plugin | The process is plug-in, and the required modules are loaded on demand |
+| OPEN API | Gateway configuration using open API is supported |
+| Log | Provide the operation log of the node, and set the level output of the log |
+| Multiple log output | The node's request log can be output to different log receivers, such as file, NSQ, Kafka,etc |
+| Cli | The gateway is controlled by cli command. The plug-in installation, download, opening and closing of the gateway can be controlled by one click command |
+| Black and white list | Support setting black-and-white list IP to intercept illegal IP |
+| Parameter mapping | Mapping the request parameters of the client to the forwarding request, you can change the location and name of the parameters as needed |
+| Additional parameters | When forwarding the request, add back-end verification parameters, such as apikey, etc |
+| Proxy rewrite | It supports rewriting of 'scheme', 'URI', 'host', and adding or deleting the value of the request header of the forwarding request |
+| flow control | Intercept abnormal traffic |
+
+#### RoadMap
+
+- **UI**: The gateway configuration can be operated through the UI interface, and different UI interfaces (Themes) can be customized by loading as required
+- **Multi protocol**:Support a variety of protocols, including but not limited to grpc, websocket, TCP / UDP and Dubbo
+- **Plugin Market**:Because Apinto mainly loads the required modules through plug-in loading, users can compile the required functions into plug-ins, or download and update the plug-ins developed by contributors from the plug-in market for one click installation
+- **Service Orchestration**:An orchestration API corresponds to multiple backends. The input parameters of backends support client input and parameter transfer between backends; The returned data of backend supports filtering, deleting, moving, renaming, unpacking and packaging of fields; The orchestration API can set the exception return when the orchestration call fails
+- **Monitor**:Capture the gateway request data and export it to Promethus and graphite for analysis
+- .....
+
+#### RoadMap for 2022
+
+![roadmap_en](https://user-images.githubusercontent.com/14105999/170408557-478830d5-3725-4fbe-a6f6-0ff0dd91d90e.jpeg)
+
+
+### Benchmark
+
+![image](https://user-images.githubusercontent.com/25589530/149748340-dc544f79-a8f9-46f5-903d-a3af4fb8b16e.png)
+
+
+
+### Deployment
+
+* Direct Deployment:[Deployment Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html)
+* [Quick Start Tutorial](https://help.apinto.com/docs/apinto/quick/quick_course.html)
+* [Source Code Compilation Tutorial](https://help.apinto.com/docs/apinto/quick/arrange.html)
+* [Docker](https://hub.docker.com/r/eolinker/apinto-gateway)
+* Kubernetes:Follow up support
+
+### Get start
+
+1. Download and unzip the installation package (here is an example of the installation package of version v0.12.1)
+
+```
+wget https://github.com/eolinker/apinto/releases/download/v0.12.1/apinto_v0.12.1_linux_amd64.tar.gz && tar -zxvf apinto_v0.12.1_linux_amd64.tar.gz && cd apinto
+```
+Apinto supports running on the arm64 and amd64 architectures.
+
+Please download the installation package of the corresponding architecture and system as required. [Click](https://github.com/eolinker/apinto/releases/) to jump to download the installation package.
+
+2. Start gateway:
+
+```
+./apinto start
+```
+
+3.To configure the gateway through the visual interface, click [apinto dashboard](https://github.com/eolinker/apinto-dashboard)
+
+### Contact
+- **Help documentation**:[https://help.apinto.com](https://help.apinto.com/docs)
+- **QQ group**: 725853895
+- **Slack**:[Join us](https://join.slack.com/t/slack-zer6755/shared_invite/zt-u7wzqp1u-aNA0XK9Bdb3kOpN03jRmYQ)
+- **Official website**:[https://www.apinto.com](https://www.apinto.com)
+- **Forum**:[https://community.apinto.com](https://community.apinto.com)
+- **Wechat**:
+
+
+### About
+
+Eolink is a leading API management service provider, providing professional API R & D management, API automation testing, API monitoring, API gateway and other services to more than 3000 enterprises around the world. It is the first enterprise to formulate API R & D management industry specifications for ITSS (China Electronics Industry Standardization Technology Association).
+
+Official website:[https://www.eolink.com](https://www.eolink.com "EOLINK官方网站")
+Download PC desktop for free:[https://www.eolink.com/pc/](https://www.eolink.com/pc/ "免费下载PC客户端")
diff --git a/app/apinto/worker.go b/app/apinto/worker.go
index e94d25a7..5b45c49d 100644
--- a/app/apinto/worker.go
+++ b/app/apinto/worker.go
@@ -27,10 +27,12 @@ import (
"github.com/eolinker/apinto/drivers/plugins/gzip"
http_to_dubbo2 "github.com/eolinker/apinto/drivers/plugins/http-to-dubbo2"
http_to_grpc "github.com/eolinker/apinto/drivers/plugins/http-to-gRPC"
+ "github.com/eolinker/apinto/drivers/plugins/http_mocking"
ip_restriction "github.com/eolinker/apinto/drivers/plugins/ip-restriction"
"github.com/eolinker/apinto/drivers/plugins/monitor"
params_transformer "github.com/eolinker/apinto/drivers/plugins/params-transformer"
prometheus_plugin "github.com/eolinker/apinto/drivers/plugins/prometheus"
+ proxy_mirror "github.com/eolinker/apinto/drivers/plugins/proxy-mirror"
proxy_rewrite "github.com/eolinker/apinto/drivers/plugins/proxy-rewrite"
proxy_rewriteV2 "github.com/eolinker/apinto/drivers/plugins/proxy_rewrite_v2"
rate_limiting "github.com/eolinker/apinto/drivers/plugins/rate-limiting"
@@ -146,4 +148,7 @@ func Register(extenderRegister eosc.IExtenderDriverRegister) {
http_to_grpc.Register(extenderRegister)
protocbuf.Register(extenderRegister)
grpc_to_http.Register(extenderRegister)
+
+ proxy_mirror.Register(extenderRegister)
+ http_mocking.Register(extenderRegister)
}
diff --git a/build/resources/install.sh b/build/resources/install.sh
index 3573927f..cc0303f2 100755
--- a/build/resources/install.sh
+++ b/build/resources/install.sh
@@ -17,6 +17,7 @@ install() {
upgrade() {
apinto stop
install
+ sleep 10s
apinto start
}
diff --git a/drivers/plugins/http_mocking/complete.go b/drivers/plugins/http_mocking/complete.go
new file mode 100644
index 00000000..33a4a3d5
--- /dev/null
+++ b/drivers/plugins/http_mocking/complete.go
@@ -0,0 +1,107 @@
+package http_mocking
+
+import (
+ "encoding/json"
+ "github.com/eolinker/apinto/utils"
+ "github.com/eolinker/eosc/eocontext"
+ http_context "github.com/eolinker/eosc/eocontext/http-context"
+ "github.com/eolinker/eosc/log"
+)
+
+type complete struct {
+ responseStatus int
+ contentType string
+ responseExample string
+ responseSchema map[string]interface{}
+ responseHeader map[string]string
+}
+
+func NewComplete(responseStatus int, contentType string, responseExample string, responseSchema map[string]interface{}, responseHeader map[string]string) *complete {
+ return &complete{responseStatus: responseStatus, contentType: contentType, responseExample: responseExample, responseSchema: responseSchema, responseHeader: responseHeader}
+}
+
+func (c *complete) Complete(org eocontext.EoContext) error {
+ ctx, err := http_context.Assert(org)
+ if err != nil {
+ return err
+ }
+ return c.writeHttp(ctx)
+}
+
+func (c *complete) writeHttp(ctx http_context.IHttpContext) error {
+ ctx.Response().SetHeader("Content-Type", c.contentType)
+ ctx.Response().SetStatus(c.responseStatus, "")
+
+ for k, v := range c.responseHeader {
+ ctx.Response().SetHeader(k, v)
+ }
+
+ if c.responseExample != "" {
+ ctx.Response().SetBody([]byte(c.responseExample))
+ return nil
+ }
+
+ schema := utils.JsonSchemaMockJsUnmarshal(c.responseSchema)
+ bytes, err := json.Marshal(schema)
+ if err != nil {
+ log.Errorf("mocking complete err=%s", err.Error())
+ return err
+ }
+
+ ctx.Response().SetBody(bytes)
+ return nil
+}
+
+//func (c *complete) writeDubbo2(ctx dubbo2_context.IDubbo2Context) error {
+// if c.responseExample != "" {
+//
+// var val interface{}
+// if err := json.Unmarshal([]byte(c.responseExample), &val); err != nil {
+// ctx.Response().SetBody(Dubbo2ErrorResult(err))
+// return err
+// }
+//
+// ctx.Response().SetBody(getDubbo2Response(val, ctx.Proxy().Attachments()))
+// return nil
+// }
+//
+// schema := jsonSchemaUnmarshal(c.responseSchema)
+// ctx.Response().SetBody(getDubbo2Response(schema, ctx.Proxy().Attachments()))
+// return nil
+//}
+
+//func (c *complete) writeGrpc(ctx grpc_context.IGrpcContext) error {
+// descriptor, err := c.descriptor.Descriptor().FindSymbol(fmt.Sprintf("%s.%s", ctx.Proxy().Service(), ctx.Proxy().Method()))
+// if err != nil {
+// return err
+// }
+// methodDesc := descriptor.GetFile().FindService(ctx.Proxy().Service()).FindMethodByName(ctx.Proxy().Method())
+//
+// message := dynamic.NewMessage(methodDesc.GetOutputType())
+//
+// fields := message.GetKnownFields()
+// for _, field := range fields {
+// switch field.GetType() {
+// case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE, descriptorpb.FieldDescriptorProto_TYPE_FLOAT: //float32
+// message.SetField(field, gofakeit.Float32())
+// case descriptorpb.FieldDescriptorProto_TYPE_INT64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_SFIXED64: //int64
+// message.SetField(field, gofakeit.Int64())
+// case descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_SFIXED32: //int32
+// message.SetField(field, gofakeit.Int32())
+// case descriptorpb.FieldDescriptorProto_TYPE_UINT32, descriptorpb.FieldDescriptorProto_TYPE_FIXED32: //uint32
+// message.SetField(field, gofakeit.Uint32())
+// case descriptorpb.FieldDescriptorProto_TYPE_UINT64, descriptorpb.FieldDescriptorProto_TYPE_FIXED64: //uint64
+// message.SetField(field, gofakeit.Uint64())
+// case descriptorpb.FieldDescriptorProto_TYPE_BOOL: //bool
+// message.SetField(field, gofakeit.Bool())
+// case descriptorpb.FieldDescriptorProto_TYPE_STRING: //string
+// message.SetField(field, gofakeit.LetterN(5))
+// case descriptorpb.FieldDescriptorProto_TYPE_BYTES: //bytes
+// message.SetField(field, []byte(gofakeit.LetterN(5)))
+// }
+//
+// }
+//
+// ctx.Response().Write(message)
+// return nil
+//}
diff --git a/drivers/plugins/http_mocking/config.go b/drivers/plugins/http_mocking/config.go
new file mode 100644
index 00000000..85e745e8
--- /dev/null
+++ b/drivers/plugins/http_mocking/config.go
@@ -0,0 +1,13 @@
+package http_mocking
+
+type Config struct {
+ ResponseStatus int `json:"response_status" default:"200" label:"返回响应的 HTTP 状态码(仅http路由有效)"`
+ ContentType string `json:"content_type" label:"返回响应的 Header Content-Type" enum:"application/json"`
+ ResponseExample string `json:"response_example" format:"text" label:"返回响应的Body,与jsonschema字段二选一"`
+ ResponseSchema string `json:"response_schema" format:"text" label:"指定响应的jsonschema对象"`
+ ResponseHeader map[string]string `json:"response_header" label:"响应头"`
+}
+
+const (
+ contentTypeJson = "application/json"
+)
diff --git a/drivers/plugins/http_mocking/driver.go b/drivers/plugins/http_mocking/driver.go
new file mode 100644
index 00000000..4a19b7ba
--- /dev/null
+++ b/drivers/plugins/http_mocking/driver.go
@@ -0,0 +1,87 @@
+package http_mocking
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/eolinker/apinto/drivers"
+ grpc_descriptor "github.com/eolinker/apinto/grpc-descriptor"
+ "github.com/eolinker/eosc"
+ "github.com/eolinker/eosc/common/bean"
+ "github.com/eolinker/eosc/log"
+ "strings"
+)
+
+func check(v interface{}) (*Config, error) {
+ conf, err := drivers.Assert[Config](v)
+ if err != nil {
+ return nil, err
+ }
+
+ if conf.ResponseStatus < 100 {
+ conf.ResponseStatus = 200
+ }
+
+ if conf.ContentType == contentTypeJson {
+ if len(strings.TrimSpace(conf.ResponseSchema)) == 0 && len(strings.TrimSpace(conf.ResponseExample)) == 0 {
+ log.Errorf("mocking check schema is null && example is null ")
+ return nil, errors.New("param err")
+ }
+ if len(strings.TrimSpace(conf.ResponseExample)) > 0 {
+ var val interface{}
+
+ if err = json.Unmarshal([]byte(conf.ResponseExample), &val); err != nil {
+ log.Errorf("mocking check example Format err = %s example=%s", err.Error(), conf.ResponseExample)
+ return nil, errors.New("param err")
+ }
+
+ }
+
+ if len(strings.TrimSpace(conf.ResponseSchema)) > 0 {
+ var val interface{}
+
+ if err = json.Unmarshal([]byte(conf.ResponseSchema), &val); err != nil {
+ log.Errorf("mocking check Schema Format err = %s Schema=%s", err.Error(), conf.ResponseSchema)
+ return nil, errors.New("param err")
+ }
+
+ }
+ }
+
+ return conf, nil
+}
+
+func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
+
+ once.Do(func() {
+ bean.Autowired(&worker)
+ })
+
+ jsonSchema := make(map[string]interface{})
+
+ if conf.ResponseSchema != "" {
+ if err := json.Unmarshal([]byte(conf.ResponseSchema), &jsonSchema); err != nil {
+ log.Errorf("create mocking err=%s,jsonSchema=%s", err.Error(), conf.ResponseSchema)
+ return nil, err
+ }
+ }
+
+ return &Mocking{
+ WorkerBase: drivers.Worker(id, name),
+ handler: NewComplete(conf.ResponseStatus, conf.ContentType, conf.ResponseExample, jsonSchema, conf.ResponseHeader),
+ }, nil
+}
+
+func getDescSource(protobufID string) (grpc_descriptor.IDescriptor, error) {
+
+ w, ok := worker.Get(protobufID)
+ if ok {
+ v, vOk := w.(grpc_descriptor.IDescriptor)
+ if !vOk {
+ return nil, fmt.Errorf("invalid protobuf id: %s", protobufID)
+ }
+ return v, nil
+ }
+ return nil, fmt.Errorf("protobuf worker(%s) is not exist", protobufID)
+
+}
diff --git a/drivers/plugins/http_mocking/factory.go b/drivers/plugins/http_mocking/factory.go
new file mode 100644
index 00000000..dc8b42e1
--- /dev/null
+++ b/drivers/plugins/http_mocking/factory.go
@@ -0,0 +1,25 @@
+package http_mocking
+
+import (
+ "sync"
+
+ "github.com/eolinker/apinto/drivers"
+ "github.com/eolinker/eosc"
+)
+
+const (
+ Name = "http-mocking"
+)
+
+var (
+ once = sync.Once{}
+ worker eosc.IWorkers
+)
+
+func Register(register eosc.IExtenderDriverRegister) {
+ register.RegisterExtenderDriver(Name, NewFactory())
+}
+
+func NewFactory() eosc.IExtenderDriverFactory {
+ return drivers.NewFactory[Config](Create)
+}
diff --git a/drivers/plugins/http_mocking/mocking.go b/drivers/plugins/http_mocking/mocking.go
new file mode 100644
index 00000000..e280c0c6
--- /dev/null
+++ b/drivers/plugins/http_mocking/mocking.go
@@ -0,0 +1,80 @@
+package http_mocking
+
+import (
+ "encoding/json"
+ "github.com/eolinker/apinto/drivers"
+ "github.com/eolinker/eosc"
+ "github.com/eolinker/eosc/eocontext"
+ http_context "github.com/eolinker/eosc/eocontext/http-context"
+ log "github.com/eolinker/eosc/log"
+)
+
+var _ eocontext.IFilter = (*Mocking)(nil)
+var _ http_context.HttpFilter = (*Mocking)(nil)
+
+type Mocking struct {
+ drivers.WorkerBase
+ responseStatus int
+ contentType string
+ responseExample string
+ responseSchema string
+ responseHeader map[string]string
+ handler eocontext.CompleteHandler
+}
+
+func (m *Mocking) DoHttpFilter(ctx http_context.IHttpContext, next eocontext.IChain) (err error) {
+ if m.handler != nil {
+ ctx.SetCompleteHandler(m.handler)
+ }
+
+ if next != nil {
+ return next.DoChain(ctx)
+ }
+
+ return nil
+}
+
+func (m *Mocking) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) {
+ return http_context.DoHttpFilter(m, ctx, next)
+}
+
+func (m *Mocking) Destroy() {
+ return
+}
+
+func (m *Mocking) Start() error {
+ return nil
+}
+
+func (m *Mocking) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
+ conf, err := check(v)
+ if err != nil {
+ return err
+ }
+ m.responseSchema = conf.ResponseSchema
+ m.responseExample = conf.ResponseExample
+ m.contentType = conf.ContentType
+ m.responseStatus = conf.ResponseStatus
+ m.responseHeader = conf.ResponseHeader
+
+ jsonSchema := make(map[string]interface{})
+
+ if conf.ResponseSchema != "" {
+ if err = json.Unmarshal([]byte(conf.ResponseSchema), &jsonSchema); err != nil {
+ log.Errorf("create mocking err=%s,jsonSchema=%s", err.Error(), conf.ResponseSchema)
+ return err
+ }
+ }
+
+ m.handler = NewComplete(m.responseStatus, m.contentType, m.responseExample, jsonSchema, m.responseHeader)
+
+ return nil
+}
+
+func (m *Mocking) Stop() error {
+ return nil
+}
+
+func (m *Mocking) CheckSkill(skill string) bool {
+ return true
+}
diff --git a/drivers/plugins/proxy-mirror/config.go b/drivers/plugins/proxy-mirror/config.go
new file mode 100644
index 00000000..b0b329c8
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/config.go
@@ -0,0 +1,71 @@
+package proxy_mirror
+
+import (
+ "github.com/eolinker/apinto/utils"
+ "strings"
+)
+
+type Config struct {
+ Addr string `json:"Addr" label:"服务地址" description:"镜像服务地址, 需要包含scheme"`
+ SampleConf *SampleConfig `json:"sample_conf" label:"采样配置"`
+ Timeout int `json:"timeout" label:"请求超时时间"`
+ PassHost string `json:"pass_host" enum:"pass,node,rewrite" default:"pass" label:"转发域名" description:"请求发给上游时的 host 设置选型,pass:将客户端的 host 透传给上游,node:使用addr中配置的host,rewrite:使用下面指定的host值"`
+ Host string `json:"host" label:"新host" description:"指定上游请求的host,只有在 转发域名 配置为 rewrite 时有效" switch:"pass_host==='rewrite'"`
+}
+
+type SampleConfig struct {
+ RandomRange int `json:"random_range" label:"随机数范围"`
+ RandomPivot int `json:"random_pivot" label:"随机数锚点"`
+}
+
+const (
+ modePass = "pass"
+ modeNode = "node"
+ modeRewrite = "rewrite"
+)
+
+func (c *Config) doCheck() error {
+ //校验addr
+ if !utils.IsMatchSchemeIpPort(c.Addr) && !utils.IsMatchSchemeDomainPort(c.Addr) {
+ return errAddr
+ }
+ //scheme小写
+ schemeIdx := strings.Index(c.Addr, "://")
+ c.Addr = strings.ToLower(c.Addr[:schemeIdx]) + c.Addr[schemeIdx:]
+
+ //校验采样配置
+ if c.SampleConf.RandomRange <= 0 {
+ return errRandomRangeNum
+ }
+ if c.SampleConf.RandomPivot <= 0 {
+ return errRandomPivotNum
+ }
+ if c.SampleConf.RandomPivot > c.SampleConf.RandomRange {
+ return errRandomPivot
+ }
+
+ //校验镜像请求超时时间
+ if c.Timeout < 0 {
+ c.Timeout = 3000
+ }
+
+ //校验passHost
+ switch c.PassHost {
+ case modePass:
+ case modeNode:
+ case modeRewrite:
+ default:
+ return errUnsupportedPassHost
+ }
+
+ //校验host
+ if c.PassHost == modeRewrite {
+ if c.Host == "" {
+ return errHostNull
+ } else if !utils.IsMatchIpPort(c.Host) && !utils.IsMatchDomainPort(c.Host) {
+ return errAddr
+ }
+ }
+
+ return nil
+}
diff --git a/drivers/plugins/proxy-mirror/driver.go b/drivers/plugins/proxy-mirror/driver.go
new file mode 100644
index 00000000..fb9637ca
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/driver.go
@@ -0,0 +1,42 @@
+package proxy_mirror
+
+import (
+ "github.com/eolinker/apinto/drivers"
+ "github.com/eolinker/eosc"
+ "time"
+)
+
+func Check(v *Config, workers map[eosc.RequireId]eosc.IWorker) error {
+ return v.doCheck()
+}
+
+func check(v interface{}) (*Config, error) {
+
+ conf, err := drivers.Assert[Config](v)
+ if err != nil {
+ return nil, err
+ }
+ err = conf.doCheck()
+ if err != nil {
+ return nil, err
+ }
+
+ return conf, nil
+}
+
+func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
+
+ err := conf.doCheck()
+ if err != nil {
+ return nil, err
+ }
+
+ pm := &proxyMirror{
+ WorkerBase: drivers.Worker(id, name),
+ randomRange: conf.SampleConf.RandomRange,
+ randomPivot: conf.SampleConf.RandomPivot,
+ service: newMirrorService(conf.Addr, conf.PassHost, conf.Host, time.Duration(conf.Timeout)),
+ }
+
+ return pm, nil
+}
diff --git a/drivers/plugins/proxy-mirror/error.go b/drivers/plugins/proxy-mirror/error.go
new file mode 100644
index 00000000..d56f72fd
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/error.go
@@ -0,0 +1,15 @@
+package proxy_mirror
+
+import "github.com/pkg/errors"
+
+var (
+ errUnsupportedContextType = errors.New("send mirror proxy fail. Unsupported Context Type")
+ errHostNull = errors.New("host can't be null when pass_host is rewrite. ")
+ errUnsupportedPassHost = errors.New("unsupported pass_host. ")
+
+ errRandomRangeNum = errors.New("random_range should be bigger than 0. ")
+ errRandomPivotNum = errors.New("random_pivot should be bigger than 0. ")
+ errRandomPivot = errors.New("random_pivot should be smaller than random_range. ")
+
+ errAddr = errors.New("addr is illegal. ")
+)
diff --git a/drivers/plugins/proxy-mirror/factory.go b/drivers/plugins/proxy-mirror/factory.go
new file mode 100644
index 00000000..7cfac0cb
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/factory.go
@@ -0,0 +1,18 @@
+package proxy_mirror
+
+import (
+ "github.com/eolinker/apinto/drivers"
+ "github.com/eolinker/eosc"
+)
+
+const (
+ Name = "proxy_mirror"
+)
+
+func Register(register eosc.IExtenderDriverRegister) {
+ register.RegisterExtenderDriver(Name, NewFactory())
+}
+
+func NewFactory() eosc.IExtenderDriverFactory {
+ return drivers.NewFactory[Config](Create, Check)
+}
diff --git a/drivers/plugins/proxy-mirror/handler.go b/drivers/plugins/proxy-mirror/handler.go
new file mode 100644
index 00000000..7ab44ad1
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/handler.go
@@ -0,0 +1,45 @@
+package proxy_mirror
+
+import (
+ "github.com/eolinker/eosc/eocontext"
+ "github.com/eolinker/eosc/log"
+)
+
+type proxyMirrorCompleteHandler struct {
+ orgComplete eocontext.CompleteHandler
+ service *mirrorService
+}
+
+func newMirrorHandler(eoCtx eocontext.EoContext, service *mirrorService) (eocontext.CompleteHandler, error) {
+ handler := &proxyMirrorCompleteHandler{
+ orgComplete: eoCtx.GetComplete(),
+ service: service,
+ }
+
+ return handler, nil
+}
+
+func (p *proxyMirrorCompleteHandler) Complete(ctx eocontext.EoContext) error {
+ cloneCtx, err := ctx.Clone()
+
+ //先执行原始Complete, 再执行镜像请求的Complete
+ orgErr := p.orgComplete.Complete(ctx)
+
+ if err != nil {
+ log.Warn(err)
+ return orgErr
+ }
+
+ cloneCtx.SetApp(p.service)
+ cloneCtx.SetBalance(p.service)
+ cloneCtx.SetUpstreamHostHandler(p.service)
+
+ go func() {
+ err = p.orgComplete.Complete(cloneCtx)
+ if err != nil {
+ log.Error(err)
+ }
+ }()
+
+ return orgErr
+}
diff --git a/drivers/plugins/proxy-mirror/mirror-service.go b/drivers/plugins/proxy-mirror/mirror-service.go
new file mode 100644
index 00000000..593335e6
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/mirror-service.go
@@ -0,0 +1,83 @@
+package proxy_mirror
+
+import (
+ "errors"
+ "fmt"
+ "github.com/eolinker/apinto/discovery"
+ "github.com/eolinker/eosc/eocontext"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var (
+ errNoValidNode = errors.New("no valid node")
+)
+
+type mirrorService struct {
+ scheme string
+ passHost eocontext.PassHostMod
+ host string
+ timeout time.Duration
+ nodes []eocontext.INode
+}
+
+func newMirrorService(target, passHost, host string, timeout time.Duration) *mirrorService {
+ labels := map[string]string{}
+
+ idx := strings.Index(target, "://")
+ scheme := target[:idx]
+ addr := target[idx+3:]
+
+ idx = strings.Index(addr, ":")
+ ip := addr
+ port := 0
+ if idx > 0 {
+ ip = addr[:idx]
+ portStr := addr[idx+1:]
+ port, _ = strconv.Atoi(portStr)
+ }
+
+ inode := discovery.NewNode(labels, fmt.Sprintf("%s:%d", ip, port), ip, port)
+
+ var mode eocontext.PassHostMod
+ switch passHost {
+ case modePass:
+ mode = eocontext.PassHost
+ case modeNode:
+ mode = eocontext.NodeHost
+ case modeRewrite:
+ mode = eocontext.ReWriteHost
+ }
+
+ return &mirrorService{
+ scheme: scheme,
+ passHost: mode,
+ host: host,
+ timeout: timeout,
+ nodes: []eocontext.INode{inode},
+ }
+}
+
+func (m *mirrorService) Nodes() []eocontext.INode {
+ return m.nodes
+}
+
+func (m *mirrorService) Scheme() string {
+ return m.scheme
+}
+
+func (m *mirrorService) TimeOut() time.Duration {
+ return m.timeout
+}
+
+func (m *mirrorService) PassHost() (eocontext.PassHostMod, string) {
+ return m.passHost, m.host
+}
+
+func (m *mirrorService) Select(ctx eocontext.EoContext) (eocontext.INode, error) {
+ if len(m.nodes) < 1 {
+ return nil, errNoValidNode
+ }
+ return m.nodes[0], nil
+}
diff --git a/drivers/plugins/proxy-mirror/proxy-mirror.go b/drivers/plugins/proxy-mirror/proxy-mirror.go
new file mode 100644
index 00000000..dd41c419
--- /dev/null
+++ b/drivers/plugins/proxy-mirror/proxy-mirror.go
@@ -0,0 +1,78 @@
+package proxy_mirror
+
+import (
+ "github.com/eolinker/apinto/drivers"
+ "github.com/eolinker/eosc"
+ "github.com/eolinker/eosc/eocontext"
+ http_service "github.com/eolinker/eosc/eocontext/http-context"
+ "github.com/eolinker/eosc/log"
+ "math/rand"
+ "time"
+)
+
+var _ eocontext.IFilter = (*proxyMirror)(nil)
+
+type proxyMirror struct {
+ drivers.WorkerBase
+ randomRange int
+ randomPivot int
+ service *mirrorService
+}
+
+func (p *proxyMirror) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) error {
+ //进行采样, 生成随机数判断
+ rand.Seed(time.Now().UnixNano())
+ randomNum := rand.Intn(p.randomRange + 1) //[0,range]范围内整型
+ if randomNum <= p.randomPivot { //若随机数在[0,pivot]范围内则进行转发
+ setMirrorProxy(p.service, ctx)
+ }
+
+ if next != nil {
+ return next.DoChain(ctx)
+ }
+
+ return nil
+}
+
+func setMirrorProxy(service *mirrorService, ctx eocontext.EoContext) {
+ //先判断当前Ctx是否能Copy
+ if !ctx.IsCloneable() {
+ log.Info(errUnsupportedContextType)
+ return
+ }
+ //给ctx设置新的FinishHandler
+ newCompleteHandler, err := newMirrorHandler(ctx, service)
+ if err != nil {
+ log.Info(err)
+ return
+ }
+ ctx.SetCompleteHandler(newCompleteHandler)
+}
+
+func (p *proxyMirror) Start() error {
+ return nil
+}
+
+func (p *proxyMirror) Reset(v interface{}, workers map[eosc.RequireId]eosc.IWorker) error {
+ conf, err := check(v)
+ if err != nil {
+ return err
+ }
+
+ p.service = newMirrorService(conf.Addr, conf.PassHost, conf.Host, time.Duration(conf.Timeout))
+ p.randomRange = conf.SampleConf.RandomRange
+ p.randomPivot = conf.SampleConf.RandomPivot
+
+ return nil
+}
+
+func (p *proxyMirror) Stop() error {
+ return nil
+}
+
+func (p *proxyMirror) Destroy() {
+}
+
+func (p *proxyMirror) CheckSkill(skill string) bool {
+ return http_service.FilterSkillName == skill
+}
diff --git a/drivers/plugins/response-rewrite/response-rewrite.go b/drivers/plugins/response-rewrite/response-rewrite.go
index 9d30e288..34dda3eb 100644
--- a/drivers/plugins/response-rewrite/response-rewrite.go
+++ b/drivers/plugins/response-rewrite/response-rewrite.go
@@ -4,6 +4,7 @@ import (
"github.com/eolinker/apinto/drivers"
"github.com/eolinker/apinto/utils"
"github.com/eolinker/eosc/eocontext"
+ "github.com/eolinker/eosc/log"
"strconv"
"github.com/eolinker/eosc"
@@ -66,9 +67,12 @@ func (r *ResponseRewrite) CheckSkill(skill string) bool {
return http_service.FilterSkillName == skill
}
-func (r *ResponseRewrite) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) (err error) {
+func (r *ResponseRewrite) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) error {
if next != nil {
- err = next.DoChain(ctx)
+ err := next.DoChain(ctx)
+ if err != nil {
+ log.Error(err)
+ }
}
return r.rewrite(ctx)
diff --git a/drivers/router/dubbo2-router/driver.go b/drivers/router/dubbo2-router/driver.go
index fdb74cf7..58f7a1c4 100644
--- a/drivers/router/dubbo2-router/driver.go
+++ b/drivers/router/dubbo2-router/driver.go
@@ -2,6 +2,8 @@ package dubbo2_router
import (
"fmt"
+ "sync"
+
"github.com/eolinker/apinto/drivers/router/dubbo2-router/manager"
"github.com/eolinker/apinto/plugin"
"github.com/eolinker/apinto/service"
@@ -9,7 +11,6 @@ import (
"github.com/eolinker/eosc"
"github.com/eolinker/eosc/log"
"github.com/eolinker/eosc/utils/config"
- "sync"
)
var (
diff --git a/drivers/router/http-router/config.go b/drivers/router/http-router/config.go
index 24682974..7441ab64 100644
--- a/drivers/router/http-router/config.go
+++ b/drivers/router/http-router/config.go
@@ -6,19 +6,24 @@ import (
)
type Config struct {
- Listen int `json:"listen" yaml:"listen" title:"port" description:"使用端口" default:"80" label:"端口号" maximum:"65535"`
- Method []string `json:"method" yaml:"method" enum:"GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS" label:"请求方式"`
- Host []string `json:"host" yaml:"host" label:"域名"`
- Path string `json:"location"`
- Rules []Rule `json:"rules" yaml:"rules" label:"路由规则"`
- Service eosc.RequireId `json:"service" yaml:"service" skill:"github.com/eolinker/apinto/service.service.IService" required:"true" label:"目标服务"`
+ Listen int `json:"listen" yaml:"listen" title:"port" description:"使用端口" default:"80" label:"端口号" maximum:"65535"`
+ Method []string `json:"method" yaml:"method" enum:"GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS" label:"请求方式"`
+ Host []string `json:"host" yaml:"host" label:"域名"`
+ Path string `json:"location"`
+ Rules []Rule `json:"rules" yaml:"rules" label:"路由规则"`
+ Service eosc.RequireId `json:"service" yaml:"service" skill:"github.com/eolinker/apinto/service.service.IService" required:"false" empty_label:"使用匿名服务" label:"目标服务"`
+
+ Status int `json:"status" yaml:"status" label:"响应状态码" switch:"service===''" default:"200" maximum:"1000" minimum:"100"`
+ Header map[string]string `json:"header" yaml:"header" label:"响应头部" switch:"service===''"`
+ Body string `json:"body" yaml:"status" format:"text" label:"响应Body" switch:"service===''"`
+
Template eosc.RequireId `json:"template" yaml:"template" skill:"github.com/eolinker/apinto/template.template.ITemplate" required:"false" label:"插件模版"`
- Websocket bool `json:"websocket" yaml:"websocket" label:"Websocket"`
+ Websocket bool `json:"websocket" yaml:"websocket" label:"Websocket" switch:"service!==''"`
Disable bool `json:"disable" yaml:"disable" label:"禁用路由"`
Plugins plugin.Plugins `json:"plugins" yaml:"plugins" label:"插件配置"`
- Retry int `json:"retry" label:"重试次数" yaml:"retry"`
- TimeOut int `json:"time_out" label:"超时时间"`
+ Retry int `json:"retry" label:"重试次数" yaml:"retry" switch:"service!==''"`
+ TimeOut int `json:"time_out" label:"超时时间" switch:"service!==''"`
}
// Rule 规则
diff --git a/drivers/router/http-router/driver.go b/drivers/router/http-router/driver.go
index ebff50aa..83d6f0b9 100644
--- a/drivers/router/http-router/driver.go
+++ b/drivers/router/http-router/driver.go
@@ -2,13 +2,14 @@ package http_router
import (
"fmt"
+ "sync"
+
"github.com/eolinker/apinto/drivers/router/http-router/manager"
"github.com/eolinker/apinto/plugin"
"github.com/eolinker/apinto/service"
"github.com/eolinker/apinto/template"
"github.com/eolinker/eosc/log"
"github.com/eolinker/eosc/utils/config"
- "sync"
"github.com/eolinker/eosc"
)
@@ -50,14 +51,16 @@ func check(v interface{}, workers map[eosc.RequireId]eosc.IWorker) (*Config, ser
if !ok {
return nil, nil, nil, fmt.Errorf("get %s but %s %w", config.TypeNameOf(v), config.TypeNameOf(new(Config)), eosc.ErrorRequire)
}
+ var target service.IService
ser, has := workers[conf.Service]
- if !has {
- return nil, nil, nil, fmt.Errorf("target %s: %w", conf.Service, eosc.ErrorRequire)
- }
- target, ok := ser.(service.IService)
- if !ok {
- return nil, nil, nil, fmt.Errorf("target name: %s type of %s,target %w", conf.Service, config.TypeNameOf(ser), eosc.ErrorNotGetSillForRequire)
+ if has {
+ target, ok = ser.(service.IService)
+ if !ok {
+ return nil, nil, nil, fmt.Errorf("target name: %s type of %s,target %w", conf.Service, config.TypeNameOf(ser), eosc.ErrorNotGetSillForRequire)
+ }
+ //return nil, nil, nil, fmt.Errorf("target %s: %w", conf.Service, eosc.ErrorRequire)
}
+
var tmp template.ITemplate
if conf.Template != "" {
tp, has := workers[conf.Template]
diff --git a/drivers/router/http-router/http-complete/complate.go b/drivers/router/http-router/http-complete/complete.go
similarity index 55%
rename from drivers/router/http-router/http-complete/complate.go
rename to drivers/router/http-router/http-complete/complete.go
index 820fcbd7..154ba946 100644
--- a/drivers/router/http-router/http-complete/complate.go
+++ b/drivers/router/http-router/http-complete/complete.go
@@ -3,6 +3,7 @@ package http_complete
import (
"errors"
"fmt"
+ "strconv"
"strings"
"time"
@@ -16,12 +17,10 @@ var (
)
type HttpComplete struct {
- retry int
- timeOut time.Duration
}
-func NewHttpComplete(retry int, timeOut time.Duration) *HttpComplete {
- return &HttpComplete{retry: retry, timeOut: timeOut}
+func NewHttpComplete() *HttpComplete {
+ return &HttpComplete{}
}
func (h *HttpComplete) Complete(org eocontext.EoContext) error {
@@ -55,9 +54,22 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error {
}
timeOut := app.TimeOut()
- for index := 0; index <= h.retry; index++ {
- if h.timeOut > 0 && time.Now().Sub(proxyTime) > h.timeOut {
+ retryValue := ctx.Value(http_service.KeyHttpRetry)
+ retry, ok := retryValue.(int)
+ if !ok {
+ retry = 1
+ }
+
+ timeoutValue := ctx.Value(http_service.KeyHttpTimeout)
+ timeout, ok := timeoutValue.(time.Duration)
+ if !ok {
+ timeout = 3000 * time.Millisecond
+ }
+
+ for index := 0; index <= retry; index++ {
+
+ if timeout > 0 && time.Now().Sub(proxyTime) > timeout {
return ErrorTimeoutComplete
}
node, err := balance.Select(ctx)
@@ -80,6 +92,40 @@ func (h *HttpComplete) Complete(org eocontext.EoContext) error {
return lastErr
}
+type NoServiceCompleteHandler struct {
+ status int
+ header map[string]string
+ body string
+}
+
+func NewNoServiceCompleteHandler(status int, header map[string]string, body string) *NoServiceCompleteHandler {
+ return &NoServiceCompleteHandler{status: status, header: header, body: body}
+}
+
+func (n *NoServiceCompleteHandler) Complete(org eocontext.EoContext) error {
+ ctx, err := http_service.Assert(org)
+ if err != nil {
+ return err
+ }
+ //设置响应开始时间
+ proxyTime := time.Now()
+
+ defer func() {
+ //设置原始响应状态码
+ ctx.Response().SetProxyStatus(ctx.Response().StatusCode(), "")
+ //设置上游响应总时间, 单位为毫秒
+ //ctx.WithValue("response_time", time.Now().Sub(proxyTime).Milliseconds())
+ ctx.Response().SetResponseTime(time.Now().Sub(proxyTime))
+ ctx.SetLabel("handler", "proxy")
+ }()
+ for key, value := range n.header {
+ ctx.Response().SetHeader(key, value)
+ }
+ ctx.Response().SetBody([]byte(n.body))
+ ctx.Response().SetStatus(n.status, strconv.Itoa(n.status))
+ return nil
+}
+
type httpCompleteCaller struct {
}
diff --git a/drivers/router/http-router/http-handler.go b/drivers/router/http-router/http-handler.go
index bc103c20..fb6559d0 100644
--- a/drivers/router/http-router/http-handler.go
+++ b/drivers/router/http-router/http-handler.go
@@ -2,6 +2,7 @@ package http_router
import (
"net/http"
+ "time"
http_service "github.com/eolinker/apinto/node/http-context"
@@ -26,10 +27,12 @@ type httpHandler struct {
filters eocontext.IChainPro
disable bool
websocket bool
+
+ retry int
+ timeout time.Duration
}
func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) {
-
httpContext, err := http_context.Assert(ctx)
if err != nil {
return
@@ -50,12 +53,18 @@ func (h *httpHandler) ServeHTTP(ctx eocontext.EoContext) {
}
ctx = wsCtx
}
+ //set retry timeout
+ ctx.WithValue(http_context.KeyHttpRetry, h.retry)
+ ctx.WithValue(http_context.KeyHttpTimeout, h.timeout)
//Set Label
ctx.SetLabel("api", h.routerName)
ctx.SetLabel("api_id", h.routerId)
ctx.SetLabel("service", h.serviceName)
- ctx.SetLabel("service_id", h.service.Id())
+ if h.service != nil {
+ ctx.SetLabel("service_id", h.service.Id())
+ }
+
ctx.SetLabel("ip", httpContext.Request().ReadIP())
ctx.SetCompleteHandler(h.completeHandler)
diff --git a/drivers/router/http-router/router.go b/drivers/router/http-router/router.go
index 86477654..2618983d 100644
--- a/drivers/router/http-router/router.go
+++ b/drivers/router/http-router/router.go
@@ -5,20 +5,19 @@ import (
"strings"
"time"
- "github.com/eolinker/apinto/drivers"
- http_router "github.com/eolinker/apinto/router"
-
"github.com/eolinker/apinto/drivers/router/http-router/websocket"
- http_complete "github.com/eolinker/apinto/drivers/router/http-router/http-complete"
+ "github.com/eolinker/apinto/service"
+ "github.com/eolinker/eosc/eocontext"
+
+ "github.com/eolinker/apinto/drivers"
+ http_complete "github.com/eolinker/apinto/drivers/router/http-router/http-complete"
"github.com/eolinker/apinto/drivers/router/http-router/manager"
"github.com/eolinker/apinto/plugin"
- "github.com/eolinker/apinto/service"
+ http_router "github.com/eolinker/apinto/router"
"github.com/eolinker/apinto/template"
-
"github.com/eolinker/eosc"
- "github.com/eolinker/eosc/eocontext"
)
type HttpRouter struct {
@@ -55,24 +54,18 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker)
methods := cfg.Method
handler := &httpHandler{
- routerName: h.name,
- routerId: h.id,
- serviceName: strings.TrimSuffix(string(cfg.Service), "@service"),
- completeHandler: http_complete.NewHttpComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond),
- finisher: defaultFinisher,
- service: nil,
- filters: nil,
- disable: cfg.Disable,
- websocket: cfg.Websocket,
+ routerName: h.name,
+ routerId: h.id,
+ serviceName: strings.TrimSuffix(string(cfg.Service), "@service"),
+ finisher: defaultFinisher,
+ disable: cfg.Disable,
+ websocket: cfg.Websocket,
+ retry: cfg.Retry,
+ timeout: time.Duration(cfg.TimeOut) * time.Millisecond,
}
if !cfg.Disable {
- serviceWorker, has := workers[cfg.Service]
- if !has || !serviceWorker.CheckSkill(service.ServiceSkill) {
- return eosc.ErrorNotGetSillForRequire
- }
-
if cfg.Plugins == nil {
cfg.Plugins = map[string]*plugin.Config{}
}
@@ -87,16 +80,24 @@ func (h *HttpRouter) reset(cfg *Config, workers map[eosc.RequireId]eosc.IWorker)
} else {
plugins = h.pluginManager.CreateRequest(h.id, cfg.Plugins)
}
-
- serviceHandler := serviceWorker.(service.IService)
-
- handler.service = serviceHandler
handler.filters = plugins
- if cfg.Websocket {
- handler.completeHandler = websocket.NewComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond)
- methods = []string{http.MethodGet}
- //handler.finisher = &websocket.Finisher{}
+ if cfg.Service == "" {
+ // 当service未指定,使用默认返回
+ handler.completeHandler = http_complete.NewNoServiceCompleteHandler(cfg.Status, cfg.Header, cfg.Body)
+ } else {
+ serviceWorker, has := workers[cfg.Service]
+ if !has || !serviceWorker.CheckSkill(service.ServiceSkill) {
+ return eosc.ErrorNotGetSillForRequire
+ }
+ serviceHandler := serviceWorker.(service.IService)
+ handler.service = serviceHandler
+ if cfg.Websocket {
+ handler.completeHandler = websocket.NewComplete(cfg.Retry, time.Duration(cfg.TimeOut)*time.Millisecond)
+ methods = []string{http.MethodGet}
+ } else {
+ handler.completeHandler = http_complete.NewHttpComplete()
+ }
}
}
diff --git a/example/grpc/server/server.go b/example/grpc/server/server.go
index 3a74e116..ed64c5b2 100644
--- a/example/grpc/server/server.go
+++ b/example/grpc/server/server.go
@@ -36,11 +36,6 @@ func (s *Server) Hello(ctx context.Context, request *service.HelloRequest) (*ser
}, nil
}
-type Request struct {
- Name string
- err error
-}
-
func (s *Server) StreamRequest(server service.Hello_StreamRequestServer) error {
trailingMD, ok := metadata.FromIncomingContext(server.Context())
if ok {
diff --git a/go.mod b/go.mod
index a61656fc..27a8e183 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,10 @@ go 1.19
require (
github.com/Shopify/sarama v1.32.0
+ github.com/brianvoe/gofakeit/v6 v6.20.1
github.com/coocood/freecache v1.2.2
github.com/dubbogo/gost v1.13.1
- github.com/eolinker/eosc v0.10.1
+ github.com/eolinker/eosc v0.11.0
github.com/fasthttp/websocket v1.5.0
github.com/fullstorydev/grpcurl v1.8.7
github.com/go-redis/redis/v8 v8.11.5
diff --git a/node/dubbo2-context/context.go b/node/dubbo2-context/context.go
index 82ac55db..29fc996f 100644
--- a/node/dubbo2-context/context.go
+++ b/node/dubbo2-context/context.go
@@ -290,6 +290,15 @@ func (d *DubboContext) LocalPort() int {
return d.port
}
+func (d *DubboContext) IsCloneable() bool {
+ return false
+}
+
+func (d *DubboContext) Clone() (eocontext.EoContext, error) {
+ //TODO
+ return nil, fmt.Errorf("%s %w", "DubboContext", eocontext.ErrEoCtxUnCloneable)
+}
+
func addrToIP(addr net.Addr) net.IP {
x, ok := addr.(*net.TCPAddr)
if !ok {
diff --git a/node/grpc-context/context.go b/node/grpc-context/context.go
index 90432287..99644c45 100644
--- a/node/grpc-context/context.go
+++ b/node/grpc-context/context.go
@@ -260,3 +260,12 @@ func (c *Context) reset() {
pool.Put(c)
}
+
+func (c *Context) IsCloneable() bool {
+ return false
+}
+
+func (c *Context) Clone() (eocontext.EoContext, error) {
+ //TODO
+ return nil, fmt.Errorf("%s %w", "GrpcContext", eocontext.ErrEoCtxUnCloneable)
+}
diff --git a/node/http-context/body.go b/node/http-context/body.go
index 2785da4c..6b95d856 100644
--- a/node/http-context/body.go
+++ b/node/http-context/body.go
@@ -2,13 +2,12 @@ package http_context
import (
"bytes"
+ "io"
"strings"
http_context "github.com/eolinker/eosc/eocontext/http-context"
"github.com/valyala/fasthttp"
- "io/ioutil"
-
"mime"
"mime/multipart"
"net/url"
@@ -227,7 +226,7 @@ func (b *BodyRequestHandler) resetFile() error {
return err
}
- data, err := ioutil.ReadAll(fio)
+ data, err := io.ReadAll(fio)
if err != nil {
fio.Close()
return err
@@ -288,6 +287,5 @@ func (b *BodyRequestHandler) SetForm(values url.Values) error {
func (b *BodyRequestHandler) SetRaw(contentType string, body []byte) {
b.request.SetBodyRaw(body)
b.request.Header.SetContentType(contentType)
- return
}
diff --git a/node/http-context/clone.go b/node/http-context/clone.go
new file mode 100644
index 00000000..4b06ba85
--- /dev/null
+++ b/node/http-context/clone.go
@@ -0,0 +1,201 @@
+package http_context
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "time"
+
+ "github.com/eolinker/eosc/utils/config"
+
+ fasthttp_client "github.com/eolinker/apinto/node/fasthttp-client"
+
+ eoscContext "github.com/eolinker/eosc/eocontext"
+ http_service "github.com/eolinker/eosc/eocontext/http-context"
+)
+
+var _ http_service.IHttpContext = (*cloneContext)(nil)
+
+// HttpContext fasthttpRequestCtx
+type cloneContext struct {
+ org *HttpContext
+ proxyRequest ProxyRequest
+ response Response
+ proxyRequests []http_service.IProxy
+ ctx context.Context
+ completeHandler eoscContext.CompleteHandler
+ finishHandler eoscContext.FinishHandler
+ app eoscContext.EoApp
+ balance eoscContext.BalanceHandler
+ upstreamHostHandler eoscContext.UpstreamHostHandler
+ labels map[string]string
+
+ responseError error
+}
+
+func (ctx *cloneContext) GetUpstreamHostHandler() eoscContext.UpstreamHostHandler {
+ return ctx.upstreamHostHandler
+}
+
+func (ctx *cloneContext) SetUpstreamHostHandler(handler eoscContext.UpstreamHostHandler) {
+ ctx.upstreamHostHandler = handler
+}
+
+func (ctx *cloneContext) LocalIP() net.IP {
+ return ctx.org.LocalIP()
+}
+
+func (ctx *cloneContext) LocalAddr() net.Addr {
+ return ctx.org.LocalAddr()
+}
+
+func (ctx *cloneContext) LocalPort() int {
+ return ctx.org.LocalPort()
+}
+
+func (ctx *cloneContext) GetApp() eoscContext.EoApp {
+ return ctx.app
+}
+
+func (ctx *cloneContext) SetApp(app eoscContext.EoApp) {
+ ctx.app = app
+}
+
+func (ctx *cloneContext) GetBalance() eoscContext.BalanceHandler {
+ return ctx.balance
+}
+
+func (ctx *cloneContext) SetBalance(handler eoscContext.BalanceHandler) {
+ ctx.balance = handler
+}
+
+func (ctx *cloneContext) SetLabel(name, value string) {
+ ctx.labels[name] = value
+}
+
+func (ctx *cloneContext) GetLabel(name string) string {
+ return ctx.labels[name]
+}
+
+func (ctx *cloneContext) Labels() map[string]string {
+ return ctx.labels
+}
+
+func (ctx *cloneContext) GetComplete() eoscContext.CompleteHandler {
+ return ctx.completeHandler
+}
+
+func (ctx *cloneContext) SetCompleteHandler(handler eoscContext.CompleteHandler) {
+ ctx.completeHandler = handler
+}
+
+func (ctx *cloneContext) GetFinish() eoscContext.FinishHandler {
+ return ctx.finishHandler
+}
+
+func (ctx *cloneContext) SetFinish(handler eoscContext.FinishHandler) {
+ ctx.finishHandler = handler
+}
+
+func (ctx *cloneContext) Scheme() string {
+ return ctx.org.Scheme()
+}
+
+func (ctx *cloneContext) Assert(i interface{}) error {
+ if v, ok := i.(*http_service.IHttpContext); ok {
+ *v = ctx
+ return nil
+ }
+ return fmt.Errorf("not suport:%s", config.TypeNameOf(i))
+}
+
+func (ctx *cloneContext) Proxies() []http_service.IProxy {
+ return ctx.proxyRequests
+}
+
+func (ctx *cloneContext) Response() http_service.IResponse {
+ return &ctx.response
+}
+
+func (ctx *cloneContext) SendTo(address string, timeout time.Duration) error {
+
+ scheme, host := readAddress(address)
+ request := ctx.proxyRequest.Request()
+
+ passHost, targetHost := ctx.GetUpstreamHostHandler().PassHost()
+ switch passHost {
+ case eoscContext.PassHost:
+ case eoscContext.NodeHost:
+ request.URI().SetHost(host)
+ case eoscContext.ReWriteHost:
+ request.URI().SetHost(targetHost)
+ }
+
+ beginTime := time.Now()
+ ctx.responseError = fasthttp_client.ProxyTimeout(address, request, ctx.response.Response, timeout)
+ agent := newRequestAgent(&ctx.proxyRequest, host, scheme, beginTime, time.Now())
+ if ctx.responseError != nil {
+ agent.setStatusCode(504)
+ } else {
+ agent.setStatusCode(ctx.response.Response.StatusCode())
+ }
+
+ agent.setResponseLength(ctx.response.Response.Header.ContentLength())
+
+ ctx.proxyRequests = append(ctx.proxyRequests, agent)
+ return ctx.responseError
+
+}
+
+func (ctx *cloneContext) Context() context.Context {
+
+ return ctx.ctx
+}
+
+func (ctx *cloneContext) AcceptTime() time.Time {
+ return ctx.org.AcceptTime()
+}
+
+func (ctx *cloneContext) Value(key interface{}) interface{} {
+ return ctx.ctx.Value(key)
+}
+
+func (ctx *cloneContext) WithValue(key, val interface{}) {
+ ctx.ctx = context.WithValue(ctx.Context(), key, val)
+}
+
+func (ctx *cloneContext) Proxy() http_service.IRequest {
+ return &ctx.proxyRequest
+}
+
+func (ctx *cloneContext) Request() http_service.IRequestReader {
+
+ return ctx.org.Request()
+}
+
+func (ctx *cloneContext) IsCloneable() bool {
+ return false
+}
+
+func (ctx *cloneContext) Clone() (eoscContext.EoContext, error) {
+ return nil, fmt.Errorf("%s %w", "HttpContext", eoscContext.ErrEoCtxUnCloneable)
+}
+
+// RequestId 请求ID
+func (ctx *cloneContext) RequestId() string {
+ return ctx.org.requestID
+}
+
+// Finish finish
+func (ctx *cloneContext) FastFinish() {
+
+ ctx.ctx = nil
+ ctx.app = nil
+ ctx.balance = nil
+ ctx.upstreamHostHandler = nil
+ ctx.finishHandler = nil
+ ctx.completeHandler = nil
+
+ ctx.proxyRequest.Finish()
+
+}
diff --git a/node/http-context/context.go b/node/http-context/context.go
index 0b9576b8..38f66891 100644
--- a/node/http-context/context.go
+++ b/node/http-context/context.go
@@ -13,7 +13,7 @@ import (
eoscContext "github.com/eolinker/eosc/eocontext"
http_service "github.com/eolinker/eosc/eocontext/http-context"
- uuid "github.com/google/uuid"
+ "github.com/google/uuid"
"github.com/valyala/fasthttp"
)
@@ -153,6 +153,7 @@ func (ctx *HttpContext) SendTo(address string, timeout time.Duration) error {
func (ctx *HttpContext) Context() context.Context {
if ctx.ctx == nil {
+
ctx.ctx = context.Background()
}
return ctx.ctx
@@ -179,6 +180,40 @@ func (ctx *HttpContext) Request() http_service.IRequestReader {
return &ctx.requestReader
}
+func (ctx *HttpContext) IsCloneable() bool {
+ return true
+}
+
+func (ctx *HttpContext) Clone() (eoscContext.EoContext, error) {
+ copyContext := copyPool.Get().(*cloneContext)
+ copyContext.org = ctx
+ copyContext.proxyRequests = make([]http_service.IProxy, 0, 2)
+
+ req := fasthttp.AcquireRequest()
+ ctx.fastHttpRequestCtx.Request.CopyTo(req)
+
+ resp := fasthttp.AcquireResponse()
+ ctx.fastHttpRequestCtx.Response.CopyTo(resp)
+
+ copyContext.proxyRequest.reset(req, ctx.requestReader.remoteAddr)
+ copyContext.response.reset(resp)
+
+ copyContext.completeHandler = ctx.completeHandler
+ copyContext.finishHandler = ctx.finishHandler
+
+ cloneLabels := make(map[string]string, len(ctx.labels))
+ for k, v := range ctx.labels {
+ cloneLabels[k] = v
+ }
+ copyContext.labels = cloneLabels
+
+ //记录请求时间
+ copyContext.ctx = context.WithValue(ctx.Context(), http_service.KeyCloneCtx, true)
+ copyContext.WithValue(http_service.KeyHttpRetry, 0)
+ copyContext.WithValue(http_service.KeyHttpTimeout, time.Duration(0))
+ return copyContext, nil
+}
+
// NewContext 创建Context
func NewContext(ctx *fasthttp.RequestCtx, port int) *HttpContext {
@@ -229,7 +264,7 @@ func (ctx *HttpContext) FastFinish() {
ctx.response.Finish()
ctx.fastHttpRequestCtx = nil
pool.Put(ctx)
- return
+
}
func NotFound(ctx *HttpContext) {
diff --git a/node/http-context/pool.go b/node/http-context/pool.go
index 4fdf83c9..b3346637 100644
--- a/node/http-context/pool.go
+++ b/node/http-context/pool.go
@@ -10,6 +10,9 @@ var (
pool = sync.Pool{
New: newContext,
}
+ copyPool = sync.Pool{
+ New: newCopyContext,
+ }
)
func newContext() interface{} {
@@ -17,3 +20,9 @@ func newContext() interface{} {
h.proxyRequests = make([]http_service.IProxy, 0, 5)
return h
}
+
+func newCopyContext() interface{} {
+ h := new(cloneContext)
+ h.proxyRequests = make([]http_service.IProxy, 0, 5)
+ return h
+}
diff --git a/node/http-context/proxy.go b/node/http-context/proxy.go
index e06cff7e..e4f8fb68 100644
--- a/node/http-context/proxy.go
+++ b/node/http-context/proxy.go
@@ -3,6 +3,7 @@ package http_context
import (
"bytes"
"fmt"
+ "github.com/eolinker/eosc/log"
http_service "github.com/eolinker/eosc/eocontext/http-context"
"github.com/valyala/fasthttp"
@@ -20,7 +21,10 @@ type ProxyRequest struct {
func (r *ProxyRequest) Finish() error {
fasthttp.ReleaseRequest(r.req)
- r.RequestReader.Finish()
+ err := r.RequestReader.Finish()
+ if err != nil {
+ log.Warn(err)
+ }
return nil
}
func (r *ProxyRequest) Header() http_service.IHeaderWriter {
diff --git a/node/http-context/websocket-context.go b/node/http-context/websocket-context.go
index 627c077c..1b7417f3 100644
--- a/node/http-context/websocket-context.go
+++ b/node/http-context/websocket-context.go
@@ -3,6 +3,7 @@ package http_context
import (
"errors"
"fmt"
+ eoscContext "github.com/eolinker/eosc/eocontext"
"io"
"net"
"sync"
@@ -79,3 +80,12 @@ func (w *WebsocketContext) Assert(i interface{}) error {
}
return fmt.Errorf("not suport:%s", config.TypeNameOf(i))
}
+
+func (w *WebsocketContext) IsCloneable() bool {
+ return false
+}
+
+func (w *WebsocketContext) Clone() (eoscContext.EoContext, error) {
+ //TODO
+ return nil, fmt.Errorf("%s %w", "WebsocketContext", eoscContext.ErrEoCtxUnCloneable)
+}
diff --git a/utils/aes_test.go b/utils/aes_test.go
index 9b6727a0..3a6325d2 100644
--- a/utils/aes_test.go
+++ b/utils/aes_test.go
@@ -1,14 +1,8 @@
package utils
import (
- "fmt"
"testing"
)
func TestAes(t *testing.T) {
- key := Md5("open-api")
- enValue := AES_CBC_Encrypt([]byte(Md5("Key123qaz:open-api")), []byte(key))
- deValue := AES_CBC_Decrypt(enValue, []byte(key))
- log.Debug(enValue)
- log.Debug(string(deValue))
}
diff --git a/utils/float.go b/utils/float.go
new file mode 100644
index 00000000..00b0a185
--- /dev/null
+++ b/utils/float.go
@@ -0,0 +1,7 @@
+package utils
+
+import "math/rand"
+
+func RandFloats(min, max float64) float64 {
+ return min + rand.Float64()*(max-min)
+}
diff --git a/utils/int.go b/utils/int.go
new file mode 100644
index 00000000..16acb4de
--- /dev/null
+++ b/utils/int.go
@@ -0,0 +1,10 @@
+package utils
+
+import "math/rand"
+
+func RandInt64(min, max int64) int64 {
+ if min >= max || min == 0 || max == 0 {
+ return max
+ }
+ return rand.Int63n(max-min) + min
+}
diff --git a/utils/json.go b/utils/json.go
index 050ed079..44ece525 100644
--- a/utils/json.go
+++ b/utils/json.go
@@ -2,12 +2,18 @@ package utils
import (
"encoding/json"
+ "errors"
"fmt"
+ "github.com/brianvoe/gofakeit/v6"
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
"github.com/robertkrimen/otto"
)
-//JSObjectToJSON 将js对象转为json
+// JSObjectToJSON 将js对象转为json
func JSObjectToJSON(s string) ([]byte, error) {
vm := otto.New()
v, err := vm.Run(fmt.Sprintf(`
@@ -20,7 +26,7 @@ func JSObjectToJSON(s string) ([]byte, error) {
return []byte(v.String()), nil
}
-//JSONUnmarshal 将json格式的s解码成v所需的json格式
+// JSONUnmarshal 将json格式的s解码成v所需的json格式
func JSONUnmarshal(s, v interface{}) error {
data, err := json.Marshal(s)
if err != nil {
@@ -28,3 +34,314 @@ func JSONUnmarshal(s, v interface{}) error {
}
return json.Unmarshal(data, v)
}
+
+// JsonSchemaMockJsUnmarshal
+// 解析mockJs生成的json schema 并根据规则生成随机值
+// 不是mockJs生成的json 走默认规则解析
+func JsonSchemaMockJsUnmarshal(valueMap interface{}) interface{} {
+ rand.Seed(time.Now().UnixMicro())
+ gofakeit.Seed(time.Now().UnixMicro())
+
+ value, vOk := valueMap.(map[string]interface{})
+ if vOk {
+ switch valType := value["properties"].(type) {
+ case []interface{}:
+ resultMap := make(map[string]interface{})
+ for _, v := range valType {
+ if m, ok := v.(map[string]interface{}); ok {
+ name, nameOk := m["name"].(string)
+ if !nameOk {
+ return jsonSchemaFormat
+ }
+ template := m["template"]
+ if t, tOk := m["type"].(string); tOk {
+ rule, ruleOk := m["rule"].(map[string]interface{})
+ if !ruleOk || len(rule) == 0 {
+ if template != nil {
+ resultMap[name] = mockConstant(template)
+ continue
+ }
+ }
+
+ switch t {
+ case "string":
+
+ minVal, maxVal, _, _, err := getMinMaxDminDmax(rule)
+ if err != nil {
+ return err
+ }
+
+ randomNum := 0
+ if minVal > 0 && maxVal == 0 {
+ randomNum = int(minVal)
+ }
+
+ if minVal > 0 && maxVal > 0 {
+ randomNum = int(RandInt64(int64(minVal), int64(maxVal)))
+ }
+
+ if template != nil {
+ templateStr, sOk := template.(string)
+ if !sOk {
+ return jsonSchemaFormat
+ }
+ temp := ""
+ for i := 0; i < randomNum; i++ {
+ temp = temp + templateStr
+ }
+ resultMap[name] = temp
+ continue
+ }
+
+ case "number":
+ minVal, maxVal, dminVal, dmaxVal, err := getMinMaxDminDmax(rule)
+ if err != nil {
+ return err
+ }
+
+ randomValue := 0.0
+ if minVal > 0.0 && maxVal == 0.0 {
+ randomValue = minVal
+ randomValue += RandFloats(0, 1)
+ } else if minVal > 0.0 && maxVal > 0.0 {
+ randomValue = RandFloats(minVal, maxVal)
+ }
+
+ if randomValue == 0.0 {
+ resultMap[name] = template
+ continue
+ }
+
+ if dminVal > 0.0 && dmaxVal == 0.0 {
+ randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', int(dminVal), 64), 64)
+ } else if dminVal > 0.0 && dmaxVal > 0.0 {
+ floats := RandFloats(dminVal, dmaxVal)
+ randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', int(floats), 64), 64)
+ } else {
+ randomValue, _ = strconv.ParseFloat(strconv.FormatFloat(randomValue, 'f', 0, 64), 64)
+ }
+
+ resultMap[name] = randomValue
+
+ case "boolean":
+ resultMap[name] = gofakeit.Bool()
+ case "object":
+ templateMap, templateOk := template.(map[string]interface{})
+ if templateOk {
+ minVal, maxVal, _, _, err := getMinMaxDminDmax(rule)
+ if err != nil {
+ return err
+ }
+ randomNum := 0
+ if minVal > 0 && maxVal == 0 {
+ randomNum = int(minVal)
+ }
+
+ if minVal > 0 && maxVal > 0 {
+ randomNum = int(RandInt64(int64(minVal), int64(maxVal)))
+ }
+ tempMap := make(map[string]interface{})
+ i := 1
+ for key, val := range templateMap {
+
+ split := strings.Split(key, "|")
+ tempMap[split[0]] = mockConstant(val)
+
+ if i == randomNum {
+ break
+ }
+ i++
+ }
+
+ resultMap[name] = tempMap
+
+ }
+ case "array":
+ templateList, templateOk := template.([]interface{})
+ if templateOk {
+ minVal, maxVal, _, _, err := getMinMaxDminDmax(rule)
+ if err != nil {
+ return err
+ }
+
+ randomNum := 0
+ if minVal > 0.0 && maxVal == 0.0 {
+ randomNum = int(minVal)
+ }
+
+ if minVal > 0.0 && maxVal > 0.0 {
+ randomNum = int(RandInt64(int64(minVal), int64(maxVal)))
+ }
+
+ if randomNum == 1 {
+ if len(templateList) > 1 {
+ resultMap[name] = templateList[rand.Intn(len(templateList))]
+ continue
+ }
+ switch templateVal := templateList[0].(type) {
+ case map[string]interface{}:
+ tempMap := make(map[string]interface{})
+ for key, val := range templateVal {
+ split := strings.Split(key, "|")
+ tempMap[split[0]] = mockConstant(val)
+ }
+ resultMap[name] = tempMap
+ default:
+ resultMap[name] = templateVal
+ }
+
+ continue
+ }
+
+ tempList := make([]interface{}, 0)
+
+ for i := 0; i < randomNum; i++ {
+
+ for _, templateType := range templateList {
+ switch templateVal := templateType.(type) {
+ case map[string]interface{}:
+ tempMap := make(map[string]interface{})
+ for key, val := range templateVal {
+ split := strings.Split(key, "|")
+ tempMap[split[0]] = mockConstant(val)
+ }
+ tempList = append(tempList, tempMap)
+ default:
+ tempList = append(tempList, templateType)
+ }
+ }
+
+ }
+
+ resultMap[name] = tempList
+
+ }
+ }
+ }
+ }
+ }
+ return resultMap
+ }
+ }
+ return jsonSchemaUnmarshal(value)
+}
+
+func mockConstant(v interface{}) interface{} {
+ if templateStr, ok := v.(string); ok {
+ templateStr = strings.ToLower(templateStr)
+ switch templateStr {
+ case "@cname":
+ return gofakeit.Username()
+ case "@cfirst":
+ return gofakeit.FirstName()
+ case "@clast":
+ return gofakeit.LastName()
+ case "@name", "@name(true)":
+ return gofakeit.Name()
+ case "@first":
+ return gofakeit.FirstName()
+ case "@last":
+ return gofakeit.LastName()
+ case "@email":
+ return gofakeit.Email()
+ case "@ip":
+ return gofakeit.IPv4Address()
+ case "@zip":
+ return gofakeit.Address().Zip
+ case "@city", "@city(true)":
+ return gofakeit.Address().Address
+ case "@url":
+ return gofakeit.URL()
+ default:
+ return v
+ }
+ }
+ return v
+}
+
+var jsonSchemaFormat = errors.New("json schema format err")
+
+func getMinMaxDminDmax(rule map[string]interface{}) (float64, float64, float64, float64, error) {
+ minVal := 0.0
+ min, minOk := rule["min"]
+ if minOk && min != nil {
+ mOk := false
+ minVal, mOk = min.(float64)
+ if !mOk {
+ return 0, 0, 0, 0, jsonSchemaFormat
+ }
+
+ }
+
+ maxVal := 0.0
+ max, maxOk := rule["max"]
+ if maxOk && max != nil {
+ mOk := false
+ maxVal, mOk = max.(float64)
+ if !mOk {
+ return 0, 0, 0, 0, jsonSchemaFormat
+ }
+ }
+ dminVal := 0.0
+ dmin, dminOk := rule["dmin"]
+ if dminOk && dmin != nil {
+ mOk := false
+ dminVal, mOk = dmin.(float64)
+ if !mOk {
+ return 0, 0, 0, 0, jsonSchemaFormat
+ }
+ }
+
+ dmaxVal := 0.0
+ dmax, dmaxOk := rule["dmax"]
+ if dmaxOk && dmax != nil {
+ mOk := false
+ dmaxVal, mOk = dmax.(float64)
+ if !mOk {
+ return 0, 0, 0, 0, jsonSchemaFormat
+ }
+ }
+ return minVal, maxVal, dminVal, dmaxVal, nil
+}
+
+func jsonSchemaUnmarshal(properties interface{}) interface{} {
+ propertiesMap, ok := properties.(map[string]interface{})
+ if !ok {
+ return jsonSchemaFormat
+ }
+ if val, ok := propertiesMap["example"]; ok {
+ return val
+ } else {
+ if t, tOk := propertiesMap["type"].(string); tOk {
+ switch t {
+ case "string":
+ return gofakeit.LetterN(10)
+ case "number":
+ return gofakeit.Float64()
+ case "integer":
+ return gofakeit.Int64()
+ case "boolean":
+ return gofakeit.Bool()
+ case "object":
+ propertiesMaps, pOk := propertiesMap["properties"].(map[string]interface{})
+ if !pOk {
+ return jsonSchemaFormat
+ }
+ resultMap := make(map[string]interface{})
+ for key, vProperties := range propertiesMaps {
+ resultMap[key] = jsonSchemaUnmarshal(vProperties)
+ }
+ return resultMap
+ case "array":
+ items, iOk := propertiesMap["items"].(map[string]interface{})
+ if !iOk {
+ return jsonSchemaFormat
+ }
+ resultList := make([]interface{}, 0)
+ resultList = append(resultList, jsonSchemaUnmarshal(items))
+ return resultList
+ }
+ }
+ return jsonSchemaFormat
+ }
+}
diff --git a/utils/json_test.go b/utils/json_test.go
new file mode 100644
index 00000000..2f2121b7
--- /dev/null
+++ b/utils/json_test.go
@@ -0,0 +1,1503 @@
+package utils
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+// 官方格式json Schema
+var test1 = `
+{
+ "properties":{
+ "field0":{
+ "example":"abcd",
+ "type":"string"
+ },
+ "field1":{
+ "example":123.12,
+ "type":"number"
+ },
+ "field3":{
+ "properties":{
+ "field3_1":{
+ "type":"string"
+ },
+ "field3_2":{
+ "properties":{
+ "field3_2_1":{
+ "example":true,
+ "type":"boolean"
+ },
+ "field3_2_2":{
+ "items":{
+ "example":155.55,
+ "type":"integer"
+ },
+ "type":"array"
+ }
+ },
+ "type":"object"
+ }
+ },
+ "type":"object"
+ },
+ "field2":{
+ "items":{
+ "type":"string"
+ },
+ "type":"array"
+ }
+ },
+ "type":"object"
+}`
+
+/*
+mockJs template
+
+ var template = {
+ 'name': '@cname', // 生成中文名字
+ 'age|18-30': 20, // 生成18~30之间的随机整数
+ 'gender|1': ['男', '女'], // 从数组中随机选取一个元素
+ 'email': '@email' // 生成邮箱
+ }
+
+Mock.toJSONSchema(template)生成的json schema
+*/
+var mock1 = `{
+ "template": {
+ "name": "@cname",
+ "age|18-30": 20,
+ "gender|1": [
+ "男",
+ "女"
+ ],
+ "email": "@email"
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "name",
+ "template": "@cname",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "name"
+ ]
+ },
+ {
+ "name": "age",
+ "template": 20,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "age|18-30",
+ "age",
+ null,
+ "18-30",
+ null
+ ],
+ "range": [
+ "18-30",
+ "18",
+ "30"
+ ],
+ "min": 18,
+ "max": 30,
+ "count": 22
+ },
+ "path": [
+ "ROOT",
+ "age"
+ ]
+ },
+ {
+ "name": "gender",
+ "template": [
+ "男",
+ "女"
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "gender|1",
+ "gender",
+ null,
+ "1",
+ null
+ ],
+ "range": [
+ "1",
+ "1",
+ null
+ ],
+ "min": 1,
+ "count": 1
+ },
+ "path": [
+ "ROOT",
+ "gender"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": "男",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "gender",
+ 0
+ ]
+ },
+ {
+ "name": 1,
+ "template": "女",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "gender",
+ 1
+ ]
+ }
+ ]
+ },
+ {
+ "name": "email",
+ "template": "@email",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "email"
+ ]
+ }
+ ]
+}`
+
+/*
+mockJs template
+
+ var template = {
+ 'list|1-10': [{
+ 'id|+1': 1,
+ 'email': '@EMAIL'
+ }]
+}
+
+Mock.toJSONSchema(template)生成的json schema
+*/
+
+var mock2 = `{
+ "template": {
+ "list|1-10": [
+ {
+ "id|+1": 7,
+ "email": "@EMAIL"
+ }
+ ]
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "list",
+ "template": [
+ {
+ "id|+1": 7,
+ "email": "@EMAIL"
+ }
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "list|1-10",
+ "list",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "list"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": {
+ "id|+1": 7,
+ "email": "@EMAIL"
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "list",
+ 0
+ ],
+ "properties": [
+ {
+ "name": "id",
+ "template": 7,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "id|+1",
+ "id",
+ "1",
+ null,
+ null
+ ]
+ },
+ "path": [
+ "ROOT",
+ "list",
+ 0,
+ "id"
+ ]
+ },
+ {
+ "name": "email",
+ "template": "@EMAIL",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "list",
+ 0,
+ "email"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}`
+
+/*
+ var template = {
+ 'list|1-10': {
+ 'id|+1': 1,
+ 'email': '@EMAIL'
+ }
+ }
+*/
+var mock3 = `{
+ "template": {
+ "list|1-10": {
+ "id|+1": 1,
+ "email": "@EMAIL"
+ }
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "list",
+ "template": {
+ "id|+1": 1,
+ "email": "@EMAIL"
+ },
+ "type": "object",
+ "rule": {
+ "parameters": [
+ "list|1-10",
+ "list",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 4
+ },
+ "path": [
+ "ROOT",
+ "list"
+ ],
+ "properties": [
+ {
+ "name": "id",
+ "template": 1,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "id|+1",
+ "id",
+ "1",
+ null,
+ null
+ ]
+ },
+ "path": [
+ "ROOT",
+ "list",
+ "id"
+ ]
+ },
+ {
+ "name": "email",
+ "template": "@EMAIL",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "list",
+ "email"
+ ]
+ }
+ ]
+ }
+ ]
+}`
+
+/*
+ var template = {
+ 'key|1-10': '★'
+ }
+*/
+var mock4 = `{
+ "template": {
+ "key|1-10": "★"
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "key",
+ "template": "★",
+ "type": "string",
+ "rule": {
+ "parameters": [
+ "key|1-10",
+ "key",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "key"
+ ]
+ }
+ ]
+}`
+
+/*
+ var template = {
+ 'title': 'Syntax Demo',
+
+ 'string1|1-10': '★',
+ 'string2|3': 'value',
+
+ 'number1|+1': 100,
+ 'number2|1-100': 100,
+ 'number3|1-100.1-10': 1,
+ 'number4|123.1-10': 1,
+ 'number5|123.3': 1,
+ 'number6|123.10': 1.123,
+
+ 'boolean1|1': true,
+ 'boolean2|1-2': true,
+
+ 'object1|2-4': {
+ '110000': '北京市',
+ '120000': '天津市',
+ '130000': '河北省',
+ '140000': '山西省'
+ },
+ 'object2|2': {
+ '310000': '上海市',
+ '320000': '江苏省',
+ '330000': '浙江省',
+ '340000': '安徽省'
+ },
+ 'object3|2': {
+ '310000': '@name',
+ '320000': '@ip',
+ '330000': '@email'
+ },
+
+ 'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'],
+ 'array2|1-10': [{
+ 'id':10,
+ 'ip':'@ip'
+ }],
+ 'array3|3': ['Mock.js'],
+ 'array4|3-5': [10,20,30,40]
+}
+*/
+
+var mock5 = `{
+ "template": {
+ "title": "Syntax Demo",
+ "string1|1-10": "★",
+ "string2|3": "value",
+ "number1|+1": 101,
+ "number2|1-100": 100,
+ "number3|1-100.1-10": 1,
+ "number4|123.1-10": 1,
+ "number5|123.3": 1,
+ "number6|123.10": 1.123,
+ "boolean1|1": true,
+ "boolean2|1-2": true,
+ "object1|2-4": {
+ "110000": "北京市",
+ "120000": "天津市",
+ "130000": "河北省",
+ "140000": "山西省"
+ },
+ "object2|2": {
+ "310000": "上海市",
+ "320000": "江苏省",
+ "330000": "浙江省",
+ "340000": "安徽省"
+ },
+ "object3|2": {
+ "310000": "@name",
+ "320000": "@ip",
+ "330000": "@email"
+ },
+ "array1|1": [
+ "AMD",
+ "CMD",
+ "KMD",
+ "UMD"
+ ],
+ "array2|1-10": [
+ {
+ "id": 10,
+ "ip": "@ip"
+ }
+ ],
+ "array3|3": [
+ "Mock.js"
+ ],
+ "array4|3-5": [
+ 10,
+ 20,
+ 30,
+ 40
+ ]
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "title",
+ "template": "Syntax Demo",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "title"
+ ]
+ },
+ {
+ "name": "string1",
+ "template": "★",
+ "type": "string",
+ "rule": {
+ "parameters": [
+ "string1|1-10",
+ "string1",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 5
+ },
+ "path": [
+ "ROOT",
+ "string1"
+ ]
+ },
+ {
+ "name": "string2",
+ "template": "value",
+ "type": "string",
+ "rule": {
+ "parameters": [
+ "string2|3",
+ "string2",
+ null,
+ "3",
+ null
+ ],
+ "range": [
+ "3",
+ "3",
+ null
+ ],
+ "min": 3,
+ "count": 3
+ },
+ "path": [
+ "ROOT",
+ "string2"
+ ]
+ },
+ {
+ "name": "number1",
+ "template": 101,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number1|+1",
+ "number1",
+ "1",
+ null,
+ null
+ ]
+ },
+ "path": [
+ "ROOT",
+ "number1"
+ ]
+ },
+ {
+ "name": "number2",
+ "template": 100,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number2|1-100",
+ "number2",
+ null,
+ "1-100",
+ null
+ ],
+ "range": [
+ "1-100",
+ "1",
+ "100"
+ ],
+ "min": 1,
+ "max": 100,
+ "count": 61
+ },
+ "path": [
+ "ROOT",
+ "number2"
+ ]
+ },
+ {
+ "name": "number3",
+ "template": 1,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number3|1-100.1-10",
+ "number3",
+ null,
+ "1-100",
+ "1-10"
+ ],
+ "range": [
+ "1-100",
+ "1",
+ "100"
+ ],
+ "min": 1,
+ "max": 100,
+ "count": 70,
+ "decimal": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "dmin": 1,
+ "dmax": 10,
+ "dcount": 6
+ },
+ "path": [
+ "ROOT",
+ "number3"
+ ]
+ },
+ {
+ "name": "number4",
+ "template": 1,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number4|123.1-10",
+ "number4",
+ null,
+ "123",
+ "1-10"
+ ],
+ "range": [
+ "123",
+ "123",
+ null
+ ],
+ "min": 123,
+ "count": 123,
+ "decimal": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "dmin": 1,
+ "dmax": 10,
+ "dcount": 7
+ },
+ "path": [
+ "ROOT",
+ "number4"
+ ]
+ },
+ {
+ "name": "number5",
+ "template": 1,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number5|123.3",
+ "number5",
+ null,
+ "123",
+ "3"
+ ],
+ "range": [
+ "123",
+ "123",
+ null
+ ],
+ "min": 123,
+ "count": 123,
+ "decimal": [
+ "3",
+ "3",
+ null
+ ],
+ "dmin": 3,
+ "dmax": null,
+ "dcount": 3
+ },
+ "path": [
+ "ROOT",
+ "number5"
+ ]
+ },
+ {
+ "name": "number6",
+ "template": 1.123,
+ "type": "number",
+ "rule": {
+ "parameters": [
+ "number6|123.10",
+ "number6",
+ null,
+ "123",
+ "10"
+ ],
+ "range": [
+ "123",
+ "123",
+ null
+ ],
+ "min": 123,
+ "count": 123,
+ "decimal": [
+ "10",
+ "10",
+ null
+ ],
+ "dmin": 10,
+ "dmax": null,
+ "dcount": 10
+ },
+ "path": [
+ "ROOT",
+ "number6"
+ ]
+ },
+ {
+ "name": "boolean1",
+ "template": true,
+ "type": "boolean",
+ "rule": {
+ "parameters": [
+ "boolean1|1",
+ "boolean1",
+ null,
+ "1",
+ null
+ ],
+ "range": [
+ "1",
+ "1",
+ null
+ ],
+ "min": 1,
+ "count": 1
+ },
+ "path": [
+ "ROOT",
+ "boolean1"
+ ]
+ },
+ {
+ "name": "boolean2",
+ "template": true,
+ "type": "boolean",
+ "rule": {
+ "parameters": [
+ "boolean2|1-2",
+ "boolean2",
+ null,
+ "1-2",
+ null
+ ],
+ "range": [
+ "1-2",
+ "1",
+ "2"
+ ],
+ "min": 1,
+ "max": 2,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "boolean2"
+ ]
+ },
+ {
+ "name": "object1",
+ "template": {
+ "110000": "北京市",
+ "120000": "天津市",
+ "130000": "河北省",
+ "140000": "山西省"
+ },
+ "type": "object",
+ "rule": {
+ "parameters": [
+ "object1|2-4",
+ "object1",
+ null,
+ "2-4",
+ null
+ ],
+ "range": [
+ "2-4",
+ "2",
+ "4"
+ ],
+ "min": 2,
+ "max": 4,
+ "count": 4
+ },
+ "path": [
+ "ROOT",
+ "object1"
+ ],
+ "properties": [
+ {
+ "name": "110000",
+ "template": "北京市",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object1",
+ "110000"
+ ]
+ },
+ {
+ "name": "120000",
+ "template": "天津市",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object1",
+ "120000"
+ ]
+ },
+ {
+ "name": "130000",
+ "template": "河北省",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object1",
+ "130000"
+ ]
+ },
+ {
+ "name": "140000",
+ "template": "山西省",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object1",
+ "140000"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "object2",
+ "template": {
+ "310000": "上海市",
+ "320000": "江苏省",
+ "330000": "浙江省",
+ "340000": "安徽省"
+ },
+ "type": "object",
+ "rule": {
+ "parameters": [
+ "object2|2",
+ "object2",
+ null,
+ "2",
+ null
+ ],
+ "range": [
+ "2",
+ "2",
+ null
+ ],
+ "min": 2,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "object2"
+ ],
+ "properties": [
+ {
+ "name": "310000",
+ "template": "上海市",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object2",
+ "310000"
+ ]
+ },
+ {
+ "name": "320000",
+ "template": "江苏省",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object2",
+ "320000"
+ ]
+ },
+ {
+ "name": "330000",
+ "template": "浙江省",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object2",
+ "330000"
+ ]
+ },
+ {
+ "name": "340000",
+ "template": "安徽省",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object2",
+ "340000"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "object3",
+ "template": {
+ "310000": "@name",
+ "320000": "@ip",
+ "330000": "@email"
+ },
+ "type": "object",
+ "rule": {
+ "parameters": [
+ "object3|2",
+ "object3",
+ null,
+ "2",
+ null
+ ],
+ "range": [
+ "2",
+ "2",
+ null
+ ],
+ "min": 2,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "object3"
+ ],
+ "properties": [
+ {
+ "name": "310000",
+ "template": "@name",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object3",
+ "310000"
+ ]
+ },
+ {
+ "name": "320000",
+ "template": "@ip",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object3",
+ "320000"
+ ]
+ },
+ {
+ "name": "330000",
+ "template": "@email",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "object3",
+ "330000"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "array1",
+ "template": [
+ "AMD",
+ "CMD",
+ "KMD",
+ "UMD"
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "array1|1",
+ "array1",
+ null,
+ "1",
+ null
+ ],
+ "range": [
+ "1",
+ "1",
+ null
+ ],
+ "min": 1,
+ "count": 1
+ },
+ "path": [
+ "ROOT",
+ "array1"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": "AMD",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array1",
+ 0
+ ]
+ },
+ {
+ "name": 1,
+ "template": "CMD",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array1",
+ 1
+ ]
+ },
+ {
+ "name": 2,
+ "template": "KMD",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array1",
+ 2
+ ]
+ },
+ {
+ "name": 3,
+ "template": "UMD",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array1",
+ 3
+ ]
+ }
+ ]
+ },
+ {
+ "name": "array2",
+ "template": [
+ {
+ "id": 10,
+ "ip": "@ip"
+ }
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "array2|1-10",
+ "array2",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 1
+ },
+ "path": [
+ "ROOT",
+ "array2"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": {
+ "id": 10,
+ "ip": "@ip"
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array2",
+ 0
+ ],
+ "properties": [
+ {
+ "name": "id",
+ "template": 10,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array2",
+ 0,
+ "id"
+ ]
+ },
+ {
+ "name": "ip",
+ "template": "@ip",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array2",
+ 0,
+ "ip"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "array3",
+ "template": [
+ "Mock.js"
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "array3|3",
+ "array3",
+ null,
+ "3",
+ null
+ ],
+ "range": [
+ "3",
+ "3",
+ null
+ ],
+ "min": 3,
+ "count": 3
+ },
+ "path": [
+ "ROOT",
+ "array3"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": "Mock.js",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array3",
+ 0
+ ]
+ }
+ ]
+ },
+ {
+ "name": "array4",
+ "template": [
+ 10,
+ 20,
+ 30,
+ 40
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "array4|3-5",
+ "array4",
+ null,
+ "3-5",
+ null
+ ],
+ "range": [
+ "3-5",
+ "3",
+ "5"
+ ],
+ "min": 3,
+ "max": 5,
+ "count": 5
+ },
+ "path": [
+ "ROOT",
+ "array4"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": 10,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array4",
+ 0
+ ]
+ },
+ {
+ "name": 1,
+ "template": 20,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array4",
+ 1
+ ]
+ },
+ {
+ "name": 2,
+ "template": 30,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array4",
+ 2
+ ]
+ },
+ {
+ "name": 3,
+ "template": 40,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "array4",
+ 3
+ ]
+ }
+ ]
+ }
+ ]
+}`
+
+var mock6 = `{
+ "template": {
+ "users|1": [
+ {
+ "email": "@email",
+ "name": "@name",
+ "ip": "@ip"
+ }
+ ]
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "users",
+ "template": [
+ {
+ "email": "@email",
+ "name": "@name",
+ "ip": "@ip"
+ }
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "users|1",
+ "users",
+ null,
+ "1",
+ null
+ ],
+ "range": [
+ "1",
+ "1",
+ null
+ ],
+ "min": 1,
+ "count": 1
+ },
+ "path": [
+ "ROOT",
+ "users"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": {
+ "email": "@email",
+ "name": "@name",
+ "ip": "@ip"
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 0
+ ],
+ "properties": [
+ {
+ "name": "email",
+ "template": "@email",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 0,
+ "email"
+ ]
+ },
+ {
+ "name": "name",
+ "template": "@name",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 0,
+ "name"
+ ]
+ },
+ {
+ "name": "ip",
+ "template": "@ip",
+ "type": "string",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 0,
+ "ip"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}`
+
+var mock7 = `{
+ "template": {
+ "users|1-10": [
+ 10,
+ 20
+ ]
+ },
+ "type": "object",
+ "rule": {},
+ "path": [
+ "ROOT"
+ ],
+ "properties": [
+ {
+ "name": "users",
+ "template": [
+ 10,
+ 20
+ ],
+ "type": "array",
+ "rule": {
+ "parameters": [
+ "users|1-10",
+ "users",
+ null,
+ "1-10",
+ null
+ ],
+ "range": [
+ "1-10",
+ "1",
+ "10"
+ ],
+ "min": 1,
+ "max": 10,
+ "count": 2
+ },
+ "path": [
+ "ROOT",
+ "users"
+ ],
+ "items": [
+ {
+ "name": 0,
+ "template": 10,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 0
+ ]
+ },
+ {
+ "name": 1,
+ "template": 20,
+ "type": "number",
+ "rule": {},
+ "path": [
+ "ROOT",
+ "users",
+ 1
+ ]
+ }
+ ]
+ }
+ ]
+}`
+
+func TestJsonSchemaMockJsUnmarshal(t *testing.T) {
+ type args struct {
+ valueMap interface{}
+ }
+
+ valueMap := make(map[string]interface{})
+ json.Unmarshal([]byte(mock7), &valueMap)
+ tests := []struct {
+ name string
+ args args
+ want interface{}
+ }{
+ {
+ name: "",
+ args: args{
+ valueMap: valueMap,
+ },
+ want: nil,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := JsonSchemaMockJsUnmarshal(tt.args.valueMap)
+ bytes, _ := json.Marshal(got)
+ t.Logf(string(bytes))
+ })
+ }
+}
diff --git a/utils/regex.go b/utils/regex.go
index 87b427c4..082b40df 100644
--- a/utils/regex.go
+++ b/utils/regex.go
@@ -7,11 +7,23 @@ const (
regexUrlPathStr = `^\/[a-zA-Z0-9\/_\-\.]*$`
//objectivesExp 校验0.5:0.1,0.9:0.001的格式
objectivesExp = `^((0\.[0-9]+)\:(0\.[0-9]+)(\,)?)+$`
+ // schemeIPPortExp scheme://IP:PORT
+ schemeIPPortExp = `^[a-zA-z]+://((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:[0-9]+$`
+ // schemeDomainPortExp scheme://域名或者域名:端口
+ schemeDomainPortExp = `^[a-zA-z]+://[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?(:[0-9]+)?$`
+ // domainPortExp IP:PORT或者IP
+ ipPortExp = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}(:[0-9]+)?$`
+ // domainPortExp 域名或者域名:端口
+ domainPortExp = `^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?(:[0-9]+)?$`
)
var (
- regexUrlPath = regexp.MustCompile(regexUrlPathStr)
- objectivesRegexp = regexp.MustCompile(objectivesExp)
+ regexUrlPath = regexp.MustCompile(regexUrlPathStr)
+ objectivesRegexp = regexp.MustCompile(objectivesExp)
+ schemeIPPortReg = regexp.MustCompile(schemeIPPortExp)
+ schemeDomainPortReg = regexp.MustCompile(schemeDomainPortExp)
+ ipPortReg = regexp.MustCompile(ipPortExp)
+ domainPortReg = regexp.MustCompile(domainPortExp)
)
func CheckUrlPath(path string) bool {
@@ -22,3 +34,23 @@ func CheckUrlPath(path string) bool {
func CheckObjectives(objectives string) bool {
return objectivesRegexp.MatchString(objectives)
}
+
+// IsMatchSchemeIpPort 判断字符串是否符合scheme://ip:port
+func IsMatchSchemeIpPort(s string) bool {
+ return schemeIPPortReg.MatchString(s)
+}
+
+// IsMatchSchemeDomainPort 判断字符串是否符合 scheme://域名或者域名:port
+func IsMatchSchemeDomainPort(s string) bool {
+ return schemeDomainPortReg.MatchString(s)
+}
+
+// IsMatchIpPort 判断字符串是否符合 ip:port或者ip
+func IsMatchIpPort(s string) bool {
+ return ipPortReg.MatchString(s)
+}
+
+// IsMatchDomainPort 判断字符串是否符合 域名或者域名:port
+func IsMatchDomainPort(s string) bool {
+ return domainPortReg.MatchString(s)
+}