특정 조건은 Rust에서 작업하고 그 조건을 제외한 로직은 proxy 서버 개념으로 Main Server로 by-pass하는 Route 로직을 구현해야하는 요구사항.
요구사항
- application/json 형식의 Http 통신을 처리해야한다.
- form-data 형식의 Http 통신을 처리해야한다.
- Header의 모든 데이터를 포함해야 한다.
- Method와 Url은 모두 Dynamic하게 처리해야 한다.
Solution
- Util로 Header 또는 Property에 해당되는 정보를 뽑아오는 함수를 개발한다.
- Util로 Main Server의 Response를 Client용으로 Convert 해주는 함수를 개발한다.
- Util로 Multipart Type인지 확인하는 함수를 개발한다.
- application/json 타입의 Http 통신을 핸들링하는 함수를 개발한다.
- multipart 타입의 Http 통신을 핸들링하는 함수를 개발한다.
Utils
use std::mem::take;
use actix_multipart::Multipart;
use actix_web::{HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder, Responder, web};
use actix_web::http::header;
use actix_web::web::Path;
use futures::{StreamExt, TryStreamExt};
use reqwest::{Error, header::HeaderMap, Method, RequestBuilder, Response};
use crate::json_api::send_application_json_type_api;
// get Request Properties(HeaderMap, HttpMethod, Uri) from actix-web's HttpRequest
pub async fn get_from_client_request_properties(request: &HttpRequest) -> (HeaderMap, Method, String) {
let mut headers: HeaderMap = HeaderMap::new();
println!("request client to this app");
for (header_name, header_value) in request.headers().iter() {
let name = header_name.clone();
let value = header_value.clone();
println!("header name: {:?}, value: {:?}", name, value);
headers.append(name, value);
}
let method = request.method();
let uri = request.uri().to_string().replace("/by-pass", "");
println!("method : {:?}", method);
println!("uri : {:?}", uri);
(headers.clone(), Method::from(method), uri)
}
// create Client Response from Server Response
pub async fn create_client_response_from_server_response(result: Result<Response, Error>) -> HttpResponse {
println!("this app response to client");
return match result {
Ok(response) => {
let status_code = response.status();
let mut res = HttpResponseBuilder::new(status_code);
for (header_name, header_value) in response.headers().iter() {
let name = header_name.clone();
let value = header_value.clone();
println!("header name: {:?}, value: {:?}", name, value);
res.insert_header((name, value));
}
let body = match response.text().await {
Ok(body_string) => body_string,
Err(e) => e.to_string(),
};
res.body(body).into()
}
Err(e) => {
HttpResponse::InternalServerError().body(e.to_string()).into()
}
};
}
// check is multipart type
pub async fn is_multipart(req: &HttpRequest) -> bool {
let content_type = req.headers().get(header::CONTENT_TYPE);
if let Some(content_type) = content_type {
let content_type = content_type.to_str().unwrap_or("");
if content_type.starts_with("multipart/form-data") {
return true;
}
}
false
}
application/json handler
use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder, Responder, web};
use reqwest::{Client, Method, RequestBuilder, Response};
use reqwest::header::HeaderMap;
use serde_json::Value;
use crate::utils::{create_client_response_from_server_response, get_from_client_request_properties};
pub async fn send_application_json_type_api(req: HttpRequest, body: web::Bytes) -> HttpResponse {
println!("start application/json type api generation");
// generate dynamic properties [ headers, method, uri ... ]
let dynamic_request_properties = get_from_client_request_properties(&req).await;
let headers = dynamic_request_properties.0;
let method = dynamic_request_properties.1;
let uri = dynamic_request_properties.2;
// generate request client
let request_body = serde_json::from_slice(&body).unwrap_or(Value::String("".to_string()));
let client = Client::new();
let full_url = "<http://localhost:8081>".to_owned() + uri.as_str();
let result = client
.request(method, full_url)
.headers(headers)
.body(request_body.to_string())
.send()
.await;
create_client_response_from_server_response(result).await
}
multipart tpye hanlder
use std::fs::File;
use std::future::Future;
use std::io::{Read, Write};
use std::process::Output;
use std::ptr::null;
use std::str::FromStr;
use actix_multipart::{Field, Multipart};
use actix_multipart::form::{bytes, MultipartCollect};
use actix_web::{Error, FromRequest, HttpRequest, HttpResponse, HttpResponseBuilder, Responder, web::Payload};
use actix_web::body::MessageBody;
use actix_web::http::header::ContentDisposition;
use actix_web::http::{header, Method};
use actix_web::web::{Buf, BufMut, Bytes, BytesMut};
use futures::{AsyncWriteExt, StreamExt, TryStreamExt};
use mime::Mime;
use reqwest::{RequestBuilder, Response};
use reqwest::header::HeaderMap;
use reqwest::multipart::{Form, Part};
use tempfile::NamedTempFile;
use crate::utils::{create_client_response_from_server_response, get_from_client_request_properties, };
// feature를 impl해야 next()를 할 수 있음...
pub async fn multipart(req: HttpRequest, mut payload: Multipart) -> HttpResponse {
let mut send_form = Form::new();
while let item_try = payload.try_next().await {
match item_try {
Ok(Some(mut item)) => {
let mut bytes = BytesMut::new();
while let Some(chunk_result) = item.next().await {
match chunk_result {
Ok(data) => {
bytes.put_slice(&data);
}
Err(e) => {
eprintln!("Error while reading chunk: {:?}", e);
return HttpResponse::InternalServerError().body(format!("Error while reading multipart data : {}", e));
}
}
}
let content_disposition = item.content_disposition();
let name = content_disposition.get_name().unwrap().to_string();
let file_name = content_disposition.get_filename().unwrap_or("").to_string();
println!("content_disposition : {:?}", content_disposition);
println!("name : {:?}", name);
println!("file_name : {:?}", file_name);
if file_name.is_empty() {
// this case not file
match String::from_utf8(bytes.to_vec()) {
Ok(s) => {
println!("value is {:?}", s);
send_form = send_form.text(name, s)
}
Err(e) => return HttpResponse::InternalServerError().body(format!("Error converting BytesMut to String: {}", e))
};
} else {
// this case is file
if let Some(content_type) = item.content_type() {
let mut file_contents = Vec::new();
Write::write_all(&mut file_contents, &bytes).unwrap();
let part = Part::bytes(file_contents)
.file_name(file_name.clone())
.mime_str(content_type.clone().as_ref())
.unwrap();
send_form = send_form.part(name, part);
} else {
return HttpResponse::InternalServerError().body("Failed to parse MIME type");
}
}
}
Ok(None) => {
println!("item is none");
break;
}
Err(e) => {
eprintln!("error : {:?}", e.to_string());
eprintln!("error request : {:?}", req);
return HttpResponse::InternalServerError().body(e.to_string());
}
}
}
println!("form-data : {:?}", send_form);
let dynamic_request_properties = get_from_client_request_properties(&req).await;
let uri = dynamic_request_properties.2;
let client = reqwest::Client::new();
let result = client
.post("<http://localhost:8081>".to_string() + uri.as_str())
.multipart(send_form)
.send()
.await;
create_client_response_from_server_response(result).await
}
GitHub - dev-donghwan/rust-study
Contribute to dev-donghwan/rust-study development by creating an account on GitHub.
github.com
728x90
반응형
'Language > Rust' 카테고리의 다른 글
Rust ERROR: linker `cc` not found (0) | 2023.04.07 |
---|
댓글