br_reqwest/
lib.rs

1use json::{array, object, JsonValue};
2use log::{debug, error};
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::{env, fs, str};
6use std::io::Write;
7use std::path::Path;
8use std::process::Command;
9use std::str::Lines;
10use xmltree::Element;
11
12pub struct Client {
13    pub url: String,
14    debug: bool,
15    pub method: Method,
16    pub header: HashMap<String, String>,
17    body_data: JsonValue,
18    pub content_type: ContentType,
19    proxy: ProxyInfo,
20    cert_p12: bool,
21    cert_p12_filename: String,
22    cert_p12_password: String,
23}
24
25impl Default for Client {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl Client {
32    pub fn new() -> Self {
33        Self {
34            url: "".to_string(),
35            debug: false,
36            method: Method::NONE,
37            header: Default::default(),
38            body_data: object! {},
39            content_type: ContentType::Text,
40            proxy: ProxyInfo::None,
41            cert_p12: false,
42            cert_p12_filename: "".to_string(),
43            cert_p12_password: "".to_string(),
44        }
45    }
46    pub fn debug(&mut self) -> &mut Self {
47        self.debug = true;
48        self
49    }
50    pub fn post(&mut self, url: &str) -> &mut Self {
51        self.url = url.to_string();
52        self.method = Method::POST;
53        self
54    }
55    pub fn get(&mut self, url: &str) -> &mut Self {
56        self.url = url.to_string();
57        self.method = Method::GET;
58        self
59    }
60    pub fn put(&mut self, url: &str) -> &mut Self {
61        self.url = url.to_string();
62        self.method = Method::PUT;
63        self
64    }
65    pub fn patch(&mut self, url: &str) -> &mut Self {
66        self.url = url.to_string();
67        self.method = Method::PATCH;
68        self
69    }
70    pub fn delete(&mut self, url: &str) -> &mut Self {
71        self.url = url.to_string();
72        self.method = Method::DELETE;
73        self
74    }
75    pub fn head(&mut self, url: &str) -> &mut Self {
76        self.url = url.to_string();
77        self.method = Method::HEAD;
78        self
79    }
80    pub fn trace(&mut self, url: &str) -> &mut Self {
81        self.url = url.to_string();
82        self.method = Method::TRACE;
83        self
84    }
85    pub fn header(&mut self, key: &str, value: &str) -> &mut Self {
86        self.header.insert(key.to_string(), value.to_string());
87        self
88    }
89
90    pub fn query(&mut self, params: JsonValue) -> &mut Self {
91        let mut txt = vec![];
92        for (key, value) in params.entries() {
93            txt.push(format!("{key}={value}"));
94        }
95        if self.url.contains('?') {
96            if !txt.is_empty() {
97                self.url = format!("{}&{}", self.url, txt.join("&"));
98            }
99        } else if !txt.is_empty() {
100            self.url = format!("{}?{}", self.url, txt.join("&"));
101        }
102        self
103    }
104
105
106    /// JSON请求
107    pub fn raw_json(&mut self, data: JsonValue) -> &mut Self {
108        self.header("Content-Type", ContentType::Json.str().as_str());
109        self.content_type = ContentType::Json;
110        self.body_data = data;
111        self
112    }
113    pub fn raw_xml(&mut self, data: JsonValue) -> &mut Self {
114        self.header("Content-Type", ContentType::Xml.str().as_str());
115        self.content_type = ContentType::Xml;
116        self.body_data = data;
117        self
118    }
119    pub fn raw_stream(&mut self, filename: &str) -> &mut Self {
120        self.content_type = ContentType::Stream;
121        self.body_data = filename.into();
122        self
123    }
124    /// FormData请求
125    pub fn form_data(&mut self, data: JsonValue) -> &mut Self {
126        self.header("Content-Type", ContentType::FormData.str().as_str());
127        self.content_type = ContentType::FormData;
128        self.body_data = data;
129        self
130    }
131    /// FormData请求
132    pub fn form_urlencoded(&mut self, data: JsonValue) -> &mut Self {
133        self.header("Content-Type", ContentType::FormUrlencoded.str().as_str());
134        self.content_type = ContentType::FormUrlencoded;
135        self.body_data = data;
136        self
137    }
138    /// 开启旧的ssl
139    pub fn set_cert_p12(&mut self, filename: &str, password: &str) -> &mut Self {
140        self.cert_p12_filename = filename.to_string();
141        self.cert_p12_password = password.to_string();
142        self.cert_p12 = true;
143        self
144    }
145    /// 指定代理
146    // curl -x socks5://proxy.example.com:1080 http://example.com
147    pub fn add_proxy(&mut self, proxy_addr: String) {
148        self.proxy = ProxyInfo::Addr(proxy_addr);
149    }
150
151    /// 指定代理, 该代理需要用户名和密码认证
152    // curl -x http://proxy-server:port -U username:password http://example.com
153    pub fn add_proxy_with_account(&mut self, proxy_addr: String, user: String, password: String) {
154        self.proxy = ProxyInfo::AddrWithAccount(proxy_addr, user, password);
155    }
156
157    pub fn send(&mut self) -> Result<Response, String> {
158        let mut output = Command::new("curl");
159
160        if self.cert_p12 {
161            output.args(["--cert-type", "P12", "--cert", self.cert_p12_filename.as_str(), "--pass", self.cert_p12_password.as_str()]);
162        }
163
164        match &self.proxy {
165            ProxyInfo::None => {
166                output.args(["--noproxy", "*"]);
167            }
168            ProxyInfo::Addr(proxy) => {
169                output.args(["-x", proxy]);
170            }
171            ProxyInfo::AddrWithAccount(proxy, user, pwd) => {
172                output.args(["-x", proxy, "-U", format!("{user}:{pwd}").as_str()]);
173            }
174        }
175
176        match self.method {
177            Method::HEAD => {
178                output.arg("-I");
179            }
180            _ => {
181                output.arg("-i");
182                output.arg("-X");
183                output.arg(self.method.to_str().to_uppercase());
184            }
185        }
186        output.arg(self.url.as_str());
187
188        if !self.header.is_empty() {
189            for (key, value) in self.header.iter() {
190                output.arg("-H");
191                output.arg(format!("{key}: {value}"));
192            }
193        }
194
195        match self.content_type {
196            ContentType::FormData => {
197                for (_, value) in self.body_data.entries() {
198                    output.arg("-F");
199                    if value[2].is_empty() {
200                        output.arg(format!("{}={}", value[0], value[1]));
201                    } else {
202                        output.arg(format!("{}={};{}", value[0], value[1], value[2]));
203                    }
204                }
205            }
206            ContentType::FormUrlencoded => {
207                output.arg("-d");
208                let mut d = vec![];
209                for (key, value) in self.body_data.entries() {
210                    d.push(format!("{key}={value}"))
211                }
212                output.arg(d.join("&"));
213            }
214            ContentType::Json | ContentType::Xml => {
215                output.arg("-d");
216                output.arg(format!("{}", self.body_data));
217            }
218            ContentType::Javascript => {}
219            ContentType::Text => {}
220            ContentType::Html => {}
221            ContentType::Other(_) => {}
222            ContentType::Stream => {
223                //output.arg("-F");
224                //output.arg(format!("file={}", self.body_data));
225                //output.arg("-H");
226                //output.arg("Transfer-Encoding: chunked");
227                // 文件路径
228                output.arg("--data-binary");
229                output.arg(format!("@{}", self.body_data));
230            }
231        }
232
233        if self.debug {
234            println!("{}", self.url);
235            println!("{output:?}");
236        }
237        let req = match output.output() {
238            Ok(e) => e,
239            Err(e) => {
240                return Err(e.to_string());
241            }
242        };
243
244        if req.status.success() {
245            let body = req.stdout.clone();
246            if self.debug {
247                let text = String::from_utf8_lossy(&req.stdout);
248                println!("响应内容:\n{text}");
249            }
250            Ok(Response::new(self.debug, body)?)
251        } else {
252            let err = String::from_utf8_lossy(&req.stderr).to_string();
253            let txt = match err.find("ms:") {
254                None => err,
255                Some(e) => {
256                    err[e + 3..].trim().to_string()
257                }
258            };
259            Err(txt)
260        }
261    }
262}
263
264enum ProxyInfo {
265    None,
266    Addr(String),
267    AddrWithAccount(String, String, String),
268}
269
270
271#[derive(Debug)]
272pub struct Response {
273    debug: bool,
274    status: i32,
275    method: Method,
276    headers: JsonValue,
277    cookies: JsonValue,
278    /// 请求体
279    body: Body,
280}
281impl Response {
282    const CRLF2: [u8; 4] = [13, 10, 13, 10];
283
284    fn new(debug: bool, body: Vec<u8>) -> Result<Self, String> {
285        let mut that = Self {
286            debug,
287            status: 0,
288            method: Method::NONE,
289            headers: object! {},
290            cookies: object! {},
291            body: Default::default(),
292        };
293
294        let (header, body) = match body.windows(Self::CRLF2.len()).position(|window| window == Self::CRLF2) {
295            None => (vec![], vec![]),
296            Some(index) => {
297                let header = body[..index].to_vec();
298                let body = Vec::from(&body[index + Self::CRLF2.len()..]);
299                (header, body)
300            }
301        };
302
303        let text = String::from_utf8_lossy(header.as_slice());
304        let lines = text.lines();
305
306        that.get_request_line(lines.clone().next().expect("get next line in response err"))?;
307        // Header处理
308        that.get_header(lines.clone())?;
309        that.body.stream = body.clone();
310        that.body.set_content(body);
311        Ok(that)
312    }
313
314    /// 获取请求行信息
315    fn get_request_line(&mut self, line: &str) -> Result<(), String> {
316        let lines = line.split_whitespace().collect::<Vec<&str>>();
317        if lines.len() < 2 {
318            return Err("请求行错误".to_string());
319        }
320        self.method = Method::from(lines[0]);
321        self.status = lines[1].parse::<i32>().unwrap();
322        Ok(())
323    }
324
325    /// Header处理
326    fn get_header(&mut self, data: Lines) -> Result<(), String> {
327        let mut header = object! {};
328        let mut cookie = object! {};
329        let mut body = Body::default();
330
331        for text in data {
332            let (key, value) = match text.trim().find(":") {
333                None => continue,
334                Some(e) => {
335                    let key = text[..e].trim().to_lowercase().clone();
336                    let value = text[e + 1..].trim().to_string();
337                    (key, value)
338                }
339            };
340            match key.as_str() {
341                "content-type" => match value {
342                    _ if value.contains("multipart/form-data") => {
343                        let boundarys = value.split("boundary=").collect::<Vec<&str>>();
344                        body.boundary = boundarys[1..].join("");
345                        body.content_type = ContentType::from("multipart/form-data");
346                        let _ = header.insert(key.as_str(), "multipart/form-data");
347                    }
348                    _ => {
349                        let value = match value.find(";") {
350                            None => value,
351                            Some(e) => value[..e].trim().to_string(),
352                        };
353                        body.content_type = ContentType::from(value.as_str());
354                        let _ = header.insert(key.as_str(), body.content_type.str());
355                    }
356                },
357                "content-length" => {
358                    body.content_length = value.to_string().parse::<usize>().unwrap_or(0);
359                }
360                "cookie" => {
361                    let _ = value.split(";").collect::<Vec<&str>>().iter().map(|&x| {
362                        match x.find("=") {
363                            None => {}
364                            Some(index) => {
365                                let key = x[..index].trim().to_string();
366                                let val = x[index + 1..].trim().to_string();
367                                let _ = cookie.insert(key.as_str(), val);
368                            }
369                        };
370                        ""
371                    }).collect::<Vec<&str>>();
372                }
373                _ => {
374                    if self.debug {
375                        debug!("header: {key} = {value}");
376                    }
377                    let _ = header.insert(key.as_str(), value);
378                }
379            };
380        }
381        self.headers = header.clone();
382        self.cookies = cookie.clone();
383        self.body = body.clone();
384        Ok(())
385    }
386    pub fn status(&self) -> i32 {
387        self.status
388    }
389    pub fn headers(&self) -> JsonValue {
390        self.headers.clone()
391    }
392    pub fn cookies(&self) -> JsonValue {
393        self.cookies.clone()
394    }
395    pub fn content_type(&self) -> String {
396        self.body.content_type.str().clone()
397    }
398    pub fn json(&self) -> Result<JsonValue, String> {
399        if self.body.content.is_empty() {
400            Ok(object! {})
401        } else {
402            match json::parse(self.body.content.to_string().as_str()) {
403                Ok(e) => Ok(e),
404                Err(e) => Err(e.to_string())
405            }
406        }
407    }
408    pub fn xml(&self) -> Result<JsonValue, String> {
409        if self.body.content.is_empty() {
410            Ok(object! {})
411        } else {
412            let json = match Element::parse(self.body.content.to_string().as_bytes()) {
413                Ok(e) => xml_element_to_json(&e),
414                Err(e) => {
415                    if self.debug {
416                        error!("{:?} {}", e.to_string(),self.body.content.clone());
417                    }
418                    self.body.content.clone()
419                }
420            };
421            Ok(json)
422        }
423    }
424    pub fn body(&self) -> JsonValue {
425        self.body.content.clone()
426    }
427    pub fn stream(&self) -> Vec<u8> {
428        self.body.stream.clone()
429    }
430}
431fn xml_element_to_json(elem: &Element) -> JsonValue {
432    let mut obj = object! {};
433
434    for child in &elem.children {
435        if let xmltree::XMLNode::Element(e) = child {
436            obj[e.name.clone()] = xml_element_to_json(e);
437        }
438    }
439
440    match elem.get_text() {
441        None => obj,
442        Some(text) => JsonValue::from(text.to_string()),
443    }
444}
445
446#[derive(Clone, Debug)]
447pub enum Method {
448    GET,
449    POST,
450    OPTIONS,
451    PATCH,
452    HEAD,
453    DELETE,
454    TRACE,
455    PUT,
456    NONE,
457}
458
459impl Method {
460    pub fn to_str(&self) -> String {
461        match self {
462            Method::GET => "GET",
463            Method::POST => "POST",
464            Method::OPTIONS => "OPTIONS",
465            Method::PATCH => "PATCH",
466            Method::HEAD => "HEAD",
467            Method::DELETE => "DELETE",
468            Method::TRACE => "TRACE",
469            Method::PUT => "PUT",
470            Method::NONE => "NONE",
471        }.to_string()
472    }
473    pub fn from(name: &str) -> Self {
474        match name.to_lowercase().as_str() {
475            "post" => Self::POST,
476            "get" => Self::GET,
477            "head" => Self::HEAD,
478            "put" => Self::PUT,
479            "delete" => Self::DELETE,
480            "options" => Self::OPTIONS,
481            "patch" => Self::PATCH,
482            "trace" => Self::TRACE,
483            _ => Self::NONE,
484        }
485    }
486}
487#[derive(Clone, Debug)]
488pub enum Version {
489    Http09,
490    Http10,
491    Http11,
492    H2,
493    H3,
494    None,
495}
496
497impl Version {
498    pub fn str(&mut self) -> String {
499        match self {
500            Version::Http09 => "HTTP/0.9",
501            Version::Http10 => "HTTP/1.0",
502            Version::Http11 => "HTTP/1.1",
503            Version::H2 => "HTTP/2.0",
504            Version::H3 => "HTTP/3.0",
505            Version::None => "",
506        }.to_string()
507    }
508    pub fn from(name: &str) -> Version {
509        match name {
510            "HTTP/0.9" => Self::Http09,
511            "HTTP/1.0" => Self::Http10,
512            "HTTP/1.1" => Self::Http11,
513            "HTTP/2.0" => Self::H2,
514            "HTTP/3.0" => Self::H3,
515            _ => Self::None,
516        }
517    }
518    pub fn set_version(name: &str) -> Version {
519        match name {
520            "0.9" => Self::Http09,
521            "1.0" => Self::Http10,
522            "1.1" => Self::Http11,
523            "2.0" => Self::H2,
524            "3.0" => Self::H3,
525            _ => Self::None,
526        }
527    }
528}
529
530
531#[derive(Clone, Debug)]
532pub enum FormData {
533    /// KEY
534    /// value
535    /// 文本类型
536    Text(String, JsonValue, String),
537    /// 文件名
538    /// 文件路径
539    /// 文件类型
540    File(String, String, String),
541    None,
542}
543
544
545#[derive(Debug, Clone)]
546pub struct Body {
547    pub content_type: ContentType,
548    pub boundary: String,
549    pub content_length: usize,
550    pub content: JsonValue,
551    pub stream: Vec<u8>,
552}
553impl Body {
554    /// 解码
555    pub fn decode(input: &str) -> Result<String, String> {
556        let mut decoded = String::new();
557        let bytes = input.as_bytes();
558        let mut i = 0;
559
560        while i < bytes.len() {
561            if bytes[i] == b'%' {
562                if i + 2 >= bytes.len() {
563                    return Err("Incomplete percent-encoding".into());
564                }
565                let hex = &input[i + 1..i + 3];
566                match u8::from_str_radix(hex, 16) {
567                    Ok(byte) => decoded.push(byte as char),
568                    Err(_) => return Err(format!("Invalid percent-encoding: %{hex}")),
569                }
570                i += 3;
571            } else if bytes[i] == b'+' {
572                decoded.push(' ');
573                i += 1;
574            } else {
575                decoded.push(bytes[i] as char);
576                i += 1;
577            }
578        }
579
580        Ok(decoded)
581    }
582    pub fn set_content(&mut self, data: Vec<u8>) {
583        match self.content_type.clone() {
584            ContentType::FormData | ContentType::Stream => {
585                let mut fields = object! {};
586                let boundary_marker = format!("--{}", self.boundary);
587                let text = unsafe { String::from_utf8_unchecked(data) };
588                let parts = text.split(&boundary_marker).collect::<Vec<&str>>();
589                for part in parts {
590                    let part = part.trim();
591                    if part.is_empty() || part == "--" {
592                        continue; // 跳过无效部分
593                    }
594
595                    let mut headers_and_body = part.splitn(2, "\r\n\r\n");
596                    if let (Some(headers), Some(body)) = (headers_and_body.next(), headers_and_body.next())
597                    {
598                        // 解析头部,查找 Content-Disposition
599                        let headers = headers.split("\r\n");
600
601                        let mut field_name = "";
602                        let mut filename = "";
603                        let mut content_type = ContentType::Text;
604
605                        for header in headers {
606                            if header.to_lowercase().starts_with("content-disposition:") {
607                                match header.find("filename=\"") {
608                                    None => {}
609                                    Some(filename_start) => {
610                                        let filename_len = filename_start + 10;
611                                        let filename_end = header[filename_len..].find('"').unwrap() + filename_len;
612                                        filename = &header[filename_len..filename_end];
613                                    }
614                                }
615
616                                match header.find("name=\"") {
617                                    None => {}
618                                    Some(name_start) => {
619                                        let name_start = name_start + 6;
620                                        let name_end = header[name_start..].find('"').unwrap() + name_start;
621                                        field_name = &header[name_start..name_end];
622                                    }
623                                }
624                            }
625                            if header.to_lowercase().starts_with("content-type:") {
626                                content_type = ContentType::from(
627                                    header.to_lowercase().trim_start_matches("content-type:").trim(),
628                                );
629                            }
630                        }
631
632                        if filename.is_empty() {
633                            fields[field_name.to_string()] = JsonValue::from(body);
634                        } else {
635                            // 获取系统临时目录
636                            let mut temp_dir = env::temp_dir();
637                            // 构造临时文件的完整路径
638                            temp_dir.push(filename);
639                            // 打开(创建)临时文件
640                            let mut temp_file = match fs::File::create(&temp_dir) {
641                                Ok(e) => e,
642                                Err(_) => continue,
643                            };
644                            if temp_file.write(body.as_bytes()).is_ok() {
645                                if fields[field_name.to_string()].is_empty() {
646                                    fields[field_name.to_string()] = array![]
647                                }
648
649                                let extension = Path::new(filename).extension() // 可能返回 None
650                                                                   .and_then(|ext| ext.to_str()); // 转换为 &str
651
652                                let suffix = extension.unwrap_or("txt");
653
654                                fields[field_name.to_string()].push(object! {
655                                        //id:sha_256(body.as_bytes().to_vec()),
656                                        name:filename,
657                                        suffix:suffix,
658                                        size:body.len(),
659                                        "type":content_type.str(),
660                                        file:temp_dir.to_str()
661                                    }).unwrap();
662                            };
663                        }
664                    }
665                }
666                self.content = fields;
667            }
668            ContentType::FormUrlencoded => {
669                let text = unsafe { String::from_utf8_unchecked(data) };
670                let params = text.split("&").collect::<Vec<&str>>();
671                let mut list = object! {};
672                for param in params.iter() {
673                    let t = param.split("=").collect::<Vec<&str>>().iter().map(|&x| Body::decode(x).unwrap_or(x.to_string())).collect::<Vec<String>>();
674                    list[t[0].to_string()] = t[1].clone().into();
675                }
676                self.content = list;
677            }
678            ContentType::Json => {
679                let text = unsafe { String::from_utf8_unchecked(data) };
680                self.content = json::parse(text.as_str()).unwrap_or(object! {});
681            }
682            ContentType::Xml => {
683                let text = unsafe { String::from_utf8_unchecked(data) };
684                self.content = text.into();
685            }
686            ContentType::Html | ContentType::Text | ContentType::Javascript => {
687                let text = unsafe { String::from_utf8_unchecked(data) };
688                self.content = text.into();
689            }
690            ContentType::Other(_) => self.content = data.into()
691        }
692    }
693}
694
695impl Default for Body {
696    fn default() -> Self {
697        Self {
698            content_type: ContentType::Other("text/plain".to_string()),
699            boundary: "".to_string(),
700            content_length: 0,
701            content: object! {},
702            stream: vec![],
703        }
704    }
705}
706
707/// 内容类型
708#[derive(Debug, Clone)]
709pub enum ContentType {
710    FormData,
711    FormUrlencoded,
712    Json,
713    Xml,
714    Javascript,
715    Text,
716    Html,
717    Stream,
718    Other(String),
719}
720impl ContentType {
721    pub fn from(name: &str) -> Self {
722        match name {
723            "multipart/form-data" => Self::FormData,
724            "application/x-www-form-urlencoded" => Self::FormUrlencoded,
725            "application/json" => Self::Json,
726            "application/xml" | "text/xml" => Self::Xml,
727            "application/javascript" => Self::Javascript,
728            "text/html" => Self::Html,
729            "text/plain" => Self::Text,
730            "application/octet-stream" => Self::Stream,
731            _ => Self::Other(name.to_string()),
732        }
733    }
734    pub fn str(&self) -> String {
735        match self {
736            Self::FormData => "multipart/form-data",
737            Self::FormUrlencoded => "application/x-www-form-urlencoded",
738            Self::Json => "application/json",
739            Self::Xml => "application/xml",
740            Self::Javascript => "application/javascript",
741            Self::Text => "text/plain",
742            Self::Html => "text/html",
743            Self::Other(name) => name,
744            Self::Stream => "application/octet-stream",
745        }.to_string()
746    }
747}