기록과 정리의 공간

[Flask] Flask로 REST API 구현하기 / CORS란? 본문

라이브러리&프레임워크/Flask

[Flask] Flask로 REST API 구현하기 / CORS란?

딸기맛도나쓰 2020. 9. 6. 01:14

Flask로 REST API를 구현하는 방법

  • URI요청에 대한 응답을 JSON형식으로 작성하여 데이터를 반환하도록 만들면 된다.

  • Flask에서는 딕셔너리 형태로 데이터를 만들고, 이를 jsonify() 메서드를 활용해 JSON포맷의 데이터로 만들 수 있다.

  • 각 요청 메서드마다 파라미터값을 추출하는 방식이 다르다.

    • GET/PUT/DELETE는 방식이 동일, POST만 다르다.
      • GET/PUT/DELETE는 request.args.get('파라미터이름') 으로 가져온다. (파라미터가 가진 값을 가져옴)
      • POST는 request.get_json() 으로 데이터를 가져온다. 이 때, 딕셔너리 형태로 가져오는데, 원하는 파라미터가 가지고 있는 값에 접근하려면 key로 접근하면 된다.(ex> data = request.get_json()으로 가져온다고 하면, 'email'이라는 파라미터의 값에 접근하고자할 때, data['email'])
  • API리턴값은 flask의 jsonify()함수를 사용하여 JSON포맷으로 보내줘야 한다.

  • HTTPie 툴을 활용하자.(상단의 두 번째 참고 링크의 #5번 참고)

예시 코드 - 프론트엔드(vue.js)와 flask를 이용하여 REST API를 구현한 코드

  • 결과값은 코드를 실행하여 직접 확인해보자.

  • flask 코드

    • #1, #2는 아래쪽의 CORS란? 참고
    • #3 : flask의 make_response() 는 아래 #1 라인에서 상태 코드를 함께 넘겨준 것 처럼, 응답을 좀 더 명시적으로 리턴하기 위해 사용한다.(공식 문서) 이는 HTTPie 로 응답 헤더를 직접 확인해보면 알 수 있다.
  • html 코드

    • vue로 제어할 코드는 id가 app인 div태그로 감싸져 있는 부분이다.

    • button 태그 : vue template에서 각 버튼 클릭시, 각 버튼에 연결된 함수를 호출하는 코드

    • restapi_test.html 코드에서 아래의 세 번째 script 태그 안의 코드

      • 프론트엔드(vue.js)에서 axios를 이용해 HTTP를 요청하는 코드이다.(백엔드 쪽으로 요청 메서드 및 파라미터값을 전달한다.)
        • HTTP요청 메서드는 method: 에 넣어주면 된다.
        • GET, PUT, DELETEparams:에 데이터를 JSON형식으로 넣어주면 된다.
        • POSTdata:JSON형식으로 넣으면 된다.
    • flask코드에서 jsonify()함수를 사용해, JSON형식으로 리턴된 값은 response.data에 담겨져있다. 해당 데이터를 JSON데이터를 꺼내듯이 추출하면 된다.

# flask 코드 

from flask import Flask, request, make_response, jsonify, render_template
from flask_cors import CORS

app = Flask(__name__)
CORS(app)


@app.route("/test", methods=['GET', 'POST', 'PUT', 'DELETE'])
def test():
    if request.method == 'POST':
        print('POST')
        data = request.get_json()
        print(data)
        print(data['email'])
    if request.method == 'GET':
        print('GET')
        user = request.args.get('email')
        print(user)
    if request.method == 'PUT':
        print('PUT')
        user = request.args.get('email')
        print(user)
    if request.method == 'DELETE':
        print('DELETE')
        user = request.args.get('email')
        print(user)

    return make_response(jsonify({'status': True}), 200)

@app.route("/html_test")
def html_test():
    return render_template('vue_restapi_test.html')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port="8082")
