[REAL Python – Flask] – “Flask HTTP API(1) – 간단한 HTTP API 구축해보기”

[REAL Python – Flask] – “Flask HTTP API(1) – 간단한 HTTP API 구축해보기”

7월 18, 2022

HTTP Verbs 란 무엇인가?

첫째로, HTTP 란 무엇일까요? 모질라 공식 문서에는 아래와 같이 소개되어 있습니다.

https://developer.mozilla.org/ko/docs/Web/HTTP

HTTP 는 인터넷에 연결된 두 개의 요소가 상호작용을 생성하고, 허용하게끔 하는 가장 인기있는 프로토콜 중 하나입니다. 그리고, 이를 위해서는 최소 인터넷에 연결되어 있는 두 요소가 필요합니다. 예를 들면, “제 노트북” 과 “이 글을 보고 있는 사람의 컴퓨터” 가 될 수도 있겠죠?

그렇다면, “웹 서버란 무엇인가?” 라는 질문을 던져 보겠습니다. 웹 서버란 과연 무엇일까요?

이곳에서는, 웹 서버를 “web requests 들을 받아들이도록 설계된 소프트웨어” 라고 정의해 보겠습니다.

그런데 web requests란 뭘까요? 예를 들면, 브라우저의 주소창에 naver.com 을 입력하고 엔터를 누르게 되면, 우리는 네이버의 웹 서버에 무언가를 보내게 됩니다. 그리고, 네이버의 웹 서버는 우리가 보낸 것이 무엇인지를 파악한 다음, 우리에게 무엇을 응답할지를 판단하게 됩니다.

다음은 우리가 네이버의 웹 서버에 보내는 정보의 일부입니다.

조금 더 자세히 볼까요? 개발자 도구의 “요청 헤더” 부분을 살펴보겠습니다.

우리가 naver.com 을 브라우저에 입력하고 엔터를 누른다면, 우리는 네이버의 웹 서버에게 위와 같은 정보를 보내주게 됩니다. 중요하게 봐야 할 것은, 위의 사진에서 두 번째 줄에 있는 'method : get' 입니다.

“GET” 는 Http method 혹은 Http verb 라고 불리는데, Http verb 는 “우리가 웹 서버가 반환해줄 것으로 기대하는 것은 이거야!” 를 말해주는 역할을 합니다.

그 아래에 있는 scheme:https 는 “https 프로토콜을 사용했어!” 가 되는 겁니다.

결국, 우리가 브라우저에 naver.com 을 치고, 엔터키를 누르면 우리는 네이버의 웹 서버에게 위와 같은 정보를 전송해주는 거고, 서버는 그에 맞추어 HTML을 보내주게 됩니다. 그리고 그 HTML은 브라우저에 의해서 우리가 보는 “네이버 화면” 이 되겠죠?

기본적으로, 우리가 웹 브라우저에 어떤 주소를 치고, 엔터를 누른다면 우리는 웹 서버에게 GET 요청을 보내게 됩니다. 그리고, 그것들은 굉장히 다양한 응답 상태를 가질 수 있죠. 예를 들면, /hello 라는 path에 대한 GET 요청이 들어왔는데, 그것에 대한 리소스가 없다면 서버는 에러를 발생시켜야 합니다. 아니면, 특정 권한을 가진 사람만 /hello 에 대한 path에 접근할 수 있는데, 그렇지 않은 사람이 요청을 해 온다면 그것도 서버에서 에러를 발생시켜야 할 겁니다.

여러 가지 웹 사이트에 접속해서, 개발자 도구를 열어 응답 헤더를 확인해 보세요. https://www.google.com/ 에 접속한다면, 우리는 구글의 웹 서버에게 GET 요청을 보내게 되는 것이고, 구글의 웹 서버는 우리에게 “HTML” 을 응답해주게 됩니다.

구글의 응답 헤더.

우리가 페이지에 접속하는 것은 기본적으로 GET 요청을 보낸다는 것을 의미한다고 했죠? 하지만, 우리가 웹 서버에게 보낼 수 있는 요청의 종류는 굉장히 많습니다. 이는 모질라 재단의 개발자 홈페이지에도 소개되어 있습니다.

