【Tauri2】37——后端处理invoke

目录

前言

正文

随便看看

看看get

看看parse_invoke_request

看看message_handler

看看handle_ipc_message

看看webview的on_message方法

第一种情况的处理

第二种情况的处理

运行通信函数

返回的处理

 整个流程


前言

【Tauri2】033 __TAURI_INTERNALS__和invoke-CSDN博客文章浏览阅读1k次,点赞24次,收藏24次。前面说过许多关于的invoke的事情,有通信函数,fetch、invoke_key等这些。这篇再来看看关于invoke的东西看来内部的通信是通过window.__TAURI_INTERNALS__对象来实现的。注册通过函数用了声明宏。笔者发现看github上的源码,比看打包后的源码更清晰,以后就使用github上的tauri的源码了,不错。哈哈哈哈t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7t=P1C7。 https://blog.csdn.net/qq_63401240/article/details/147523382?spm=1001.2014.3001.5502前面介绍了前端invoke本质就是通过fetch发送一个post请求。

发送过去后,在后端是如何处理请求,如何调用并返回结果?

这篇就来看看这个核心的问题

正文

随便看看

解析请求核心源代码如下——protocol.rs

tauri/crates/tauri/src/ipc/protocol.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/ipc/protocol.rsprotocol.rs主要的方法

看名字,简单说说

message_handler,消息处理器,不知道干什么

get:得到,得到什么,直觉认为,应该得到请求。

handle_ipc_message :处理ipc消息,干什么,后面再说。

parse_invoke_message:解析invoke消息。

而且,get方法和message_handler方法是pub的

这个pub就非常有趣了。

pub说明可能会被调用,只能可能,不一定。

因此,在github中的tauri源码中搜索一下

repo:tauri-apps/tauri ipc::protocol

结果如下

居然搜到了,进去看看

tauri/crates/tauri/src/manager/webview.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/manager/webview.rs在里面,笔者发现了更多东西

在如下代码调用了get方法

    if !registered_scheme_protocols.contains(&"ipc".into()) {let protocol = crate::ipc::protocol::get(manager.manager_owned());pending.register_uri_scheme_protocol("ipc", move |webview_id, request, responder| {protocol(webview_id, request, UriSchemeResponder(responder))});registered_scheme_protocols.push("ipc".into());}

这段代码的意思,调用get获得了protocol ,然后注册ipc 这个uri_scheme_protocolscheme_protocol

这个pending是什么?

PendingWebview<EventLoopMessage, R>

可以发现是个 PendingWebview。虽然不知道干什么。

tauri/crates/tauri-runtime/src/webview.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri-runtime/src/webview.rs#L82可以找到,就是一个结构体,

里面有uri_scheme_protocols和ipc_handler等之类的字段

还可以发现ipc是个协议,tauri自定义的协议

虽然前面说过

发送的确是post请求,但是并不是使用http协议

总之,注册ipc这个protocol。

    pending.ipc_handler = Some(crate::ipc::protocol::message_handler(manager.manager_owned(),));

也使用了message_handler。

注册了这个ipc,发现请求,就会被webview处理。

以及message_handler处理handler

上面还注册了tauri这个protocol

    if !registered_scheme_protocols.contains(&"tauri".into()) {......}

看看get

