自学内容网 自学内容网

RUST语言的初印象-从一个模拟登陆谈起-slint+reqwest+aes

本文就一个做了三四天的小程序讲第一次学用RUST的感受,内附代码。

了角语言

从一些渠道听说了R,这个字母挺魔性,那个文章说C++和R的团体已经上升到了宗教崇拜的高度,然后,我觉得必 有过人之处,大约10年没碰C++,只知皮毛。于是想要去学一下R,这样简写是为了方便凑合看。粗粗一打听,这语言是系统的,平台通吃,android,arm,x86.苹果,安装起来也十分方便,curl一段代码就行。除了win下要用VStuty。正好我上次python,exe化的进候安装上了。所以大约10分就安好了,拿出helloworld,cargo build,cargo run,也能通过了。后来发现R在文档和库管理这一块是不是要比C++这几十年的老 将好很多呢,虽然很久没用C++,不知道发展了没有,感叹时代真是进步太大了。R的学习资料,网上有太多电子版,网页搜索查询方便,库,文档,规范也自动, 这种方便性真是让人爽快,不说别的了,还是挺推荐一学的。

任务

以前有一个模拟访问网页的python,全套代码就在里面,就剩下要了解R的语法和库就能完成任务了。于是了解reqwest是模仿request来的就用它了。结果它总是aynsc方法,看着头大,怕是掌握不住,于是就找到了reqwest::blocking这就顺利多了。还是习惯老方法。不过完成了任务就可以尝试新的了。剩下的是cookies。好在,目前的版本一步启用,

use reqwest::blocking::Client;
let client = Client::builder()
   .cookie_store(true)
    .build()?;

这就具备第一个轮子了。在登陆密码的以后,来了一个成功地址,做下一步的引导,结果因为这个地址,没有mut,而初始化成了,空。造成下一步,成功后也无法更新。这就让后面的所有请求是失败的。我一开始一直在怀疑cookie不能跨域fun。因为在一个fun里是好的。为此还找了cookiestore的本地文件json在实现。结果还是一样。直到后来找到了真实原因是一个变量可变的声明,

  1. 建立图形界面,调查对比后使用slint。
  2. 提交reqwest,分form.和json数据,cookie保存登陆状态,取回response并且regex分析结果,解析json结果。
  3. 更新界面状态,使用两个线程,一个交互web server结果保存在全局变量,一个刷新全局变量到界面状态。
  4. 编码用户密码以方便登陆,用户明文到AES和MD5加密,然后post去后台。

按部就班

老实的看了几个小时的教程,参数,变量,引用,数组,字符串。发现这个言语会劲量发现错的,直到按他要求来,才能通过,当然所有语言都这样。但是还是挺喜欢他的味道。和js,python形成鲜明的对比。 fn要用什么参数,传回什么类型,都要提前说好。而且,Result,Option,这样的东西,? <>这样的字符。都有自己的用处。接下了学了要用到的regex。就像reqwest、它要实现的功能都是老的,只有工具是新的。而且说心里,它跨平台,在鸿蒙也是能跑的,而且资源这么多,多好。regex r#“”#这是原始字符。以前那些提取,基本是照样出来的。

图形界面

捎带又了角一个新的slint框架,他是跨语言的,有自己的slint文件,进行窗口程序的布局。安装也像R一样,又无声又快,只是我没在vscode上,找到可拖放的插件。 在线的倒是有一个。用了半天时间,放了两个输入,一个状态,一个计数按钮。 这是从它的github,example下载来的。cargo build,cargo run。 一下就打开了。想像有一天在鸿蒙也hell world一下。但是真想不到有什么像样的任务要做。 它的in- out变量,可以在main中,set, get。在完成了后台以后,这里有一个小难点。就是状态的适时刷新。
Cargo.toml

[package]
name = "slint-rust-template"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
slint = "1.7.2"
reqwest = { version = "0.12.7", features = ["json", "cookies","blocking","gzip"] }
tokio= {version= "1" ,features= ["full"] }
regex = "1.10.6"
#recode_rs="1.0.6"
encoding_rs="0.8.34"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

main.rs

// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod wjc;
use slint::Weak;
use wjc::kkuser;
use std::error::Error;
use std::sync::MutexGuard;
//use reqwest::Client;
use reqwest::blocking::Client;
use std::{thread, time::Duration};
use reqwest;
use regex::Regex;
use std::collections::HashMap;
slint::include_modules!();

fn main() -> Result<(), Box<dyn Error>> {
  
    let ui = AppWindow::new()?;
    let handle_weak = ui.as_weak();
    let thread = std::thread::spawn(move || {
        //更新状态
       loop{
        handle_weak.upgrade_in_event_loop(move |handle|{ 
           unsafe { 
            handle.set_counter(kkuser::progbar);
           
        handle.set_status(kkuser::thestat.unwrap().into());
            }
        });
        thread::sleep(Duration::from_millis(500));
        }
    });
    ui.on_request_increase_value({
        let ui_handle = ui.as_weak();
        move || {
            let ui = ui_handle.unwrap();
            ui.set_counter(ui.get_counter() + 0.01);
            kkuser::test( );
        }
    });


    ui.run()?;

    Ok(())
}

 

