본문 바로가기

GET과 POST의 정체, 그리고 사라지는 글

1. 이번 절의 목표

브라우저와 서버가 실제로 주고받는 글자를 눈으로 봅니다. 그리고 서버를 껐다 켜서 글이 사라지는 것을 확인합니다.

지금까지 GET은 "주세요", POST는 "받으세요"라고만 했습니다. 이번 절에서는 그 둘이 실제로 어떻게 생겼는지 봅니다. 백엔드 이해의 마지막 조각입니다. 이걸 보고 나면 "비밀번호는 어디로 가는가"에 스스로 답할 수 있습니다.

2. 로그인 화면 하나 만들기

실험용으로 로그인 화면을 하나 만듭니다. 진짜 로그인은 아니고, 폼이 서버로 무엇을 보내는지 보기 위한 것입니다. main.py 맨 아래에 붙입니다. 이번에는 변수 주소가 없으니 순서를 신경 쓰지 않아도 됩니다.

# 실험용 로그인 화면
@app.get("/login", response_class=HTMLResponse)
def login_form():
    return f"""
        {style}
        <h1>로그인</h1>
        <form action="/login" method="get">
            <input name="a" placeholder="아이디">
            <input name="b" type="password" placeholder="비밀번호">
            <button class="btn">로그인</button>
        </form>
    """

/login을 열고 아이디에 hello, 비밀번호에 1234를 넣고 로그인을 누릅니다. 화면은 그대로인데 주소창을 보세요.

http://127.0.0.1:8000/login?a=hello&b=1234

비밀번호가 주소창에 그대로 보입니다. method="get"으로 보냈기 때문입니다.

3. GET은 주소에 실어 보냅니다

GET 방식은 폼에 적은 값을 주소 뒤에 붙여서 보냅니다. ? 뒤에 이름=값이 &로 이어집니다. 이 부분을 쿼리 스트링이라고 부릅니다.

주소에 실리기 때문에 세 가지 일이 생깁니다.

  • 주소창에 보입니다. 옆 사람이 봅니다.
  • 브라우저 방문 기록에 남습니다.
  • 주소를 복사해서 보내면 값도 같이 갑니다.

검색어처럼 남아도 되는 값은 GET이 편합니다. naver.com/search?query=날씨 같은 주소를 본 적이 있을 것입니다. 하지만 비밀번호는 안 됩니다.

4. POST로 바꾸기

method="get"을 method="post"로 바꿉니다. 그리고 받는 함수를 하나 추가합니다.

@app.post("/login", response_class=HTMLResponse)
def login(a: str = Form(), b: str = Form()):
    return f"{style}<h1>{a}님, 받았습니다</h1>"

다시 /login에서 로그인을 눌러 봅니다. 주소창은 /login 그대로이고 값이 안 보입니다. 화면에는 hello님, 받았습니다가 나옵니다. 값은 분명히 서버에 갔는데 주소에는 없습니다. 그러면 어디에 실려 갔을까요.

5. 실제로 오간 글자 보기

Chrome에서 F12를 누릅니다. 개발자 도구가 열립니다. 위쪽 탭에서 Network를 누릅니다. 그 상태에서 /login 화면을 새로 고침하고 다시 로그인을 누릅니다.

왼쪽 목록에 login이 나타납니다. 누르면 오른쪽에 자세한 내용이 나오고, Headers 탭에 브라우저가 서버에 보낸 것이 적혀 있습니다. 정리하면 아래 모양입니다.

GET으로 보냈을 때:

GET /login?a=hello&b=1234 HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/140.0.0.0
Accept: text/html,application/xhtml+xml,*/*
Referer: http://127.0.0.1:8000/login
Connection: keep-alive

POST로 보냈을 때:

POST /login HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/140.0.0.0
Accept: text/html,application/xhtml+xml,*/*
Referer: http://127.0.0.1:8000/login
Origin: http://127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
Connection: keep-alive

a=hello&b=1234