<!-- restapi_test.html --> 

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />

    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
      integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
      crossorigin="anonymous"
    />

    <title>Hello, world!</title>
  </head>
  <body>
    <div id="app">
      <br />
      <center>
        <button type="button" class="btn btn-primary" v-on:click="test_get">
          GET
        </button>
        <button type="button" class="btn btn-secondary" v-on:click="test_post">
          POST
        </button>
        <button type="button" class="btn btn-success" v-on:click="test_put">
          PUT
        </button>
        <button type="button" class="btn btn-danger" v-on:click="test_delete">
          DELETE
        </button>
      </center>
    </div>
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script
      src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
      integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
      integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
      crossorigin="anonymous"
    ></script>

    <!-- Vue Start -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        methods: {
          test_get: () => {
            axios("http://localhost:8082/test", {
              method: "get",
              params: {
                email: "test@test.com",
              },
            })
              .then((response) => {
                console.log(response.data["status"]);
              })
              .catch((error) => {
                console.log(error);
              });
          },
          test_post: () => {
            axios("http://localhost:8082/test", {
              method: "post",
              data: {
                email: "test@test.com",
              },
            })
              .then((response) => {
                console.log(response.data["status"]);
              })
              .catch((error) => {
                console.log(error);
              });
          },
          test_put: () => {
            axios("http://localhost:8082/test", {
              method: "put",
              params: {
                email: "test@test.com",
              },
            })
              .then((response) => {
                console.log(response.data["status"]);
              })
              .catch((error) => {
                console.log(error);
              });
          },
          test_delete: () => {
            axios("http://localhost:8082/test", {
              method: "delete",
              params: {
                email: "test@test.com",
              },
            })
              .then((response) => {
                console.log(response.data["status"]);
              })
              .catch((error) => {
                console.log(error);
              });
          },
        },
      });
    </script>
  </body>
</html>

CORS(Cross Origin Resource Sharing, 교차 출처 리소스 공유)란?

  • 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조이다. (위키백과)

  • 등장하게 된 배경

    • 웹에서 사용하는 HTTP request는 기본적으로 다른 도메인의 데이터를 요청할 수 있다.
      • 예를 들어, 내가 접속한 웹페이지가 www.test.com/index.html 라고 하면, 해당 웹페이지 안에서 html태그로 www.image.com/image1.jpg 파일을 가져와서 이미지로 보여줄 수 있다. 그러나, script태그로 둘러싸인 코드에서 실행되는 HTTP request는 동일한 출처(www.test.com) 에만 요청할 수 있다. 이를 Same Origin Policy라고 한다. 정확하게는 프로토콜, 호스트명, 포트가 동일해야 한다.
      • 만약 다른 서버에 HTTP request를 요청한다면, 아래와 같은 에러가 뜬다.
        • HTTP 응답 헤더Access-Contorl-Allow-Origin 관련 정보가 없기 때문에 CORS를 허용하지 않는다는 의미이다.
    • Same Origin Policy(동일 출처 정책) : 웹 브라우저의 기본 정책으로, 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식이다. 동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여준다. (링크)
    • ajax, axios와 같은 기술이 생김에 따라, 다른 서버로의 데이터 요청을 지원해야한다는 요구가 생겨 CORS라는 가이드가 마련되었다고 한다. (각 언어별로)
  • (링크)에 따르면, CORS는 다음과 같은 방법으로 허용할 수 있다고 한다.

    • 프론트엔드에서는 요청 헤더에 CORS관련 옵션을 추가하고,
    • 서버에서는 응답 헤더에 CORS관련 옵션을 추가해줘야 한다.

flask 서비스에 CORS 지원하는 방법

pip install flask_cors
  • 사용법
    • 아래 코드와 같이 CORS(Flask객체) 를 선언해주면, 전체 요청/응답 헤더에 CORS 지원 헤더 정보를 넣어서 CORS를 지원해준다.
      • HTTP 응답헤더를 확인해보면 헤더에 Access-Control-Allow-Origin 부분이 추가되고, 요청한 데이터를 다른 서버에서 정상적으로 가져오게 된다.
from flask_cors import CORS 

app = Flask(name) 
CORS(app) 
Comments