加入我们,从10月8日至10日,在纽约市了解GraphQL联盟和API平台工程的最新的技巧、趋势和新闻。加入我们,参加2024年纽约市GraphQL峰会
文档
免费开始

Rhai 脚本API参考

定制路由器API


本参考文档记录了特定于Rhai定制对于的符号和行为。

入口钩子

您的Rhai脚本的主文件钩子连接到路由器的各个服务请求处理生命周期。为了这样做,它定义了所需的以下入口钩子的任意组合:

fn router_service(service) {}
fn supergraph_service(service) {}
fn execution_service(service) {}
fn subgraph_service(service, subgraph) {}

在每个钩子内部,您定义自定义逻辑以根据需要与当前活动的请求和/或响应进行交互。这通常涉及使用提供的方法的service对象来注册服务回调,如下所示:

main.rhai
fn supergraph_service(service) {
let request_callback = |request| {
print("Supergraph service: Client request received");
};
let response_callback = |response| {
print("Supergraph service: Client response ready to send");
};
service.map_request(request_callback);
service.map_response(response_callback);
}

记录

如果您的脚本使用Rhai的内置print()函数记录消息,则该消息将以router“info”级别记录到日志中:

print("logged at the info level");

要更精细地控制消息的日志级别,您可以使用以下函数

log_error("error-level log message");
log_warn("warn-level log message");
log_info("info-level log message");
log_debug("debug-level log message");
log_trace("trace-level log message");

终止客户端请求

您的Rhai脚本可以终止触发它的关联客户端请求。为此,它抛出异常。这向客户端返回一个Internal Server Error,带有500响应代码。

例如

fn supergraph_service(service) {
// Define a closure to process our response
let f = |response| {
// Something goes wrong during response processing...
throw "An error occurred setting up the supergraph_service...";
};
// Map our response using our closure
service.map_response(f);
}

如果希望对返回给客户端的HTTP状态代码有更多控制,则可以抛出一个对象映射错误,其中包含键:状态和消息。

键必须是数字,消息必须是可以转换为字符串的事物。

例如

fn supergraph_service(service) {
// Define a closure to process our response
let f = |response| {
// Something goes wrong during response processing...
throw #{
status: 400,
message: "An error occurred processing the response..."
};
};
// Map our response using our closure
service.map_response(f);
}

您还可以throw一个有效的响应,它将被反序列化并由router处理。

例如

fn supergraph_service(service) {
// Define a closure to process our request
let f = |request| {
// Something goes wrong during request processing...
throw #{
status: 403,
body: #{
errors: [#{
message: `I have raised a 403`,
extensions: #{
code: "ACCESS_DENIED"
}
}]
}
};
};
// Map our request using our closure
service.map_request(f);
}

Rhai在map_request层上抛出时会表现得与ControlFlow::Break相同,这在外部扩展部分中进行了说明。

如果提供的状态码不是有效的HTTP状态码,则将导致500响应码。

定时执行

您的Rhai自定义可以使用全局Router.APOLLO_START常量来计算持续时间。这类似于Unix环境中的Epoch

// Define a closure to process our response
// Note: We can't use a closure in this example because we are referencing our global Router during function execution
fn process_response(response) {
let start = Router.APOLLO_START.elapsed;
// Do some processing here...
let duration = Router.APOLLO_START.elapsed - start;
print(`response processing took: ${duration}`);
// Log out any errors we may have
print(response.body.errors);
}
fn supergraph_service(service) {
const response_callback = Fn("process_response");
service.map_response(response_callback);
}

访问SDL

您的Rhai自定义可以使用全局Router.APOLLO_SDL常量来检查

fn supergraph_service(service) {
print(`${Router.APOLLO_SDL}`);
}

访问TraceId

您的Rhai自定义可以使用函数traceid()来检索opentelemetry跟踪ID。如果不存在span,该函数将抛出异常,因此在使用此函数时始终要处理异常。

fn supergraph_service(service) {
try {
let id = traceid();
print(`id: ${id}`);
}
catch(err)
{
// log any errors
log_error(`span id error: ${err}`);
}
}

URL编码/解码字符串

您的Rhai自定义可以使用函数urlencode()urldecode()来编码/解码字符串。encode()不会失败,但decode()可能会失败,因此在使用decode()函数时始终要处理异常。

fn supergraph_service(service) {
let original = "alice and bob";
let encoded = urlencode(original);
// encoded will be "alice%20and%20bob"
try {
let and_back = urldecode(encoded);
// and_back will be "alice and bob"
}
catch(err)
{
// log any errors
log_error(`urldecode error: ${err}`);
}
}

