[6] Nginx 서버 셋업 - Up/Down 구현 + 간단한 업로드 client



리스팅 까지 진행됫고, 업/다운을 Drogon을 통해 구현한다 

---

## 1. 서버 기능 구현

### 1-1. Drogon Contoller 구현
Drogon Contoller 등록 진행을 한다
( 아래 작업을 해야 빌드시 인식이 됨 ) 

```
cd /root/drogon2/drogon/build/drogon_ctl 
drogon_ctl create controller FileController
```
---

### 1-2. 업/다운 구현
소스 위치할 경로 ( /root/drogon2/drogon/build/drogon_ctl/testAPI/controllers ) 

FileController.h
<pre class="brush:c++"
class="brush:plain; gutter:true">
#pragma once
#include <drogon/HttpController.h>

using namespace drogon;

class FileController : public HttpController<FileController>
{
public:
    std:: string _storagePath = "/root/storage/";
    METHOD_LIST_BEGIN
    // "/upload" 경로의 POST 요청 처리
    ADD_METHOD_TO(FileController::handleUpload, "/upload", Post);

    // "/download/{filename}" 경로의 GET 요청 처리
    ADD_METHOD_TO(FileController::handleDownload, "/download/{1}", Get);
    METHOD_LIST_END

    // 메서드 선언
    void handleUpload(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback);
    void handleDownload(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, const std::string& filename);
};

</pre>
---

FileController.cc
<pre class="brush:c++"
class="brush:plain; gutter:true">
#include "FileController.h"
#include <drogon/utils/Utilities.h>
#include <fstream>

// 업로드 핸들러
void FileController::handleUpload(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback)
{
    Json::Value respStr;
    HttpStatusCode code = k200OK;

    MultiPartParser fileUpload;
    do
    {
        if ( fileUpload.parse(req) != 0 )
        {
            code = k400BadRequest;
            respStr = Json::Value("Multipart Format Invalid");
            break ;
        }

        auto &file = fileUpload.getFiles()[0];
        LOG_INFO << "file:" << file.getFileName()
            << " (extension=" << file.getFileExtension()
            << ", type=" << file.getFileType()
            << ", len=" << file.fileLength()
            << ", md5=" << file.getMd5() << ")";

        std::string uploadPath =_storagePath + file.getFileName();
        LOG_INFO << "uploadPath:" << uploadPath.c_str();

        if ( file.saveAs(uploadPath) != 0 )
        {
            code = k500InternalServerError;
            respStr = Json::Value("Internal Server Error");
            break ;
        }
    }
    while(false);

    auto resp = HttpResponse::newHttpResponse();
    resp->setStatusCode(code);
    callback(resp);
}

// 다운로드 핸들러
void FileController::handleDownload(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, const std::string& filename)
{
    std::string filePath = _storagePath.c_str() + filename;
    if ( std::ifstream(filePath).good() == true )
    {
        auto resp = HttpResponse::newFileResponse(filePath.c_str(), filename.c_str());
        resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM);
        resp->addHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
        callback(resp);
    }
    else
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k404NotFound);
        callback(resp);
    }

}
</pre>

작업 이후 아래 경로로 이동후 make
```
cd /root/drogon2/drogon/build/drogon_ctl/testAPI/build
make
```

---

### 1-3. drogon 설정 수정

/root/drogon2/drogon/build/drogon_ctl/testAPI/config.json 파일의

`client_max_body_size` 값을 수정한다 ( 기본 1M으로 되어있음 ) 
```
"client_max_body_size": "256M" # 기본 1M, 원하는 만큼 변경하면된다
```

위 값 수정하지않으면 
413 Request Entity Too Large 에러를 리턴한다

이건 로그도 안찍어서 소스 뒤져봐야되는데, 머리아프다

위 작업 까지 완료되면 서버쪽은 완료된다 

---

## 2. 클라이언트 기능 구현 

아래 경로에 html 추가 
/root/nginx/html/upload.html

<pre class="brush:html">
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;File Upload&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h2&gt;Upload a File&lt;/h2&gt;
    &lt;form action=&quot;/upload&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
        &lt;input type=&quot;file&quot; name=&quot;file&quot; required&gt;
        &lt;br&gt;&lt;br&gt;
        &lt;button type=&quot;submit&quot;&gt;Send&lt;/button&gt;
    &lt;/form&gt;
&lt;/body&gt;
</html>
</pre>




---

## 3. nginx 설정
- API Alias 설정 수정
- 마찬가지로 client_body 수정

```
user nobody;
worker_processes  10;

events {
    worker_connections  1024;
    use epoll;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    client_max_body_size 256m;

    proxy_buffer_size 128k;
    proxy_buffers 4 256k;

    server {
        listen       10099;
        server_name  localhost;

        location /doUpload {
            alias /root/nginx/html/;
            index upload.html;
            autoindex on;
        }

        location /list {
            alias /root/storage/;
            autoindex on;                  # 디렉터리 인덱스 표시
            autoindex_exact_size off;      # 파일 크기를 간소화된 형식으로 표시 (선택사항)
            autoindex_localtime on;        # 로컬 시간으로 파일 시간 표시 (선택사항)
        }

        location /upload {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://127.0.0.1:8848;  # 프록시할 대상 서버
        }

        location ~ ^/download/(.*) {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://127.0.0.1:8848/download/$1;  # 캡처된 변수를 백엔드 서버로 전달
        }

    }
}

```
413 Request Entity Too Large 에러 리턴되면 client_max_body_size 만져보시길

