Rust でのカスタムポリシー機能の実装

以降のページでは、さまざまなポリシータスクを完了するための Rust コード例を示します。ポリシーの Rust コードは ​src/lib.rs​ ファイルで実装します。

コード例は、Rust プログラミング言語に関する基礎知識があることを前提としています。Rust のプログラミングに関する詳細は、以下を参照してください。

始める前に

ポリシーテンプレート

Flex Gateway ポリシー開発キット (PDK) には、ポリシーの実装を開始するためのテンプレートとして、ポリシープロジェクトの最初の ​src/lib.rs​ ファイルが用意されています。

// Copyright 2024 Salesforce, Inc. All rights reserved.
mod generated;

use anyhow::{anyhow, Result};

use pdk::hl::*;
use pdk::logger;

use crate::generated::config::Config;

// This filter shows how to log a specific request header.
// You can extend the function and use the configurations exposed in config.rs file
async fn request_filter(request_state: RequestState, _config: &Config) {
    let headers_state = request_state.into_headers_state().await;
    let token = headers_state.handler().header("Token").unwrap_or_default();
    // Log the header value
    logger::info!("Header value: {token}");
}

#[entrypoint]
async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> {
    let config: Config = serde_json::from_slice(&bytes).map_err(|err| {
        anyhow!(
            "Failed to parse configuration '{}'. Cause: {}",
            String::from_utf8_lossy(&bytes),
            err
        )
    })?;
    let filter = on_request(|rs| request_filter(rs, &config));
    launcher.launch(filter).await?;
    Ok(())
}

ポリシーは簡単なログを実行します。受信した要求ごとに、ポリシーは ​INFO​ ログレベルでヘッダー​トークン​を記録します。

以下の要素でポリシーを設定します。

  • use pdk::api::hl::*;​: PDK のすべてのコンポーネントを ​lib.rs​ ソースコードにインポートします。

  • #[entrypoint]​ 関数: ポリシーが適用されたときに実行され、​request_filter​ 関数を呼び出します。この関数の内部で定義された変数は、ポリシーが適用されている間は有効です。

    #[entrypoint]​ 関数は、以下のパラメーターを受け取ります。

  • request_filter​: ポリシーが適用される API インスタンスに送信される要求ごとに 1 回実行されます。この関数は、ポリシー例で実行される検索条件を実装します。この関数の中で定義されている変数は、要求の実行中に利用できます。

    request_filter​ は、ラップされた関数の例です。

    #[entrypoint]​ 関数はラップされた関数を実行します。

    let filter = on_request(|rs| request_filter(rs, &config));
    launcher.launch(filter).await?;

ラップされた関数

on_response​ または ​on_request​ ラッパーは、検索条件関数が実行されるタイミングを定義します。

#[entrypoint]​ 関数では、提供されている ​requests_filter​ と ​response_filter​ 以外のカスタム関数を定義することができます。​requests_filter​ と ​response_filter​ は例に過ぎません。

前の例で示したように、受信要求を絞り込む代わりに、​on_response​ ラッパーを使用して送信応答を処理することができます。

async fn response_filter(response_state: ResponseState, _config: &Config) {
    ...
}

#[entrypoint]
async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> {
    let config: Config = serde_json::from_slice(&bytes).map_err(|err| {
        anyhow!(
            "Failed to parse configuration '{}'. Cause: {}",
            String::from_utf8_lossy(&bytes),
            err
        )
    })?;
    let filter = on_response(|rs| response_filter(rs, &config));
    launcher.launch(filter).await?;
    Ok(())
}
request_filter​ 関数は ​response_filter​ 関数になりました。

要求と応答の両方を絞り込むには、両方のラッパーを使用します。

async fn request_filter(request_state: RequestState,_config: &Config) {
    ...
}

async fn response_filter(response_state: ResponseState,_config: &Config) {
    ...
}

#[entrypoint]
async fn configure(launcher: Launcher, Configuration(bytes): Configuration) -> Result<()> {
    let config: Config = serde_json::from_slice(&bytes).map_err(|err| {
        anyhow!(
            "Failed to parse configuration '{}'. Cause: {}",
            String::from_utf8_lossy(&bytes),
            err
        )
    })?;
    let filter = on_request(|rs| request_filter(rs, &config))
    .on_response(|rs| response_filter(rs, &config));
    launcher.launch(filter).await?;
    Ok(())
}

フローのキャンセル

カスタムポリシーはシングルスレッド環境で実行されます。ただし、ラップされた関数 (​非同期​関数の ​on_request​ と ​on_response​) のすべてのセットは同時タスクとして実行されるため、単一ポリシーのインスタンスが複数の要求を同時に処理することになります。

この動作をサポートするために、すべての ​.await​ が潜在的なタスク中断ポイントとなります。​.await​ が呼び出されると、基盤となる非同期ランタイムは現在のタスクをしばらくスリープさせて、まだ完了していない別のタスクを目覚めさせることができます。非同期ランタイムはタスクをキャンセルすることができ、最後の ​.await​ 呼び出しの後にあるコードを実行することはありません。 on_request​ 関数と ​on_response​ 関数は、​.await​ 呼び出しが潜在的なキャンセルポイントであることを想定しています。

次のコード例に示すように、タスクキャンセルの最も一般的な状況は、要求関数がボディを待っている途中でアップストリームポリシーが応答を早期に返した場合です。

// Request function for upstream policy
async fn upstream_request(state: RequestState) -> Response {
    Response::new(404)
}

// Request function for downstream policy
async fn downstream_request(state: RequestState) {

    // Request function will be cancelled after this .await point.
    let body_state = state.into_body_state().await;

    // Code here will never be executed
}