pub fn get<R: Runtime>(manager: Arc<AppManager<R>>) -> UriSchemeProtocolHandler {Box::new(move |label, request, responder| {#[cfg(feature = "tracing")]let span =...let respond = ...match *request.method() {Method::POST => {if let Some(webview) = manager.get_webview(label) {match parse_invoke_request(&manager, request) {...}} else {....}}Method::OPTIONS => {....}_ => {let mut r = http::Response::new("only POST and OPTIONS are allowed".as_bytes().into());....}}})
}

可以发现,这个get方法

首先判断request的method,是post请求和option请求,其他请求返回,只能使用post和option。

如果是post请求就获取webview,然后调用parse_invoke_request方法,处理后续的结果

看看parse_invoke_request

fn parse_invoke_request<R: Runtime>(#[allow(unused_variables)] manager: &AppManager<R>,request: http::Request<Vec<u8>>,
) -> std::result::Result<InvokeRequest, String> {#[allow(unused_mut)]let (parts, mut body) = request.into_parts();let cmd =....let has_payload = !body.is_empty();let invoke_key = parts...let url = Url::parse...let callback = CallbackFn(...)let error = CallbackFn(...)let body = ....let payload = InvokeRequest {cmd,callback,error,url,body,headers: parts.headers,invoke_key,};Ok(payload)
}

笔者删减了许多,总体上看,就是从request请求中提取数据

cmd、callback、error、url、body、header、invoke_key

将提取到的数据合并成一个InvokeRequest ,返回。

笔者省略了许多东西。

传入的是http::Request<Vec<u8>>

返回InvokeRequest

总之——将前端的request变成Rust可以处理的InvokeRequest

看看message_handler

pub fn message_handler<R: Runtime>(manager: Arc<AppManager<R>>,
) -> crate::runtime::webview::WebviewIpcHandler<crate::EventLoopMessage, R> {Box::new(move |webview, request| handle_ipc_message(request, &manager, &webview.label))
}

返回WebviewIpcHandler

pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;

WebviewIpcHandler是一个Box,Box指向一个实现了send的动态闭包。

有点复杂。

看看handle_ipc_message

fn handle_ipc_message<R: Runtime>(request: Request<String>, manager: &AppManager<R>, label: &str) {if let Some(webview) = manager.get_webview(label) {#[derive(Deserialize, Default)]#[serde(rename_all = "camelCase")]struct RequestOptions {....}#[derive(Deserialize)]struct Message {...}#[allow(unused_mut)]let mut invoke_message: Option<crate::Result<Message>> = None;let message = invoke_message.unwrap_or_else(|| {...serde_json::from_str::<Message>(request.body()).map_err(Into::into)});match message {Ok(message) => {let options = message.options.unwrap_or_default();let request = InvokeRequest {...};webview.on_message(request,Box::new(move |webview, cmd, response, callback, error| {...});}Err(e) => {....}}}
}

传入了http::Request,

这一段代码

serde_json::from_str::<Message>(request.body()).map_err(Into::into)

从请求中提取到Message,然后变成InvokeRequest 

这个逻辑和get+parse_invoke_request差不多

获得了InvokeRequest之后,使用webview的on_message方法

实际上在get中也是这样的,从 parse_invoke_request返回InvokeRequest然后使用

看看webview的on_message方法

 pub fn on_message(self, request: InvokeRequest, responder: Box<OwnedInvokeResponder<R>>{//获取mangerlet manager = self.manager_owned();// 判断invoke_keylet expected = manager.invoke_key();....// 初始化resolverlet resolver =  ...// 初始化messagelet message = InvokeMessage...);//判断请求的来源let acl_origin = ...let (resolved_acl, has_app_acl_manifest) = ...// 初始化Invoke  let mut invoke = Invoke {message,resolver: resolver.clone(),acl: resolved_acl,};// 获取插件名字和cmd的名字let plugin_command = request.cmd.strip_prefix("plugin:").map(|raw_command| {...(plugin, command)});// 判断插件是否存在以及相关权限if (plugin_command.is_some() || has_app_acl_manifest){...}

InvokeRequest传进来之后,

前面一大推都是在处理初始化和权限问题。

现在获得了plugin_command 

后面就要对plugin_command 进行操作了

    if let Some((plugin, command_name)) = plugin_command {...} else {...}

然后就分成了两部分,因为cmd有两种情况

1、插件和插件的cmd,比如plugin:window|theme

2、自定义的cmd,比如greet

分别处理这两种情况

第一种情况的处理

     //  为invoke的command设置要执行cmd的名字invoke.message.command = command_name;// 克隆一下,后面会发生所有权的转移,无法使用command let command = invoke.message.command.clone();#[cfg(mobile)]let message = invoke.message.clone();#[allow(unused_mut)] // 执行cmd,返回boollet mut handled = manager.extend_api(plugin, invoke);#[cfg(mobile)]{// 移动端的插件            }// 不是true,说明没找到命令if !handled {resolver.reject(format!("Command {command} not found"));}

核心处理是这一行代码,把plugin和invoke传进去

      let mut handled = manager.extend_api(plugin, invoke);

extend_api一直往下走

tauri/crates/tauri/src/plugin.rs at dev · tauri-apps/taurihttps://github.com/tauri-apps/tauri/blob/dev/crates/tauri/src/plugin.rs#L777发现如下代码

  fn extend_api(&mut self, invoke: Invoke<R>) -> bool {(self.invoke_handler)(invoke)}

里面的代码就是执行结构体TauriPlugin中的invoke_handler方法中的闭包。

感觉有点绕口,总之,执行闭包。

为什么是这样写的代码?(self.invoke_handler)(invoke)

看看插件中的invoke_handler的定义

  invoke_handler: Box<InvokeHandler<R>>,
pub type InvokeHandler<R> = dyn Fn(Invoke<R>) -> bool + Send + Sync + 'static;

提取关键的部分,如下

Box<dyn Fn>

这表示一种动态分发的函数类型。简单地说

1、左边的括号是用于获取Box包装的闭包

2、右边括号运行Box里面的闭包

总之,获取闭包,运行闭包。

举个简单地例子

如下代码,

fn use_world() {type World = Box<dyn Fn(String)>;struct Api {world: World,}let api = Api {world: Box::new(|s| {println!("hello {}", s);}),};(api.world)("world".to_string());
}

或者直接使用

fn use_world() {(Box::new(|s|{ println!("hello {}", s);}))("world".to_string());
}

闭包传参Invoke。没问题。

第二种情况的处理

只有cmd命令,没有插件

  let command = invoke.message.command.clone();let handled = manager.run_invoke_handler(invoke);if !handled {resolver.reject(format!("Command {command} not found"));}

使用的是run_invoke_handler这个函数

  pub fn run_invoke_handler(&self, invoke: Invoke<R>) -> bool {(self.webview.invoke_handler)(invoke)}

一模一样,不必细说

再看看注册通信函数用的invoke_handler

  #[must_use]pub fn invoke_handler<F>(mut self, invoke_handler: F) -> SelfwhereF: Fn(Invoke<R>) -> bool + Send + Sync + 'static,{self.invoke_handler = Box::new(invoke_handler);self}

不必细说。 

运行通信函数

现在把Invoke传进来了,至于内部又是如何运行的,可参考如下

【Tauri2】005——tauri::command属性与invoke函数_tauri invoke-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146581991?spm=1001.2014.3001.5502

【Tauri2】007——Tauri2和cargo expand-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146632055?spm=1001.2014.3001.5502不必细说。

返回的处理

调用完闭包,还是是on_message这个函数中处理结果,返回给前端

webview.on_message(request,Box::new(move |_webview, _cmd, response, _callback, _error| {...respond(response);}),);

调用的respond 或者使用from_callback_fn、responder_eval

  match response {....Channel::from_callback_fn(...);} else {....responder_eval(...)}}

这就不细说,就是返回结果,比如需要考虑返回的字符串、还是使用回调函数callback、还是二进制数组等的原始数据等。 

比如事件Event,就需要callback

返回不一样,处理不一样。

 fn responder_eval<R: Runtime>(webview: &crate::Webview<R>,js: crate::Result<String>,error: CallbackFn,)

responder_eval的函数签名中可以看出,好像还是处理错误的回调函数。

 整个流程

综上所述,简单地说,后端解析的流程如下

        -> invoke

        -> windon._TAURI_INTERNALS__.invoke

        -> fetch

        -> parse_invoke_request / handle_ipc_message 

        -> on_message 

        -> extend_api /   run_invoke_handler

        -> invoke_handler

        -> response

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79533.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kotlin 05flow -从 LiveData 迁移到 Kotlin Flow 完整教程

一 从 LiveData 迁移到 Kotlin Flow 完整教程 LiveData 长期以来是 Android 架构组件中状态管理的核心&#xff0c;但随着 Kotlin Flow 的成熟&#xff0c;Google 官方推荐将现有 LiveData 迁移到 Flow。本教程基于官方文章并扩展实践细节&#xff0c;完成平滑迁移。 一、为什…

C++负载均衡远程调用学习之获取主机信息功能

目录 01Lars-lbAgentV0.2-赋值均衡数据结构关系分析 02 Lars-lbAgent0.2-host_info-load_balance-route_lb数据结构的定义 03Lars-lbAgentV0.2-proto协议的定义 04 Lars-lbAgentV0.2-route_lb与UDP server的关联 05 -Lars-lbAgentV0.2-route_lb与UDP server的关联 06Lars…

2025系统架构师---论软件的设计模式论文

2023 年,我所在的公司承担了某部网络靶场的研发任务。我作为公司的技 术总监,希望能打造基于网络靶场的系列产品,参与到项目的设计中,以期开发 扩展性和可维护性良好的网络靶场,为以后的产品开发打下基础。网络靶场是网 络安全技术研究的基础支撑平台,它利用虚拟的和实物…

Kubernetes排错(七)-节点排错

1、节点 Crash 与 Vmcore 分析 kdump 介绍​ 目前大多 Linux 发新版都会默认开启 kdump 服务&#xff0c;以方便在内核崩溃的时候, 可以通过 kdump 服务提供的 kexec 机制快速的启用保留在内存中的第二个内核来收集并转储内核崩溃的日志信息(vmcore 等文件), 这种机制需要服务…

【QT】QT中的软键盘设计

QT的软键盘设计 1.软键盘制作步骤2.介绍有关函数的使用3.出现的编译错误及解决办法示例代码1&#xff1a;按键事件实现软键盘现象&#xff1a;示例代码2&#xff1a;按键事件实现软键盘&#xff08;加特殊按键&#xff09;现象&#xff1a; 软键盘移植到新的工程的步骤&#xf…

【LaTeX+VSCode本地Win11编译教程】

LaTeXVSCode本地编译教程参考视频&#xff1a; LaTeXVSCode本地编译教程 下面提供一种Win11的Latex环境配置和设置方案&#xff0c;首先vscode安装参考博客&#xff1a;【VscodeGit教程】&#xff0c;然后准备安装Latex相关组件 在 https://miktex.org/download 下载 miktex 并…

2025五一杯数学建模ABC题赛题已出

2025五一杯数学建模ABC题赛题已出 A: B: C:

Springclound常用五大组件及其使用原理

注册中心Eureka Eureka-Server&#xff1a;就是服务注册中心&#xff08;可以是一个集群&#xff09;&#xff0c;对外暴露自己的地址。 提供者&#xff1a;启动后向Eureka注册自己信息&#xff08;地址&#xff0c;服务名称等&#xff09;&#xff0c;并且定期进行服务续约 …

Docker —— 隔离的基本操作(2)

Docker —— 隔离的基本操作&#xff08;2&#xff09; unshareunshare 命令详解基本语法常用选项常用示例实际应用场景注意事项与 Docker 的关系1. 执行命令2. 修改主机名3. 退出命名空间4. 验证宿主机主机名关键原理类比 Docker 容器总结 实战操作一&#xff08;PID 隔离&…

Java List分页工具

PageUtil.java import com.google.common.collect.Lists; import com.jd.platform.hotkey.dashboard.common.domain.Page; import org.springframework.util.CollectionUtils;import java.util.ArrayList; import java.util.List;public class PageUtil {/*** 通用分页工具类*…

中阳策略:如何从K线行为中提取交易逻辑信号?

中阳策略&#xff1a;如何从K线行为中提取交易逻辑信号&#xff1f; 在量化趋势研究中&#xff0c;中阳形态常被视作市场动能变化的重要标志。它不仅代表价格的强势上行&#xff0c;更隐含着主力资金换手与情绪转换的信号。将“中阳”这一结构元素抽象为模型中的“强动能突破”…

Java SE(8)——继承

1.继承的概念&作用 在Java中&#xff0c;继承是面向对象编程的三大基本特性之一&#xff08;还有封装和多态&#xff09;&#xff0c;允许一个类&#xff08;子类/继承类&#xff09;继承另一个类&#xff08;父类/基类&#xff09;的属性和方法 继承的核心目的是&#xf…

Python爬虫(18)反爬攻防战:动态IP池构建与代理IP实战指南(突破95%反爬封禁率)

目录 引言一、背景&#xff1a;为什么代理IP是爬虫的“第二生命”&#xff1f;1.1 反爬系统的IP检测三把刀1.2 代理IP的核心价值 二、基础实战&#xff1a;快速搭建代理IP系统2.1 免费代理IP的获取与筛选2.2 代理IP的智能容错机制 三、高阶攻防&#xff1a;突破企业级反爬封锁3…

LFU算法解析

文章目录 LFU缓存中关键变量的访问与更新机制1. min_freq - 最小频率访问时机更新时机更新示例 2. capacity - 缓存容量访问时机更新时机访问示例 3. key_to_node - 键到节点的映射访问时机更新时机更新示例 4. freq_to_dummy - 频率到链表哑节点的映射访问时机更新时机更新示例…

ByteArrayInputStream 类详解

ByteArrayInputStream 类详解 ByteArrayInputStream 是 Java 中用于从字节数组读取数据的输入流&#xff0c;位于 java.io 包。它允许将内存中的字节数组当作输入流来读取&#xff0c;是处理内存数据的常用工具。 1. 核心特性 内存数据源&#xff1a;从字节数组&#xff08;b…

rvalue引用()

一、先确定基础:左值(Lvalue)和右值(Rvalue) 理解Rvalue引用,首先得搞清楚左值和右值的概念。 左值(Lvalue):有明确内存地址的表达式,可以取地址。比如变量名、引用等。 复制代码 int a = 10; // a是左值 int& ref = a; // ref也是左值右值(Rval…

吴恩达深度学习作业 RNN模型——字母级语言模型

一. 简单复习一下RNN RNN RNN适用于处理序列数据&#xff0c;令是序列的第i个元素&#xff0c;那么就是一个长度为的序列&#xff0c;NLP中最常见的元素是单词&#xff0c;对应的序列是句子。 RNN使用同一个神经网络处理序列中的每一个元素。同时&#xff0c;为了表示序列的…

基于python的哈希查表搜索特定文件

Python有hashlib库&#xff0c;支持多种哈希算法&#xff0c;比如MD5、SHA1、SHA256等。通常SHA256比较安全&#xff0c;但MD5更快&#xff0c;但可能存在碰撞风险&#xff0c;得根据自己需求决定。下面以SHA256做例。 import hashlib import os from typing import Dict, Lis…

idea创建springboot项目无法创建jdk8原因及多种解决方案

idea创建springboot项目无法创建jdk8原因及多种解决方案 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是springboot的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系列文章】&#x…

【C++进阶十】多态深度剖析

【C进阶十】多态深度剖析 1.多态的概念及条件2.虚函数的重写3.重写、重定义、重载区别4.C11新增的override 和final5.抽象类6.虚表指针和虚表6.1什么是虚表指针6.2指向谁调用谁&#xff0c;传父类调用父类&#xff0c;传子类调用子类 7.多态的原理8.单继承的虚表状态9.多继承的…