JSON编码/解码字符串

您的Rhai自定义可以使用函数json::encode()json::decode()将Rhai对象转换为有效的JSON编码字符串或从有效的JSON编码字符串转换为Rhai对象。这两个函数都可能会失败,因此在使用它们时始终要处理异常。

fn router_service(service) {
let original = `{"valid":"object"}`;
try {
let encoded = json::decode(original);
// encoded is a Rhai object, with a property (or key) named valid with a String value of "object"
print(`encoded.valid: ${encoded.valid}`);
let and_back = json::encode(encoded);
// and_back will be a string == original.
if and_back != original {
throw "something has gone wrong";
}
}
catch(err)
{
// log any errors
log_error(`json coding error: ${err}`);
}
}

Base64编码/解码字符串

您的Rhai自定义可以使用函数base64::encode()base64::decode()来编码/解码字符串。encode()不会失败,但decode()可能会失败,因此在使用decode()函数时始终要处理异常。

fn supergraph_service(service) {
let original = "alice and bob";
let encoded = base64::encode(original);
// encoded will be "YWxpY2UgYW5kIGJvYgo="
try {
let and_back = base64::decode(encoded);
// and_back will be "alice and bob"
}
catch(err)
{
// log any errors
log_error(`base64::decode error: ${err}`);
}
}

注意

您不需要导入“base64”模块。它在router中已经导入。

不同的字母表

Base64支持多种字母表进行数据编码,具体取决于它所在环境中支持字符。路由器支持以下字母表:

要使用它们,我们可以在 encodedecode 方法中添加一个

fn supergraph_service(service) {
let original = "alice and bob";
let encoded = base64::encode(original, base64::URL_SAFE);
// encoded will be "YWxpY2UgYW5kIGJvYgo="
try {
let and_back = base64::decode(encoded, base64::URL_SAFE);
// and_back will be "alice and bob"
}
catch(err)
{
// log any errors
log_error(`base64::decode error: ${err}`);
}
}

SHA256散列字符串

您的Rhai定制可以使用函数 sha256::digest() 使用SHA256哈希算法对字符串进行哈希。

fn supergraph_service(service){
service.map_request(|request|{
let sha = sha256::digest("hello world");
log_info(sha);
});
}

注意

您不需要导入 "sha256" 模块。它已包含在 router 中。

具有多个值的头部

处理单值头部的简单 get/set API 对于大多数用例来说已经足够了。如果您想对一个键设置多个值,则应提供值数组来自行设置。

如果您想获取头键的多个值,则必须使用 values() 函数,而不是索引访问器。如果您使用索引访问器,它将仅返回与键关联的第一个值(作为字符串)。

查看示例以了解其实际应用。

Unix时间戳

您的Rhai定制可以使用函数 unix_now() 以秒为单位从Unix纪元以来获取当前Unix时间戳。

fn supergraph_service(service) {
let now = unix_now();
}

Unix时间戳(以毫秒为单位)

您的Rhai定制可以使用函数 unix_ms_now() 以毫秒为单位从Unix纪元以来获取当前Unix时间戳。

fn supergraph_service(service) {
let now = unix_ms_now();
}

唯一标识符(UUID)

您的Rhai定制可以使用函数 uuid_v4() 获取UUIDv4 ID。

fn supergraph_service(service) {
let id = uuid_v4();
}

环境变量

您的Rhai定制可以使用 env 模块访问环境变量。使用 env::get() 函数。

fn router_service(service) {
try {
print(`HOME: ${env::get("HOME")}`);
print(`LANG: ${env::get("LANG")}`);
} catch(err) {
print(`exception: ${err}`);
}
}

注意

  • 您不需要导入 "env" 模块。它已包含在 router 中。
  • get()可能失败,因此在使用它时最好处理异常。

可用常量

router提供了对您的Rhai脚本大部分用于从上下文获取数据的常量。

Router.APOLLO_SDL // Context key to access the SDL
Router.APOLLO_START // Constant to calculate durations
Router.APOLLO_AUTHENTICATION_JWT_CLAIMS // Context key to access authentication jwt claims
Router.APOLLO_SUBSCRIPTION_WS_CUSTOM_CONNECTION_PARAMS // Context key to modify or access the custom connection params when using subscriptions in WebSocket to subgraphs (cf subscription docs)
Router.APOLLO_ENTITY_CACHE_KEY // Context key to access the entity cache key
Router.APOLLO_OPERATION_ID // Context key to get the value of apollo operation id (studio trace id) from the context

Request 接口

通过 map_request 注册的所有回调函数都会接收到一个 request 对象,该对象表示客户端发送的请求。此对象包含以下

