Skip to content

Commit

Permalink
[ISSUE apache#4612] init shenyu-plugin-wasm-base module
Browse files Browse the repository at this point in the history
  • Loading branch information
loongs-zhang committed Jan 20, 2024
1 parent 1a54081 commit 5504efc
Show file tree
Hide file tree
Showing 15 changed files with 828 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ Thumbs.db
# agent build ignore
/agent/

# rust ignore
*.lock
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
<casdoor-java-sdk.version>1.9.0</casdoor-java-sdk.version>
<huawei-log-sdk.version>1.0.1</huawei-log-sdk.version>
<opengauss-jdbc.version>5.0.0-og</opengauss-jdbc.version>
<wasmtime-java.version>0.19.0</wasmtime-java.version>
<!-- dependency version end -->
</properties>

Expand Down Expand Up @@ -585,6 +586,12 @@
<artifactId>kafka-clients</artifactId>
<version>${kafka-clients.version}</version>
</dependency>

<dependency>
<groupId>io.github.kawamuray.wasmtime</groupId>
<artifactId>wasmtime-java</artifactId>
<version>${wasmtime-java.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
1 change: 1 addition & 0 deletions shenyu-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<modules>
<module>shenyu-plugin-api</module>
<module>shenyu-plugin-base</module>
<module>shenyu-plugin-wasm-base</module>
<module>shenyu-plugin-httpclient</module>
<module>shenyu-plugin-rewrite</module>
<module>shenyu-plugin-waf</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,12 @@ public enum ShenyuResultEnum {
/**
* Key is incorrect.
*/
ERROR_KEY(401, "Key is incorrect");
ERROR_KEY(401, "Key is incorrect"),

/**
* WASM function not found.
*/
WASM_FUNC_NOT_FOUND(533, "WASM function not found");

/**
* the code.
Expand Down
49 changes: 49 additions & 0 deletions shenyu-plugin/shenyu-plugin-wasm-base/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin</artifactId>
<version>2.6.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shenyu-plugin-wasm-base</artifactId>

<dependencies>
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.kawamuray.wasmtime</groupId>
<artifactId>wasmtime-java</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shenyu.plugin.wasm.base;

import io.github.kawamuray.wasmtime.Extern;
import io.github.kawamuray.wasmtime.Func;
import io.github.kawamuray.wasmtime.Store;
import io.github.kawamuray.wasmtime.WasmFunctions;
import io.github.kawamuray.wasmtime.WasmValType;
import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Complex plugins implemented in other languages should extend this class, we still need to write Java subclasses,
* so we can reuse the convenient/powerful control of ShenYu, such as {@link #getOrder}/{@link #skip}
* /{@link #skipExcept}/{@link #skipExceptHttpLike}.
*
* @see org.apache.shenyu.plugin.base.AbstractShenyuPlugin
* @see io.github.kawamuray.wasmtime.WasmValType
* @see org.apache.shenyu.plugin.wasm.base.WasmLoader
*/
public abstract class AbstractShenyuWasmPlugin extends AbstractShenyuPlugin {

protected static final Logger LOG = LoggerFactory.getLogger(AbstractShenyuWasmPlugin.class);

protected static final Map<Long, Argument> ARGUMENTS = new ConcurrentHashMap<>();

protected static final String DO_EXECUTE_METHOD_NAME = "doExecute";

protected static final String BEFORE_METHOD_NAME = "before";

protected static final String AFTER_METHOD_NAME = "after";

private final WasmLoader wasmLoader;

public AbstractShenyuWasmPlugin() {
this.wasmLoader = new WasmLoader(this.getClass(), this::initWasmCallJavaFunc);
}

protected Map<String, Func> initWasmCallJavaFunc(final Store<Void> store) {
return null;
}

@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange,
final ShenyuPluginChain chain,
final SelectorData selector,
final RuleData rule) {
return wasmLoader.getWasmExtern(DO_EXECUTE_METHOD_NAME).map(doExecute -> {
final Long argumentId = callWASI(exchange, chain, selector, rule, doExecute);
return doExecute(exchange, chain, selector, rule, argumentId);
}).orElseGet(() -> {
LOG.error("{} function not found in {}", DO_EXECUTE_METHOD_NAME, wasmLoader.getWasmName());
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = ShenyuResultWrap.error(exchange, ShenyuResultEnum.WASM_FUNC_NOT_FOUND);
return WebFluxResultUtils.result(exchange, error);
});
}

/**
* this is Template Method child has implements your own logic.
*
* @param exchange exchange the current server exchange {@linkplain ServerWebExchange}
* @param chain chain the current chain {@linkplain ServerWebExchange}
* @param selector selector {@linkplain SelectorData}
* @param rule rule {@linkplain RuleData}
* @param argumentId the argument id {@linkplain #getArgumentId}
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
protected abstract Mono<Void> doExecute(ServerWebExchange exchange, ShenyuPluginChain chain, SelectorData selector, RuleData rule, Long argumentId);

private Long callWASI(final ServerWebExchange exchange,
final ShenyuPluginChain chain,
final SelectorData selector,
final RuleData rule,
final Extern doExecute) {
// WASI cannot easily pass Java objects like JNI, here we pass Long as arg
// then we can get the argument by Long
final Long argumentId = getArgumentId(exchange, chain, selector, rule);
ARGUMENTS.put(argumentId, new Argument(exchange, chain, selector, rule));
// call WASI function
WasmFunctions.consumer(wasmLoader.getStore(), doExecute.func(), WasmValType.I64)
.accept(argumentId);
ARGUMENTS.remove(argumentId);
return argumentId;
}

protected abstract Long getArgumentId(ServerWebExchange exchange,
ShenyuPluginChain chain,
SelectorData selector,
RuleData rule);

@Override
public void before(final ServerWebExchange exchange) {
wasmLoader.getWasmExtern(BEFORE_METHOD_NAME)
.ifPresent(before -> callWASI(exchange, null, null, null, before));
}

@Override
public void after(final ServerWebExchange exchange) {
wasmLoader.getWasmExtern(AFTER_METHOD_NAME)
.ifPresent(before -> callWASI(exchange, null, null, null, before));
}

protected static final class Argument {

private final ServerWebExchange exchange;

private final ShenyuPluginChain chain;

private final SelectorData selector;

private final RuleData rule;

private Argument(final ServerWebExchange exchange,
final ShenyuPluginChain chain,
final SelectorData selector,
final RuleData rule) {
this.exchange = exchange;
this.chain = chain;
this.selector = selector;
this.rule = rule;
}

public ServerWebExchange getExchange() {
return exchange;
}

public ShenyuPluginChain getChain() {
return chain;
}

public SelectorData getSelector() {
return selector;
}

public RuleData getRule() {
return rule;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shenyu.plugin.wasm.base;

import io.github.kawamuray.wasmtime.Extern;
import io.github.kawamuray.wasmtime.WasmFunctions;
import io.github.kawamuray.wasmtime.WasmValType;
import org.apache.shenyu.plugin.api.ShenyuPlugin;
import org.apache.shenyu.plugin.api.ShenyuPluginChain;
import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Simple plugins implemented in other languages should extend this class, we still need to write Java subclasses,
* so we can reuse the convenient/powerful control of ShenYu, such as {@link #getOrder}/{@link #skip}
* /{@link #skipExcept}/{@link #skipExceptHttpLike}.
*
* @see org.apache.shenyu.plugin.api.ShenyuPlugin
* @see io.github.kawamuray.wasmtime.WasmValType
* @see org.apache.shenyu.plugin.wasm.base.WasmLoader
*/
public abstract class AbstractWasmPlugin extends WasmLoader implements ShenyuPlugin {

protected static final Logger LOG = LoggerFactory.getLogger(AbstractWasmPlugin.class);

protected static final Map<Long, Argument> ARGUMENTS = new ConcurrentHashMap<>();

protected static final String EXECUTE_METHOD_NAME = "execute";

protected static final String BEFORE_METHOD_NAME = "before";

protected static final String AFTER_METHOD_NAME = "after";

@Override
public Mono<Void> execute(final ServerWebExchange exchange, final ShenyuPluginChain chain) {
return super.getWasmExtern(EXECUTE_METHOD_NAME).map(execute -> {
final Long argumentId = callWASI(exchange, chain, execute);
return doExecute(exchange, chain, argumentId);
}).orElseGet(() -> {
LOG.error("{} function not found in {}", EXECUTE_METHOD_NAME, super.getWasmName());
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = ShenyuResultWrap.error(exchange, ShenyuResultEnum.WASM_FUNC_NOT_FOUND);
return WebFluxResultUtils.result(exchange, error);
});
}

/**
* this is Template Method child has Implement your own logic.
*
* @param exchange exchange the current server exchange {@linkplain ServerWebExchange}
* @param chain chain the current chain {@linkplain ServerWebExchange}
* @param argumentId the argument id {@linkplain #getArgumentId}
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
protected abstract Mono<Void> doExecute(ServerWebExchange exchange, ShenyuPluginChain chain, Long argumentId);

private Long callWASI(final ServerWebExchange exchange, final ShenyuPluginChain chain, final Extern execute) {
// WASI cannot easily pass Java objects like JNI, here we pass Long as arg
// then we can get the argument by Long
final Long argumentId = getArgumentId(exchange, chain);
ARGUMENTS.put(argumentId, new Argument(exchange, chain));
// call WASI function
WasmFunctions.consumer(super.getStore(), execute.func(), WasmValType.I64)
.accept(argumentId);
ARGUMENTS.remove(argumentId);
return argumentId;
}

protected abstract Long getArgumentId(ServerWebExchange exchange, ShenyuPluginChain chain);

@Override
public void before(final ServerWebExchange exchange) {
super.getWasmExtern(BEFORE_METHOD_NAME)
.ifPresent(before -> callWASI(exchange, null, before));
}

@Override
public void after(final ServerWebExchange exchange) {
super.getWasmExtern(AFTER_METHOD_NAME)
.ifPresent(before -> callWASI(exchange, null, before));
}

protected static final class Argument {

private final ServerWebExchange exchange;

private final ShenyuPluginChain chain;

private Argument(final ServerWebExchange exchange,
final ShenyuPluginChain chain) {
this.exchange = exchange;
this.chain = chain;
}

public ServerWebExchange getExchange() {
return exchange;
}

public ShenyuPluginChain getChain() {
return chain;
}
}
}
Loading

0 comments on commit 5504efc

Please sign in to comment.