1use json::{array, object, JsonValue};
2use log::{debug, error, info};
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::io::Write;
6use std::path::Path;
7use std::process::Command;
8use std::str::Lines;
9use std::{env, fs, str};
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([
202 "--cert-type".to_string(),
203 "P12".to_string(),
204 "--cert".to_string(),
205 self.cert_p12_filename.to_string(),
206 "--pass".to_string(),
207 self.cert_p12_password.to_string(),
208 ]);
209 }
210
211 if self.debug {
212 self.args
213 .extend(["--trace-ascii".to_string(), "br-reqwest.log".to_string()]);
214 }
215
216 if self.request_timeout > 0 {
217 self.args.extend([
218 "--connect-timeout".to_string(),
219 self.request_timeout.to_string(),
220 ]);
221 }
222 if self.response_timeout > 0 {
223 self.args
224 .extend(["--max-time".to_string(), self.response_timeout.to_string()]);
225 }
226
227 if self.retry > 0 {
228 self.args
229 .extend(["--retry".to_string(), self.retry.to_string()]);
230 }
231
232 match &self.proxy {
233 ProxyInfo::None => {
234 self.args.extend(["--noproxy".to_string(), "*".to_string()]);
235 }
236 ProxyInfo::Addr(proxy) => {
237 self.args.extend(["-x".to_string(), proxy.to_string()]);
238 }
239 ProxyInfo::AddrWithAccount(proxy, user, pwd) => {
240 self.args.extend([
241 "-x".to_string(),
242 proxy.to_string(),
243 "-U".to_string(),
244 format!("{user}:{pwd}"),
245 ]);
246 }
247 }
248
249 match self.method {
250 Method::HEAD => {
251 self.args.push("-I".to_string());
252 }
253 _ => {
254 self.args.extend([
255 "-i".to_string(),
256 "-X".to_string(),
257 self.method.to_str().to_uppercase(),
258 ]);
259 }
260 }
261 self.args.push(self.url.to_string());
262
263 if !self.header.is_empty() {
264 for (key, value) in self.header.iter() {
265 self.args
266 .extend(["-H".to_string(), format!("{key}: {value}")]);
267 }
268 }
269
270 match self.content_type {
271 ContentType::FormData => {
272 for (_, value) in self.body_data.entries() {
273 self.args.push("-F".to_string());
274
275 if value[2].is_empty() {
276 self.args.push(format!("{}={}", value[0], value[1]));
277 } else {
278 self.args
279 .push(format!("{}={};{}", value[0], value[1], value[2]));
280 }
281 }
282 }
283 ContentType::FormUrlencoded => {
284 self.args.push("-d".to_string());
285 let mut d = vec![];
286 for (key, value) in self.body_data.entries() {
287 d.push(format!("{key}={value}"))
288 }
289 self.args.push(d.join("&").to_string());
290 }
291 ContentType::Json | ContentType::Xml => {
292 self.args
293 .extend(["-d".to_string(), format!("{}", self.body_data)]);
294 }
295 ContentType::Javascript => {}
296 ContentType::Text => {}
297 ContentType::Html => {}
298 ContentType::Other(_) => {}
299 ContentType::Stream => {
300 self.args.push("--data-binary".to_string());
302 self.args.push(format!("@{}", self.body_data));
303 }
304 ContentType::BinaryStr => {
305 self.args.push("--data-binary".to_string());
306 self.args.push(format!("{}", self.body_data));
307 }
308 ContentType::StreamUrlencode => {
309 self.args.push("--data-urlencode".to_string());
310 self.args.push(format!("{}", self.body_data));
311 }
312 }
313 output.args(self.args.as_slice());
314 if self.debug {
315 info!("curl {}", self.args.join(" "));
316 }
317 let req = match output.output() {
318 Ok(e) => e,
319 Err(e) => {
320 return Err(e.to_string());
321 }
322 };
323
324 if req.status.success() {
325 let body = req.stdout.clone();
326 if self.debug {
327 let text = String::from_utf8_lossy(&req.stdout);
328 info!("响应内容:\n{text}");
329 }
330 Ok(Response::new(self.debug, body)?)
331 } else {
332 let err = String::from_utf8_lossy(&req.stderr).to_string();
333 let txt = match err.find("ms:") {
334 None => err,
335 Some(e) => err[e + 3..].trim().to_string(),
336 };
337 Err(txt)
338 }
339 }
340}
341
342enum ProxyInfo {
343 None,
344 Addr(String),
345 AddrWithAccount(String, String, String),
346}
347
348#[derive(Debug)]
349pub struct Response {
350 debug: bool,
351 version: String,
352 status: i32,
353 status_text: String,
354 headers: JsonValue,
355 cookies: JsonValue,
356 body: Body,
358}
359impl Response {
360 const CRLF2: [u8; 4] = [13, 10, 13, 10];
361
362 fn new(debug: bool, body: Vec<u8>) -> Result<Self, String> {
363 let mut that = Self {
364 debug,
365 version: "".to_string(),
366 status: 0,
367 status_text: "".to_string(),
368 headers: object! {},
369 cookies: object! {},
370 body: Default::default(),
371 };
372
373 let (header, body) = match body
374 .windows(Self::CRLF2.len())
375 .position(|window| window == Self::CRLF2)
376 {
377 None => (vec![], vec![]),
378 Some(index) => {
379 let header = body[..index].to_vec();
380 let body = Vec::from(&body[index + Self::CRLF2.len()..]);
381 (header, body)
382 }
383 };
384
385 let text = String::from_utf8_lossy(header.as_slice());
386 let lines = text.lines();
387
388 that.get_request_line(lines.clone().next().expect("get next line in response err"))?;
389 that.get_header(lines.clone())?;
391 that.body.stream = body.clone();
392 that.body.set_content(body);
393 Ok(that)
394 }
395
396 fn get_request_line(&mut self, line: &str) -> Result<(), String> {
398 let lines = line.split_whitespace().collect::<Vec<&str>>();
399 if lines.len() < 2 {
400 return Err("请求行错误".to_string());
401 }
402 self.version = lines[0].to_string();
403 self.status = lines[1].parse::<i32>().unwrap();
404 self.status_text = lines[2].trim().to_string();
405 Ok(())
406 }
407
408 fn get_header(&mut self, data: Lines) -> Result<(), String> {
410 let mut header = object! {};
411 let mut cookie = object! {};
412 let mut body = Body::default();
413
414 for text in data {
415 let (key, value) = match text.trim().find(":") {
416 None => continue,
417 Some(e) => {
418 let key = text[..e].trim().to_lowercase().clone();
419 let value = text[e + 1..].trim().to_string();
420 (key, value)
421 }
422 };
423 match key.as_str() {
424 "content-type" => match value {
425 _ if value.contains("multipart/form-data") => {
426 let boundarys = value.split("boundary=").collect::<Vec<&str>>();
427 body.boundary = boundarys[1..].join("");
428 body.content_type = ContentType::from("multipart/form-data");
429 let _ = header.insert(key.as_str(), "multipart/form-data");
430 }
431 _ => {
432 let value = match value.find(";") {
433 None => value,
434 Some(e) => value[..e].trim().to_string(),
435 };
436 body.content_type = ContentType::from(value.as_str());
437 let _ = header.insert(key.as_str(), body.content_type.str());
438 }
439 },
440 "content-length" => {
441 body.content_length = value.to_string().parse::<usize>().unwrap_or(0);
442 }
443 "cookie" => {
444 let _ = value
445 .split(";")
446 .collect::<Vec<&str>>()
447 .iter()
448 .map(|&x| {
449 match x.find("=") {
450 None => {}
451 Some(index) => {
452 let key = x[..index].trim().to_string();
453 let val = x[index + 1..].trim().to_string();
454 let _ = cookie.insert(key.as_str(), val);
455 }
456 };
457 ""
458 })
459 .collect::<Vec<&str>>();
460 }
461 _ => {
462 if self.debug {
463 debug!("header: {key} = {value}");
464 }
465 let _ = header.insert(key.as_str(), value);
466 }
467 };
468 }
469 self.headers = header.clone();
470 self.cookies = cookie.clone();
471 self.body = body.clone();
472 Ok(())
473 }
474 pub fn status(&self) -> i32 {
475 self.status
476 }
477 pub fn status_text(&self) -> String {
478 self.status_text.clone()
479 }
480 pub fn version(&self) -> String {
481 self.version.clone()
482 }
483 pub fn headers(&self) -> JsonValue {
484 self.headers.clone()
485 }
486 pub fn cookies(&self) -> JsonValue {
487 self.cookies.clone()
488 }
489 pub fn content_type(&self) -> String {
490 self.body.content_type.str().clone()
491 }
492 pub fn json(&self) -> Result<JsonValue, String> {
493 if self.body.content.is_empty() {
494 Ok(object! {})
495 } else {
496 match json::parse(self.body.content.to_string().as_str()) {
497 Ok(e) => Ok(e),
498 Err(e) => Err(e.to_string()),
499 }
500 }
501 }
502 pub fn xml(&self) -> Result<JsonValue, String> {
503 if self.body.content.is_empty() {
504 Ok(object! {})
505 } else {
506 let json = match Element::parse(self.body.content.to_string().as_bytes()) {
507 Ok(e) => xml_element_to_json(&e),
508 Err(e) => {
509 if self.debug {
510 error!("{:?} {}", e.to_string(), self.body.content.clone());
511 }
512 self.body.content.clone()
513 }
514 };
515 Ok(json)
516 }
517 }
518 pub fn body(&self) -> JsonValue {
519 self.body.content.clone()
520 }
521 pub fn stream(&self) -> Vec<u8> {
522 self.body.stream.clone()
523 }
524}
525fn xml_element_to_json(elem: &Element) -> JsonValue {
526 let mut obj = object! {};
527
528 for child in &elem.children {
529 if let xmltree::XMLNode::Element(e) = child {
530 obj[e.name.clone()] = xml_element_to_json(e);
531 }
532 }
533
534 match elem.get_text() {
535 None => obj,
536 Some(text) => JsonValue::from(text.to_string()),
537 }
538}
539
540#[derive(Clone, Debug)]
541pub enum Method {
542 GET,
543 POST,
544 OPTIONS,
545 PATCH,
546 HEAD,
547 DELETE,
548 TRACE,
549 PUT,
550 NONE,
551}
552
553impl Method {
554 pub fn to_str(&self) -> String {
555 match self {
556 Method::GET => "GET",
557 Method::POST => "POST",
558 Method::OPTIONS => "OPTIONS",
559 Method::PATCH => "PATCH",
560 Method::HEAD => "HEAD",
561 Method::DELETE => "DELETE",
562 Method::TRACE => "TRACE",
563 Method::PUT => "PUT",
564 Method::NONE => "NONE",
565 }
566 .to_string()
567 }
568 pub fn from(name: &str) -> Self {
569 match name.to_lowercase().as_str() {
570 "post" => Self::POST,
571 "get" => Self::GET,
572 "head" => Self::HEAD,
573 "put" => Self::PUT,
574 "delete" => Self::DELETE,
575 "options" => Self::OPTIONS,
576 "patch" => Self::PATCH,
577 "trace" => Self::TRACE,
578 _ => Self::NONE,
579 }
580 }
581}
582#[derive(Clone, Debug)]
583pub enum Version {
584 Http09,
585 Http10,
586 Http11,
587 H2,
588 H3,
589 None,
590}
591
592impl Version {
593 pub fn str(&mut self) -> String {
594 match self {
595 Version::Http09 => "HTTP/0.9",
596 Version::Http10 => "HTTP/1.0",
597 Version::Http11 => "HTTP/1.1",
598 Version::H2 => "HTTP/2.0",
599 Version::H3 => "HTTP/3.0",
600 Version::None => "",
601 }
602 .to_string()
603 }
604 pub fn from(name: &str) -> Version {
605 match name {
606 "HTTP/0.9" => Self::Http09,
607 "HTTP/1.0" => Self::Http10,
608 "HTTP/1.1" => Self::Http11,
609 "HTTP/2.0" => Self::H2,
610 "HTTP/3.0" => Self::H3,
611 _ => Self::None,
612 }
613 }
614 pub fn set_version(name: &str) -> Version {
615 match name {
616 "0.9" => Self::Http09,
617 "1.0" => Self::Http10,
618 "1.1" => Self::Http11,
619 "2.0" => Self::H2,
620 "3.0" => Self::H3,
621 _ => Self::None,
622 }
623 }
624}
625
626#[derive(Clone, Debug)]
627pub enum FormData {
628 Text(String, JsonValue, String),
632 File(String, String, String),
636 None,
637}
638
639#[derive(Debug, Clone)]
640pub struct Body {
641 pub content_type: ContentType,
642 pub boundary: String,
643 pub content_length: usize,
644 pub content: JsonValue,
645 pub stream: Vec<u8>,
646}
647impl Body {
648 pub fn decode(input: &str) -> Result<String, String> {
650 let mut decoded = String::new();
651 let bytes = input.as_bytes();
652 let mut i = 0;
653
654 while i < bytes.len() {
655 if bytes[i] == b'%' {
656 if i + 2 >= bytes.len() {
657 return Err("Incomplete percent-encoding".into());
658 }
659 let hex = &input[i + 1..i + 3];
660 match u8::from_str_radix(hex, 16) {
661 Ok(byte) => decoded.push(byte as char),
662 Err(_) => return Err(format!("Invalid percent-encoding: %{hex}")),
663 }
664 i += 3;
665 } else if bytes[i] == b'+' {
666 decoded.push(' ');
667 i += 1;
668 } else {
669 decoded.push(bytes[i] as char);
670 i += 1;
671 }
672 }
673
674 Ok(decoded)
675 }
676 pub fn set_content(&mut self, data: Vec<u8>) {
677 match self.content_type.clone() {
678 ContentType::FormData | ContentType::Stream => {
679 let mut fields = object! {};
680 let boundary_marker = format!("--{}", self.boundary);
681 let text = unsafe { String::from_utf8_unchecked(data) };
682 let parts = text.split(&boundary_marker).collect::<Vec<&str>>();
683 for part in parts {
684 let part = part.trim();
685 if part.is_empty() || part == "--" {
686 continue; }
688
689 let mut headers_and_body = part.splitn(2, "\r\n\r\n");
690 if let (Some(headers), Some(body)) =
691 (headers_and_body.next(), headers_and_body.next())
692 {
693 let headers = headers.split("\r\n");
695
696 let mut field_name = "";
697 let mut filename = "";
698 let mut content_type = ContentType::Text;
699
700 for header in headers {
701 if header.to_lowercase().starts_with("content-disposition:") {
702 match header.find("filename=\"") {
703 None => {}
704 Some(filename_start) => {
705 let filename_len = filename_start + 10;
706 let filename_end =
707 header[filename_len..].find('"').unwrap()
708 + filename_len;
709 filename = &header[filename_len..filename_end];
710 }
711 }
712
713 match header.find("name=\"") {
714 None => {}
715 Some(name_start) => {
716 let name_start = name_start + 6;
717 let name_end =
718 header[name_start..].find('"').unwrap() + name_start;
719 field_name = &header[name_start..name_end];
720 }
721 }
722 }
723 if header.to_lowercase().starts_with("content-type:") {
724 content_type = ContentType::from(
725 header
726 .to_lowercase()
727 .trim_start_matches("content-type:")
728 .trim(),
729 );
730 }
731 }
732
733 if filename.is_empty() {
734 fields[field_name.to_string()] = JsonValue::from(body);
735 } else {
736 let mut temp_dir = env::temp_dir();
738 temp_dir.push(filename);
740 let mut temp_file = match fs::File::create(&temp_dir) {
742 Ok(e) => e,
743 Err(_) => continue,
744 };
745 if temp_file.write(body.as_bytes()).is_ok() {
746 if fields[field_name.to_string()].is_empty() {
747 fields[field_name.to_string()] = array![]
748 }
749
750 let extension = Path::new(filename)
751 .extension() .and_then(|ext| ext.to_str()); let suffix = extension.unwrap_or("txt");
755
756 fields[field_name.to_string()]
757 .push(object! {
758 name:filename,
760 suffix:suffix,
761 size:body.len(),
762 "type":content_type.str(),
763 file:temp_dir.to_str()
764 })
765 .unwrap();
766 };
767 }
768 }
769 }
770 self.content = fields;
771 }
772 ContentType::FormUrlencoded => {
773 let text = unsafe { String::from_utf8_unchecked(data) };
774 let params = text.split("&").collect::<Vec<&str>>();
775 let mut list = object! {};
776 for param in params.iter() {
777 let t = param
778 .split("=")
779 .collect::<Vec<&str>>()
780 .iter()
781 .map(|&x| Body::decode(x).unwrap_or(x.to_string()))
782 .collect::<Vec<String>>();
783 list[t[0].to_string()] = t[1].clone().into();
784 }
785 self.content = list;
786 }
787 ContentType::Json => {
788 let text = unsafe { String::from_utf8_unchecked(data) };
789 self.content = json::parse(text.as_str()).unwrap_or(object! {});
790 }
791 ContentType::Xml => {
792 let text = unsafe { String::from_utf8_unchecked(data) };
793 self.content = text.into();
794 }
795 ContentType::Html | ContentType::Text | ContentType::Javascript => {
796 let text = unsafe { String::from_utf8_unchecked(data) };
797 self.content = text.into();
798 }
799 ContentType::Other(_) => self.content = data.into(),
800 ContentType::BinaryStr => todo!(),
801 ContentType::StreamUrlencode => todo!(),
802 }
803 }
804}
805
806impl Default for Body {
807 fn default() -> Self {
808 Self {
809 content_type: ContentType::Other("text/plain".to_string()),
810 boundary: "".to_string(),
811 content_length: 0,
812 content: object! {},
813 stream: vec![],
814 }
815 }
816}
817
818#[derive(Debug, Clone)]
820pub enum ContentType {
821 FormData,
822 FormUrlencoded,
823 Json,
824 Xml,
825 Javascript,
826 Text,
827 Html,
828 Stream,
829 BinaryStr,
830 StreamUrlencode,
831 Other(String),
832}
833impl ContentType {
834 pub fn from(name: &str) -> Self {
835 match name {
836 "multipart/form-data" => Self::FormData,
837 "application/x-www-form-urlencoded" => Self::FormUrlencoded,
838 "application/json" => Self::Json,
839 "application/xml" | "text/xml" => Self::Xml,
840 "application/javascript" => Self::Javascript,
841 "text/html" => Self::Html,
842 "text/plain" => Self::Text,
843 "application/octet-stream" => Self::Stream,
844 _ => Self::Other(name.to_string()),
845 }
846 }
847 pub fn str(&self) -> String {
848 match self {
849 Self::FormData => "multipart/form-data",
850 Self::FormUrlencoded => "application/x-www-form-urlencoded",
851 Self::Json => "application/json",
852 Self::Xml => "application/xml",
853 Self::Javascript => "application/javascript",
854 Self::Text => "text/plain",
855 Self::Html => "text/html",
856 Self::Other(name) => name,
857 Self::Stream => "application/octet-stream",
858 Self::BinaryStr => "application/octet-stream",
859 ContentType::StreamUrlencode => "application/x-www-form-urlencoded",
860 }
861 .to_string()
862 }
863}