---

## 4. 동작 확인

### 4-1. upload 동작 확인
- upload api 호출 및 clinet파일 선택 
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxY-7BFP1y4uLVFWRthy5eTJdZ8aBeWRUYPgt42tNiodWc-JhuSSoM3QrRdOT9uVIdU4_clXiTZnznu20qYxONKfT0nTpAS944CIUZ507letmkRTL3Yc3KU4oUbl2fR_tCk3PUISvYh4PI1jygqn1BmodjUJTJWZDI9wnVtYghWKb6UrruFAzWG9HE/s1600/upload-start.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="drogon upload" border="0" data-original-height="386" data-original-width="560" loading="lazy" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxY-7BFP1y4uLVFWRthy5eTJdZ8aBeWRUYPgt42tNiodWc-JhuSSoM3QrRdOT9uVIdU4_clXiTZnznu20qYxONKfT0nTpAS944CIUZ507letmkRTL3Yc3KU4oUbl2fR_tCk3PUISvYh4PI1jygqn1BmodjUJTJWZDI9wnVtYghWKb6UrruFAzWG9HE/s1600-rw/upload-start.PNG"/></a></div>

---

- 동작, 200ok return 

<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLp6e5Cb9ooHOUoRzA2BfLiTBjTvJguVZYXdHevifJQKUnLV9GAPUKrlRn1mmVhhQ06pUQlRKCxkDv8sPK1C7CLIDXlPApcytZfmgVH-h0-8JVJiHq_eujj1nP5dy7bz7fKkef_-d_mt_JYRkfvh0boe2juHmzmoChNrRSrNkzPkfeZo6nZjzqwPmN/s1600/uploading.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="200 ok" border="0" data-original-height="491" data-original-width="1075" loading="lazy" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLp6e5Cb9ooHOUoRzA2BfLiTBjTvJguVZYXdHevifJQKUnLV9GAPUKrlRn1mmVhhQ06pUQlRKCxkDv8sPK1C7CLIDXlPApcytZfmgVH-h0-8JVJiHq_eujj1nP5dy7bz7fKkef_-d_mt_JYRkfvh0boe2juHmzmoChNrRSrNkzPkfeZo6nZjzqwPmN/s1600-rw/uploading.PNG"/></a></div>

---

- 아래 경로에 저장된다
```
[root@vbox storage] $ ll
합계 72
-rw-r--r--. 1 root   root   66662 11월  4 08:48 7.PNG
-rw-r--r--. 1 nobody nobody     2 11월  4 06:21 test.txt
```

- 이전 '[5] Nginx 서버 셋업' 에서 만든 리스트로 보아도 이상없다.
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpJMCMbFSk1PGLklcdfmWHXcJB-UUe4ZVddSvuUSVcMlC-IbF8GXvCCs4k-UfmqSaiw_QIHzXamvSSGpm86GowVfbWwBnbDF16-T4FMMV1WEHZoq6gSu8wXFr9Xc772Wnb5mxB_mOyp5W3k-kpPO_5oVA0KgAP-I_2qZTrwqoajAHEprf2krMs94Ql/s1600/list.PNG" style="display: block; padding: 1em 0; text-align: center; "><img alt="drogon download" border="0" data-original-height="360" data-original-width="686" loading="lazy" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpJMCMbFSk1PGLklcdfmWHXcJB-UUe4ZVddSvuUSVcMlC-IbF8GXvCCs4k-UfmqSaiw_QIHzXamvSSGpm86GowVfbWwBnbDF16-T4FMMV1WEHZoq6gSu8wXFr9Xc772Wnb5mxB_mOyp5W3k-kpPO_5oVA0KgAP-I_2qZTrwqoajAHEprf2krMs94Ql/s1600-rw/list.PNG"/></a></div>

---

### 4-2. download 동작 확인
- 웹에서 http://127.0.0.1:10099/download/7.PNG 호출 시 다운로드 발생까지 확인 하면 된다.
( 사실 리스트에서 클릭해도 다운로드 되긴하는데, 만든 기능 확인을 위해 API로 해보자 ) 

```
# wget으로 다운로드 받아보면 size 특이사항 없다 
[root@vbox ~] $ wget http://127.0.0.1:10099/download/7.PNG
--2024-11-04 08:59:27--  http://127.0.0.1:10099/download/7.PNG
Connecting to 127.0.0.1:10099... connected.
HTTP request sent, awaiting response... 200 OK
Length: 66662 (65K) [application/octet-stream]
Saving to: `7.PNG'

7.PNG                                   100%[============================================================================>]  65.10K  --.-KB/s    in 0s

2024-11-04 08:59:27 (397 MB/s) - `7.PNG' saved [66662/66662]
```


---


추가로 읽으면 좋을 것

댓글

이 블로그의 인기 게시물

윤석열 계엄령 선포! 방산주 대폭발? 관련주 투자 전략 완벽 분석

대통령 퇴진운동 관련주: 방송·통신·촛불수혜주 완벽 분석

키움 OPEN API MFC 개발 (1)