mirror of
https://github.com/alibaba/higress.git
synced 2026-03-07 10:00:48 +08:00
feat: support wasm-assemblyscript sdk (#1175)
This commit is contained in:
53
plugins/wasm-assemblyscript/README.md
Normal file
53
plugins/wasm-assemblyscript/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
## 介绍
|
||||
|
||||
此 SDK 用于使用 AssemblyScript 语言开发 Higress 的 Wasm 插件。
|
||||
|
||||
### 如何使用SDK
|
||||
|
||||
创建一个新的 AssemblyScript 项目。
|
||||
|
||||
```
|
||||
npm init
|
||||
npm install --save-dev assemblyscript
|
||||
npx asinit .
|
||||
```
|
||||
|
||||
在asconfig.json文件中,作为传递给asc编译器的选项之一,包含"use": "abort=abort_proc_exit"。
|
||||
|
||||
```
|
||||
{
|
||||
"options": {
|
||||
"use": "abort=abort_proc_exit"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
将`"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.1"`和`"@higress/wasm-assemblyscript": "^0.0.3"`添加到你的依赖项中,然后运行`npm install`。
|
||||
|
||||
### 本地构建
|
||||
|
||||
```
|
||||
npm run asbuild
|
||||
```
|
||||
|
||||
构建结果将在`build`文件夹中。其中,`debug.wasm`和`release.wasm`是已编译的文件,在生产环境中建议使用`release.wasm`。
|
||||
|
||||
注:如果需要插件带有 name section 信息需要带上`"debug": true`,编译参数解释详见[using-the-compiler](https://www.assemblyscript.org/compiler.html#using-the-compiler)。
|
||||
|
||||
```json
|
||||
"release": {
|
||||
"outFile": "build/release.wasm",
|
||||
"textFile": "build/release.wat",
|
||||
"sourceMap": true,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 0,
|
||||
"converge": false,
|
||||
"noAssert": false,
|
||||
"debug": true
|
||||
}
|
||||
```
|
||||
|
||||
### AssemblyScript 限制
|
||||
|
||||
此 SDK 使用的 AssemblyScript 版本为`0.27.29`,参考[AssemblyScript Status](https://www.assemblyscript.org/status.html)该版本尚未支持闭包、异常、迭代器等特性,并且JSON,正则表达式等功能还尚未在标准库中实现,暂时需要使用社区提供的实现。
|
||||
|
||||
23
plugins/wasm-assemblyscript/asconfig.json
Normal file
23
plugins/wasm-assemblyscript/asconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"targets": {
|
||||
"debug": {
|
||||
"outFile": "build/debug.wasm",
|
||||
"textFile": "build/debug.wat",
|
||||
"sourceMap": true,
|
||||
"debug": true
|
||||
},
|
||||
"release": {
|
||||
"outFile": "build/release.wasm",
|
||||
"textFile": "build/release.wat",
|
||||
"sourceMap": true,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 0,
|
||||
"converge": false,
|
||||
"noAssert": false
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"bindings": "esm",
|
||||
"use": "abort=abort_proc_exit"
|
||||
}
|
||||
}
|
||||
214
plugins/wasm-assemblyscript/assembly/cluster_wrapper.ts
Normal file
214
plugins/wasm-assemblyscript/assembly/cluster_wrapper.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
log,
|
||||
LogLevelValues,
|
||||
get_property,
|
||||
WasmResultValues,
|
||||
} from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
import { getRequestHost } from "./request_wrapper";
|
||||
|
||||
export abstract class Cluster {
|
||||
abstract clusterName(): string;
|
||||
abstract hostName(): string;
|
||||
}
|
||||
|
||||
export class RouteCluster extends Cluster {
|
||||
host: string;
|
||||
constructor(host: string = "") {
|
||||
super();
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
let result = get_property("cluster_name");
|
||||
if (result.status != WasmResultValues.Ok) {
|
||||
log(LogLevelValues.error, "get route cluster failed");
|
||||
return "";
|
||||
}
|
||||
return String.UTF8.decode(result.returnValue);
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return getRequestHost();
|
||||
}
|
||||
}
|
||||
|
||||
export class K8sCluster extends Cluster {
|
||||
serviceName: string;
|
||||
namespace: string;
|
||||
port: i64;
|
||||
version: string;
|
||||
host: string;
|
||||
|
||||
constructor(
|
||||
serviceName: string,
|
||||
namespace: string,
|
||||
port: i64,
|
||||
version: string = "",
|
||||
host: string = ""
|
||||
) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.namespace = namespace;
|
||||
this.port = port;
|
||||
this.version = version;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
let namespace = this.namespace != "" ? this.namespace : "default";
|
||||
return `outbound|${this.port}|${this.version}|${this.serviceName}.${namespace}.svc.cluster.local`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return `${this.serviceName}.${this.namespace}.svc.cluster.local`;
|
||||
}
|
||||
}
|
||||
|
||||
export class NacosCluster extends Cluster {
|
||||
serviceName: string;
|
||||
group: string;
|
||||
namespaceID: string;
|
||||
port: i64;
|
||||
isExtRegistry: boolean;
|
||||
version: string;
|
||||
host: string;
|
||||
|
||||
constructor(
|
||||
serviceName: string,
|
||||
namespaceID: string,
|
||||
port: i64,
|
||||
// use DEFAULT-GROUP by default
|
||||
group: string = "DEFAULT-GROUP",
|
||||
// set true if use edas/sae registry
|
||||
isExtRegistry: boolean = false,
|
||||
version: string = "",
|
||||
host: string = ""
|
||||
) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.group = group.replace("_", "-");
|
||||
this.namespaceID = namespaceID;
|
||||
this.port = port;
|
||||
this.isExtRegistry = isExtRegistry;
|
||||
this.version = version;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
let tail = "nacos" + (this.isExtRegistry ? "-ext" : "");
|
||||
return `outbound|${this.port}|${this.version}|${this.serviceName}.${this.group}.${this.namespaceID}.${tail}`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return this.serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticIpCluster extends Cluster {
|
||||
serviceName: string;
|
||||
port: i64;
|
||||
host: string;
|
||||
|
||||
constructor(serviceName: string, port: i64, host: string = "") {
|
||||
super()
|
||||
this.serviceName = serviceName;
|
||||
this.port = port;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
return `outbound|${this.port}||${this.serviceName}.static`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return this.serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
export class DnsCluster extends Cluster {
|
||||
serviceName: string;
|
||||
domain: string;
|
||||
port: i64;
|
||||
|
||||
constructor(serviceName: string, domain: string, port: i64) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.domain = domain;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
return `outbound|${this.port}||${this.serviceName}.dns`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
return this.domain;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsulCluster extends Cluster {
|
||||
serviceName: string;
|
||||
datacenter: string;
|
||||
port: i64;
|
||||
host: string;
|
||||
|
||||
constructor(
|
||||
serviceName: string,
|
||||
datacenter: string,
|
||||
port: i64,
|
||||
host: string = ""
|
||||
) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.datacenter = datacenter;
|
||||
this.port = port;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
return `outbound|${this.port}||${this.serviceName}.${this.datacenter}.consul`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return this.serviceName;
|
||||
}
|
||||
}
|
||||
|
||||
export class FQDNCluster extends Cluster {
|
||||
fqdn: string;
|
||||
host: string;
|
||||
port: i64;
|
||||
|
||||
constructor(fqdn: string, port: i64, host: string = "") {
|
||||
super();
|
||||
this.fqdn = fqdn;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
clusterName(): string {
|
||||
return `outbound|${this.port}||${this.fqdn}`;
|
||||
}
|
||||
|
||||
hostName(): string {
|
||||
if (this.host != "") {
|
||||
return this.host;
|
||||
}
|
||||
return this.fqdn;
|
||||
}
|
||||
}
|
||||
120
plugins/wasm-assemblyscript/assembly/http_wrapper.ts
Normal file
120
plugins/wasm-assemblyscript/assembly/http_wrapper.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
Cluster
|
||||
} from "./cluster_wrapper"
|
||||
|
||||
import {
|
||||
log,
|
||||
LogLevelValues,
|
||||
Headers,
|
||||
HeaderPair,
|
||||
root_context,
|
||||
BufferTypeValues,
|
||||
get_buffer_bytes,
|
||||
BaseContext,
|
||||
stream_context,
|
||||
WasmResultValues,
|
||||
RootContext,
|
||||
ResponseCallBack
|
||||
} from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
|
||||
export interface HttpClient {
|
||||
get(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
head(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
options(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
post(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
put(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
patch(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
delete(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
connect(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
trace(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32): boolean;
|
||||
}
|
||||
|
||||
const methodArrayBuffer: ArrayBuffer = String.UTF8.encode(":method");
|
||||
const pathArrayBuffer: ArrayBuffer = String.UTF8.encode(":path");
|
||||
const authorityArrayBuffer: ArrayBuffer = String.UTF8.encode(":authority");
|
||||
|
||||
const StatusBadGateway: i32 = 502;
|
||||
|
||||
export class ClusterClient {
|
||||
cluster: Cluster;
|
||||
|
||||
constructor(cluster: Cluster) {
|
||||
this.cluster = cluster;
|
||||
}
|
||||
|
||||
private httpCall(method: string, path: string, headers: Headers, body: ArrayBuffer, callback: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
if (root_context == null) {
|
||||
log(LogLevelValues.error, "Root context is null");
|
||||
return false;
|
||||
}
|
||||
for (let i: i32 = headers.length - 1; i >= 0; i--) {
|
||||
const key = String.UTF8.decode(headers[i].key)
|
||||
if ((key == ":method") || (key == ":path") || (key == ":authority")) {
|
||||
headers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
headers.push(new HeaderPair(methodArrayBuffer, String.UTF8.encode(method)));
|
||||
headers.push(new HeaderPair(pathArrayBuffer, String.UTF8.encode(path)));
|
||||
headers.push(new HeaderPair(authorityArrayBuffer, String.UTF8.encode(this.cluster.hostName())));
|
||||
|
||||
const result = (root_context as RootContext).httpCall(this.cluster.clusterName(), headers, body, [], timeoutMillisecond, root_context as BaseContext, callback,
|
||||
(_origin_context: BaseContext, _numHeaders: u32, body_size: usize, _trailers: u32, callback: ResponseCallBack): void => {
|
||||
const respBody = get_buffer_bytes(BufferTypeValues.HttpCallResponseBody, 0, body_size as u32);
|
||||
const respHeaders = stream_context.headers.http_callback.get_headers()
|
||||
let code = StatusBadGateway;
|
||||
let headers = new Array<HeaderPair>();
|
||||
for (let i = 0; i < respHeaders.length; i++) {
|
||||
const h = respHeaders[i];
|
||||
if (String.UTF8.decode(h.key) == ":status") {
|
||||
code = <i32>parseInt(String.UTF8.decode(h.value))
|
||||
}
|
||||
headers.push(new HeaderPair(h.key, h.value));
|
||||
}
|
||||
log(LogLevelValues.debug, `http call end, code: ${code}, body: ${String.UTF8.decode(respBody)}`)
|
||||
callback(code, headers, respBody);
|
||||
})
|
||||
log(LogLevelValues.debug, `http call start, cluster: ${this.cluster.clusterName()}, method: ${method}, path: ${path}, body: ${String.UTF8.decode(body)}, timeout: ${timeoutMillisecond}`)
|
||||
if (result != WasmResultValues.Ok) {
|
||||
log(LogLevelValues.error, `http call failed, result: ${result}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
get(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("GET", path, headers, new ArrayBuffer(0), cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
head(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("HEAD", path, headers, new ArrayBuffer(0), cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
options(path: string, headers: Headers, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("OPTIONS", path, headers, new ArrayBuffer(0), cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
post(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("POST", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
put(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("PUT", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
patch(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("PATCH", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
delete(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("DELETE", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
connect(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("CONNECT", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
|
||||
trace(path: string, headers: Headers, body: ArrayBuffer, cb: ResponseCallBack, timeoutMillisecond: u32 = 500): boolean {
|
||||
return this.httpCall("TRACE", path, headers, body, cb, timeoutMillisecond);
|
||||
}
|
||||
}
|
||||
18
plugins/wasm-assemblyscript/assembly/index.ts
Normal file
18
plugins/wasm-assemblyscript/assembly/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export {RouteCluster,
|
||||
K8sCluster,
|
||||
NacosCluster,
|
||||
ConsulCluster,
|
||||
FQDNCluster,
|
||||
StaticIpCluster} from "./cluster_wrapper"
|
||||
export {HttpClient,
|
||||
ClusterClient} from "./http_wrapper"
|
||||
export {Log} from "./log_wrapper"
|
||||
export {SetCtx,
|
||||
HttpContext,
|
||||
ParseConfigBy,
|
||||
ProcessRequestBodyBy,
|
||||
ProcessRequestHeadersBy,
|
||||
ProcessResponseBodyBy,
|
||||
ProcessResponseHeadersBy,
|
||||
Logger, RegisteTickFunc} from "./plugin_wrapper"
|
||||
export {ParseResult} from "./rule_matcher"
|
||||
66
plugins/wasm-assemblyscript/assembly/log_wrapper.ts
Normal file
66
plugins/wasm-assemblyscript/assembly/log_wrapper.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { log, LogLevelValues } from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
|
||||
enum LogLevel {
|
||||
Trace = 0,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Critical,
|
||||
}
|
||||
|
||||
export class Log {
|
||||
private pluginName: string;
|
||||
|
||||
constructor(pluginName: string) {
|
||||
this.pluginName = pluginName;
|
||||
}
|
||||
|
||||
private log(level: LogLevel, msg: string): void {
|
||||
let formattedMsg = `[${this.pluginName}] ${msg}`;
|
||||
switch (level) {
|
||||
case LogLevel.Trace:
|
||||
log(LogLevelValues.trace, formattedMsg);
|
||||
break;
|
||||
case LogLevel.Debug:
|
||||
log(LogLevelValues.debug, formattedMsg);
|
||||
break;
|
||||
case LogLevel.Info:
|
||||
log(LogLevelValues.info, formattedMsg);
|
||||
break;
|
||||
case LogLevel.Warn:
|
||||
log(LogLevelValues.warn, formattedMsg);
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
log(LogLevelValues.error, formattedMsg);
|
||||
break;
|
||||
case LogLevel.Critical:
|
||||
log(LogLevelValues.critical, formattedMsg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Trace(msg: string): void {
|
||||
this.log(LogLevel.Trace, msg);
|
||||
}
|
||||
|
||||
public Debug(msg: string): void {
|
||||
this.log(LogLevel.Debug, msg);
|
||||
}
|
||||
|
||||
public Info(msg: string): void {
|
||||
this.log(LogLevel.Info, msg);
|
||||
}
|
||||
|
||||
public Warn(msg: string): void {
|
||||
this.log(LogLevel.Warn, msg);
|
||||
}
|
||||
|
||||
public Error(msg: string): void {
|
||||
this.log(LogLevel.Error, msg);
|
||||
}
|
||||
|
||||
public Critical(msg: string): void {
|
||||
this.log(LogLevel.Critical, msg);
|
||||
}
|
||||
}
|
||||
445
plugins/wasm-assemblyscript/assembly/plugin_wrapper.ts
Normal file
445
plugins/wasm-assemblyscript/assembly/plugin_wrapper.ts
Normal file
@@ -0,0 +1,445 @@
|
||||
import { Log } from "./log_wrapper";
|
||||
import {
|
||||
Context,
|
||||
FilterHeadersStatusValues,
|
||||
RootContext,
|
||||
setRootContext,
|
||||
proxy_set_effective_context,
|
||||
log,
|
||||
LogLevelValues,
|
||||
FilterDataStatusValues,
|
||||
get_buffer_bytes,
|
||||
BufferTypeValues,
|
||||
set_tick_period_milliseconds,
|
||||
get_current_time_nanoseconds
|
||||
} from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
import {
|
||||
getRequestHost,
|
||||
getRequestMethod,
|
||||
getRequestPath,
|
||||
getRequestScheme,
|
||||
isBinaryRequestBody,
|
||||
} from "./request_wrapper";
|
||||
import { RuleMatcher, ParseResult } from "./rule_matcher";
|
||||
import { JSON } from "assemblyscript-json/assembly";
|
||||
|
||||
export function SetCtx<PluginConfig>(
|
||||
pluginName: string,
|
||||
setFuncs: usize[] = []
|
||||
): void {
|
||||
const rootContextId = 1
|
||||
setRootContext(new CommonRootCtx<PluginConfig>(rootContextId, pluginName, setFuncs));
|
||||
}
|
||||
|
||||
export interface HttpContext {
|
||||
Scheme(): string;
|
||||
Host(): string;
|
||||
Path(): string;
|
||||
Method(): string;
|
||||
SetContext(key: string, value: usize): void;
|
||||
GetContext(key: string): usize;
|
||||
DontReadRequestBody(): void;
|
||||
DontReadResponseBody(): void;
|
||||
}
|
||||
|
||||
type ParseConfigFunc<PluginConfig> = (
|
||||
json: JSON.Obj,
|
||||
) => ParseResult<PluginConfig>;
|
||||
type OnHttpHeadersFunc<PluginConfig> = (
|
||||
context: HttpContext,
|
||||
config: PluginConfig,
|
||||
) => FilterHeadersStatusValues;
|
||||
type OnHttpBodyFunc<PluginConfig> = (
|
||||
context: HttpContext,
|
||||
config: PluginConfig,
|
||||
body: ArrayBuffer,
|
||||
) => FilterDataStatusValues;
|
||||
|
||||
|
||||
export var Logger: Log = new Log("");
|
||||
|
||||
class CommonRootCtx<PluginConfig> extends RootContext {
|
||||
pluginName: string;
|
||||
hasCustomConfig: boolean;
|
||||
ruleMatcher: RuleMatcher<PluginConfig>;
|
||||
parseConfig: ParseConfigFunc<PluginConfig> | null;
|
||||
onHttpRequestHeaders: OnHttpHeadersFunc<PluginConfig> | null;
|
||||
onHttpRequestBody: OnHttpBodyFunc<PluginConfig> | null;
|
||||
onHttpResponseHeaders: OnHttpHeadersFunc<PluginConfig> | null;
|
||||
onHttpResponseBody: OnHttpBodyFunc<PluginConfig> | null;
|
||||
onTickFuncs: Array<TickFuncEntry>;
|
||||
|
||||
constructor(context_id: u32, pluginName: string, setFuncs: usize[]) {
|
||||
super(context_id);
|
||||
this.pluginName = pluginName;
|
||||
Logger = new Log(pluginName);
|
||||
this.hasCustomConfig = true;
|
||||
this.onHttpRequestHeaders = null;
|
||||
this.onHttpRequestBody = null;
|
||||
this.onHttpResponseHeaders = null;
|
||||
this.onHttpResponseBody = null;
|
||||
this.parseConfig = null;
|
||||
this.ruleMatcher = new RuleMatcher<PluginConfig>();
|
||||
this.onTickFuncs = new Array<TickFuncEntry>();
|
||||
for (let i = 0; i < setFuncs.length; i++) {
|
||||
changetype<Closure<PluginConfig>>(setFuncs[i]).lambdaFn(
|
||||
setFuncs[i],
|
||||
this
|
||||
);
|
||||
}
|
||||
if (this.parseConfig == null) {
|
||||
this.hasCustomConfig = false;
|
||||
this.parseConfig = (json: JSON.Obj): ParseResult<PluginConfig> =>{ return new ParseResult<PluginConfig>(null, true); };
|
||||
}
|
||||
}
|
||||
|
||||
createContext(context_id: u32): Context {
|
||||
return new CommonCtx<PluginConfig>(context_id, this);
|
||||
}
|
||||
|
||||
onConfigure(configuration_size: u32): boolean {
|
||||
super.onConfigure(configuration_size);
|
||||
const data = this.getConfiguration();
|
||||
let jsonData: JSON.Obj = new JSON.Obj();
|
||||
if (data == "{}") {
|
||||
if (this.hasCustomConfig) {
|
||||
log(LogLevelValues.warn, "config is empty, but has ParseConfigFunc");
|
||||
}
|
||||
} else {
|
||||
const parseData = JSON.parse(data);
|
||||
if (parseData.isObj) {
|
||||
jsonData = changetype<JSON.Obj>(JSON.parse(data));
|
||||
} else {
|
||||
log(LogLevelValues.error, "parse json data failed")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.ruleMatcher.parseRuleConfig(jsonData, this.parseConfig as ParseConfigFunc<PluginConfig>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (globalOnTickFuncs.length > 0) {
|
||||
this.onTickFuncs = globalOnTickFuncs;
|
||||
set_tick_period_milliseconds(100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onTick(): void {
|
||||
for (let i = 0; i < this.onTickFuncs.length; i++) {
|
||||
const tickFuncEntry = this.onTickFuncs[i];
|
||||
const now = getCurrentTimeMilliseconds();
|
||||
if (tickFuncEntry.lastExecuted + tickFuncEntry.tickPeriod <= now) {
|
||||
tickFuncEntry.tickFunc();
|
||||
tickFuncEntry.lastExecuted = getCurrentTimeMilliseconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentTimeMilliseconds(): u64 {
|
||||
return get_current_time_nanoseconds() / 1000000;
|
||||
}
|
||||
|
||||
class TickFuncEntry {
|
||||
lastExecuted: u64;
|
||||
tickPeriod: u64;
|
||||
tickFunc: () => void;
|
||||
|
||||
constructor(lastExecuted: u64, tickPeriod: u64, tickFunc: () => void) {
|
||||
this.lastExecuted = lastExecuted;
|
||||
this.tickPeriod = tickPeriod;
|
||||
this.tickFunc = tickFunc;
|
||||
}
|
||||
}
|
||||
|
||||
var globalOnTickFuncs = new Array<TickFuncEntry>();
|
||||
|
||||
export function RegisteTickFunc(tickPeriod: i64, tickFunc: () => void): void {
|
||||
globalOnTickFuncs.push(new TickFuncEntry(0, tickPeriod, tickFunc));
|
||||
}
|
||||
|
||||
class Closure<PluginConfig> {
|
||||
lambdaFn: (closure: usize, ctx: CommonRootCtx<PluginConfig>) => void;
|
||||
parseConfigFunc: ParseConfigFunc<PluginConfig> | null;
|
||||
onHttpHeadersFunc: OnHttpHeadersFunc<PluginConfig> | null;
|
||||
OnHttpBodyFunc: OnHttpBodyFunc<PluginConfig> | null;
|
||||
|
||||
constructor(
|
||||
lambdaFn: (closure: usize, ctx: CommonRootCtx<PluginConfig>) => void
|
||||
) {
|
||||
this.lambdaFn = lambdaFn;
|
||||
this.parseConfigFunc = null;
|
||||
this.onHttpHeadersFunc = null;
|
||||
this.OnHttpBodyFunc = null;
|
||||
}
|
||||
|
||||
setParseConfigFunc(f: ParseConfigFunc<PluginConfig>): void {
|
||||
this.parseConfigFunc = f;
|
||||
}
|
||||
|
||||
setHttpHeadersFunc(f: OnHttpHeadersFunc<PluginConfig>): void {
|
||||
this.onHttpHeadersFunc = f;
|
||||
}
|
||||
|
||||
setHttpBodyFunc(f: OnHttpBodyFunc<PluginConfig>): void {
|
||||
this.OnHttpBodyFunc = f;
|
||||
}
|
||||
}
|
||||
|
||||
export function ParseConfigBy<PluginConfig>(
|
||||
f: ParseConfigFunc<PluginConfig>
|
||||
): usize {
|
||||
const lambdaFn = function (
|
||||
closure: usize,
|
||||
ctx: CommonRootCtx<PluginConfig>
|
||||
): void {
|
||||
const f = changetype<Closure<PluginConfig>>(closure).parseConfigFunc;
|
||||
if (f != null) {
|
||||
ctx.parseConfig = f;
|
||||
}
|
||||
};
|
||||
const closure = new Closure<PluginConfig>(lambdaFn);
|
||||
closure.setParseConfigFunc(f);
|
||||
return changetype<usize>(closure);
|
||||
}
|
||||
|
||||
export function ProcessRequestHeadersBy<PluginConfig>(
|
||||
f: OnHttpHeadersFunc<PluginConfig>
|
||||
): usize {
|
||||
const lambdaFn = function (
|
||||
closure: usize,
|
||||
ctx: CommonRootCtx<PluginConfig>
|
||||
): void {
|
||||
const f = changetype<Closure<PluginConfig>>(closure).onHttpHeadersFunc;
|
||||
if (f != null) {
|
||||
ctx.onHttpRequestHeaders = f;
|
||||
}
|
||||
};
|
||||
const closure = new Closure<PluginConfig>(lambdaFn);
|
||||
closure.setHttpHeadersFunc(f);
|
||||
return changetype<usize>(closure);
|
||||
}
|
||||
|
||||
export function ProcessRequestBodyBy<PluginConfig>(
|
||||
f: OnHttpBodyFunc<PluginConfig>
|
||||
): usize {
|
||||
const lambdaFn = function (
|
||||
closure: usize,
|
||||
ctx: CommonRootCtx<PluginConfig>
|
||||
): void {
|
||||
const f = changetype<Closure<PluginConfig>>(closure).OnHttpBodyFunc;
|
||||
if (f != null) {
|
||||
ctx.onHttpRequestBody = f;
|
||||
}
|
||||
};
|
||||
const closure = new Closure<PluginConfig>(lambdaFn);
|
||||
closure.setHttpBodyFunc(f);
|
||||
return changetype<usize>(closure);
|
||||
}
|
||||
|
||||
export function ProcessResponseHeadersBy<PluginConfig>(
|
||||
f: OnHttpHeadersFunc<PluginConfig>
|
||||
): usize {
|
||||
const lambdaFn = function (
|
||||
closure: usize,
|
||||
ctx: CommonRootCtx<PluginConfig>
|
||||
): void {
|
||||
const f = changetype<Closure<PluginConfig>>(closure).onHttpHeadersFunc;
|
||||
if (f != null) {
|
||||
ctx.onHttpResponseHeaders = f;
|
||||
}
|
||||
};
|
||||
const closure = new Closure<PluginConfig>(lambdaFn);
|
||||
closure.setHttpHeadersFunc(f);
|
||||
return changetype<usize>(closure);
|
||||
}
|
||||
|
||||
export function ProcessResponseBodyBy<PluginConfig>(
|
||||
f: OnHttpBodyFunc<PluginConfig>
|
||||
): usize {
|
||||
const lambdaFn = function (
|
||||
closure: usize,
|
||||
ctx: CommonRootCtx<PluginConfig>
|
||||
): void {
|
||||
const f = changetype<Closure<PluginConfig>>(closure).OnHttpBodyFunc;
|
||||
if (f != null) {
|
||||
ctx.onHttpResponseBody = f;
|
||||
}
|
||||
};
|
||||
const closure = new Closure<PluginConfig>(lambdaFn);
|
||||
closure.setHttpBodyFunc(f);
|
||||
return changetype<usize>(closure);
|
||||
}
|
||||
|
||||
class CommonCtx<PluginConfig> extends Context implements HttpContext {
|
||||
commonRootCtx: CommonRootCtx<PluginConfig>;
|
||||
config: PluginConfig |null;
|
||||
needRequestBody: boolean;
|
||||
needResponseBody: boolean;
|
||||
requestBodySize: u32;
|
||||
responseBodySize: u32;
|
||||
contextID: u32;
|
||||
userContext: Map<string, usize>;
|
||||
|
||||
constructor(context_id: u32, root_context: CommonRootCtx<PluginConfig>) {
|
||||
super(context_id, root_context);
|
||||
this.userContext = new Map<string, usize>();
|
||||
this.commonRootCtx = root_context;
|
||||
this.contextID = context_id;
|
||||
this.requestBodySize = 0;
|
||||
this.responseBodySize = 0;
|
||||
this.config = null
|
||||
if (this.commonRootCtx.onHttpRequestHeaders != null) {
|
||||
this.needResponseBody = true;
|
||||
} else {
|
||||
this.needResponseBody = false;
|
||||
}
|
||||
if (this.commonRootCtx.onHttpRequestBody != null) {
|
||||
this.needRequestBody = true;
|
||||
} else {
|
||||
this.needRequestBody = false;
|
||||
}
|
||||
}
|
||||
|
||||
SetContext(key: string, value: usize): void {
|
||||
this.userContext.set(key, value);
|
||||
}
|
||||
|
||||
GetContext(key: string): usize {
|
||||
return this.userContext.get(key);
|
||||
}
|
||||
|
||||
Scheme(): string {
|
||||
proxy_set_effective_context(this.contextID);
|
||||
return getRequestScheme();
|
||||
}
|
||||
|
||||
Host(): string {
|
||||
proxy_set_effective_context(this.contextID);
|
||||
return getRequestHost();
|
||||
}
|
||||
|
||||
Path(): string {
|
||||
proxy_set_effective_context(this.contextID);
|
||||
return getRequestPath();
|
||||
}
|
||||
|
||||
Method(): string {
|
||||
proxy_set_effective_context(this.contextID);
|
||||
return getRequestMethod();
|
||||
}
|
||||
|
||||
DontReadRequestBody(): void {
|
||||
this.needRequestBody = false;
|
||||
}
|
||||
|
||||
DontReadResponseBody(): void {
|
||||
this.needResponseBody = false;
|
||||
}
|
||||
|
||||
onRequestHeaders(_a: u32, _end_of_stream: boolean): FilterHeadersStatusValues {
|
||||
const parseResult = this.commonRootCtx.ruleMatcher.getMatchConfig();
|
||||
if (parseResult.success == false) {
|
||||
log(LogLevelValues.error, "get match config failed");
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
this.config = parseResult.pluginConfig;
|
||||
|
||||
if (isBinaryRequestBody()) {
|
||||
this.needRequestBody = false;
|
||||
}
|
||||
|
||||
if (this.commonRootCtx.onHttpRequestHeaders == null) {
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
return this.commonRootCtx.onHttpRequestHeaders(
|
||||
this,
|
||||
this.config as PluginConfig
|
||||
);
|
||||
}
|
||||
|
||||
onRequestBody(
|
||||
body_buffer_length: usize,
|
||||
end_of_stream: boolean
|
||||
): FilterDataStatusValues {
|
||||
if (this.config == null || !this.needRequestBody) {
|
||||
return FilterDataStatusValues.Continue;
|
||||
}
|
||||
|
||||
if (this.commonRootCtx.onHttpRequestBody == null) {
|
||||
return FilterDataStatusValues.Continue;
|
||||
}
|
||||
this.requestBodySize += body_buffer_length as u32;
|
||||
|
||||
if (!end_of_stream) {
|
||||
return FilterDataStatusValues.StopIterationAndBuffer;
|
||||
}
|
||||
|
||||
const body = get_buffer_bytes(
|
||||
BufferTypeValues.HttpRequestBody,
|
||||
0,
|
||||
this.requestBodySize
|
||||
);
|
||||
|
||||
return this.commonRootCtx.onHttpRequestBody(
|
||||
this,
|
||||
this.config as PluginConfig,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
onResponseHeaders(_a: u32, _end_of_stream: bool): FilterHeadersStatusValues {
|
||||
if (this.config == null) {
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
|
||||
if (isBinaryRequestBody()) {
|
||||
this.needResponseBody = false;
|
||||
}
|
||||
|
||||
if (this.commonRootCtx.onHttpResponseHeaders == null) {
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
|
||||
return this.commonRootCtx.onHttpResponseHeaders(
|
||||
this,
|
||||
this.config as PluginConfig
|
||||
);
|
||||
}
|
||||
|
||||
onResponseBody(
|
||||
body_buffer_length: usize,
|
||||
end_of_stream: bool
|
||||
): FilterDataStatusValues {
|
||||
if (this.config == null) {
|
||||
return FilterDataStatusValues.Continue;
|
||||
}
|
||||
|
||||
if (this.commonRootCtx.onHttpResponseBody == null) {
|
||||
return FilterDataStatusValues.Continue;
|
||||
}
|
||||
|
||||
if (!this.needResponseBody) {
|
||||
return FilterDataStatusValues.Continue;
|
||||
}
|
||||
|
||||
this.responseBodySize += body_buffer_length as u32;
|
||||
|
||||
if (!end_of_stream) {
|
||||
return FilterDataStatusValues.StopIterationAndBuffer;
|
||||
}
|
||||
const body = get_buffer_bytes(
|
||||
BufferTypeValues.HttpResponseBody,
|
||||
0,
|
||||
this.responseBodySize
|
||||
);
|
||||
|
||||
return this.commonRootCtx.onHttpResponseBody(
|
||||
this,
|
||||
this.config as PluginConfig,
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
65
plugins/wasm-assemblyscript/assembly/request_wrapper.ts
Normal file
65
plugins/wasm-assemblyscript/assembly/request_wrapper.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
stream_context,
|
||||
log,
|
||||
LogLevelValues
|
||||
} from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
|
||||
export function getRequestScheme(): string {
|
||||
let scheme: string = stream_context.headers.request.get(":scheme");
|
||||
if (scheme == "") {
|
||||
log(LogLevelValues.error, "Parse request scheme failed");
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
|
||||
export function getRequestHost(): string {
|
||||
let host: string = stream_context.headers.request.get(":authority");
|
||||
if (host == "") {
|
||||
log(LogLevelValues.error, "Parse request host failed");
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
export function getRequestPath(): string {
|
||||
let path: string = stream_context.headers.request.get(":path");
|
||||
if (path == "") {
|
||||
log(LogLevelValues.error, "Parse request path failed");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function getRequestMethod(): string {
|
||||
let method: string = stream_context.headers.request.get(":method");
|
||||
if (method == "") {
|
||||
log(LogLevelValues.error, "Parse request method failed");
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
export function isBinaryRequestBody(): boolean {
|
||||
let contentType: string = stream_context.headers.request.get("content-type");
|
||||
if (contentType != "" && (contentType.includes("octet-stream") || contentType.includes("grpc"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let encoding: string = stream_context.headers.request.get("content-encoding");
|
||||
if (encoding != "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isBinaryResponseBody(): boolean {
|
||||
let contentType: string = stream_context.headers.response.get("content-type");
|
||||
if (contentType != "" && (contentType.includes("octet-stream") || contentType.includes("grpc"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let encoding: string = stream_context.headers.response.get("content-encoding");
|
||||
if (encoding != "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
346
plugins/wasm-assemblyscript/assembly/rule_matcher.ts
Normal file
346
plugins/wasm-assemblyscript/assembly/rule_matcher.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
import { getRequestHost } from "./request_wrapper";
|
||||
import {
|
||||
get_property,
|
||||
LogLevelValues,
|
||||
log,
|
||||
WasmResultValues,
|
||||
} from "@higress/proxy-wasm-assemblyscript-sdk/assembly";
|
||||
import { JSON } from "assemblyscript-json/assembly";
|
||||
|
||||
enum Category {
|
||||
Route,
|
||||
Host,
|
||||
RoutePrefix,
|
||||
Service
|
||||
}
|
||||
|
||||
enum MatchType {
|
||||
Prefix,
|
||||
Exact,
|
||||
Suffix,
|
||||
}
|
||||
|
||||
const RULES_KEY: string = "_rules_";
|
||||
const MATCH_ROUTE_KEY: string = "_match_route_";
|
||||
const MATCH_DOMAIN_KEY: string = "_match_domain_";
|
||||
const MATCH_SERVICE_KEY: string = "_match_service_";
|
||||
const MATCH_ROUTE_PREFIX_KEY: string = "_match_route_prefix_"
|
||||
|
||||
class HostMatcher {
|
||||
matchType: MatchType;
|
||||
host: string;
|
||||
|
||||
constructor(matchType: MatchType, host: string) {
|
||||
this.matchType = matchType;
|
||||
this.host = host;
|
||||
}
|
||||
}
|
||||
|
||||
class RuleConfig<PluginConfig> {
|
||||
category: Category;
|
||||
routes!: Map<string, boolean>;
|
||||
services!: Map<string, boolean>;
|
||||
routePrefixs!: Map<string, boolean>;
|
||||
hosts!: Array<HostMatcher>;
|
||||
config: PluginConfig | null;
|
||||
|
||||
constructor() {
|
||||
this.category = Category.Route;
|
||||
this.config = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseResult<PluginConfig> {
|
||||
pluginConfig: PluginConfig | null;
|
||||
success: boolean;
|
||||
constructor(pluginConfig: PluginConfig | null, success: boolean) {
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.success = success;
|
||||
}
|
||||
}
|
||||
|
||||
export class RuleMatcher<PluginConfig> {
|
||||
ruleConfig: Array<RuleConfig<PluginConfig>>;
|
||||
globalConfig: PluginConfig | null;
|
||||
hasGlobalConfig: boolean;
|
||||
|
||||
constructor() {
|
||||
this.ruleConfig = new Array<RuleConfig<PluginConfig>>();
|
||||
this.globalConfig = null;
|
||||
this.hasGlobalConfig = false;
|
||||
}
|
||||
|
||||
getMatchConfig(): ParseResult<PluginConfig> {
|
||||
const host = getRequestHost();
|
||||
if (host == "") {
|
||||
return new ParseResult<PluginConfig>(null, false);
|
||||
}
|
||||
let result = get_property("route_name");
|
||||
if (result.status != WasmResultValues.Ok && result.status != WasmResultValues.NotFound) {
|
||||
return new ParseResult<PluginConfig>(null, false);
|
||||
}
|
||||
const routeName = String.UTF8.decode(result.returnValue);
|
||||
|
||||
result = get_property("cluster_name");
|
||||
if (result.status != WasmResultValues.Ok && result.status != WasmResultValues.NotFound) {
|
||||
return new ParseResult<PluginConfig>(null, false);
|
||||
}
|
||||
const serviceName = String.UTF8.decode(result.returnValue);
|
||||
|
||||
for (let i = 0; i < this.ruleConfig.length; i++) {
|
||||
const rule = this.ruleConfig[i];
|
||||
// category == Host
|
||||
if (rule.category == Category.Host) {
|
||||
if (this.hostMatch(rule, host)) {
|
||||
log(LogLevelValues.debug, "getMatchConfig: match host " + host);
|
||||
return new ParseResult<PluginConfig>(rule.config, true);
|
||||
}
|
||||
}
|
||||
// category == Route
|
||||
if (rule.category == Category.Route) {
|
||||
if (rule.routes.has(routeName)) {
|
||||
log(LogLevelValues.debug, "getMatchConfig: match route " + routeName);
|
||||
return new ParseResult<PluginConfig>(rule.config, true);
|
||||
}
|
||||
}
|
||||
// category == RoutePrefix
|
||||
if (rule.category == Category.RoutePrefix) {
|
||||
for (let i = 0; i < rule.routePrefixs.keys().length; i++) {
|
||||
const routePrefix = rule.routePrefixs.keys()[i];
|
||||
if (routeName.startsWith(routePrefix)) {
|
||||
return new ParseResult<PluginConfig>(rule.config, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// category == Cluster
|
||||
if (this.serviceMatch(rule, serviceName)) {
|
||||
return new ParseResult<PluginConfig>(rule.config, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasGlobalConfig) {
|
||||
return new ParseResult<PluginConfig>(this.globalConfig, true);
|
||||
}
|
||||
return new ParseResult<PluginConfig>(null, false);
|
||||
}
|
||||
|
||||
parseRuleConfig(
|
||||
config: JSON.Obj,
|
||||
parsePluginConfig: (json: JSON.Obj) => ParseResult<PluginConfig>
|
||||
): boolean {
|
||||
const obj = config;
|
||||
let keyCount = obj.keys.length;
|
||||
if (keyCount == 0) {
|
||||
this.hasGlobalConfig = true;
|
||||
const parseResult = parsePluginConfig(config);
|
||||
if (parseResult.success) {
|
||||
this.globalConfig = parseResult.pluginConfig;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let rules: JSON.Arr | null = null;
|
||||
if (obj.has(RULES_KEY)) {
|
||||
rules = obj.getArr(RULES_KEY);
|
||||
keyCount--;
|
||||
}
|
||||
|
||||
if (keyCount > 0) {
|
||||
const parseResult = parsePluginConfig(config);
|
||||
if (parseResult.success) {
|
||||
this.globalConfig = parseResult.pluginConfig;
|
||||
this.hasGlobalConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rules) {
|
||||
if (this.hasGlobalConfig) {
|
||||
return true;
|
||||
}
|
||||
log(LogLevelValues.error, "parse config failed, no valid rules; global config parse error");
|
||||
return false;
|
||||
}
|
||||
|
||||
const rulesArray = rules.valueOf();
|
||||
for (let i = 0; i < rulesArray.length; i++) {
|
||||
if (!rulesArray[i].isObj) {
|
||||
log(LogLevelValues.error, "parse rule failed, rules must be an array of objects");
|
||||
continue;
|
||||
}
|
||||
const ruleJson = changetype<JSON.Obj>(rulesArray[i]);
|
||||
const rule = new RuleConfig<PluginConfig>();
|
||||
const parseResult = parsePluginConfig(ruleJson);
|
||||
if (parseResult.success) {
|
||||
rule.config = parseResult.pluginConfig;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
rule.routes = this.parseRouteMatchConfig(ruleJson);
|
||||
rule.hosts = this.parseHostMatchConfig(ruleJson);
|
||||
rule.services = this.parseServiceMatchConfig(ruleJson);
|
||||
rule.routePrefixs = this.parseRoutePrefixMatchConfig(ruleJson);
|
||||
|
||||
const noRoute = rule.routes.size == 0;
|
||||
const noHosts = rule.hosts.length == 0;
|
||||
const noServices = rule.services.size == 0;
|
||||
const noRoutePrefixs = rule.routePrefixs.size == 0;
|
||||
|
||||
if ((boolToInt(noRoute) + boolToInt(noHosts) + boolToInt(noServices) + boolToInt(noRoutePrefixs)) != 3) {
|
||||
log(LogLevelValues.error, "there is only one of '_match_route_', '_match_domain_', '_match_service_' and '_match_route_prefix_' can present in configuration.");
|
||||
return false;
|
||||
}
|
||||
if (!noRoute) {
|
||||
rule.category = Category.Route;
|
||||
} else if (!noHosts) {
|
||||
rule.category = Category.Host;
|
||||
} else if (!noServices) {
|
||||
rule.category = Category.Service;
|
||||
} else {
|
||||
rule.category = Category.RoutePrefix;
|
||||
}
|
||||
this.ruleConfig.push(rule);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
parseRouteMatchConfig(config: JSON.Obj): Map<string, boolean> {
|
||||
const keys = config.getArr(MATCH_ROUTE_KEY);
|
||||
const routes = new Map<string, boolean>();
|
||||
if (keys) {
|
||||
const array = keys.valueOf();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const key = array[i].toString();
|
||||
if (key != "") {
|
||||
routes.set(key, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
parseRoutePrefixMatchConfig(config: JSON.Obj): Map<string, boolean> {
|
||||
const keys = config.getArr(MATCH_ROUTE_PREFIX_KEY);
|
||||
const routePrefixs = new Map<string, boolean>();
|
||||
if (keys) {
|
||||
const array = keys.valueOf();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const key = array[i].toString();
|
||||
if (key != "") {
|
||||
routePrefixs.set(key, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return routePrefixs;
|
||||
}
|
||||
|
||||
parseServiceMatchConfig(config: JSON.Obj): Map<string, boolean> {
|
||||
const keys = config.getArr(MATCH_SERVICE_KEY);
|
||||
const clusters = new Map<string, boolean>();
|
||||
if (keys) {
|
||||
const array = keys.valueOf();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const key = array[i].toString();
|
||||
if (key != "") {
|
||||
clusters.set(key, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
|
||||
parseHostMatchConfig(config: JSON.Obj): Array<HostMatcher> {
|
||||
const hostMatchers = new Array<HostMatcher>();
|
||||
const keys = config.getArr(MATCH_DOMAIN_KEY);
|
||||
if (keys !== null) {
|
||||
const array = keys.valueOf();
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const item = array[i].toString(); // Assuming the array has string elements
|
||||
let hostMatcher: HostMatcher;
|
||||
if (item.startsWith("*")) {
|
||||
hostMatcher = new HostMatcher(MatchType.Suffix, item.substr(1));
|
||||
} else if (item.endsWith("*")) {
|
||||
hostMatcher = new HostMatcher(
|
||||
MatchType.Prefix,
|
||||
item.substr(0, item.length - 1)
|
||||
);
|
||||
} else {
|
||||
hostMatcher = new HostMatcher(MatchType.Exact, item);
|
||||
}
|
||||
hostMatchers.push(hostMatcher);
|
||||
}
|
||||
}
|
||||
return hostMatchers;
|
||||
}
|
||||
|
||||
stripPortFromHost(reqHost: string): string {
|
||||
// Port removing code is inspired by
|
||||
// https://github.com/envoyproxy/envoy/blob/v1.17.0/source/common/http/header_utility.cc#L219
|
||||
let portStart: i32 = reqHost.lastIndexOf(":");
|
||||
if (portStart != -1) {
|
||||
// According to RFC3986 v6 address is always enclosed in "[]".
|
||||
// section 3.2.2.
|
||||
let v6EndIndex: i32 = reqHost.lastIndexOf("]");
|
||||
if (v6EndIndex == -1 || v6EndIndex < portStart) {
|
||||
if (portStart + 1 <= reqHost.length) {
|
||||
return reqHost.substring(0, portStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reqHost;
|
||||
}
|
||||
|
||||
hostMatch(rule: RuleConfig<PluginConfig>, reqHost: string): boolean {
|
||||
reqHost = this.stripPortFromHost(reqHost);
|
||||
for (let i = 0; i < rule.hosts.length; i++) {
|
||||
let hostMatch = rule.hosts[i];
|
||||
switch (hostMatch.matchType) {
|
||||
case MatchType.Suffix:
|
||||
if (reqHost.endsWith(hostMatch.host)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MatchType.Prefix:
|
||||
if (reqHost.startsWith(hostMatch.host)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MatchType.Exact:
|
||||
if (reqHost == hostMatch.host) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
serviceMatch(rule: RuleConfig<PluginConfig>, serviceName: string): boolean {
|
||||
const parts = serviceName.split('|');
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
const port = parts[1];
|
||||
const fqdn = parts[3];
|
||||
for (let i = 0; i < rule.services.keys().length; i++) {
|
||||
let configServiceName = rule.services.keys()[i];
|
||||
let colonIndex = configServiceName.lastIndexOf(':');
|
||||
if (colonIndex != -1) {
|
||||
let configFQDN = configServiceName.slice(0, colonIndex);
|
||||
let configPort = configServiceName.slice(colonIndex + 1);
|
||||
if (fqdn == configFQDN && port == configPort) return true;
|
||||
} else if (fqdn == configServiceName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function boolToInt(value: boolean): i32 {
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
6
plugins/wasm-assemblyscript/assembly/tsconfig.json
Normal file
6
plugins/wasm-assemblyscript/assembly/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "assemblyscript/std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# 功能说明
|
||||
`custom-response`插件支持配置自定义的响应,包括自定义 HTTP 应答状态码、HTTP 应答头,以及 HTTP 应答 Body。可以用于 Mock 响应,也可以用于判断特定状态码后给出自定义应答,例如在触发网关限流策略时实现自定义响应。
|
||||
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| status_code | number | 选填 | 200 | 自定义 HTTP 应答状态码 |
|
||||
| headers | array of string | 选填 | - | 自定义 HTTP 应答头,key 和 value 用`=`分隔 |
|
||||
| body | string | 选填 | - | 自定义 HTTP 应答 Body |
|
||||
| enable_on_status | array of number | 选填 | - | 匹配原始状态码,生成自定义响应,不填写时,不判断原始状态码 |
|
||||
|
||||
# 配置示例
|
||||
|
||||
## Mock 应答场景
|
||||
|
||||
```yaml
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
- Hello=World
|
||||
body: "{\"hello\":\"world\"}"
|
||||
|
||||
```
|
||||
|
||||
根据该配置,请求将返回自定义应答如下:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Hello: World
|
||||
Content-Length: 17
|
||||
|
||||
{"hello":"world"}
|
||||
```
|
||||
|
||||
## 触发限流时自定义响应
|
||||
|
||||
```yaml
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 302
|
||||
headers:
|
||||
- Location=https://example.com
|
||||
```
|
||||
|
||||
触发网关限流时一般会返回 `429` 状态码,这时请求将返回自定义应答如下:
|
||||
|
||||
```text
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://example.com
|
||||
```
|
||||
|
||||
从而实现基于浏览器 302 重定向机制,将限流后的用户引导到其他页面,比如可以是一个 CDN 上的静态页面。
|
||||
|
||||
如果希望触发限流时,正常返回其他应答,参考 Mock 应答场景配置相应的字段即可。
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 matchRules 字段进行细粒度规则配置
|
||||
matchRules:
|
||||
# 规则一:按 Ingress 名称匹配生效
|
||||
- ingress:
|
||||
- default/foo
|
||||
- default/bar
|
||||
body: "{\"hello\":\"world\"}"
|
||||
# 规则二:按域名匹配生效
|
||||
- domain:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
body: "{\"errmsg\": \"rate limited\"}"
|
||||
```
|
||||
此例 `ingress` 中指定的 `default/foo` 和 `default/bar` 对应 default 命名空间下名为 foo 和 bar 的 Ingress,当匹配到这两个 Ingress 时,将使用此段配置;
|
||||
此例 `domain` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `matchRules` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"targets": {
|
||||
"debug": {
|
||||
"outFile": "build/debug.wasm",
|
||||
"textFile": "build/debug.wat",
|
||||
"sourceMap": true,
|
||||
"debug": true
|
||||
},
|
||||
"release": {
|
||||
"outFile": "build/release.wasm",
|
||||
"textFile": "build/release.wat",
|
||||
"sourceMap": true,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 0,
|
||||
"converge": false,
|
||||
"noAssert": false,
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"bindings": "esm",
|
||||
"use": "abort=abort_proc_exit"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
export * from "@higress/proxy-wasm-assemblyscript-sdk/assembly/proxy";
|
||||
import { SetCtx, HttpContext, ProcessRequestHeadersBy, Logger, ParseConfigBy, ParseResult, ProcessResponseHeadersBy } from "@higress/wasm-assemblyscript/assembly";
|
||||
import { FilterHeadersStatusValues, Headers, send_http_response, stream_context, HeaderPair } from "@higress/proxy-wasm-assemblyscript-sdk/assembly"
|
||||
import { JSON } from "assemblyscript-json/assembly";
|
||||
|
||||
class CustomResponseConfig {
|
||||
statusCode: u32;
|
||||
headers: Headers;
|
||||
body: ArrayBuffer;
|
||||
enableOnStatus: Array<u32>;
|
||||
contentType: string;
|
||||
constructor() {
|
||||
this.statusCode = 200;
|
||||
this.headers = [];
|
||||
this.body = new ArrayBuffer(0);
|
||||
this.enableOnStatus = [];
|
||||
this.contentType = "text/plain; charset=utf-8";
|
||||
}
|
||||
}
|
||||
|
||||
SetCtx<CustomResponseConfig>(
|
||||
"custom-response",
|
||||
[ParseConfigBy<CustomResponseConfig>(parseConfig),
|
||||
ProcessRequestHeadersBy<CustomResponseConfig>(onHttpRequestHeaders),
|
||||
ProcessResponseHeadersBy<CustomResponseConfig>(onHttpResponseHeaders),])
|
||||
|
||||
function parseConfig(json: JSON.Obj): ParseResult<CustomResponseConfig> {
|
||||
let headersArray = json.getArr("headers");
|
||||
let config = new CustomResponseConfig();
|
||||
if (headersArray != null) {
|
||||
for (let i = 0; i < headersArray.valueOf().length; i++) {
|
||||
let header = headersArray._arr[i];
|
||||
let jsonString = (<JSON.Str>header).toString()
|
||||
let kv = jsonString.split("=")
|
||||
if (kv.length == 2) {
|
||||
let key = kv[0].trim();
|
||||
let value = kv[1].trim();
|
||||
if (key.toLowerCase() == "content-type") {
|
||||
config.contentType = value;
|
||||
} else if (key.toLowerCase() == "content-length") {
|
||||
continue;
|
||||
} else {
|
||||
config.headers.push(new HeaderPair(String.UTF8.encode(key), String.UTF8.encode(value)));
|
||||
}
|
||||
} else {
|
||||
Logger.Error("parse header failed");
|
||||
return new ParseResult<CustomResponseConfig>(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = json.getString("body");
|
||||
if (body != null) {
|
||||
config.body = String.UTF8.encode(body.valueOf());
|
||||
}
|
||||
config.headers.push(new HeaderPair(String.UTF8.encode("content-type"), String.UTF8.encode(config.contentType)));
|
||||
|
||||
let statusCode = json.getInteger("statusCode");
|
||||
if (statusCode != null) {
|
||||
config.statusCode = statusCode.valueOf() as u32;
|
||||
}
|
||||
|
||||
let enableOnStatus = json.getArr("enableOnStatus");
|
||||
|
||||
if (enableOnStatus != null) {
|
||||
for (let i = 0; i < enableOnStatus.valueOf().length; i++) {
|
||||
let status = enableOnStatus._arr[i];
|
||||
if (status.isInteger) {
|
||||
config.enableOnStatus.push((<JSON.Integer>status).valueOf() as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ParseResult<CustomResponseConfig>(config, true);
|
||||
}
|
||||
|
||||
function onHttpRequestHeaders(context: HttpContext, config: CustomResponseConfig): FilterHeadersStatusValues {
|
||||
if (config.enableOnStatus.length != 0) {
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
send_http_response(config.statusCode, "custom-response", config.body, config.headers);
|
||||
return FilterHeadersStatusValues.StopIteration;
|
||||
}
|
||||
|
||||
function onHttpResponseHeaders(context: HttpContext, config: CustomResponseConfig): FilterHeadersStatusValues {
|
||||
let statusCodeStr = stream_context.headers.response.get(":status")
|
||||
if (statusCodeStr == "") {
|
||||
Logger.Error("get http response status code failed");
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
let statusCode = parseInt(statusCodeStr);
|
||||
for (let i = 0; i < config.enableOnStatus.length; i++) {
|
||||
if (statusCode == config.enableOnStatus[i]) {
|
||||
send_http_response(config.statusCode, "custom-response", config.body, config.headers);
|
||||
}
|
||||
}
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "assemblyscript/std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
||||
75
plugins/wasm-assemblyscript/extensions/custom-response/package-lock.json
generated
Normal file
75
plugins/wasm-assemblyscript/extensions/custom-response/package-lock.json
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "custom-response",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "custom-response",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2",
|
||||
"@higress/wasm-assemblyscript": "^0.0.3",
|
||||
"assemblyscript": "^0.27.29",
|
||||
"assemblyscript-json": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@higress/proxy-wasm-assemblyscript-sdk": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@higress/proxy-wasm-assemblyscript-sdk/-/proxy-wasm-assemblyscript-sdk-0.0.2.tgz",
|
||||
"integrity": "sha512-0J1tFJMTE6o37JpGJBLq0wc5kBC/fpbISrP+KFb4bAEeshu6daXzD2P3bAfJXmW+oZdY0WGptTGXWx8pf9Fk+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@higress/wasm-assemblyscript": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@higress/wasm-assemblyscript/-/wasm-assemblyscript-0.0.3.tgz",
|
||||
"integrity": "sha512-D9hTvjAt54WoedNBsYAp9q/mDPWOO9yoGY7yG7Gkgp3KB7O5lHEEu2T6V8K14DpfC8ObSP26EhBcJ6G70JjODg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/assemblyscript": {
|
||||
"version": "0.27.29",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript/-/assemblyscript-0.27.29.tgz",
|
||||
"integrity": "sha512-pH6udb7aE2F0t6cTh+0uCepmucykhMnAmm7k0kkAU3SY7LvpIngEBZWM6p5VCguu4EpmKGwEuZpZbEXzJ/frHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binaryen": "116.0.0-nightly.20240114",
|
||||
"long": "^5.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"asc": "bin/asc.js",
|
||||
"asinit": "bin/asinit.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/assemblyscript"
|
||||
}
|
||||
},
|
||||
"node_modules/assemblyscript-json": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript-json/-/assemblyscript-json-1.1.0.tgz",
|
||||
"integrity": "sha512-UbE8ts8csTWQgd5TnSPN7MRV9NveuHv1bVnKmDLoo/tzjqxkmsZb3lu59Uk8H7SGoqdkDSEE049alx/nHnSdFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binaryen": {
|
||||
"version": "116.0.0-nightly.20240114",
|
||||
"resolved": "https://registry.npmmirror.com/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz",
|
||||
"integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"wasm-opt": "bin/wasm-opt",
|
||||
"wasm2js": "bin/wasm2js"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-5.2.3.tgz",
|
||||
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "custom-response",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node tests",
|
||||
"asbuild:debug": "asc assembly/index.ts --target debug",
|
||||
"asbuild:release": "asc assembly/index.ts --target release",
|
||||
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
|
||||
"start": "npx serve ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"assemblyscript": "^0.27.29",
|
||||
"assemblyscript-json": "^1.1.0",
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2",
|
||||
"@higress/wasm-assemblyscript": "^0.0.3"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./build/release.js",
|
||||
"types": "./build/release.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"targets": {
|
||||
"debug": {
|
||||
"outFile": "build/debug.wasm",
|
||||
"textFile": "build/debug.wat",
|
||||
"sourceMap": true,
|
||||
"debug": true
|
||||
},
|
||||
"release": {
|
||||
"outFile": "build/release.wasm",
|
||||
"textFile": "build/release.wat",
|
||||
"sourceMap": true,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 0,
|
||||
"converge": false,
|
||||
"noAssert": false,
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"bindings": "esm",
|
||||
"use": "abort=abort_proc_exit"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
export * from "@higress/proxy-wasm-assemblyscript-sdk/assembly/proxy";
|
||||
import { SetCtx, HttpContext, ProcessRequestHeadersBy, Logger, ParseResult, ParseConfigBy, RegisteTickFunc, ProcessResponseHeadersBy } from "@higress/wasm-assemblyscript/assembly";
|
||||
import { FilterHeadersStatusValues, send_http_response, stream_context } from "@higress/proxy-wasm-assemblyscript-sdk/assembly"
|
||||
import { JSON } from "assemblyscript-json/assembly";
|
||||
class HelloWorldConfig {
|
||||
}
|
||||
|
||||
SetCtx<HelloWorldConfig>("hello-world",
|
||||
[ParseConfigBy<HelloWorldConfig>(parseConfig),
|
||||
ProcessRequestHeadersBy<HelloWorldConfig>(onHttpRequestHeaders),
|
||||
ProcessResponseHeadersBy<HelloWorldConfig>(onHttpResponseHeaders)
|
||||
])
|
||||
|
||||
function parseConfig(json: JSON.Obj): ParseResult<HelloWorldConfig> {
|
||||
RegisteTickFunc(2000, () => {
|
||||
Logger.Debug("tick 2s");
|
||||
})
|
||||
RegisteTickFunc(5000, () => {
|
||||
Logger.Debug("tick 5s");
|
||||
})
|
||||
return new ParseResult<HelloWorldConfig>(new HelloWorldConfig(), true);
|
||||
}
|
||||
|
||||
class TestContext{
|
||||
value: string
|
||||
constructor(value: string){
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
function onHttpRequestHeaders(context: HttpContext, config: HelloWorldConfig): FilterHeadersStatusValues {
|
||||
stream_context.headers.request.add("hello", "world");
|
||||
Logger.Debug("[hello-world] logger test");
|
||||
context.SetContext("test-set-context", changetype<usize>(new TestContext("value")))
|
||||
send_http_response(200, "hello-world", String.UTF8.encode("[wasm-assemblyscript]hello world"), []);
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
|
||||
function onHttpResponseHeaders(context: HttpContext, config: HelloWorldConfig): FilterHeadersStatusValues {
|
||||
const str = changetype<TestContext>(context.GetContext("test-set-context")).value;
|
||||
Logger.Debug("[hello-world] test-set-context: " + str);
|
||||
return FilterHeadersStatusValues.Continue;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "assemblyscript/std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
||||
75
plugins/wasm-assemblyscript/extensions/hello-world/package-lock.json
generated
Normal file
75
plugins/wasm-assemblyscript/extensions/hello-world/package-lock.json
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "hello-world",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hello-world",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2",
|
||||
"@higress/wasm-assemblyscript": "^0.0.3",
|
||||
"assemblyscript": "^0.27.29",
|
||||
"assemblyscript-json": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@higress/proxy-wasm-assemblyscript-sdk": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@higress/proxy-wasm-assemblyscript-sdk/-/proxy-wasm-assemblyscript-sdk-0.0.2.tgz",
|
||||
"integrity": "sha512-0J1tFJMTE6o37JpGJBLq0wc5kBC/fpbISrP+KFb4bAEeshu6daXzD2P3bAfJXmW+oZdY0WGptTGXWx8pf9Fk+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@higress/wasm-assemblyscript": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@higress/wasm-assemblyscript/-/wasm-assemblyscript-0.0.3.tgz",
|
||||
"integrity": "sha512-D9hTvjAt54WoedNBsYAp9q/mDPWOO9yoGY7yG7Gkgp3KB7O5lHEEu2T6V8K14DpfC8ObSP26EhBcJ6G70JjODg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/assemblyscript": {
|
||||
"version": "0.27.29",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript/-/assemblyscript-0.27.29.tgz",
|
||||
"integrity": "sha512-pH6udb7aE2F0t6cTh+0uCepmucykhMnAmm7k0kkAU3SY7LvpIngEBZWM6p5VCguu4EpmKGwEuZpZbEXzJ/frHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binaryen": "116.0.0-nightly.20240114",
|
||||
"long": "^5.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"asc": "bin/asc.js",
|
||||
"asinit": "bin/asinit.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/assemblyscript"
|
||||
}
|
||||
},
|
||||
"node_modules/assemblyscript-json": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript-json/-/assemblyscript-json-1.1.0.tgz",
|
||||
"integrity": "sha512-UbE8ts8csTWQgd5TnSPN7MRV9NveuHv1bVnKmDLoo/tzjqxkmsZb3lu59Uk8H7SGoqdkDSEE049alx/nHnSdFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binaryen": {
|
||||
"version": "116.0.0-nightly.20240114",
|
||||
"resolved": "https://registry.npmmirror.com/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz",
|
||||
"integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"wasm-opt": "bin/wasm-opt",
|
||||
"wasm2js": "bin/wasm2js"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-5.2.3.tgz",
|
||||
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "hello-world",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node tests",
|
||||
"asbuild:debug": "asc assembly/index.ts --target debug",
|
||||
"asbuild:release": "asc assembly/index.ts --target release",
|
||||
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
|
||||
"start": "npx serve ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"assemblyscript": "^0.27.29",
|
||||
"assemblyscript-json": "^1.1.0",
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2",
|
||||
"@higress/wasm-assemblyscript": "^0.0.3"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./build/release.js",
|
||||
"types": "./build/release.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
75
plugins/wasm-assemblyscript/package-lock.json
generated
Normal file
75
plugins/wasm-assemblyscript/package-lock.json
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@higress/wasm-assemblyscript",
|
||||
"version": "0.0.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@higress/wasm-assemblyscript",
|
||||
"version": "0.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2",
|
||||
"as-uuid": "^0.0.4",
|
||||
"assemblyscript": "^0.27.29",
|
||||
"assemblyscript-json": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@higress/proxy-wasm-assemblyscript-sdk": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@higress/proxy-wasm-assemblyscript-sdk/-/proxy-wasm-assemblyscript-sdk-0.0.2.tgz",
|
||||
"integrity": "sha512-0J1tFJMTE6o37JpGJBLq0wc5kBC/fpbISrP+KFb4bAEeshu6daXzD2P3bAfJXmW+oZdY0WGptTGXWx8pf9Fk+g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/as-uuid": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/as-uuid/-/as-uuid-0.0.4.tgz",
|
||||
"integrity": "sha512-ZHNv0ETSzg5ZD0IWWJVyip/73LWtrWeMmvRi+16xbkpU/nZ0O8EegvgS7bgZ5xRqrUbc2NqZqHOWMOtPqbLrhg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/assemblyscript": {
|
||||
"version": "0.27.29",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript/-/assemblyscript-0.27.29.tgz",
|
||||
"integrity": "sha512-pH6udb7aE2F0t6cTh+0uCepmucykhMnAmm7k0kkAU3SY7LvpIngEBZWM6p5VCguu4EpmKGwEuZpZbEXzJ/frHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binaryen": "116.0.0-nightly.20240114",
|
||||
"long": "^5.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"asc": "bin/asc.js",
|
||||
"asinit": "bin/asinit.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/assemblyscript"
|
||||
}
|
||||
},
|
||||
"node_modules/assemblyscript-json": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/assemblyscript-json/-/assemblyscript-json-1.1.0.tgz",
|
||||
"integrity": "sha512-UbE8ts8csTWQgd5TnSPN7MRV9NveuHv1bVnKmDLoo/tzjqxkmsZb3lu59Uk8H7SGoqdkDSEE049alx/nHnSdFw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binaryen": {
|
||||
"version": "116.0.0-nightly.20240114",
|
||||
"resolved": "https://registry.npmmirror.com/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz",
|
||||
"integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"wasm-opt": "bin/wasm-opt",
|
||||
"wasm2js": "bin/wasm2js"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-5.2.3.tgz",
|
||||
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
37
plugins/wasm-assemblyscript/package.json
Normal file
37
plugins/wasm-assemblyscript/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@higress/wasm-assemblyscript",
|
||||
"version": "0.0.4",
|
||||
"main": "assembly/index.ts",
|
||||
"scripts": {
|
||||
"test": "node tests",
|
||||
"asbuild:debug": "asc assembly/index.ts --target debug",
|
||||
"asbuild:release": "asc assembly/index.ts --target release",
|
||||
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
|
||||
"start": "npx serve ."
|
||||
},
|
||||
"author": "jingze.dai",
|
||||
"license": "Apache-2.0",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"assemblyscript": "^0.27.29",
|
||||
"as-uuid": "^0.0.4",
|
||||
"assemblyscript-json": "^1.1.0",
|
||||
"@higress/proxy-wasm-assemblyscript-sdk": "^0.0.2"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./build/release.js",
|
||||
"types": "./build/release.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"/assembly",
|
||||
"package-lock.json",
|
||||
"index.js"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Jing-ze/wasm-assemblyscript.git"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user