感受一下,另一个主文件
appwindow.slint


export component AppWindow inherits Window {
    in-out property <float> counter: 0.42;
    in-out property <string> user: "";
    in-out property <string> status: "";
    in-out property <string> pass: "";
    callback request-increase-value();
    preferred-width: 600px;
    VerticalBox {
    
        Text {
            text: "用户:";
        }
        LineEdit{
            text<=> root.user;
            placeholder-text:"";
        }
        Text {
            text: "密码:";
        }
        LineEdit{
            text <=> root.pass;
            placeholder-text: "";
        }
        ProgressIndicator {
            preferred-width: 100%;
            height: 25px;
            progress <=> counter;
            
          
        }
        Text {
            text: "状态: \{root.status}用户: \{root.user}密码: \{root.pass}";
        }
        Button {
            text: "登陆";
            clicked => {
                root.request-increase-value();
            }
        }
    }
}

完成上线

前面的reqwest中的cookie解决以后,用了一天,折腾regex,把以前定义的两三个提取,请求,再实现了一把。 又细细看了几个regex演示。学不来,学不会, 这玩意真是和R语言一样,太复杂了。我只是按照旧方式实现了。然后,任务做成了长时的。界面就在转圈。于是在改成,后才thread前。我试着让任务和界面通信。把ui传递给,网页处理程序。结果总是不行。然后找了共享全局变量的办法,static mut &。这一串下来,把全局变量,从main,移动到了reqwest主程序的模块。让它处一个独立thread、去完成上线。上main里的ui,独立运行一个loop。长久的2秒,取一个全局变量,set给一个界面元素。到于slint 里的todolist。model这一类的代码,看得一头雾水。有点早。
在main.rs下建立wjc文件夹,作为mod在练习。建立两个文件,一个
mod.rs

pub mod kkuser;
pub mod jscrypto;

kkuser.rs

