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