이것이 요청입니다. 브라우저가 서버에 보내는 것은 결국 이런 글자 뭉치입니다. 첫 줄만 읽어도 됩니다.

  • 첫 단어가 방식입니다. GET 또는 POST. 우리가 @app.get, @app.post로 구분한 것이 이것입니다.
  • 두 번째가 주소입니다. 우리가 스티커에 적은 것이 이것입니다.
  • GET은 값이 첫 줄의 주소에 붙어 있고, POST는 값이 맨 아래 따로 붙어 있습니다. 이 아래 부분을 본문(body)이라고 합니다. Content-Length: 14는 본문이 14글자라는 뜻입니다. a=hello&b=1234를 세어 보면 14입니다.

값이 실리는 자리만 다릅니다. GET은 첫 줄의 주소 안에, POST는 맨 아래 본문에 실립니다. 그래서 GET은 주소창에 보이고 POST는 안 보입니다.

서버가 돌려주는 것도 같은 모양의 글자 뭉치이고, 이것을 응답이라고 합니다. 개발자 도구의 Response 탭을 누르면 우리 함수가 return한 HTML이 그대로 보입니다. <h1>hello님, 받았습니다</h1>가 거기 있습니다.

주소 하나에 함수 하나를 붙인 것, id: int로 주소에서 값을 받은 것, Form()으로 본문에서 값을 받은 것, return으로 글자를 돌려준 것. 이 책에서 한 일이 전부 이 요청과 응답 안에 있습니다.

POST면 안전한가

주소창에 안 보일 뿐입니다. 개발자 도구로 본 것처럼 본문에는 그대로 적혀 있고, 이 글자 뭉치는 인터넷을 타고 서버까지 갑니다. 중간에서 누가 들여다보면 읽힙니다. 그래서 실제 서비스는 https로 글자 뭉치 전체를 암호화합니다. 주소가 http가 아니라 https로 시작하는 이유입니다. 이 책에서는 여기까지만 알아 둡니다.

6. 서버를 끄면 글이 사라집니다

마지막 실험입니다. 게시판에 글을 두세 개 씁니다. 목록에 잘 나오는지 확인합니다. 그리고 터미널에서 Ctrl + C로 서버를 끕니다. 위쪽 화살표로 fastapi dev main.py를 다시 켭니다.

/notice를 열어 봅니다. 방금 쓴 글이 없습니다. 처음 data에 적어 둔 세 개만 남아 있습니다.

data는 파이썬 리스트이고, 리스트는 프로그램이 도는 동안 메모리에만 있습니다. 프로그램이 꺼지면 메모리도 비워집니다. append로 붙인 글은 파일 어디에도 적히지 않았습니다.

실제 게시판은 이러면 안 됩니다. 글은 서버를 껐다 켜도, 컴퓨터를 바꿔도 남아 있어야 합니다. 그러려면 메모리가 아니라 어딘가 오래 남는 곳에 적어야 합니다. 그 어딘가가 데이터베이스입니다.

7. 우리가 만든 서버의 모양

마지막으로 우리가 만든 것이 어떤 모양인지 한 발 떨어져서 봅니다. AI에게 "게시판 만들어줘"라고 하면 이 책과 다른 모양의 코드가 오는 경우가 많습니다. 모양이 다르면 화면을 만드는 자리가 달라지고, 어디를 고쳐 달라고 해야 하는지도 달라집니다. 그래서 모양을 구분하는 눈이 필요합니다.

7.1 서버 하나가 화면까지 만든다

이 책의 모양입니다. 브라우저는 주소만 보내고, 서버 하나가 함수를 찾고 데이터를 꺼내고 HTML까지 만들어 통째로 돌려줍니다. 파일도 main.py 하나, 프로그램도 하나입니다. 이런 모양을 모놀리식이라고 부릅니다. 한 덩어리라는 뜻입니다.

처음 배우기에 가장 좋은 모양입니다. 무슨 일이 어디서 일어나는지 한 파일 안에서 다 보이기 때문입니다.

7.2 서버는 데이터만 주고, 화면은 따로 만든다

AI가 자주 만들어 오는 모양입니다. 서버는 HTML을 만들지 않고 데이터만 돌려줍니다. 이때 데이터는 우리가 본 data 리스트와 비슷한 모양의 글자, JSON으로 갑니다. 화면은 브라우저에서 도는 별도의 프로그램(프론트엔드)이 그 데이터를 받아서 그립니다.