request.context
request.id
request.headers
request.method
request.body.query
request.body.operation_name
request.body.variables
request.body.extensions
request.uri.host
request.uri.path

注意

这些 字段通常是可修改的,除了 method 外,该方法始终为只读。但是,当回调服务是 subgraph_service 时,唯一可修改的 request.context.

仅针对 subgraph_service 回调, request 对象提供了 额外的 可修改的字段,用于与将发送给相应 的请求进行交互:

request.subgraph.headers
request.subgraph.body.query
request.subgraph.body.operation_name
request.subgraph.body.variables
request.subgraph.body.extensions
request.subgraph.uri.host
request.subgraph.uri.path

request.context

上下文是一个通用的键/值存储,在整个客户端请求的生命周期中存在。您可以使用它来在请求的生命周期中共享信息。

键必须是字符串,但值可以是任何Rh daß 对象。

// You can interact with request.context as an indexed variable
request.context["contextual"] = 42; // Adds value 42 to the context with key "contextual"
print(`${request.context["contextual"]}`); // Writes 42 to the router log at info level
// Rhai also supports extended dot notation for indexed variables, so this is equivalent
request.context.contextual = 42;

upsert()

上下文提供了一个 upsert() 函数来解决在设置特定键的值时需要更新或插入的情况。

要使用 upsert(),您必须定义一个回调函数,该函数接收一个键的现有值(如果有的话)并将其修改,然后在返回最终要设置的值之前进行修改。

// Get a reference to a cache-key
let my_cache_key = response.headers["cache-key"];
// Define an upsert resolver callback
// The `current` parameter is the current value for the specified key.
// This particular callback checks whether `current` is an ObjectMap
// (default is the unit value of ()). If not, assign an empty ObjectMap.
// Finally, update the stored ObjectMap with our subgraph name as key
// and the returned cache-key as a value.
let resolver = |current| {
if current == () {
// No map found. Create an empty object map
current = #{};
}
// Update our object map with a key and value
current[subgraph] = my_cache_key;
return current;
};
// Upsert our context with our resolver
response.context.upsert("surrogate-cache-key", resolver);

request.id

此 ID 是一个字符串,在整个生命周期中唯一地标识请求/响应上下文。如果您有一个请求(或响应),您可以通过以下方式访问 ID。

print(`request id is: ${request.id}`);

request.headers

请求的标题可以作为可读/写的索引 访问。键和值必须是有效的标题名称和值字符串。

// You can interact with request.headers as an indexed variable
request.headers["x-my-new-header"] = 42.to_string(); // Inserts a new header "x-my-new-header" with value "42"
print(`${request.headers["x-my-new-header"]}`); // Writes "42" into the router log at info level
// Rhai also supports extended dot notation for indexed variables, so this is equivalent
request.headers.x-my-new-header = 42.to_string();
// You can also set an header value from an array. Useful with the "set-cookie" header,
// Note: It's probably more useful to do this on response headers. Simply illustrating the syntax here.
request.headers["set-cookie"] = [
"foo=bar; Domain=localhost; Path=/; Expires=Wed, 04 Jan 2023 17:25:27 GMT; HttpOnly; Secure; SameSite=None",
"foo2=bar2; Domain=localhost; Path=/; Expires=Wed, 04 Jan 2023 17:25:27 GMT; HttpOnly; Secure; SameSite=None",
];
// You can also get multiple header values for a header using the values() fn
// Note: It's probably more useful to do this on response headers. Simply illustrating the syntax here.
print(`${request.headers.values("set-cookie")}`);

request.method

这是客户端请求的 HTTP 方法。

print(`${request.method}`); // Log the HTTP method

request.body.query

这是客户端提供的 GraphQL 字符串,用于执行。

要在查询规划 supergraph_service() 请求回调中进行。如果稍后修改它,将使用原始提供的 operation 字符串生成查询规划。

以下示例修改了传入的 并将其转换为完全无效的游戏:

print(`${request.body.query}`); // Log the query string before modification
request.body.query="query invalid { _typnam }}"; // Update the query string (in this case to an invalid query)
print(`${request.body.query}`); // Log the query string after modification

request.body.operation_name

这是要执行的 GraphQL 操作的名称,如果请求中提供了名称。如果 request.body.query 包含多个操作定义,则此值 必须 存在。

以下是与operation_name交互的示例,请参见examples/op-name-to-header目录

print(`${request.body.operation_name}`); // Log the operation_name before modification
request.body.operation_name +="-my-suffix"; // Append "-my-suffix" to the operation_name
print(`${request.body.operation_name}`); // Log the operation_name after modification