우리는 위와 같은 형태의 Http methods를 요청할 수 있고, 서버는 “아, 이 친구가 POST 요청을 했네! 어떤 작업을 수행해야지!” 를 알 수 있게 되는 것입니다. 간단히, 주로 사용되는 네 가지의 메서드 종류를 살펴보면 아래와 같을 겁니다.

HTTP Verb에는 위의 네 가지 말고도 몇 가지가 더 있지만, 우리는 앞으로 REST API 를 구축하기 위해서 위의 메서드들을 주로 활용할 겁니다.

REST 규칙

지금까지 우리가 위에서 HTTP에 관해서 이해한 것들을 정리해 봅시다.

  1. 우리는 서버에 GET 요청을 보냄으로서 그곳에 접속할 수 있습니다.
  2. 보통, 위의 종류와 같은 요청들은 HTML 을 반환하게 됩니다.
  3. 혹은, 다른 걸 반환할 수도 있습니다. (파일, 에러 등등)
  4. 또한, 우리는 GET 요청 말고도 다른 요청들을 보낼 수 있다는 걸 알게 되었습니다.
  5. 예를 들면, POST, DELETE 와 같은 Http Verbs가 있겠죠?

그렇다면 REST API는 과연 무엇일까요?

  1. REST 는 “URL 을 정의하는 규칙” 따위가 아닙니다.
  2. REST 는 “우리가 보낸 요청에, 어떻게 응답할 것인가?” 에 대한 생각입니다.
  3. https://ko.wikipedia.org/wiki/REST -> 소프트웨어 아키텍처의 한 형식이므로, 프로토콜의 종류에 독립적입니다. 보통은,REST 를 구현하기 위해서 http 프로토콜을 사용합니다.
  4. 그것은 데이터만 가지고 응답을 하지 않고,
  5. 리소스와 함께 응답을 해 줍니다.

Resources

위의 말만 들어보면 너무 추상적이네요. 아래의 요청을 받아들일 수 있는 서버가 있다고 가정해 봅시다.

GETblog/post/ 
POSTblog/post/추가적인 데이터
PUTblog/post/추가적인 데이터
DELETEblog/post/ 

이전의 플라스크에서 “새 포스트 생성” 과 같은 걸 구현했던 것과는 꽤 다릅니다. 확인하시는 바와 같이, 위의 네 가지 행동들은 모두 같은 엔드포인트, blog/post/1 을 공유하고 있습니다. 그리고, 그 이유는 위의 것들은 모두 같은 자원에 접근하고 있기 때문입니다.

그렇기에 blog/post-create 와 같은 요청이 아니라, 자원 중심으로 요청을 해서 “게시물을 조회” 하거나, “삭제” 하거나 등등.. 여러 가지 행동들을 할 수 있다는 걸 알 수 있게 됩니다.

이는 ,예컨대 blog/post-create와 같은 추가적인 엔드포인트 없이, “자원” 을 중심으로 다루고 있는 모습입니다. “자원” 이 가지고 있는 메서드들을 활용해, 그것들을 다루는 것이죠. 이는 OOP와도 꽤 비슷하네요.

Stateless

REST 의 또 다른 특징은 Stateless 입니다.

이는, “하나의 요청은 다른 요청들에 대해 의존 관계를 가지면 안 된다” 라는 의미입니다. -> 그러므로, 서버는 “현재의 요청” 에 대해서만 알아야 하고, “이전의 요청” 에 관해서는 알지 못해야 한다는 이야기입니다.

먼저, 우리가 POST요청을 blog/post 에 보내 하나의 블로그 포스트를 생성했다고 가정해 봅시다. -> 그렇다면, 서버는 데이터베이스에 우리가 보낸 블로그 포스트 데이터를 넣을 겁니다. id는 1이고, 그렇기에 blog/post/1 이라는 새로운 디테일 페이지가 생성되었다고 가정하겠습니다.

그러한 일련의 과정들을 거치고 나면, 서버는 방금 전 생성한 포스트가 있는지 없는지를 모르게 됩니다.

