【Tauri2】37——后端处理invoke

发布于:2025-05-08 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

前言

正文

随便看看

看看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>>{
    //获取manger
    let manager = self.manager_owned();
     // 判断invoke_key
    let expected = manager.invoke_key();
     ....
    // 初始化resolver
    let resolver =  ...

    // 初始化message
    let 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,返回bool
      let 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) -> Self
  where
    F: 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


网站公告

今日签到

点亮在社区的每一天
去签到