이 모양에서는 서버 함수가 <h1> 같은 HTML을 돌려주지 않고 {"title": "...", "contents": "..."} 같은 글자를 돌려줍니다. FastAPI에서 response_class=HTMLResponse를 빼면 기본으로 이 모양이 됩니다. 이 책이 매 함수에 HTMLResponse를 붙인 이유가 이것입니다. 위니북스의 FastAPI 베이스캠프는 이 모양으로 진행합니다.

두 모양이 한 파일에 섞여 있으면 헷갈립니다. 어떤 주소는 HTML을 주고 어떤 주소는 JSON을 주는 코드가 AI에게서 올 때가 있습니다. 그럴 때 "이 함수는 화면을 만드는 함수인가, 데이터만 주는 함수인가"를 return 뒤를 보고 구분하면 됩니다.

7.3 백엔드를 여러 개로 쪼갠다

서비스가 커지면 백엔드 하나가 게시판, 로그인, 결제, 알림을 다 맡기 힘들어집니다. 그래서 기능별로 서버를 따로 두고, 앞에 문지기 서버 하나를 세워 주소를 보고 나눠 보냅니다. 이런 모양을 마이크로서비스라고 부릅니다.

이 책에서는 이 모양을 만들지 않습니다. 다만 그림을 보면 알 수 있듯이, 서버가 몇 개로 늘어나든 서버 하나하나가 하는 일은 같습니다. 주소를 받아 함수를 찾고, 데이터를 꺼내고, 돌려줍니다. 우리가 만든 main.py가 저 상자 하나입니다.

7.4 세 모양 비교

서버 하나가 화면까지서버는 데이터만백엔드 여러 개
화면을 만드는 곳서버 함수의 return브라우저의 JavaScript브라우저의 JavaScript
서버가 돌려주는 것HTMLJSONJSON
프로그램 개수1개2개 (프론트, 백)여러 개
이 책이 모양FastAPI 베이스캠프다루지 않음

AI가 준 코드가 어느 칸에 있는지 먼저 보고, 그 다음에 무엇을 고쳐 달라고 할지 정하세요. 화면이 이상하면 화면을 만드는 곳을, 데이터가 이상하면 데이터를 꺼내는 곳을 짚어 주면 됩니다.

8. 여기서 멈추는 이유

이 책은 데이터베이스 앞에서 멈춥니다. 대신 여러분은 이제 아래를 압니다.

  • 백엔드는 주소를 받아 글자를 돌려주는 함수 모음이다.
  • 주소에는 변수를 넣을 수 있고, 데이터는 함수 밖에 따로 둔다.
  • 브라우저는 GET으로 달라고 하고 POST로 보낸다. 둘 다 결국 글자 뭉치다.
  • 메모리에 둔 데이터는 서버를 끄면 사라진다. 그래서 데이터베이스가 필요하다.
  • 서버가 화면까지 만드는 모양과 데이터만 주는 모양이 있고, return 뒤를 보면 구분된다.

이 다섯 줄을 알면 AI에게 "게시판 만들어줘"라고 시켰을 때 돌아온 코드에서 주소, 함수, 데이터, 요청 방식을 찾아낼 수 있습니다. 어디를 고쳐 달라고 할지 짚을 수 있습니다. 그것이 이 책이 말한 이해입니다.

데이터베이스까지 가고 싶다면 위니북스의 FastAPI 베이스캠프 4장으로 이어집니다. 그 책의 data 리스트가 SQLite라는 파일 데이터베이스로 바뀌는 지점부터 읽으면 됩니다. 여러분이 만든 게시판이 그대로 출발점입니다.

AI에게 물어보기

FastAPI로 만든 게시판에서 data 리스트에 append한 글이 서버를 껐다 켜면 사라져. 왜 그런지 설명하고, 사라지지 않게 하려면 어떤 선택지가 있는지 쉬운 것부터 알려줘. 코드는 아직 바꾸지 마.
(main.py 붙여넣기)
아래 요청 메시지의 각 줄이 무슨 뜻인지 한 줄씩 설명해줘. Host, User-Agent, Content-Type이 특히 궁금해.
(개발자 도구에서 본 요청 붙여넣기)