playdata/daily

10주차 : Day 4,5 (9/12,13)

soojin1 2024. 9. 23. 23:11

 

 프로젝트 준비

$ pdm add fastapi "uvicorn[standard]"

# 아래 위치에 index.html 이동 n00 부분 본인 번호로 변경
$ mkdir public
$ vi public/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>삼둘푸드</title>
</head>
<body>
    <h1>음식 이름 입력</h1>
    <form id="foodForm">
        <label for="foodName">음식 이름: </label>
        <input type="text" id="foodName" name="foodName" required>
        <button type="submit">저장</button>
    </form>

    <script>
        document.getElementById("foodForm").addEventListener("submit", function(event) {
            event.preventDefault(); // 폼 기본 제출 방지

            const foodName = document.getElementById("foodName").value;
            const url = `http://ec2-43-203-204-195.ap-northeast-2.compute.amazonaws.com/n23/food?name=${encodeURIComponent(foodName)}`;

            fetch(url, {
                method: 'GET',
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    </script>
</body>
</html>

 

□ firebase 웹앱 배포 및 환경 설정

방법은 8주차 회고의 내용을 참고하기

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>삼둘푸드</title>
</head>
<body>
    <h1>음식 이름 입력</h1>
    <form id="foodForm">
        <label for="foodName">음식 이름: </label>
        <input type="text" id="foodName" name="foodName" required>
        <button type="submit">저장</button>
    </form>

    <script>
        document.getElementById("foodForm").addEventListener("submit", function(event) {
            event.preventDefault(); // 폼 기본 제출 방지

            const foodName = document.getElementById("foodName").value;
            const url =`http://ec2-43-203-204-195.ap-northeast-2.compute.amazonaws.com/n23/food?name=${encodeURIComponent(foodName)}`;
	   

            fetch(url, {
                method: 'GET',
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    </script>
    <script type="module">
  // Import the functions you need from the SDKs you need
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-app.js";
  import { getAnalytics } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-analytics.js";
  // TODO: Add SDKs for Firebase products that you want to use
  // https://firebase.google.com/docs/web/setup#available-libraries

  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  const firebaseConfig = {
    apiKey: "AIzaSyBC7h1Yuup0DYm4YQB3gMg41ncFRY6brag",
    authDomain: "samdul23food.firebaseapp.com",
    projectId: "samdul23food",
    storageBucket: "samdul23food.appspot.com",
    messagingSenderId: "486885652110",
    appId: "1:486885652110:web:ebeb50826544893af57bdf",
    measurementId: "G-P4QBPQE5FG"
  };

  // Initialize Firebase
  const app = initializeApp(firebaseConfig);
  const analytics = getAnalytics(app);
</script>
</body>
</html>

 

음식이름에 문자열을 입력하면 AWS에 위치한 FASTAPI 프로그램으로 데이터가 전송되어, 해당 문자열과 시간을 csv 파일로 저장하고자 한다.

여기서 해결해야 할 몇가지 이슈가 존재한다.

먼저 FASTAPI의 기본 구성은 다음과 같다.

from typing import Union
from fastapi import FastAPI
import pickle

app = FastAPI()

@app.get("/")
def read_root():
	return {"Hello": "n23"}
	
@app.get("/food")
def food(name:str):
	#시간을 구함
	#음식 이름과 시간을 csv로 저장 -> /code/data/food.csv
    # DB 저장 
    print("=================================" + name)
	return {"food":name, "time":"2024-09-15 11:12:13"}

 

□입력값을 CSV저장 

datetime 모듈을 사용하여 YYYY-MM-DD HH:MM:SS 형식의 타임스탬프를 생성한다.

CSV 파일 저장 디렉토리의 존재 여부를 확인하고, 없으면 생성한다.

CSV 파일이 새로 생성되거나 비어있을 때만 헤더(열 구분) 작성한다.

'a' 모드로 데이터를 덮어쓰지 않고 추가한다.

 

from typing import Union
from fastapi import FastAPI
from datetime import datetime
import csv
import os

app = FastAPI()

@app.get("/")
def read_root():
        return {"Hello": "n23"}

@app.get("/food")
def food(name:str):
    # 시간 구하기
    now = datetime.now()
    t = now.strftime('%Y-%m-%d %H:%M:%S')
    print("=================================" + name)
    print("=================================" + t)

    # 저장 경로
    #home_dir = os.path.expanduser('~')
    directory = 'data'
    print("**********************************" + directory)

    # 폴더가 없는 경우 폴더 생성
    if not os.path.exists(directory):
        os.makedirs(directory)

    # CSV 파일 경로
    csv_file_path = f'{directory}/food.csv'

    # 데이터 저장
    data = {"Name": name, "time": t}

    # CSV 파일이 비어 있는지 확인 후 헤더 작성
    file_exists = os.path.exists(csv_file_path) and os.stat(csv_file_path).st_size > 0

    # CSV 파일에 데이터 쓰기
    with open(csv_file_path, mode='a', newline='') as file:  # append 모드로 열기
        fieldnames = ['Name', 'time']
        writer = csv.DictWriter(file, fieldnames=fieldnames)

        # 파일이 존재하지 않거나 비어 있을 때 헤더 작성
        if not file_exists:
            writer.writeheader()

        # 단일 행 쓰기
        writer.writerow(data)

    return {"food": name, "time": t}

 

□ CORS( cross origin resource sharing ) 보안 이슈

출처가 다른 자원들을 공유한다는 뜻으로, 한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념이다.

공식문서를 참조하여 FASTAPI에서 CORS 이슈 해결법을 알 수 있다.

 

main.py 에 아래 내용을 추가해준다.

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8023"],  # 허용할 출처 (클라이언트 URL)
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

 

□ https(웹앱) -> http(API)호출 이슈

firebase는 https로 제공되지만, AWS(아마존서버)는 http로 제공된다. 이 둘 사이의 요청과 응답은 기본적으로 허용되지 않는다. http가 보안적으로 취약하기 때문이다. 

아래 두 파일의 http부분을 모두 https로 바꾸어서 문제를 해결했다.

$ cat index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>삼둘푸드</title>
</head>
<body>
    <h1>음식 이름 입력</h1>
    <form id="foodForm">
        <label for="foodName">음식 이름: </label>
        <input type="text" id="foodName" name="foodName" required>
        <button type="submit">저장</button>
    </form>

    <script>
        document.getElementById("foodForm").addEventListener("submit", function(event) {
            event.preventDefault(); // 폼 기본 제출 방지

            const foodName = document.getElementById("foodName").value;
            #수정부분
            const url = `https://api.samdulshop.shop/n23/food?name=${encodeURIComponent(foodName)}`;
	   

            fetch(url, {
                method: 'GET',
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    </script>
    <script type="module">
  // Import the functions you need from the SDKs you need
  import { initializeApp } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-app.js";
  import { getAnalytics } from "https://www.gstatic.com/firebasejs/10.13.1/firebase-analytics.js";
  // TODO: Add SDKs for Firebase products that you want to use
  // https://firebase.google.com/docs/web/setup#available-libraries

  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  const firebaseConfig = {
    apiKey: "AIzaSyBC7h1Yuup0DYm4YQB3gMg41ncFRY6brag",
    authDomain: "samdul23food.firebaseapp.com",
    projectId: "samdul23food",
    storageBucket: "samdul23food.appspot.com",
    messagingSenderId: "486885652110",
    appId: "1:486885652110:web:ebeb50826544893af57bdf",
    measurementId: "G-P4QBPQE5FG"
  };

  // Initialize Firebase
  const app = initializeApp(firebaseConfig);
  const analytics = getAnalytics(app);
</script>
</body>
</html>
$ cat main.py

from typing import Union
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from datetime import datetime
import csv
import os

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    #수정부분
    allow_origins=["https://samdul23food.web.app"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

 

이렇게 해서 firebase 웹앱에서 음식을 입력하면 로컬에 csv 파일이 생성되는 것을 확인하며 마무리했다.

남은 것은 csv파일이 아닌 데이터베이스로 값을 저장하여 영구 저장을 하는 것 이다.

'playdata > daily' 카테고리의 다른 글

12주차 : Day 3 (9/25)  (1) 2024.09.30
12주차 : Day 2 (9/24)  (0) 2024.09.30
12주차 : Day 1 (9/23)  (2) 2024.09.24
10주차 : Day 2,3 (9/10,11)  (0) 2024.09.23
10주차 - Day 1(9/9)  (0) 2024.09.23