
특정 조건은 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 | 
|---|
댓글