Language/Rust

Rust로 by-pass 기능을 가진 Proxy Server 개발하기

by Donghwan 2023. 4. 20.

특정 조건은 Rust에서 작업하고 그 조건을 제외한 로직은 proxy 서버 개념으로 Main Server로 by-pass하는 Route 로직을 구현해야하는 요구사항.

 

요구사항

  1. application/json 형식의 Http 통신을 처리해야한다.
  2. form-data 형식의 Http 통신을 처리해야한다.
  3. Header의 모든 데이터를 포함해야 한다.
  4. Method와 Url은 모두 Dynamic하게 처리해야 한다.

 

Solution

  1. Util로 Header 또는 Property에 해당되는 정보를 뽑아오는 함수를 개발한다.
  2. Util로 Main Server의 Response를 Client용으로 Convert 해주는 함수를 개발한다.
  3. Util로 Multipart Type인지 확인하는 함수를 개발한다.
  4. application/json 타입의 Http 통신을 핸들링하는 함수를 개발한다.
  5. 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

댓글