@sleepysoong
[Flask] 스크립트에서 API로의 도약, 그 여정의 시작 본문
여러 프로그래밍 언어들을 찍먹해보고 싶었는데 프로그래밍 언어를 배워보려고 찾아보면 거의 문법 강의 밖에 없어서 아쉬웠다. 물론 문법으로 시작해야하는게 맞다.
컴퓨터공학부에 입학하고 이제 한 학기를 마쳤다. 파이썬을 배웠다. 인덱스 슬라이싱이 짜증났고 시험을 그닥 잘보지도 못했다. 그래도 A+이 나와서 너무 기쁘다. 최소한 우리 학교 학생들 중에서는 중간 이상은 한다는 거 아니겠는가?
근데 끝나고 나니 뭔가 허전하다. 정렬 함수만 써봤지 직접 구현해본 적도 없었는데 구현해보고, 인덱스 슬라이싱도 배우고 많이 배웠다.
그런데 막상 내가 구현 할 수 있는건 최소공배수, 단순 정렬 이런 것 밖에 없다...
난 스크립트 말고 간단하게라도 서비스를 만들어보고 싶었는데 해보고 싶어도 뭐부터 해야할지도 모르겠고, 뭐라고 검색해야할지도 모르겠고, 뭘 만들어야할지도 모르겠다.
갑자기 미친놈처럼 다른 이야기를 해보자면, 마인크래프트에 Form에는 버튼에 이미지를 넣을 수 있는데 리소스팩 안에 포함된 이미지를 불러오거나 url을 넣어서 이미지를 불러올 수 있는데 서버에서 사용할 플러그인을 만들다보면 가끔 플레이어 스킨을 버튼에 띄우면 좋겠다는 생각을 하는데, 리소스팩에 모든 플레이어의 스킨 이미지를 넣을 순 없고 (매번 수정할 수도 없고 용량도...) 웹에 올리기엔 '.......'라는 생각 밖에 안들어서 그냥 항상 아쉬워만 했다.
그래서 그냥 갑자기(!) 올릴 웹서버를 만들었다. 난 php로 플러그인을 만들긴 하지만 이건 좀 아닌 것 같고(?) Python 쪽은 알아보니까 django와 flask가 유명하다고 한다. 실무에서는 django 쪽을 더 많이 쓴다고 하는 것 같은데 처음하는거라 더 쉽다는 flask로 해보기로 했다.
그냥 간단하게만 (PHP) 대충 끄적여보면 마인크래프트 서버에 접속하면 스킨 데이터를 구해오고 -> 불러온 스킨 데이터에서 스킨의 얼굴만 추출하고 -> 얼굴의 r, g, b를 다 더 한다음에 이제 톤을 조절해서 파스톤 계열의 배경 색을 입혀 1920x1080 픽셀 이미지를 만들고 -> 얼굴 이미지를 500x500으로 박아 이미지를 만들고 -> (여기까지 php에서 비동기로 처리) -> (파이썬) 이미지를 웹 서버에 업로드 -> 이미지 링크 반환 ->(여기까지 파이썬에서 처리) -> (PHP) 링크를 다시 php에서 받아와서 캐싱을 해두고 -> MySQL에 저장 -> 링크를 Form에 넣고 Form 띄우기
from flask import Flask, request, jsonify, send_from_directory, url_for
from flask_cors import CORS
import os
import urllib.parse
import logging
VERSION = "1.0.1"
app = Flask(__name__)
CORS(app)
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB 파일 크기 제한
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def load_secret_key():
key_file = 'key.txt'
if os.path.exists(key_file):
with open(key_file, 'r') as f:
return f.read().strip()
else:
raise FileNotFoundError("key.txt 파일이 존재하지 않습니다.")
SECRET_KEY = load_secret_key()
print("[로그] SECRET KEY를 로딩했습니다..")
@app.route('/upload', methods=['POST'])
def upload_file():
key = request.form.get('key') # key 검증
if key != SECRET_KEY:
log_request_info(request, is_error=True)
return jsonify({'error': '올바른 인증 키가 필요합니다.'}), 403
if 'file' not in request.files:
return jsonify({'error': '파일이 없습니다.'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': '파일 이름이 없습니다.'}), 400
if not file.filename.lower().endswith('.png'):
return jsonify({'error': 'png 파일만 업로드할 수 있습니다.'}), 400
file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
try:
file.save(file_path)
file_link = url_for('get_file', filename=urllib.parse.quote(file.filename), _external=True)
message = {'message': '파일이 업로드되었습니다.', 'filename': file.filename, 'file_link': file_link}
log_request_info(request, is_error=False, response=message)
return jsonify(message), 201
except Exception as e:
error_message = {'error': f'파일 저장 중 오류가 발생했습니다: {str(e)}'}
log_request_info(request, is_error=True, response=error_message)
return jsonify(error_message), 500
@app.route('/uploads/<path:filename>', methods=['GET'])
def get_file(filename):
key = request.args.get('key') # query parameter에서 key 검증
if key != SECRET_KEY:
log_request_info(request, is_error=True)
return jsonify({'error': '올바른 인증 키가 필요합니다.'}), 403
try:
response = send_from_directory(app.config['UPLOAD_FOLDER'], filename)
log_request_info(request, is_error=False)
return response
except Exception as e:
error_message = {'error': f'파일을 찾을 수 없습니다: {str(e)}'}
log_request_info(request, is_error=True, response=error_message)
return jsonify(error_message), 404
def log_request_info(req, is_error=False, response=None):
ip = req.remote_addr
user_agent = req.headers.get('User-Agent')
method = req.method
url = req.url
status = '오류' if is_error else '성공'
if is_error:
print(f" @@ 오류 발생: IP={ip}, Method={method}, URL={url}, User-Agent={user_agent}, Response={response}")
logging.warning( f'올바르지 않은 인증 키가 사용되었습니다: IP={ip}, Method={method}, URL={url}, User-Agent={user_agent}, Response={response}')
return
logging.info(f' ## [요청] {status}: IP={ip}, Method={method}, URL={url}, User-Agent={user_agent}, Response={response}')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
이게 최종 소스인데 처음에 만들면서는 생각하지 못했지만 갑자기 보안이 두렵기도 하고(?)
사실 어떻게 해야할지 모르겠는데 그냥 키를 넣어서 하도록 했다.........................
네 죄송해요.... 사실 다들 어떻게 하시는지 몰라요 ㅠㅠㅠㅠㅠㅠㅠ
이제 시작인 만큼 많이 알아보고.. 많이 공부하고,.......
https://github.com/sleepysoong/SkinGroundServer
GitHub - sleepysoong/SkinGroundServer: 마인크래프트 스킨을 업로드하는 서버
마인크래프트 스킨을 업로드하는 서버. Contribute to sleepysoong/SkinGroundServer development by creating an account on GitHub.
github.com
https://github.com/sleepysoong/SkinGround
GitHub - sleepysoong/SkinGround: Form에 사용할 마인크래프트 스킨 이미지 링크를 관리하는 PocketMine-MP 플
Form에 사용할 마인크래프트 스킨 이미지 링크를 관리하는 PocketMine-MP 플러그인 - sleepysoong/SkinGround
github.com
웹 서버 호출해보는게 너무 재밌었다.
앞으로 홈페이지에 디자인도 씌워보고 정말 프로젝트 다운걸 해보고 싶다!
스크립트 같은거나 짜다 API로의 도약, 그 여정의 시작이였다.