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