QQ账号在网页上一键登录的实现原理
Tag 登录, 鉴权, QQ, on by view 257

我们经常可以在网页上看到QQ快捷登录,只需要点击一下QQ图像,不需要账号,不需要密码,不需要扫码,就可以直接登录了。

apnf6a4m

下面简单介绍一下这其中的原理,我们可以在浏览器请求中找到这么一个请求,如下图

4fnj1xbe

可以发现它请求的是127.0.0.1:4301,很明显这个服务是一个本地服务,它就运行在你电脑上。其实它就是你QQ客户端上运行的一个服务,可以看到,请求了这个服务的接口之后,服务响应中设置了相关的cookies,后续,QQ系的网站就可以根据这个cookies进行鉴权了。

所以说,要实现类似于QQ的这种一键登录能力,你需要有个本地客户端,客户端要提供一个http接口,你的网站会请求这个本地客户端,已鉴权的本地客户端收到这个网页上的请求之后,就会将当前登录账号的鉴权cookies植入到浏览器了,这样就成功的实现了一键快捷登录鉴权。这也就是所谓的桌面客户端为浏览器植入cookies的技术。


初学rust,初始化一个C语言结构体
Tag rust, clang, 结构体, on by view 75

最近用到了libc这个包,调用其中的statfs函数,用于查询指定路径挂载的磁盘占用。可以看到

pub fn statfs(path: *const ::c_char, buf: *mut statfs) -> ::c_int;

statfs第二个参数是一个C语言结构体statfs,其定义如下

pub struct statfs {
    pub f_type: ::__fsword_t,
    pub f_bsize: ::__fsword_t,
    pub f_blocks: ::fsblkcnt_t,
    pub f_bfree: ::fsblkcnt_t,
    pub f_bavail: ::fsblkcnt_t,

    pub f_files: ::fsfilcnt_t,
    pub f_ffree: ::fsfilcnt_t,
    pub f_fsid: ::fsid_t,

    pub f_namelen: ::__fsword_t,
    pub f_frsize: ::__fsword_t,
    f_spare: [::__fsword_t; 5],
}

如果用常规的rust初始化方法,必须填充结构体的各字段

let x = statfs{
    ...,
    f_spare: ...,
}

可以看到其中f_spare字段是一个数组,属于复杂类型,用rust的初始化方式,需要一个个字段的填充,而且需要填充为初始值0,将会非常复杂。其实有一种简单方法,使用std::mem::zeroed函数即可。举例如下

fn statfs(&mut self, mount_path: String) -> String {
    let x = CString::new(mount_path.as_bytes()).expect("covert cstring failed");

    let mut info = String::from("");
    let mut statfs_buf = unsafe { std::mem::zeroed() };
    let ret = unsafe { libc::statfs(x.as_ptr(), &mut statfs_buf) };
    if ret == 0 {
        info = format!(
            "{} {} {} {}",
            statfs_buf.f_bsize, statfs_buf.f_blocks, statfs_buf.f_bfree, statfs_buf.f_bavail
        );
    }
    return info;
}

可以看到std::mem::zeroed()方法,直接能够初始化一段0值的内存空间,这段空间具体大小,直接由下一行libc::statfs(...)调用的第二个参数类型决定,由这一行直接能推导出该申请多大的0空间。


初学rust,tokio无法在spawn中使用MutexGuard
Tag rust, tokio, MutexGuard, spawn, on by view 120

最近再次在rust中尝试用tokio::spawn实现类似go语言中goroutine的用法,但是报错了。

#[tokio::main]
async fn main() {
    let cfg = cfg::get_config();
    let client_id = cfg::get_client_id();
    let ws_addr = cfg.agent.as_ref().unwrap().remote_ws_addr.as_ref().unwrap();

    let mut wsc = ws::WebSocketClient::new(ws_addr.clone(), client_id);
    tokio::spawn(wsc.start());

    let monitor_addr = cfg.agent.as_ref().unwrap().remote_ws_addr.as_ref().unwrap();
    let mut monitor_client = monitor::Monitor::new(monitor_addr.to_string());
    monitor_client.start().await;
}

报错位置在这一行 tokio::spawn(wsc.start());,报错内容如下

error: future cannot be sent between threads safely
   --> src/main.rs:19:18
    |
19  |     tokio::spawn(wsc.start());
    |                  ^^^^^^^^^^^ future returned by `start` is not `Send`
    |
    = help: the trait `Sync` is not implemented for `std::sync::mpsc::Receiver<Message>`
note: future is not `Send` as this value is used across an await
   --> src/ws.rs:117:47
    |
115 |                 if let Ok(msg) = rx.recv() {
    |                                  -- has type `&std::sync::mpsc::Receiver<Message>` which is not `Send`
116 |                     println!("Socket Send    : {:?}", msg);
117 |                     let rst = writer.send(msg).await;
    |                                               ^^^^^^ await occurs here, with `rx` maybe used later
...
126 |             }
    |             - `rx` is later dropped here
help: consider moving this into a `let` binding to create a shorter lived borrow

可以看到原因是,我在WebSocketClient中用到了MutexGuard,而MutexGuard不支持被tokio::spawn调用。解释参考这篇文档。实际上我是在另一个结构体Pty中使用了Arc<Mutex<Receiver<Message>>>类型,然后WebSocketClient中又实用了Pty结构体,所以在调用tokio::spawn时报错。

如何解决?别用标准库的Mutex了,用tokio的。而且,我同样发现了Sender,Receiver都在报类似的错误the trait std::marker::Send is not implemented for ...

use std::sync::mpsc::{channel, Receiver, Sender}

// 改为
use tokio::sync::mpsc::channel;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::Mutex;

channel,Receiver,Sender全部改为tokio版本的,就可以兼容tokio了。