request.body.variables

这些是提供给操作的任何GraphQL变量的值。,它们以一个对象映射的形式暴露给Rhai。

print(`${request.body.variables}`); // Log all GraphQL variables

request.body.extensions

请求可以读取或修改。它们以一个对象映射的形式暴露给Rhai。

print(`${request.body.extensions}`); // Log all extensions

request.uri.host

这是请求URI的主机组件,作为一个字符串。

修改客户端请求的此值没有任何效果,因为请求已经到达router。但是,在subgraph_service回调中修改request.subgraph.uri.host确实会修改router与相应subgraph通讯所使用的URI。

print(`${request.uri.host}`); // Log the request host

request.uri.path

这是请求URI的路径组件,作为一个字符串。

修改客户端请求的此值没有任何效果,因为请求已经到达router。但是,在subgraph_service回调中修改request.subgraph.uri.path确实会修改router与相应subgraph通讯所使用的URI。

print(`${request.uri.path}`); // log the request path
request.uri.path += "/added-context"; // Add an extra element to the query path

request.subgraph.*

仅在对subgraph_service中注册的map_request回调中,才可使用request.subgraph对象。该对象与request对象具有完全相同的fields,但这些fields适用于router将发送到相应subgraph的HTTP请求。

// You can interact with request.subgraph.headers as an indexed variable
request.subgraph.headers["x-my-new-header"] = 42.to_string(); // Inserts a new header "x-my-new-header" with value "42"
print(`${request.subgraph.headers["x-my-new-header"]}`); // Writes "42" into the router log at info level
// Rhai also supports extended dot notation for indexed variables, so this is equivalent
request.subgraph.headers.x-my-new-header = 42.to_string();

Response接口

通过map_response注册的所有回调函数都传入了表示HTTP响应的response对象。

  • subgraph_service中,该对象表示相应subgraph发送给router的响应。
  • 在其他所有服务中,此对象表示路由器将发送给请求客户端的响应。

响应对象包含以下字段:

response.context
response.id
response.status_code
response.headers
response.body.label
response.body.data
response.body.errors
response.body.extensions

上述所有字段都是可读写。

以下字段的行为与其请求对应字段相同:

response.is_primary()

在与响应上下文中的头部状态码交互时请特别小心。对于router_service()supergraph_service()execution_service(),在延迟响应流中的第一个响应才存在头部状态码。您可以通过使用is_primary()函数来处理这种情况,如果响应是第一个(或主要)响应,则它会返回true。如果您尝试访问非主要响应的头部状态码,则将引发异常,这可以像处理任何其他Rhai异常一样处理,但不如使用is_primary()方法方便。

if response.is_primary() {
print(`all response headers: ${response.headers}`);
} else {
print(`don't try to access headers`);
}

其他字段如下所述。

response.body.label

响应可能包含一个标签,可以将其作为字符串读取/写入。

print(`${response.body.label}`); // logs the response label

response.body.data

响应可能包含数据(有些带有错误的响应不包含数据)。在操作数据(和错误)时请小心,以确保响应保持有效。data对象映射的形式暴露给Rhai。

examples/data-response-mutate目录中有一个与响应数据交互的完整示例。

print(`${response.body.data}`); // logs the response data

response.body.errors

响应可能包含错误。错误以Rhai中对象映射的数组形式表示。

每个错误必须包含至少以下内容:

  • 消息(字符串)
  • 位置(数组)

(位置可以是空数组。)

错误可选地还可以包含扩展,用作对象映射。

examples/error-response-mutate目录中有一个与响应错误交互的完整示例。

// Create an error with our message
let error_to_add = #{
message: "this is an added error",
locations: [],
// Extensions are optional, adding some arbitrary extensions to illustrate syntax
extensions: #{
field_1: "field 1",
field_2: "field_2"
}
};
// Add this error to any existing errors
response.body.errors += error_to_add;
print(`${response.body.errors}`); // logs the response errors

response.status_code.to_string()

将响应状态码转换为字符串。

if response.status_code.to_string() == "200" {
print(`ok`);
}

如果要将响应状态码转换为数字,这也很有用

if parse_int(response.status_code.to_string()) == 200 {
print(`ok`);
}

您也可以从整数创建自己的状态码

if response.status_code == status_code_from_int(200) {
print(`ok`);
}

注意

`response.status_code` 对象仅对在 `router_service` 或 `subgraph_service` 中注册的 `map_response` 回调可用。
上一页
Rhai 脚本
下一页
外部协同处理
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,作为Apollo GraphQL运营。

隐私政策

公司