feat: add fetch-rs
This commit is contained in:
@@ -142,6 +142,7 @@ Project or files:
|
||||
├── __network
|
||||
│ ├── async-speed-limit
|
||||
│ ├── dingo
|
||||
│ ├── fetch-rs
|
||||
│ ├── ip
|
||||
│ ├── ipnet-demo
|
||||
│ ├── iptables
|
||||
@@ -274,6 +275,6 @@ Project or files:
|
||||
├── vec.rs
|
||||
└── while.rs
|
||||
|
||||
243 directories, 40 files
|
||||
244 directories, 40 files
|
||||
```
|
||||
|
||||
|
||||
1201
__network/fetch-rs/Cargo.lock
generated
Normal file
1201
__network/fetch-rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
__network/fetch-rs/Cargo.toml
Normal file
10
__network/fetch-rs/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "fetch-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
reqwest = { version = "0.11.18", features = ["blocking"] }
|
||||
rust_util = "0.6.41"
|
||||
18
__network/fetch-rs/README.md
Normal file
18
__network/fetch-rs/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
```js
|
||||
const response = fetch("http://example.com/movies.json")
|
||||
const responseJson = response.json();
|
||||
```
|
||||
|
||||
```js
|
||||
const response = fetch(url, {
|
||||
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// 'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
redirect: "follow", // manual, *follow, error
|
||||
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||
});
|
||||
```
|
||||
143
__network/fetch-rs/src/main.rs
Normal file
143
__network/fetch-rs/src/main.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::{Method, Url};
|
||||
use reqwest::blocking::{Body, Client, Request};
|
||||
use reqwest::header::{HeaderName, HeaderValue};
|
||||
use rust_util::{simple_error, XResult};
|
||||
|
||||
fn main() -> XResult<()> {
|
||||
let response = fetch(
|
||||
"https://hatter.ink/util/print_request.action",
|
||||
&FetchOptions {
|
||||
method: FetchMethod::Post,
|
||||
headers: Some(vec![
|
||||
FetchHeader {
|
||||
key: "content-type".to_string(),
|
||||
value: "application/json".to_string(),
|
||||
}
|
||||
]),
|
||||
body: Some("{}".as_bytes().to_vec()),
|
||||
..Default::default()
|
||||
})?;
|
||||
println!("Status: {}", response.status);
|
||||
response.headers.iter().for_each(|h| {
|
||||
println!("- {}: {}", h.key, h.value);
|
||||
});
|
||||
println!("----- BEGIN RESPONSE BODY -----\n{}\n----- END RESPONSE BODY -----",
|
||||
String::from_utf8(response.body().clone().unwrap().to_vec())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FetchMethod {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
}
|
||||
|
||||
impl FetchMethod {
|
||||
pub fn require_body(&self) -> bool {
|
||||
match self {
|
||||
FetchMethod::Get => false,
|
||||
FetchMethod::Post | FetchMethod::Put => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FetchMethod {
|
||||
fn default() -> Self {
|
||||
Self::Get
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FetchHeader {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct FetchOptions {
|
||||
pub method: FetchMethod,
|
||||
pub headers: Option<Vec<FetchHeader>>,
|
||||
pub body: Option<Vec<u8>>,
|
||||
// connect timeout or read timeout?
|
||||
pub timeout: Option<Duration>,
|
||||
// proxy supports
|
||||
// hsts supports
|
||||
}
|
||||
|
||||
impl FetchOptions {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FetchResponse {
|
||||
status: u16,
|
||||
headers: Vec<FetchHeader>,
|
||||
body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl FetchResponse {
|
||||
pub fn body(&self) -> &Option<Vec<u8>> {
|
||||
&self.body
|
||||
}
|
||||
|
||||
pub fn headers(&self) /* TODO */ {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn ok(&self) -> bool {
|
||||
self.status == 200
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u16 {
|
||||
self.status
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch(url: &str, option: &FetchOptions) -> XResult<FetchResponse> {
|
||||
let client = Client::builder()
|
||||
// .proxy()
|
||||
.build()?;
|
||||
let method = match option.method {
|
||||
FetchMethod::Get => Method::GET,
|
||||
FetchMethod::Post => Method::POST,
|
||||
FetchMethod::Put => Method::PUT,
|
||||
};
|
||||
let mut request = Request::new(method, Url::from_str(url)?);
|
||||
if let Some(timeout) = &option.timeout {
|
||||
let _ = request.timeout_mut().insert(timeout.to_owned());
|
||||
}
|
||||
if let Some(headers) = &option.headers {
|
||||
let headers_mut = request.headers_mut();
|
||||
for header in headers {
|
||||
let header_name = HeaderName::from_str(&header.key)?;
|
||||
let header_value = HeaderValue::from_str(&header.value)?;
|
||||
headers_mut.insert(header_name, header_value);
|
||||
}
|
||||
}
|
||||
|
||||
if option.method.require_body() && option.body.is_none() {
|
||||
return simple_error!("Require body.");
|
||||
}
|
||||
if let Some(body) = &option.body {
|
||||
let _ = request.body_mut().insert(Body::from(body.to_vec()));
|
||||
}
|
||||
let response = client.execute(request)?;
|
||||
|
||||
let response_status = response.status();
|
||||
let mut response_headers = vec![];
|
||||
let response_header_map = response.headers();
|
||||
for (response_header_name, response_header_vlaue) in response_header_map {
|
||||
let key = response_header_name.to_string();
|
||||
let value = response_header_vlaue.to_str()?.to_string();
|
||||
response_headers.push(FetchHeader { key, value });
|
||||
}
|
||||
let bytes = response.bytes()?;
|
||||
|
||||
Ok(FetchResponse {
|
||||
status: response_status.as_u16(),
|
||||
headers: response_headers,
|
||||
body: Some(bytes.to_vec()),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user