
python으로 만든 모델을 돌린 결과를 웹에서 보여줘야 할 때가 있다. streamlit이나 gradio같이 third-party 툴을 쓰면 간단하게 해결할 수도 있지만, 원하는대로 커스터마이징을 하기 어렵다는 문제가 종종 발생할 때 마다 '이럴거면 그냥 내가 개발하지!' 라는 생각을 하기 마련이다. 물론... 쉽진 않다. 😂
정적인 웹페이지를 주로 만들고 backend 부분은 내가 담당하지 않아서 잘 모르는 부분을 이번에 full-stack으로 아주 다 해봤다. 그만큼 날림 코드가 많고 완성도가 떨어지긴 하지만... 일단 된다는 것에 만족하며ㅋㅋㅋ
특히 backend부분의 설명은 부족할 수 있으니, 사용 코드 위주로 보고 설명은 추가로 자료를 참고하길 바란다.
우선 나의 구조는 크게 다음과 같다.
backend
- main.py
frontend
- app.vue
- pages/
- components/
backend에서 웹어플리케이션 부분은 Flask로 구현하고, front는 Nuxt3 framework를 쓰며, tailwindcss를 붙였다.
flask를 사용해서 통신을 하는 과정에서 GET, POST 와 동기, 비동기 부분에 대한 이해가 어려웠다. 아직도 완전히 이해하진 못했지만, ChatGPT의 도움을 받아면서 하나씩 해결해나갔다.
추가로, CORS 에러는 정말 뜬금없이, 그리고 자주 등장해서 끙끙대다가 기본적인 CORS 에러를 해결하는 방법도 조금은 터득한 것 같다.
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY')
CORS(app, supports_credentials=True, resources={r"/*": {"origins": "*"}})
기본 설정은 위와 같다. flask를 import 해주고, app 변수를 정의한다. app.secret_key 부분은 사실 왜 필요한 건지 잘 모르겠는데, CORS 에러를 구글링하다가 어디서 보고 그냥 추가해줬다. FLASK_SECRET_KEY는 .env 파일에 그냥 임의의 string으로 설정해주면된다.
아래 CORS 부분은 현재 모든 경로(*)에 대해서 열어준(?) 상태이다. 이후에 deploy를 할 때에는 또 다르게 설정을 해줬는데, 일단 localhost를 기준으로는 저렇게 설정하면 된다.
@app.route('/', methods=['POST', 'GET'])
def get_answer():
if request.method == 'POST':
# 블라블라 본인 코드
return jsonify({"":""})
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
다음은 기본적인 함수 설정 방법이다. 위에 @app.route 부분에서 라우팅되는 경로를 설정한다. 여기서는 가장 최상단의 /로 설정한건데, 필요에 따라서 /login과 같이 바꿔준다. 여기서 설정한 값을 이후에 vue에서 인식해서 통신하게 된다.
그리고 마지막에 5000포트(flask는 default로 5000)로 할당해주고, 터미널에 debugging이 되도록 True로 설정해준다. python main.py를 실행해주면 localhost:5000 포트가 열리면서 해당 파일을 실행할 준비가 된다.
nuxt는 공식문서가 정리가 잘 되어 있는 편이다.
npx nuxi@latest init <project-name>
homebrew, node, npx, npm 등은 설치가 되어 있다고 가정한다. 위 코드를 실행하면 여러가지를 설정하라고 하는데, 대부분 default를 선택하고 넘어가면 된다. css 정도 tailwind를 선택하거나 git을 선택하는 정도로만 진행한다.
설치가 쭉 다 되고 나면, root에 package.json이 생성된다. npm install을 해준 뒤, 에러 없이 잘 설치된 뒤, npm run dev를 해주면locslhost:3000이 열리고, 웹 브라우저에서 열어서 Welcome!~~~ 와 같은 페이지가 등장하면 성공이다.
nuxt3의 전체 view는 이 문서에서 잘 설명해주고 있다. 간단하게 보면, 우선 최상단에 app.vue가 있다. 이 파일에서는 딱히 수정해줄 부분이 없는데, 만약 초기 프로젝트를 생성했을 때 pages라는 폴더가 없다면(nuxt3.12 이후에는 pages가 필수가 아니라고 하는 듯) app.vue와 같은 위치에 pages라는 폴더를 생성한 뒤, app.vue에서 <NuxtPage />를 추가해주면 된다.
<!-- app.vue -->
<template>
<NuxtPage />
</template>
pages 폴더는 말 그대로 각 세부 페이지를 생성하는 폴더이다. 가장 메인이 되는 index.vue는 반드시 존재해야 한다(없다면 app.vue가 index가 됨).
component 폴더는 index.vue나 app.vue 등의 페이지에서 사용하는 각 컴포넌트(개별 구현 요소)들을 저장하는 폴더이다. 예를 들어, Header나 Footer와 같은 경우에는 메인페이지의 하위 구성요소이므로, 따로 떼서 Hearder.vue, Footer.vue와 같은 컴포넌트 파일을 만든 뒤 index.vue에서 <Header.vue />와 같은 식으로 불러오면 된다. 물론 모든 구현을 하나의 파일에서 진행해도 된다. 다만 코드의 가독성 재사용성을 위해서 컴포넌트를 적절하게 사용하면 훨씬 효율적인 코드를 만들 수 있다.
pages와 component 폴더 외에도 assets 폴더에 css를 추가하고 다시 하위에 main.css 등을 추가해서 index.vue에 붙여주면 css파일도 따로 관리할 수 있다.
<style>
@import '../assets/css/main.css';
</style>
이제 backend와 데이터를 어떻게 받고, 보내는지에 대한 부분이다.
이렇게 다 만들어서 localhost에서 정상적으로 작동하는 걸 확인했는데, 이제 deploy 하려니까 이건 또 다른 문제였다. 그래서 deploy는 직접 못하고ㅋㅋㅋㅠ deploy를 할 수 있도록 하는 웹서버 기본 설정까지는 마쳐서 넘겨드렸다.
우선 uwsgi를 설정한다. 내가 이해하기로는 웹 어플리케이션인 flask와 웹 서버인 nginx 사이에서 소통하는 middleware 역할을 하는게 uwsgi이고, gunicorn 등을 사용할 수도 있다고 한다.
pip install uwsgi
이후 backend의 root 폴더에 wsgi.py라는 파일을 만들과 다음과 같은 코드를 추가한다.
from main import app
if __name__ == "__main__":
app.run()
이후 uwsgi --socket localhost:5000 --protocol=http -w wsgi:app를 실행하면 정상적으로 실행된다. 실행 코드가 너무 길고 복잡해서 'uwshi.ini'라는 이름을 파일을 추가하고 아래 코드를 작성한 뒤, uwsgi --ini uwsgi.ini이 코드를 실행하면 똑같은 결과가 나와야 한다.
[uwsgi]
module = wsgi:app
master = true
socket = /tmp/uwsgi.sock
processes = 5
chmod-socket = 666
vacuum = true
die-on-term = true
그래야만 하는데,,, uwshi.ini의 설정이 뭔가 잘못된 것인지, 이렇게 하면 제대로 실행이 안됐다. 그래서 그냥 위에 저 긴~~ 코드를 사용해서 실행해줬다.
python main.py로 실행하는 거랑 다르지 않지만, 배포 버전에서는 이렇게 middleware를 사용해줘야 한다고 한다.
deploy를 하지 않을거라면 nginx를 설치하지 않아도 된다
nginx는 macos 기준 homebrew로 설치할 수 있다. 이전에 node가 우선적으로 설치되어 있어야 하는 것 같다.
brew install nginx
sites-available과 site-enabled라는 파일(?) 폴더(?)가 없으면 직접 생성해줘야 한다고 해서 sudo mkdir sites-available;sudo mkdir sites-enabled; 실행 뒤 nginx.conf 파일에 include /etc/nginx/sites-enabled/*;를 추가해줬다.
이때 'nginx.conf'라는 파일이 도통 이 파일이 어디에 있는건지 찾기가 어려웠다. 로컬을 이곳저곳 뒤져서 결국 찾은 경로는 '/opt/homebrew/etc/nginx/nginx.conf' 였다.
이 이후에 수정을 하는 부분들은 deploy할 주소의 domain 주소를 넣고, 기타 등등등~~을 수정해줘야 하는데, deploy는 포기했기 때문에 더이상은 진행하지 않았다.
참고자료