本文就一个做了三四天的小程序讲第一次学用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在实现。结果还是一样。直到后来找到了真实原因是一个变量可变的声明,
- 建立图形界面,调查对比后使用slint。
- 提交reqwest,分form.和json数据,cookie保存登陆状态,取回response并且regex分析结果,解析json结果。
- 更新界面状态,使用两个线程,一个交互web server结果保存在全局变量,一个刷新全局变量到界面状态。
- 编码用户密码以方便登陆,用户明文到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任务。我觉得也挺好的,有容了再提交一篇。