그러므로, 예를 들어 blog/post 에 GET 요청을 보내 데이터를 조회하고 싶다고 한다면, 서버는 이 요청을 보고 “아, 이 게시물은 무조건 존재하지, 왜냐면 방금 전에 생성했으니까!” 라고 말할 수 없어야 합니다. 데이터를 조회하고 싶다면, 데이터베이스에서 id==1 인 게시물이 있나? 를 찾아보고 있다면 응답을 만들어주어야 합니다.

위의 과정들은 우리가 1번 게시물에 대한 GET 요청을 보내기 위해서 POST 요청을 보낼 필요가 없다는 것을 의미합니다. “다른 요청들에 대해 의존 관계를 가지면 안 된다” 라는 말과 일맥상통하죠? 서버는 해당 포스트가 있는지 없는지 모르기 때문에, GET 요청을 보내면 -> 데이터베이스에서 해당 리소스가 있는지 찾아보고 -> 적절한 응답을 생성하는 과정을 거치게 될 겁니다.

또 다른 예시를 만들어 볼까요? 한 사용자가, 예를 들면 “네이버 메일” 과 같은 곳에 로그인했다고 가정해 보겠습니다. 로그인 과정을 거치면, 서버는 우리에게 어떠한 중요한 데이터를 보내주게 됩니다. 그리고, 그 “중요한 데이터” 는 사용자에 대해서 유일해야 합니다.

로그인 과정을 거친다고 한들, 서버는 그 유저의 state를 기억하기 전까지 “아, 이 유저가 로그인했구나!” 를 모릅니다. 웹 애플리케이션은, 모든 요청에서 사용자를 식별하기에 충분한 데이터를 보내줘야 합니다. 그리고 유저는, 그 데이터를 통해서 인증 과정을 거치죠. 서버는,”이 유저가 보내준 중요한 데이터를 보고, 아, 이 데이터는 내가 미리 보내줬던 데이터지! 그리고 이 데이터는 한 사용자에 대해서 유일한 데이터지! 이 사용자는 로그인되어 있구나!” 를 알 수 있게 됩니다.

간단한 HTTP API 구축해보기

프로젝트를 하나 만들어 볼까요? “제임스의 flower shop” 혹은 “스미스의 hair shop” 처럼 shop을 추가할 수 있고, 각각의 shop에 “물망초” 와 같은 item들을 추가할 수 있도록 하고, 각각의 조회가 가능하게끔 해 보겠습니다. 결국 하고 싶은 건, shop과 item들을 다루는 겁니다.

위에서 살펴보았던 메소드들 중 GET, POST 를 살펴볼까요?

각각의 메소드들은 위의 역할을 해 줄 겁니다.

from flask import Flask

app = Flask(__name__)


'''
서버의 입장에서,
POST -  클라이언트가 보낸 데이터를 받는 데에 사용한다.
GET  -  데이터를 다시 돌려주는 데에만 사용한다.
'''

# 아래에서 다룰 리소스들 : shop, item

# POST /shop -> name 이라는 데이터를 받아서, 새로운 shop을 생성함
@app.route('/shop', methods=['POST'])
def create_shop():
    pass

# GET  /shop/<string:shop_name> -> name 에 맞는 shop 을 보여줌
@app.route('/shop/<string:shop_name>', methods=['GET'])
def get_shop_detail():
    pass

# GET  /shop -> shop의 목록들을 보여줌
@app.route('/shop', methods=['GET'])
def get_shop_list():
    pass

# POST /shop/<string:shop_name>/item -> item_name, price 를 받아서, name에 맞는 shop에 새로운 item을 추가
@app.route('/shop/<string:shop_name>/item', methods=['POST'])
def create_item_in_shop():
    pass

# GET  /shop/<string:shop_name>/item -> shop에 들어있는 item들의 목록을 반환
@app.route('/shop/<string:shop_name>/item', methods=['GET'])
def get_item_list_in_shop(shop_name):
    pass

if __name__ == '__main__':
    app.run()

일단은 위와 같은 엔드포인트들을 구축할 수 있을 겁니다. 아직은 리소스들에 대한 수정, 삭제와 같은 기능은 존재하지 않지만, 일단 간단하게 시작해 보죠!

그럼, 이쯤에서 JSON 이란 무엇인지 알아보아야 할 필요가 있을 것 같네요!

