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