Apache ShenYu
gateway. Every plugin handles matched requests when enabled.Apache ShenYu
gateway.<dependency> <groupId>org.apache.shenyu</groupId> <artifactId>shenyu-plugin-api</artifactId> <version>${project.version}</version> </dependency>
MyShenyuPlugin
and implements org.apache.shenyu.plugin.api.ShenyuPlugin
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.Spring
as a Bean
, or simply apply @Component
in implementation class.@Bean public ShenyuPlugin myShenyuPlugin() { return new MyShenyuPlugin(); }
WASM
, you can find resources here . After you have learned about WASM
, let's introduce the following dependency to build the Java part of the plugin:<dependency> <groupId>org.apache.shenyu</groupId> <artifactId>shenyu-plugin-wasm-api</artifactId> <version>${project.version}</version> </dependency>
MyShenyuWasmPlugin
, inherit from org.apache.shenyu.plugin.wasm.api.AbstractWasmPlugin
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; } }
cd {shenyu}/shenyu-plugin/{your_plugin_moodule}/src/main cargo new --lib your_plugin_name
execute
method in lib.rs
:#[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); }
[lib]
to Cargo.toml
and change crate-type
to ["cdylib"]
. Ultimately, your Cargo.toml
should look like:[package] name = "your_plugin_name" version = "0.1.0" edition = "2021" [dependencies] # ...... [lib] crate-type = ["cdylib"]
cargo build --target wasm32-wasi --release
{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.<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:
/** * 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.
@Bean public ShenyuPlugin customPlugin() { return new CustomPlugin(); }
Single Responsibility Plugin in Multiple Languages
. The following are the dependency required for the Java part of the Multi Language Matching Traffic Processing Plugin
:<dependency> <groupId>org.apache.shenyu</groupId> <artifactId>shenyu-plugin-wasm-base</artifactId> <version>${project.version}</version> </dependency>
#[no_mangle] pub unsafe extern "C" fn doExecute(arg_id: i64) { //...... }
#[no_mangle] pub unsafe extern "C" fn before(arg_id: i64) { //...... } #[no_mangle] pub unsafe extern "C" fn after(arg_id: i64) { //...... }
PluginDataHandler
and implements org.apache.shenyu.plugin.base.handler.PluginDataHandler
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(); }
pluginNamed()
is same as the plugin name you defined.Spring Bean
, or simply apply @Component
in implementation class.@Bean public PluginDataHandler pluginDataHandler() { return new PluginDataHandler(); }
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:
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)
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)
ShenyuPlugin
extension as a custom ShenyuPlugin JarShenyuAdmin - BasicConfig - Plugin
add plugin in pluginJar
click upload button-cp
directory if it depends on other third-party packages in shenyu-bootstrapTips:
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’