blob: 76645c8dcdbad8c4e8b75da42a02d959da214757 [file] [view]
---
title: Custom Plugin
keywords: ["Custom Plugin"]
description: plugins
---
## Description
* Plugins are core executors of `Apache ShenYu` gateway. Every plugin handles matched requests when enabled.
* There are two kinds of plugins in the `Apache ShenYu` gateway.
* The first type is a chain with single responsibility, and can not custom filtering of traffic.
* The other one can do its own chain of responsibility for matched traffic.
* You could reference from [shenyu-plugin](https://github.com/apache/shenyu/tree/master/shenyu-plugin) module and develop plugins by yourself. Please fire pull requests of your wonderful plugins without hesitate.
## Single Responsibility Plugins
* Add following dependency:
```xml
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-api</artifactId>
<version>${project.version}</version>
</dependency>
```
* Declare a new class named `MyShenyuPlugin` and implements `org.apache.shenyu.plugin.api.ShenyuPlugin`
```java
public interface ShenyuPlugin {
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link ShenyuPluginChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
Mono<Void> execute(ServerWebExchange exchange, ShenyuPluginChain chain);
/**
* return plugin order .
* This attribute To determine the plugin execution order in the same type plugin.
*
* @return int order
*/
int getOrder();
/**
* acquire plugin name.
* this is plugin name define you must offer the right name.
* if you impl AbstractShenyuPlugin this attribute not use.
*
* @return plugin name.
*/
default String named() {
return "";
}
/**
* plugin is execute.
* if return true this plugin can not execute.
*
* @param exchange the current server exchange
* @return default false.
*/
default Boolean skip(ServerWebExchange exchange) {
return false;
}
}
```
Detailed instruction of interface methods:
* `execute()` core method, you can do any task here freely.
* `getOrder()` get the order of current plugin.
* `named()` acquire the name of specific plugin that uses the `Camel Case`, eg: `dubbo`, `springCloud` .
* `skip()` determines whether this plugin should be skipped under certain conditions.
* Register plugin in `Spring` as a `Bean`, or simply apply `@Component` in implementation class.
```java
@Bean
public ShenyuPlugin myShenyuPlugin() {
return new MyShenyuPlugin();
}
```
## Single Responsibility Plugin in Multiple Languages
* The above is about writing a single responsibility plugin in Java. If you want to write plugins in another language, at least the language you are good at supporting `WASM`, you can find resources [here](https://shenyu.apache.org/docs/next/design/wasm-plugin-design/) . After you have learned about `WASM`, let's introduce the following dependency to build the Java part of the plugin:
```xml
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-wasm-api</artifactId>
<version>${project.version}</version>
</dependency>
```
* Add a new class `MyShenyuWasmPlugin`, inherit from `org.apache.shenyu.plugin.wasm.api.AbstractWasmPlugin`
```java
package x.y.z;
public class MyShenyuWasmPlugin extends AbstractWasmPlugin {
private static final Map<Long, String> RESULTS = new ConcurrentHashMap<>();
@Override
public int getOrder() {
// your plugin order
return 0;
}
@Override
public String named() {
return "yourPluginName";
}
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final Long argumentId) {
final String result = RESULTS.remove(argumentId);
// Results returned by calling other languages
return chain.execute(exchange);
}
@Override
protected Long getArgumentId(final ServerWebExchange exchange, final ShenyuPluginChain chain) {
// Need to generate unique IDs for parameters based on exchange and chain
return 0L;
}
@Override
protected Map<String, Func> initWasmCallJavaFunc(final Store<Void> store) {
Map<String, Func> funcMap = new HashMap<>();
funcMap.put("get_args", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32,
(argId, addr, len) -> {
// Callbacks for obtaining parameters from Java in other languages
String config = "hello from java " + argId;
LOG.info("java side->" + config);
ByteBuffer buf = super.getBuffer();
for (int i = 0; i < len && i < config.length(); i++) {
buf.put(addr.intValue() + i, (byte) config.charAt(i));
}
return Math.min(config.length(), len);
}));
funcMap.put("put_result", WasmFunctions.wrap(store, WasmValType.I64, WasmValType.I64, WasmValType.I32, WasmValType.I32,
(argId, addr, len) -> {
// Callbacks that pass call results to Java in other languages
ByteBuffer buf = super.getBuffer();
byte[] bytes = new byte[len];
for (int i = 0; i < len; i++) {
bytes[i] = buf.get(addr.intValue() + i);
}
String result = new String(bytes, StandardCharsets.UTF_8);
RESULTS.put(argId, result);
LOG.info("java side->" + result);
return 0;
}));
return funcMap;
}
}
```
* Create projects in other languages, using the Rust language as an example:
```shell
cd {shenyu}/shenyu-plugin/{your_plugin_moodule}/src/main
cargo new --lib your_plugin_name
```
* Add `execute` method in `lib.rs`:
```rust
#[link(wasm_import_module = "shenyu")]
extern "C" {
fn get_args(arg_id: i64, addr: i64, len: i32) -> i32;
fn put_result(arg_id: i64, addr: i64, len: i32) -> i32;
}
// Adding `#[no_mangle]` to prevent the Rust compiler from modifying method names is mandatory
#[no_mangle]
pub unsafe extern "C" fn execute(arg_id: i64) {
let mut buf = [0u8; 32];
let buf_ptr = buf.as_mut_ptr() as i64;
eprintln!("rust side-> buffer base address: {}", buf_ptr);
// Get parameters from Java
let len = get_args(arg_id, buf_ptr, buf.len() as i32);
let java_arg = std::str::from_utf8(&buf[..len as usize]).unwrap();
eprintln!("rust side-> recv:{}", java_arg);
// Add plugin logic for the Rust section here, such as rpc calls, etc
// Pass the call result of rust to Java
let rust_result = "rust result".as_bytes();
let result_ptr = rust_result.as_ptr() as i64;
_ = put_result(arg_id, result_ptr, rust_result.len() as i32);
}
```
* Add `[lib]` to `Cargo.toml` and change `crate-type` to `["cdylib"]`. Ultimately, your `Cargo.toml` should look like:
```toml
[package]
name = "your_plugin_name"
version = "0.1.0"
edition = "2021"
[dependencies]
# ......
[lib]
crate-type = ["cdylib"]
```
* Generate the wasm file:
```shell
cargo build --target wasm32-wasi --release
```
* You will see `{shenyu}/shenyu-plugin/{your_plugin_moodule}/src/main/{your_plugin_name}/target/wasm32-wasi/release/{your_plugin_name}.wasm`, then rename it, due to the `x.y.z.MyShenyuWasmPlugin`,the final wasm file name should be `x.y.z.MyShenyuWasmPlugin.wasm`, finally, put the wasm file in the `resources` folder of your plugin module.
## Matching Traffic Processing Plugin
* Introduce the following dependency:
```xml
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-base</artifactId>
<version>${project.version}</version>
</dependency>
```
* Add a new class `CustomPlugin`, inherit from `org.apache.shenyu.plugin.base.AbstractShenyuPlugin`
* examples down below:
```java
/**
* This is your custom plugin.
* He is running in after before plugin, implement your own functionality.
* extends AbstractShenyuPlugin so you must user shenyu-admin And add related plug-in development.
*
* @author xiaoyu(Myth)
*/
public class CustomPlugin extends AbstractShenyuPlugin {
/**
* return plugin order .
* The same plugin he executes in the same order.
*
* @return int
*/
@Override
public int getOrder() {
return 0;
}
/**
* acquire plugin name.
* return you custom plugin name.
* It must be the same name as the plug-in you added in the admin background.
*
* @return plugin name.
*/
@Override
public String named() {
return "shenYu";
}
/**
* plugin is execute.
* Do I need to skip.
* if you need skip return true.
*
* @param exchange the current server exchange
* @return default false.
*/
@Override
public Boolean skip(final ServerWebExchange exchange) {
return false;
}
/**
* this is Template Method child has Implement your own logic.
*
* @param exchange exchange the current server exchange
* @param chain chain the current chain
* @param selector selector
* @param rule rule
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
protected abstract Mono<Void> doExecute(ServerWebExchange exchange, ShenyuPluginChain chain, SelectorData selector, RuleData rule) {
LOGGER.debug(".......... function plugin start..............");
/*
* Processing after your selector matches the rule.
* rule.getHandle() is you Customize the json string to be processed.
* for this example.
* Convert your custom json string pass to an entity class.
*/
final String ruleHandle = rule.getHandle();
final Test test = GsonUtils.getInstance().fromJson(ruleHandle, Test.class);
/*
* Then do your own business processing.
* The last execution chain.execute(exchange).
* Let it continue on the chain until the end.
*/
System.out.println(test.toString());
return chain.execute(exchange);
}
}
```
* Detailed explanation:
* Plugins will match the selector rule for customized plugins inherit from this abstract class.
* Firstly define a new plugin in `shenyu-admin` –> BasicConfig –> Plugin, please mind that your plugin name should match the `named()` method overridden in your class.
* Re-login `shenyu-admin`, the plugin you added now showing on plugin-list page, you can choose selectors for matching.
* There is a field named `handler` in rules, it is customized json string to be processed. You can process data after acquiring a ruleHandle (`final String ruleHandle = rule.getHandle();`) in `doExecute()` method.
* Register plugin in `Spring` as a `Bean`, or simply apply `@Component` in implementation class.
```java
@Bean
public ShenyuPlugin customPlugin() {
return new CustomPlugin();
}
```
## Matching Traffic Processing Plugin in Multiple Languages
* The general logic is similar to [Single Responsibility Plugin in Multiple Languages](#Single Responsibility Plugin in Multiple Languages) , but the dependency in Java and the methods that need to be added in other languages are different from `Single Responsibility Plugin in Multiple Languages`. The following are the dependency required for the Java part of the `Multi Language Matching Traffic Processing Plugin`:
```xml
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-wasm-base</artifactId>
<version>${project.version}</version>
</dependency>
```
* The following are the methods that must be added (using the Rust language as an example):
```rust
#[no_mangle]
pub unsafe extern "C" fn doExecute(arg_id: i64) {
//......
}
```
* The following are optional methods (using the Rust language as an example):
```rust
#[no_mangle]
pub unsafe extern "C" fn before(arg_id: i64) {
//......
}
#[no_mangle]
pub unsafe extern "C" fn after(arg_id: i64) {
//......
}
```
## Subscribe your plugin data and do customized jobs
* Declare a new class named `PluginDataHandler` and implements `org.apache.shenyu.plugin.base.handler.PluginDataHandler`
```java
public interface PluginDataHandler {
/**
* Handler plugin.
*
* @param pluginData the plugin data
*/
default void handlerPlugin(PluginData pluginData) {
}
/**
* Remove plugin.
*
* @param pluginData the plugin data
*/
default void removePlugin(PluginData pluginData) {
}
/**
* Handler selector.
*
* @param selectorData the selector data
*/
default void handlerSelector(SelectorData selectorData) {
}
/**
* Remove selector.
*
* @param selectorData the selector data
*/
default void removeSelector(SelectorData selectorData) {
}
/**
* Handler rule.
*
* @param ruleData the rule data
*/
default void handlerRule(RuleData ruleData) {
}
/**
* Remove rule.
*
* @param ruleData the rule data
*/
default void removeRule(RuleData ruleData) {
}
/**
* Plugin named string.
*
* @return the string
*/
String pluginNamed();
}
```
* Ensure `pluginNamed()` is same as the plugin name you defined.
* Register defined class as a `Spring Bean`, or simply apply `@Component` in implementation class.
```java
@Bean
public PluginDataHandler pluginDataHandler() {
return new PluginDataHandler();
}
```
## Dynamic loading
* When using this feature, the above extensions `ShenyuPlugin`, `PluginDataHandler`, do not need to be `spring bean`. You just need to build the jar package of the extension project.
* Config in Yaml:
```yaml
shenyu:
extPlugin:
path: //Load the extension plugin jar package path
enabled: true //Whether to turn on
threads: 1 //Number of loading plug-in threads
scheduleTime: 300 //Cycle time (in seconds)
scheduleDelay: 30 //How long the shenyu gateway is delayed to load after it starts (in seconds)
```
#### Plugin loading path details
* This path is for the directory where the extended plugin jar package is stored。
* Used `-Dplugin-ext=xxxx`, Also used `shenyu.extPlugin.path` in yaml,If neither is configured, the `ext-lib` directory in the apache shenyu gateway boot path will be loaded by default.
* Priority :`-Dplugin-ext=xxxx` > `shenyu.extPlugin.path` > `ext-lib(default)`
## Plugin jar upload
* To use this feature, you will need to package the `ShenyuPlugin` extension as a custom ShenyuPlugin Jar
* Configure it in ShenyuAdmin
* use `ShenyuAdmin - BasicConfig - Plugin` add plugin in `pluginJar` click upload button
* Custom ShenyuPlugin can be started by loading third-party jars into the `-cp` directory if it depends on other third-party packages in shenyu-bootstrap
Tips:
The Upload jar package plugin supports hot loading
If you need to modify the jar online. You can make a new jar. And raise the version number, for example '1.0.1' to '1.0.2'