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