use reqwest::blocking::Client;
use reqwest;
use regex::Regex;
use std::{collections::HashMap, fmt::format};
use std::error::Error;
use reqwest::{cookie::Jar, Url};
use serde::{Deserialize, Serialize};
use std::{thread, time::Duration};
use serde_json::json;  
// 共享状态
pub static mut thestat: Option< &str> = Some(""); 
pub static mut progbar:f32=0.001;
pub struct KKUser {
    active: bool,
    username: String,
    passwd: String,
    client: Client,
   
} 
pub fn build_kkuser(username: String, passwd: String) -> Result<KKUser,Box<dyn Error>>{
   let client = Client::builder()
   .cookie_store(true)
   .build()?;
    let ret= KKUser {
        active: false,
        username,
        passwd,
         client,
    };
    Ok(ret)

}
impl KKUser {
    fn getloginurl(&self)->Result<String,Box<dyn Error>>{
             
         let re = Regex::new(
            r#"form_login_true" action="(.+?)""#).unwrap();
         let ret =self.client
           .get(BURL)
           .send()?
           .text_with_charset("GBK")?;
        let cap=re.captures(&*ret).unwrap() ;  
        println!("{:?}",&cap[1]);
        let id=String::from(&cap[1]);
        Ok(id)

    }
    pub    fn login(&self )->Result<bool,Box<dyn Error >> {
        let id=self.getloginurl().unwrap();  
       // let check=self.getloginurl(&client).unwrap();
        let var_name = format!("{BURL}{id}");
        let mut map = HashMap::new();
        map.insert("u_dlcode", self.username.as_str());
        map.insert("p_dlcode", self.passwd.as_str());
        let ret:String =  self.client
     .post(var_name)
     .form(&map)
     .send()?
   .text_with_charset("GBK")?;
      println!("{ret}\n");
      assert_eq!(true,ret.contains("home"));
     Ok(ret.to_string().contains("home"))
   }
  }
pub fn test() -> Result<(), Box<dyn Error>> {
     
     let handle= std::thread::spawn(||{
     let  u =build_kkuser("V/JNlfP3SBG8tUJW5tIAo+UogWKJ6StCzwbt4zzm4=".to_string(), "E1Kf42cdpAwXknHXIy6eqIDhtQj85wCqwer6nUTzw".to_string())
      .unwrap(); 
      let r=u.login().unwrap() ;
      let ee=format!("{r}");
        u.v_jiaru().unwrap();
    //更新全局变量,用时间较长。
        u.v_ksxx(  ).unwrap() ;
      Ok(())
    }

在这里插入图片描述

设置密码

这个网站的密码和用户名是AES-MD5,加密的串。算法是js实现 的、为模拟用的是现在加密过的,为了真实使用,还需要有自己的加密算法。由于是浏览器 的算法,我就算是web从服务器来一个询问都是做不到的。为此大学AES一天24小时。也考虑从rust,访问js脚本和语句。用了两个库,都是失败的。虽然有声称可用的。我想,他会加大程序 的体积和依赖外部环境。失去灵动能力。无耐,rust本身,一大堆的半成品。从中总算发现一个现成的实现。其实是发现了两个,一个不含ZeroPading。是js正用的。另一个倒是含有。我算过了大概的一致参数,却不能得到一致结果。后来就想改写js到rust。https://www.codeconvert.ai/free-converter 它会转换js到R代码,在折腾了快一半的时候,深感难。后来看AES的文章说,算法和参数一样,就会有一样的结果。我就又回头比对参数。从cha数组 u8,数组,分析,第个参数,是传过去的什么值。 型号浏览器devtool。看起js的内容,那太方便。才知道刚好差不多。而rust最终给的 [u8] 不是最后js得到的,js最后很像BASE64,最后证明真是,完成后的长充40-43,用parase,解码成bytest、长度刚好32bits。这和rust取得的结果也挺像的。于是base64,处理过。js和R终于成功会合。另外就md5是套在密码外,然后给AES处理的。于是这就好办了。R有很标准的MD5. 有一个点是,处理出的结果还是 [u8].可以直接做AES的输入。但是要是拿来用,就又不对了。 感谢js的直观。它的md5,得到的u8的十六进制小写字符串。然后给了AES。这就好了,R,结果又安装了个小库, 只为了转成十六进制。看来,语言全靠外挂,怪不得可以跑嵌入式400K内存的板子。 我猜也许AI可以做到很多,我还是纯靠搜索了。
在Cargo.toml加入

[dependencies]
 aes="0.8.4"
 block-padding ="0.3.3"
 cbc="0.1.2" 
 base64="0.22.1"
 hex-literal="0.4.1" 
 md-5 = "0.10.6"
 data-encoding = "2.6.0"

在wjc目录建立新文件jscrypto.rs
在同目录下的kkuser.rs添加use super::jscrypto ;以使用其功能

//前端js,使用crypto-js对数据进行AES加密
//function js_encrypt(text){
//var key = CryptoJS.enc.Latin1.parse('1E785CMD585LLS4S');//为了避免补位,直接用16位的秘钥
//var iv = CryptoJS.enc.Latin1.parse('1234431890129056');//16位初始向量
//var encrypted = CryptoJS.AES.encrypt(text, key, {
//iv: iv,
//mode:CryptoJS.mode.CBC,
//padding:CryptoJS.pad.ZeroPadding
//use std::char;
use block_padding::*;
use data_encoding::{HEXLOWER, DecodeError};
use std::error::Error; 
use base64::prelude::*;
 
use md5::{Md5, Digest};
use aes::cipher::{ BlockEncryptMut, KeyIvInit};
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;

pub fn cry_AES(input: &str) -> Result<String, Box<dyn Error>> {

    let mut key= *b"1E785CMD585LLS4S"; 
let mut iv=*b"1234431890129056";
    let mut buf = [0u8; 48];
    let pt_len = input.len();
    buf[..pt_len].copy_from_slice(&input.as_bytes());
    let ct =Aes128CbcEnc::new(&key.into(), &iv.into())
        .encrypt_padded_mut::<ZeroPadding>(&mut buf, pt_len)
        .unwrap();
       
    let bstr =BASE64_STANDARD.encode(ct);
    Ok(bstr)
         
}
pub fn test() {

    let mut key= *b"1E785CMD585LLS4S"; 
let mut iv=*b"1234431890129056";

    let ct = cry_AES("username");
    println!("{:?}",ct.unwrap());
    
    let mut hasher = Md5::new();
    hasher.update(b"passwd");
    let     mdu8 = hasher.finalize();
    let encoded = HEXLOWER.encode(&mdu8);

    println!("{:?}",  cry_AES(encoded.as_str()).unwrap() );
    
}



fn main (){

    test();
}

整理完毕

至此完成,生成的程序10M上下,一个前台窗口,一个后台任务。点一下动一下。我是在苹果Mac上开发的,它 的虚拟机是一个win11 X64,拷贝工程前,删除了target目录,才让移动的,不然动不了。也不知道累积到了几百M、 拷贝过去,cargo build
cargo build --release ,都是干一会就好,本来也没啥内容。 把结果运行一下。换个目录再运行一下。感觉还是蛮爽的。

去年做过一个python jupyter下的,相似程序。它在server上运行,是python在wedget编程,后而传给celery任务。我觉得也挺好的,有容了再提交一篇。


原文地址:https://blog.csdn.net/wjcroom/article/details/142568711

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!