use std::collections::BTreeMap; use std::io::{ErrorKind, Read, Write}; use std::time::Duration; use reqwest::Method; use serde::{Deserialize, Serialize}; use serde_json::{Map, Number, Value}; use crate::fn_common::FnResult; #[derive(Clone, Debug, Serialize, Deserialize)] struct FetchParams { url: String, options: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] struct FetchOptions { method: Option, headers: Option>, body: Option, } pub fn do_fetch(params: &str) -> FnResult { let fetch_params: FetchParams = match serde_json::from_str(params) { Err(e) => return FnResult::fail(format!("Fetch param parse error: {}, raw params: {}", e, params)), Ok(params) => params, }; if fetch_params.url.is_empty() { return FnResult::fail("Param url cannot be empty"); } let client = match reqwest::blocking::Client::builder() .timeout(Duration::from_secs(8)) .connect_timeout(Duration::from_secs(3)) // .proxy(reqwest::Proxy::all("http://127.0.0.1:1086").expect("to proxy failed")) .build() { Err(e) => return FnResult::fail(format!("Create client failed: {}", e)), Ok(client) => client, }; let method = fetch_params.options.as_ref().map(|options| options.method.as_ref().map(|m| m.to_uppercase())).flatten(); let method_str = method.as_ref().map(|m| m.as_str()).unwrap_or_else(|| "GET"); let request_method = match method_str { "GET" => Method::GET, "POST" => Method::POST, m => return FnResult::fail(format!("Unsupported method: {}", m)), }; let mut request_builder = client.request(request_method.clone(), &fetch_params.url); let mut has_user_agent = false; if let Some(options) = &fetch_params.options { if let Some(headers) = &options.headers { for (k, v) in headers { let k = k.to_lowercase(); if k == "user-agent" { has_user_agent = true; } request_builder = request_builder.header(k, v.to_string()); } } } if !has_user_agent { request_builder = request_builder.header("User-Agent", "JavaScriptSandboxContainer/0.1"); } if let Some(options) = &fetch_params.options { if let Some(body) = &options.body { if Method::POST == request_method { let body = reqwest::blocking::Body::from(body.to_string()); request_builder = request_builder.body(body); } } } let mut fetch_response = match request_builder.send() { Err(e) => return FnResult::fail(format!("Send request failed: {}", e)), Ok(fetch_response) => fetch_response, }; let status = fetch_response.status().as_u16(); let headers = fetch_response.headers().clone(); let mut body_buff = Vec::new(); let mut buff = [0_u8; 1024]; loop { match fetch_response.read(&mut buff) { Err(e) if e.kind() == ErrorKind::Interrupted => continue, Err(e) => return FnResult::fail(format!("Fetch response failed: {}", e)), Ok(len) => if len == 0 { break; } else { if let Err(e) = body_buff.write_all(&buff[0..len]) { return FnResult::fail(format!("Fetch response failed: {}", e)); } if body_buff.len() > 1024 * 1024 { return FnResult::fail(format!("Fetch response too large, {} more than 1MB", body_buff.len())); } } } } let mut result_map = Map::new(); result_map.insert("status".to_string(), Value::Number(Number::from(status))); let mut headers_map = Map::new(); for (k, v) in headers { let header_key = k.map(|n| n.as_str().to_string()).unwrap_or_else(|| "_".to_string()); let header_value = match v.to_str() { Err(_) => continue, Ok(v) => v.to_string(), }; headers_map.insert(header_key, Value::String(header_value)); } result_map.insert("headers".to_string(), Value::Object(headers_map)); result_map.insert("body".to_string(), Value::String(String::from_utf8_lossy(&body_buff).to_string())); FnResult::success(format!("{}", Value::Object(result_map))) }