우리는 맨 위부터 우리가 브라우저와 같은 곳에 “naver.com” 을 입력하고 들어가면 네이버의 서버는 우리에게 “HTML”과 같은 것들을 보내준다는 사실을 알고 있습니다. 하지만 HTML 전체를 주고받는 건, 우리가 개발하려는 백엔드 서버로서는 좋지 않은 방법이죠. 우리는 프론트엔드 단이나, 안드로이드, iOS에서도 쓸 수 있는 데이터를 보내줘야 합니다.

그러한 의미에서, JSON 은 웹 어플리케이션끼리 데이터를 주고받는 데에 굉장히 좋은 방법입니다. 아래의 예시를 살펴볼까요?

HTML
JSON

위의 두 예시는 같은 내용을 전달하지만, 하나는 HTML이고 하나는 JSON이죠? 프론트엔드, iOS, 안드로이드에서 필요한 건 “데이터” 그 자체이지, <head> 태그와 같은 불필요한 정보는 필요가 없습니다. 아래의 그림을 보면, 우리가 왜 데이터 형식으로서 JSON 포맷을 사용하려는지 알 수 있을 겁니다.

여러 클라이언트에서 사용할 수 있고, 대부분의 프로그래밍 환경에서 다룰 수 있는 환경이 보장되는 JSON으로 데이터를 주고받을 것이고, 우리는 그것을 구현해 볼 겁니다.

shop 목록 조회하기

다시 프로젝트 이야기로 돌아와서, /shop 을 입력했을 때, shop들의 목록을 얻고 싶다면 -> 우리의 플라스크 서버가 shop 목록들에 대한 json을 반환하고 싶게끔 만들고 싶다면 어떻게 해야 할까요? 일단, 데이터베이스를 사용하는 대신 파이썬의 리스트로 shop 들을 저장해 봅시다.

shop_list 라는 리스트 안에, 딕셔너리가 있습니다. items 라는 키에는, 또 리스트가 있고, 그 안에 딕셔너리가 존재하네요. 위에서 만든 shop_list 라는 리스트는 파이썬의 자료구조이므로 이를 JSON 형식으로 바꾸어주는 작업이 필요합니다. return shop_list 와 같은 코드를 쓴다면, 프론트엔드의 자바스크립트나, 다른 클라이언트들은 이를 이해하지 못할 것이기 때문입니다.

플라스크에서는, 파이썬의 딕셔너리와 같은 자료구조를 JSON 형식으로 바꾸어주는 작업을 jsonify 라는 메서드를 통해서 지원합니다.

위의 코드를 입력하고, /shop 에 접속해 볼까요? 아래와 같은 결과가 나올 겁니다.

그리고 개발자 도구의 응답 헤더를 보면, json 형식으로 응답되었다고 알려주고 있네요!

그런데 위의 응답에서는 “물망초” 라고 나와야 하는 부분이 깨지는 현상이 있습니다. 그리고, 아무래도 들여쓰기가 잘 되어 있으면 보기가 편하겠죠? 아래의 코드를 입력해 줍니다.

원하던 대로 결과가 출력되는 것을 볼 수 있다.

그리고, 조금 더 데이터를 편하게 쓸 수 있게끔 우리의 JSON 에 key 를 하나 추가해 주겠습니다.

결국, 우리가 앞으로 개발할 HTTP API 는 “요청이 들어오면, 어떠한 과정을 수행하고, JSON을 응답해준다” 가 될 겁니다. “어떠한 과정” 이라는 건, 예를 들면 새로운 shop을 생성하거나, 새로운 item을 생성하거나, shop들의 목록을 조회하거나 등등이 될 겁니다.

shop 생성 구현하기

먼저 shop 을 생성한다는 것은 어떤 걸 의미할까요? 우리는 리스트에 shop 들의 목록을 저장해 두었으므로, 아래와 같이 리스트에 하나의 요소가 더 추가되는 것을 의미할 겁니다.

그렇다면, 아래와 같은 구현을 할 수 있을 겁니다.

먼저 우리는 get_json() 을 사용해서 클라이언트가 보낸 json을 파이썬의 딕셔너리 형태로 다시 변환할 겁니다.

