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