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