Postman을 사용해서 POST 요청을 직접 보내주겠습니다. 위처럼 헤더 부분에 “application/json” 을 설정해 줍니다. 이는 서버로 하여금 “아 내 이 클라이언트로부터 받을 데이터는 JSON이겠구나!” 를 알려주는 역할을 합니다.

참고사항 : 서버를 다시 시작한다면, 우리의 shop과 같은 것들은 데이터베이스에 저장되어 있는 것이 아니기에 초기화가 될 겁니다. 예를 들면, “James shop” 을 추가하고 나서 서버를 다시 실행하지 않고 목록을 조회한다면 잘 추가되어 있을 겁니다. 하지만 서버를 다시 실행한다면, 우리가 추가한 shop들은 하나로 다시 초기화 될 겁니다. 초기화되는 것이 정상입니다!

그리고 실제 json 형태를 위와 같이 작성해서 보내 볼까요?

send 버튼을 누르면, 아래와 같이 새로운 요청에 대한 json응답을 서버가 반환해 주는 것을 볼 수 있을 것입니다. print(request_data), print(type(request_data)) 의 결과는 콘솔 창에 어떻게 찍혔나 볼까요?

type이 <class ‘dict’> 로, 파이썬의 딕셔너리로 찍힌 것을 확인할 수 있네요. get_json() 을 사용해서 request로 받아온 json을 파이썬의 딕셔너리 형태로 변환하였습니다. 그렇다는 이야기는, 이제부턴 파이썬에서 []로 키 값을 통해 접근할 수 있게 되었으므로 new_shop 이라는 딕셔너리를 하나 만들어 shop_list 라는 리스트에 하나 추가한 뒤, 새로 만들어진 shop의 정보를 다시 json의 형태로 바꾸어 리턴해주는 것입니다.

그렇다면 GET 요청을 보냈을 때에, 우리가 추가한 “김밥지옥” 이라는 shop이 리스트에 추가되어 있어야 하겠죠?

결과 화면. 제대로 되었다!

제대로 우리가 추가한 shop이 shop_list 로 뜨는 것을 확인할 수 있네요.

shop 상세 조회 구현하기

shop/샵 이름 으로 요청했을 때에 해당 이름에 맞는 shop에 대한 상세정보를 리턴해 주도록 구현해 보겠습니다.

간단하게, 모든 shop_list에 대하여 shop_name과 일치하다면 그것에 대한 상세정보를 json으로 바꾸어 리턴해주도록 했죠? 만약 name에 없는 shop에 대한 상세 정보를 요청한다면, “이름에 맞는 shop이 없어요~” 라고 불평불만을 리턴해 줄 겁니다.

위와 같이 shop 들을 몇 개 추가해 두고,

Postman에서 요청을 보내 보세요. 위와 같이 서버는 우리에게 shop에 대한 상세 정보를 잘 리턴해줄 겁니다.

shop item 목록 조회 구현하기

url로 받은 shop 이름을 받아서, 그 안에 있는 item들만 목록으로 반환하게끔 하면 되겠죠?

포스트맨에서 위의 주소로 GET 요청을 보내 보세요. item들의 목록이 잘 반환되는 걸 확인할 수 있을 겁니다.

Item 생성 구현하기

위에서 사용했던 새 shop 만들기의 로직과 상당히 유사합니다. 새 아이템을 맞는 shop의 items라는 리스트에 추가해 주도록 했습니다.

postman 을 사용해서 테스트해 볼까요?

위와 같이 body에 값을 넣어서 send를 수행해 주면, 우리가 추가한 item에 대한 정보가 잘 반환되는 것을 볼 수 있네요. 같은 주소로 get 요청을 보낸다면, item들의 목록이 나타날 것이므로 확인할 수 있을 겁니다.

우리가 추가한 “장미” 라는 아이템이 잘 추가되었네요!

아직은 추가, 조회 정도의 기능만 있을 뿐, 수정이나 삭제와 같은 기능은 존재하지 않습니다. 앞으로의 포스팅에서는, flask-restful 을 이용해 그것들을 구현해 보도록 하겠습니다!

Leave A Comment

Avada Programmer

Hello! We are a group of skilled developers and programmers.

Hello! We are a group of skilled developers and programmers.

We have experience in working with different platforms, systems, and devices to create products that are compatible and accessible.