br_reqwest/
lib.rs

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