【Tauri2】016——后端Invoke结构体和invoke_key
前言
【Tauri2】015——前端的事件、方法和invoke函数-CSDN博客https://blog.csdn.net/qq_63401240/article/details/147003241?spm=1001.2014.3001.5502【Tauri2】005——tauri::command属性与invoke函数-CSDN博客
https://blog.csdn.net/qq_63401240/article/details/146581991?spm=1001.2014.3001.5502
前面说过,前端的invoke函数本质就是发送一个post请求。
这篇就来看看后端的Invoke,这个结构体
#[default_runtime(crate::Wry, wry)]
pub struct Invoke<R: Runtime> {
/// The message passed.
pub message: InvokeMessage<R>,
/// The resolver of the message.
pub resolver: InvokeResolver<R>,
/// Resolved ACL for this IPC invoke.
pub acl: Option<Vec<ResolvedCommand>>,
}
还有 InvokeMessage
pub struct InvokeMessage<R: Runtime> {
/// The webview that received the invoke message.
pub(crate) webview: Webview<R>,
/// Application managed state.
pub(crate) state: Arc<StateManager>,
/// The IPC command.
pub(crate) command: String,
/// The JSON argument passed on the invoke message.
pub(crate) payload: InvokeBody,
/// The request headers.
pub(crate) headers: HeaderMap,
}
正文
准备
首先,写一段简单地通信函数
#[command(rename_all = "snake_case")]
fn greet(name_go: &str) -> String {
format!("Hello, {}!", name_go)
}
这个rename_all 是command属性的参数,里面有三个参数
struct WrapperAttributes {
root: TokenStream2,
execution_context: ExecutionContext,
argument_case: ArgumentCase,
}
更具体来说,是root、rename_all、async
rename_all 可设为camelCase和snake_case,总之,设置变量的命名规则。
async,设置为异步函数
root:用于指定命令的根路径
总之,上面通信函数就是接受一个字符串切片,返回字符串。
注册
.invoke_handler(tauri::generate_handler![greet])
简单通信一下
function handleClick() {
invoke('greet', { name_go: 'World' })
}
结果如下
没问题
cargo expand宏展开
命令如下
cargo expand >expanded.rs
宏展开的结果如下
.invoke_handler(move |__tauri_invoke__| {
let __tauri_cmd__ = __tauri_invoke__.message.command();
match __tauri_cmd__ {
"greet" => {
#[allow(unused_imports)]
use ::tauri::ipc::private::*;
#[allow(unused_variables)]
let ::tauri::ipc::Invoke {
message: __tauri_message__,
resolver: __tauri_resolver__,
acl: __tauri_acl__,
} = __tauri_invoke__;
let result = greet(
match ::tauri::ipc::CommandArg::from_command(::tauri::ipc::CommandItem {
plugin: ::core::option::Option::None,
name: "greet",
key: "name_go",
message: &__tauri_message__,
acl: &__tauri_acl__,
}) {
Ok(arg) => arg,
Err(err) => {
__tauri_resolver__.invoke_error(err);
return true;
}
},
);
let kind = (&result).blocking_kind();
kind.block(result, __tauri_resolver__);
return true;
}
_ => {
return false;
}
}
})
实际上,笔者把用于注册中的代码,替换成宏展开的结果的代码,依然可以运行,很显然。
笔者本来想打印__tauri_invoke__,这个__tauri_invoke__就是Invoke,但是打印不了,没有实现Debug trait,被迫只能打印message,代码如下
let __tauri_cmd__ = __tauri_invoke__.message.command();
println!("{:#?}",__tauri_invoke__.message);
再次运行
打印了非常多的东西
笔者给出关键的打印
command: "greet",
payload: Json(
Object {
"name_go": String("World"),
},
),
headers: {
"accept": "*/*",
"content-type": "application/json",
"origin": "http://localhost:1420",
"referer": "http://localhost:1420/",
"tauri-callback": "509703145",
"tauri-error": "2011447483",
"tauri-invoke-key": "k25Bp{sKYlL]8PUx26sG",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
"sec-ch-ua": "\"Microsoft Edge\";v=\"134\", \"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Microsoft Edge WebView2\";v=\"134\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
},
这个headers正是InvokeMessage中的。
后面调用command方法
let __tauri_cmd__ = __tauri_invoke__.message.command();
获取到greet字符串,然后通过match匹配,执行greet函数,返回结果,完成了通信。
现在看来,过程也就这么回事。
当然,宏展开后,就可以改前端的名字了
如果把match中的"greet"改成"greet123"
那么前端代码
function handleClick() {
invoke('greet123', { name_go: 'World' })
}
正常运行。
通过fetch,进行通信
代码如下
export async function useWindowEvent() {
const error=transformCallback((event:Event) => {
console.log("失败")
})
const success=transformCallback((event:Event) => {
console.log("成功")
})
const __TAURI_INVOKE_KEY__ = JSON.parse('"MW76q<X9iZ}Cv<yWv#l."')
fetch("http://ipc.localhost/greet123", {
method: 'POST',
body: JSON.stringify({
'name_go':"123123"
}),
headers: {
'Content-Type': "application/json",
'Tauri-Callback': success,
'Tauri-Error': error,
'Tauri-Invoke-Key': __TAURI_INVOKE_KEY__,
}
})
}
报错了,这个__TAURI_INVOKE_KEY__ 不对
笔者在RustRover中搜索关键字__TAURI_INVOKE_KEY__ expected
找到了这个打印语句出现的位置
use tauri::webview::Webview
在Webview的on_message方法中
笔者还找到其他东西
#[derive(Deserialize)]
struct Message {
cmd: String,
callr: CallbackFn,
payload: serde_json::Value,
options: Option<RequestOptions>,
#[serde(rename = "__TAURI_INVOKE_KEY__")]
invoke_key: String,
}
原来本来的名字叫invoke_key,
该这么获得呢? 很简单
AppHandle中有invoke_key方法,App也有。
AppHandle in tauri - Rusthttps://docs.rs/tauri/latest/tauri/struct.AppHandle.html#method.invoke_key现在就明白了,哈哈哈哈,原来如此
把invoke_key发送给前端,并通过fetch使用
这其实挺麻烦的,笔者想使用事件,写在setup中,居然没反应,检测不到。
笔者暂时使用通信函数传输了
#[command]
pub fn send_key(app:AppHandle) -> String {
app.invoke_key().to_string().clone()
}
前端获得并使用
function handleClick() {
invoke('send_key').then(
(res: any) => {
useWindowEvent(res)
}
)
}
感觉有点怪,简单地意思意思一下。
简单修改一下fetch,成功
总之,获得了这个__TAURI_INVOKE_KEY__ ,
实际上,运行后,这个就相当于全局的状态一样,不会变的,只要不关闭程序,获取一次,就可以了。
再看看invoke_handler的函数签名
#[must_use]
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
参数invoke_handler的泛型是F,F需要传入一个闭包或者函数,这个闭包中的参数是正是Invoke,返回bool
自定义注册和使用通信函数
从上面这么多信息,实际上,这很简单了
首先,不需要command属性了
fn greet(name_go: &str) -> String {
format!("Hello, {}!", name_go)
}
就像是普通函数一样了。
关键导包
use tauri::ipc::private::ResponseKind;
use tauri::ipc::InvokeBody;
注册
.invoke_handler(move |invoke| {
let cmd = invoke.message.command();
match cmd {
"greet" => {
let name_go = match invoke.message.payload() {
InvokeBody::Json(json) => json.get("name_go")
.and_then(|v| v.as_str()).unwrap_or_default(),
_ => {
return false;
}
};
let result = greet(name_go);
let kind = (&result).blocking_kind();
kind.block(result, invoke.resolver);
return true;
}
_ => {
return false;
}
}
})
payload方法返回是InvokeBody 的引用,是个enum,通过match处理enum
#[inline(always)]
pub fn payload(&self) -> &InvokeBody {
&self.payload
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum InvokeBody {
/// Json payload.
Json(JsonValue),
/// Bytes payload.
Raw(Vec<u8>),
}
调用greet函数,返回结果。
前端无论使用invoke函数还是fetch,都没有问题
当然,非常不严谨,有很多细节需要考虑。
总之,大致是这样的。
没问题。哈哈哈哈哈
最后的补充
前端的invoke函数,除了使用
import {invoke} from "@tauri-apps/api/core";
还可以使用全局的
const invoke = window.__TAURI__.core.invoke;
需要在配置文件中进行如下设置
这个window不是窗口,是一个全局对象
总结
invoke无论在前端和还是在后端,都是重中之重,写了几篇关于invoke,算是差不多了,Invoke暂时就写到这。
后面就写点更具体的,比如托盘,全局状态管理等之类,总之,感觉还有很多东西。
不慌。