컴퓨터가 외부와 데이터를 주고받기 위한 수단을 생각해 보자. 과거에는 사람이 직접 손으로 타이핑하거나 디스크를 연결해 정보를 복사하는 방법을 주로 썼다. 하지만 지금은 인터넷이라는 훨씬 빠르고 유용한 정보 교환 수단을 훨씬 많이 사용한다. 세계의 컴퓨터들을 연결한 통신 네트워크 덕분에 사람이 직접 이동하지 않고 멀리 떨어진 곳까지 빠르게 정보를 교환할 수 있게 되었다.

인터넷을 직접 다루려면 통신 환경에 관한 지식이 많이 필요하지만, 일반적인 응용 프로그램을 만들 때는 인터넷 서비스의 하나인 ‘웹(월드 와이드 웹, WWW)’을 다룰 수 있으면 충분하다. 웹은 인터넷을 통해 다양한 정보를 서로 연결해 제공하는 정보 환경이다. 웹 브라우저로 웹을 탐색해 본 경험이 있을 것이고, 웹 서버(웹 사이트)들이 제공하는 다양한 정보와 서비스도 이용해 보았을 것이다. 이처럼 웹 탐색을 수행하는 프로그램이나 웹 서비스를 제공하는 프로그램을 만드는 활동을 웹 프로그래밍이라고 한다.

이 절에서는 파이썬 라이브러리를 이용해 간단한 웹 프로그래밍을 체험해 볼 것이다. 웹 환경은 많은 연구와 사업이 이루어지는 분야이기 때문에 깊이 들어가려면 다뤄야 할 주제가 훨씬 많다. 여기서는 아주 기본적인 기능만 알아볼 것이다. 하지만 이것만으로도 활용할 여지가 많다는 것을 느낄 것이다. 흥미를 더 느껴 다른 주제에 관해서도 학습하는 계기가 되기 바란다.1

11.5.1 요청과 응답

웹 브라우저(클라이언트)로 웹 사이트(서버)에 접속할 때 일어나는 일을 순서대로 생각해 보자.

  1. 사용자가 웹 브라우저의 주소창에 주소를 입력한다.
  2. 웹 브라우저는 요청 메시지를 작성해 웹 서버에 보낸다. (요청)
  3. 요청 메시지가 인터넷의 복잡한 통신망을 거쳐 웹 서버에 전달된다. (요청 완료)
  4. 웹 서버는 요청받은 정보를 요청자에게 보낸다. (응답)
  5. 응답 메시지가 인터넷의 복잡한 통신망을 거쳐 웹 브라우저에 전달된다. (응답 완료)
  6. 웹 브라우저가 사용자에게 정보를 출력해준다.

우리가 웹 사이트에 접속할 때마다 이 과정이 대략 1초 이내에 처리된다. 여기서 핵심은 2번과 4번의 요청(request)응답(response)이다. 요청이란 일정한 약속(HTTP)에 따라 클라이언트(서비스 이용자)가 특정 주소(URL)에 해당하는 정보를 달라는 메시지를 서버(서비스 제공자)에 전하는 것이다. 정보를 요구하는 입장에서는 요청만 제대로 할 줄 알면 된다. 요청된 정보를 올바르게 제공하는 것은 웹 서버가 담당한다. 클라이언트로서 요청하는 방법을 먼저 살펴보고, 웹 서버로서 응답하는 방법도 확인해보자.

11.5.2 웹 클라이언트로서 정보 요청하기

웹 환경에 공개된 모든 자원은 요청을 통해 접근할 수 있다. 웹 문서 뿐 아니라, 이미지·음악·영상 등 여러분이 원하는 자원이 무엇이든지 말이다. 게다가 단순히 자원을 조회할 뿐 아니라, 로그인·업로드·삭제 등 서버에 변화를 일으키는 요청도 수행할 수 있다. 즉, 웹 브라우저 입장에서는 게시판의 글을 읽는 것, 댓글을 쓰는 것, 사진을 올리는 것이 모두 요청이라는 동일한 행위다.

파이썬으로 웹 요청을 수행하는 것은 여러분이 평소 웹 브라우저로 웹 사이트에 접속하는 것과 똑같은 작업이다. 차이가 있다면, 주소를 입력하는 곳이 주소창이 아니라 함수의 매개변수라는 것 정도다. 인터넷 공간에 존재하는 수많은 자원은 https://python.bakyeono.net과 같은 형식의 주소로 식별하는데, 이를 ‘URL(Uniform Resource Locator)’이라고 한다. 어떤 자원의 URL만 알면, 파이썬으로 그 자원을 요청할 수 있다.

웹에 정보 요청하기

파이썬은 URL과 웹 요청에 관련된 모듈들을 urllib(URL 관련 라이브러리라는 의미)이라는 패키지로 묶어 제공한다. 이 패키지에 담긴 모듈 중에서 주소를 해석해주는 urllib.parse 모듈과 특정 주소에 HTTP 요청을 수행하는 urllib.request 모듈이 주로 쓰인다.

urllib.request 모듈의 HTTP 요청 기능부터 살펴보자. urllib.request 모듈을 임포트한 후, urllib.request.urlopen(요청할URL).read().decode('utf-8') 이라는 표현을 실행하면 웹 요청을 보낼 수 있다. 명령이 조금 길어 어렵게 느껴질 듯하다. 명령이 긴 이유는 다음과 같은 중간 과정을 처리해야 하기 때문이다.

  1. urllib.request.urlopen() 함수는 웹 서버에 정보를 요청한 후, 돌려받은 응답을 저장하여 ‘응답 객체(HTTPResponse)’를 반환한다.
  2. 응답 객체의 read() 메서드를 실행하여 서버가 전달한 데이터를 바이트 배열로 읽어들인다.
  3. 바이트 배열은 십육진수로 이루어진 수열이기 때문에 사람의 눈으로 읽기가 어렵다. 웹 서버가 보낸 문서는 텍스트 데이터일 것이므로 이것을 텍스트 형식으로 변환한다. 바이트 배열의 decode() 메서드를 실행하여 ‘UTF-8’(유니코드 부호화 형식의 한 종류)로 해석하면 문자열이 반환된다.

이것을 매번 입력하기 귀찮다면 다음과 같이 함수로 정의해두는 것도 좋은 생각이다.

코드 11-77 웹 문서 요청 함수 정의해 두기

import urllib.request

def request(url):
    """지정한 url의 웹 문서를 요청하여, 본문을 반환한다."""
    http_response = urllib.request(url)
    byte_data     = http_response.read()
    text_data     = byte_data.decode('utf-8')
    return text_data

하지만 그냥 웹 요청이 필요할 때마다 이 페이지를 펼쳐 보고 따라 입력하는 것도 괜찮다. 여러 번 하다 보면 저절로 익혀진다. 다음은 이 책을 소개하는 웹 사이트 https://python.bakyeono.net에 접속(요청)해 본 예다. 따라 입력해보기 바란다. 요청을 실행했을 때 인터넷 연결이 원활하지 않거나, URL이 잘못되었다면 예외가 발생할 수 있으니 주의하자.

코드 11-78 웹 문서 요청하기

>>> import urllib.request
>>> url = 'https://python.bakyeono.net'  # 요청할 URL
>>> webpage = urllib.request.urlopen(url).read().decode('utf-8')
>>> print(webpage)  # 응답 받은 텍스트 확인: HTML 문서가 출력된다
<!DOCTYPE html>
<html>
... (뭔가 복잡한 내용이 출력된다) ...
</html>

웹 문서의 형식

요청한 결과로 서버가 응답해 준 텍스트를 살펴보자. 텍스트를 print() 함수로 출력해보니, 뭔가 이해하기 힘든 복잡한 텍스트가 화면을 가득 메운다. 이것은 HTML(HyperText Markup Language)이라는 언어로 작성된 마크업 문서인데, 우리가 웹 브라우저로 동일한 URL에 접속했을 때 전달받는 문서와 동일한 문서다. 웹 브라우저는 이런 마크업 문서를 사람이 보기 좋게 디자인하여 출력해 준다. 그래서 이런 마크업 문서를 날 것으로 보면 생소하게 느껴질 것이다. 웹 브라우저에서도 ‘소스 보기’ 기능을 이용해보면, 디자인되지 않은 HTML 문서를 날 것으로 볼 수 있다.

그림 11-2 웹 브라우저로 HTML 문서 '소스 보기'

그림 11-2 웹 브라우저로 HTML 문서 ‘소스 보기’

웹의 정보를 웹 브라우저가 아니라 파이썬 프로그램으로 읽어들인다면, 그 목적은 읽어들인 정보를 분석·재가공하여 유용하게 쓰고자 함일 것이다. 그런데 HTML 문서는 웹 브라우저로 보기에는 편리하지만, 프로그램에서 해석하고 처리하기에는 다소 까다롭다.2 다행히 웹에 HTML 문서만이 존재하는 것은 아니다. JSON 처럼 좀 더 프로그램에서 다루기 좋은 데이터도 제공된다. 웹 브라우저로 https://python.bakyeono.net/data/movies.json에 접속해 JSON 형식의 데이터를 열람해 보자.

그림 11-3 인터넷의 JSON 데이터를 웹 브라우저로 열기

그림 11-3 인터넷의 JSON 데이터를 웹 브라우저로 열기

JSON 형식의 데이터라면 파이썬으로 처리하기에도 어려움이 없지 않을까? 이 데이터를 파이썬으로 읽어들여 보자. urllib.request.urlopen() 함수로 위의 URL에 요청해 봐라.

코드 11-79 웹에서 JSON 데이터 읽어들이기

>>> url = 'https://python.bakyeono.net/data/movies.json'  # 요청할 주소
>>> text_data = urllib.request.urlopen(url).read().decode('utf-8')
>>> print(text_data)
[
    {
        "title": "Interstella",
        "genre": "SF",
        "year": 2014,
        "starring": ["M. McConaughey", "A. Hathaway", "J. Chastain"]
    },
    {
        "title": "Braveheart",
        "genre": "Drama",
        "year": 1995,
        "starring": ["M. Gibson", "S. Marceau", "P. McGoohan"]
    },
    {
        "title": "Mary Poppins",
        "genre": "Fantasy",
        "year": 1964,
        "starring": ["J. Andrews", "D. Van Dyke"]
    }
]

동일한 URL의 정보를 읽어들이자 웹 브라우저에서와 마찬가지로 JSON 데이터가 응답되었다. 앞서 배운 것처럼, JSON 데이터는 json.loads() 함수를 이용해 파이썬 컬렉션으로 해석할 수 있다. 다음과 같이 컬렉션으로 해석해 두고, 데이터를 원하는 대로 재가공·활용할 수 있다.

코드 11-80 웹에서 받은 JSON 데이터 해석·가공하기

>>> import json
>>> movies = json.loads(text_data)
>>> sorted_by_year = sorted(movies, key = lambda movie: movie['year'])
>>> for movie in sorted_by_year:
...     print(str(movie['year']) + ' ' + movie['title'].upper())
... 
1964 MARY POPPINS
1995 BRAVEHEART
2014 INTERSTELLA

이상으로 웹의 정보를 요청하는 기본 방법을 알아보았다. 웹 환경에 대한 배경 지식이 필요하다는 것을 제외하면, 놀랍게도 알아둬야 하는 것은 겨우 한 행 짜리 파이썬 명령 뿐이다. 복잡한 인터넷 통신 과정은 파이썬 라이브러리가 대신 처리해 준다. 하지만 여전히 많은 의문이 남을 것이다. 예를 들면, “내가 원하는 그 정보는 어디에 요청해서 얻을 수 있을까?”, “읽어들인 정보가 어떤 구조로 되어있는지 어떻게 알 수 있을까?” 같은 것들.

웹 API 활용하기

웹에서는 다양한 기관과 회사가 프로그래밍에 필요한 정보를 제공하고 있다. 이런 서비스를 웹 API(Application Programming Interface)라고 한다. 웹 API에서 제공하는 데이터는 대부분 (HTML이 아니라) JSON 또는 XML 형식이어서 프로그래밍에 활용하기 용이하다. 웹 API로 제공되는 정보의 종류도 매우 다양한데, 다음은 몇 가지만 예를 들어본 것이다.

  • 대한민국 공공기관 정보: 공공데이터포털(data.go.kr)에서 API 사용허가를 얻은 후 열람. 기상청에서 제공하는 기상 예보 정보가 특히 많이 사용된다.
  • 세계 다국어 사전: Glosbe(glosbe.com)에서 사용신청 없이 자유롭게 사용
  • 세계 영화 정보: TMDb(themoviedb.org)에 계정 생성 후 사용
  • 페이스북 사용자·게시물 정보: 페이스북 개발자 사이트(developers.facebook.com)에서 개발자 계정 등록 후 페이스북 그래프 API 열람
  • 텔레그램 메신저의 채팅 봇 조작: 텔레그램 계정으로 봇 계정 등록 후, 텔레그램 봇 API(https://core.telegram.org/bots/api) 사이트의 문서를 참고하여 이용

이 정보는 책을 쓰는 시점을 기준으로 한 것이며, 여러분이 책을 보는 시점에는 운영상황이나 사용허가가 변경되었을 가능성이 있다. 필요한 정보가 있다면 찾고자 하는 정보에 ‘API’ 검색어를 붙여 인터넷 검색을 해 보자.

웹 API를 사용할 때는 크게 두 가지 문제가 걸린다. 첫번째 문제는 사용 권한을 얻어야 한다는 것이다. 모두에게 공개된 API도 있지만, 허락을 얻어야 하는 것들도 많다. 두번째는 API 사용법을 익혀야 한다는 것이다. API마다 요청해야 하는 URL의 구조와 제공되는 정보의 구조가 다 다르기 때문이다. 이런 URL과 정보 구조에 대한 설명은 API 제공자가 제공하는 문서에서 확인하면 된다. 이것들은 자신에게 필요한 작업을 실제로 수행하면서 배우는 수밖에 없을 듯하다. 이 책에서 모든 것을 알려줄 수 없는 점 양해 바란다.

다음 예제는 Glosbe 다국어 사전 API를 이용해 영어 단어의 뜻을 검색하는 프로그램이다. 이 API의 사용법을 직접 설명할 수는 없지만, “이런 식으로 활용할 수 있구나” 하고 봐두라는 의미에서 실어 둔다.

코드 11-81 Glosbe API를 이용해 영어 단어의 뜻 검색하기

import urllib.request
import urllib.parse
import json

def request_to_glosbe_api(source, dest, phrase):
    """glosbe API에 단어를 질의한다.
    * source: 원래언어
    * dest:   목적언어
    * phrase: 질의어
    """
    base_url = 'https://glosbe.com/gapi/translate'
    quoted   = urllib.parse.quote(phrase)
    query    = f'?from={source}&dest={dest}&phrase={quoted}&format=json'
    url      = base_url + query
    response = urllib.request.urlopen(url).read().decode('utf-8')
    return json.loads(response)

result = request_to_glosbe_api('eng', 'kor', 'life')
print(result['tuc'][0]['phrase'])
# 출력 결과: {'text': '생명', 'language': 'ko'}

11.5.3 URL 다루기

URL은 인터넷 공간에 존재하는 자원을 가리키기 위한 절대 주소다. URL을 작성하는 양식은 다음과 같이 정해져 있다.

프로토콜://계정:패스워드@호스트:포트번호/하위경로?질의문#색인

이 양식에서 가장 자주 사용되는 요소는 프로토콜, 호스트, 하위 경로다. 그 외의 요소는 생략될 때가 많다.

  • 프로토콜: 자원에 접근하기 위한 통신 방법을 나타낸다. 웹에서는 httphttps가 사용된다. HTTPS는 HTTP에 SSL이라는 암·복호화 단계를 적용하여 보안 통신을 수행하는 프로토콜이다.
  • 호스트: 자원이 위치한 네트워크(또는 컴퓨터)의 도메인 주소 또는 IP 주소.
  • 하위 경로: 한 호스트는 여러 개의 자원을 제공할 수 있다. 그 하위 자원을 가리키기 위해 호스트 이름 뒤에 /wiki/Python_(programming_language)와 같이 표기한다.

    https:// python.bakyeono.net /data/movies.json 프로토콜 호스트 하위 경로

그림 11-4 URL 구성의 예 (준비중)

URL 분할·수정·재결합

파이썬에서 URL을 조작할 때는 urllib.parse 모듈을 사용한다. 이 모듈의 함수 urllib.parse.urlsplit()를 이용하면 URL을 여러 부분으로 나눌 수 있다.

코드 11-82 URL을 여러 부분으로 나누기

>>> import urllib.parse
>>> url = https://python.bakyeono.net/data/movies.json
>>> url_parts = urllib.parse.urlsplit(url)  # URL 나누기
>>> url_parts[0]   # 프로토콜 확인
'https'

>>> url_parts[1]   # 호스트 확인
'python.bakyeono.net'

>>> url_parts[2]   # 하위 경로 확인
'/data/movies.json'

urllib.parse.urlsplit() 함수는 나눈 URL 부분들을 튜플에 담아 반환된다. 그러므로 URL의 각 부분을 수정하려면 튜플을 리스트로 변경한 뒤에 해야 한다. 수정을 마친 후 다시 하나의 URL로 합칠 때는 urllib.parse.urlunsplit() 함수를 사용하면 된다. 다음 코드는 나눈 URL에서 하위 경로를 수정한 후 다시 합쳐 본 것이다.

코드 11-83 나눈 URL을 수정한 뒤 다시 합치기

>>> url_parts = list(url_parts)
>>> url_parts[2] = '/chapter-11.html'
>>> urllib.parse.urlunsplit(url_parts)
'https://python.bakyeono.net/chapter-11.html'

퍼센트 인코딩

URL에 사용할 수 있는 문자는 영문자, 숫자, 몇몇 기호 뿐이다. 그 밖의 문자(한글·한자·특수문자 등)는 사용할 수 없다. 그래서 예를 들어 위키백과의 파이썬 문서를 가리키는 https://ko.wikipedia.org/wiki/파이썬 URL을 파이썬에서 요청하면 다음과 같이 오류가 발생한다.

코드 11-84 URL에 한글이 섞여 있으면 요청할 때 오류가 발생한다.

>>> urllib.request.urlopen('https://ko.wikipedia.org/wiki/파이썬')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-12: ordinal not in range(128)

이를 피하기 위해서는 URL에서 아스키 코드가 아닌 문자들을 퍼센트 인코딩(percent encoding)이라는 방식을 이용해 이스케이프 해주어야 한다. 우리가 사용하는 웹 브라우저는 퍼센트 인코딩을 자동으로 수행해준다. 파이썬에서는 urllib.parse.quote() 함수로 텍스트에 퍼센트 인코딩을 적용한 값을 구할 수 있다.

코드 11-85 한글 텍스트를 퍼센트 인코딩하기

>>> urllib.parse.quote('파이썬')
'%ED%8C%8C%EC%9D%B4%EC%8D%AC'

퍼센트 인코딩된 텍스트를 거꾸로 되돌릴 때는 urllib.parse.unquote() 함수를 사용한다.

코드 11-86 퍼센트 인코딩된 텍스트를 되돌리기

>>> urllib.parse.unquote('%ED%8C%8C%EC%9D%B4%EC%8D%AC')
'파이썬'

URL을 퍼센트 인코딩하여 요청하면, 요청이 정상적으로 수행된다.

코드 11-87 URL에 한글이 들어간 문서 요청하기

>>> base_url = 'https://ko.wikipedia.org'
>>> path = urllib.parse.quote('/wiki/파이썬')
>>> url = base_url + path
>>> urllib.request.urlopen(url).read().decode('utf-8')
(요청에 성공하여 HTML 문서가 화면에 출력된다. 출력 결과 생략.)

이제 URL을 다루는 방법까지 살펴보았으니, 웹 클라이언트로서 웹 서버에 각종 자원과 명령을 요청하는 기본적인 방법을 배웠다. 이어서 웹 클라이언트가 보내온 요청을 처리하는 웹 서버 프로그램에 관해 알아보자.

11.5.4 웹 서버로서 정보 제공하기

우리가 접속하는 수많은 웹 사이트들은 어떤 방식으로 우리의 요청에 맞는 응답을 해 주는 걸까? 웹 통신을 어떤 입력(요청)에 대해 출력(응답)이 이루어지는 것이라고 생각해 보자. 그런 관점으로 보면, 웹 통신은 사용자의 명령에 따라 작업이 수행되는 일반적인 프로그램이나, 인자를 입력받아 반환값을 출력하는 함수와 크게 다르지 않다. 다음은 많은 웹 사이트에서 실제로 이루어지는 처리의 예다.

  • 검색어를 입력(요청)받아 그 검색어와 매치하는 웹 문서들을 출력(응답)한다.
  • 파일의 경로를 입력(요청)받아 그에 해당하는 파일의 내용을 출력(응답)한다.
  • API를 조작하기 위한 경로를 입력(요청)받아 그 명령을 처리하고 결과를 출력(응답)한다.

요청하는 쪽에서는 호출하는 방법(URL)만 알면 되지만, 응답하는 쪽에서는 그 요청에 대해 어떤 동작을 하고 어떤 결과를 낼 것인지 정의하고 프로그램으로 작성해 두어야 한다. 이 프로그램이 서버 프로그램이다.

웹 서버 프로그램의 실행 과정

프로그램과 함수가 입력과 출력 사이에서 보이지 않는 복잡한 중간 처리 과정을 거치는 것처럼, 웹 서버 프로그램도 요청과 응답 사이에 여러 단계의 실행 과정을 거친다. 웹 서버의 실행 과정은 크게 다음과 같이 나눌 수 있다.

  • 수신 대기(listen): 클라이언트의 요청이 오기를 기다린다.
  • 중계(route): 요청을 받으면, 요청 메시지(URL, 메서드 등)를 해석하여 그에 해당하는 기능(함수)을 호출한다.
  • 실행: 중계 과정에서 호출된 기능을 실제로 처리한다. 이 과정에서 데이터베이스 시스템과 같은 프로그램 외부의 자원을 활용하기도 한다.
  • 출력 결과 가공(render): 실행된 결과를 클라이언트에게 제공하기 위한 특정 형식으로 가공한다. 이 과정에서 템플릿(틀을 이용한 가공) 도구를 활용하기도 한다.
  • 응답: 실행된 결과를 클라이언트에게 되돌려준다.

이 과정을 응용 프로그래머가 전부 직접 구현하기는 번거롭다. 그래서 실무에서는 웹 서버의 전체 기능을 미리 만들어 제공하는 웹 프레임워크(web framework)라는 라이브러리를 사용할 때가 많다. 파이썬을 위한 웹 프레임워크가 많이 있으며, 그 중에서 ‘장고(Django)’와 ‘플라스크(Flask)’가 가장 인기 있다. 본격적인 웹 서버 프로그래밍을 원한다면 이런 프레임워크를 학습하여 사용하기를 권한다.

여기서는 웹 프레임워크를 이용하지 않고, 표준 라이브러리만을 이용해 매우 기본적인 웹 서버를 만들어 볼 것이다. 가장 기본적인 기능만 구현하겠지만, 웹 서버를 어떻게 만드는지에 대한 개념을 이해하는 데는 도움이 될 것이다. 실무에 사용하는 웹 프레임워크도 이런 기초적인 기능을 발전시켜 만든 것이다.

간단한 웹 서버 만들기

파이썬은 HTTP에 관련된 몇 가지 모듈을 http 패키지에 모아 두었는데, 그 중 웹 서버를 만들 때 필요한 기능들이 http.server 모듈에 구현되어 있다. 그 중에서 다음 두 클래스를 사용해 보자.

  • http.server.HTTPServer: 통신 채널을 열고, 클라이언트의 요청을 수신 대기하는 클래스
  • http.server.BaseHTTPRequestHandler: 요청받은 내용을 해석하여 처리하기 위한 클래스. 이 클래스를 상속하는 새 클래스를 만들어, 동작을 정의한다.

다음은 클라이언트의 요청을 기다리다가, 요청을 받으면 인사를 응답하는 웹 서버다. 세부 사항을 자세히 알 필요는 없으니 부담 갖지 말고 전체적인 흐름만 살펴보도록 하자.

코드 11-88 GET 요청을 처리해주는 간단한 웹 서버

import http.server

class Handler(http.server.BaseHTTPRequestHandler):
    """HTTP 요청을 처리하는 클래스"""
    def do_GET(self):
        """요청 메시지의 메서드가 GET 일 때 호출되어, 응답 메시지를 전송한다."""
        # 응답 메시지의 상태 코드를 전송한다
        self.send_response(200)

        # 응답 메시지의 헤더를 전송한다
        self.send_header('Content-type', 'text/plain; charset=utf-8')
        self.end_headers()
        
        # 응답 메시지의 본문을 전송한다
        self.wfile.write(bytes('안녕하세요!\n', 'utf-8'))
        self.wfile.write(bytes('클라이언트가 요청한 경로: ', 'utf-8'))
        self.wfile.write(bytes(self.path, 'utf-8'))

# 요청받을 주소 (요청을 감시할 주소)
address = ('localhost', 8000)

# 요청 대기하기
listener = http.server.HTTPServer(address, Handler)
print(f'http://{address[0]}:{address[1]} 주소에서 요청 대기중...')
listener.serve_forever()

요청을 받았을 때 실행될 동작은 Handler 클래스로 정의했다. 이 클래스에 정의한 do_GET() 메서드는 요청 메시지의 메서드가 GET일 때 호출된다. 마찬가지로, do_POST() 메서드를 정의한다면 요청 메시지의 메서드가 POST일 때 호출된다.

do_GET() 메서드 안에서 응답 메시지를 전송할 때는 http.server.BaseHTTPRequestHandler 클래스에서 상속받은 메서드와 속성을 이용한다. 클라이언트가 요청한 하위 경로도 path 속성을 읽어 구할 수 있다. 응답 메시지의 상태 코드, 헤더, 본문 등을 작성하는 방법은 부록에서 설명하니 참고하기 바란다.

클라이언트의 요청을 기다리다가 요청을 받았을 때 Handler 클래스의 do_GET 메서드를 실행해주는 기능은 http.server.HTTPServer 클래스가 처리해 준다. 이 클래스를 사용하려면 인스턴스화를 통해 객체를 생성해야 한다. 인스턴스화에 지정하는 첫 번째 인자는 서버의 주소로, 호스트(localhost)와 포트 번호(8000)를 튜플에 담아 지정한다. 두 번째 인자는 요청을 받았을 때 처리해 줄 클래스로, 앞서 정의한 Handler 클래스로 지정하면 된다.

http.server.HTTPServer 클래스의 인스턴스가 준비됐으면, serve_forever() 메서드를 실행하여 클라이언트의 요청을 대기하도록 할 수 있다. 이 메서드의 실행은 무한히 지속되며 요청을 계속 처리한다. 중지하려면 Ctrl+C 키를 누르면 된다.

이 서버 프로그램을 실행하고, 웹 브라우저의 주소 창에 http://localhost:8000/data/movies.json URL을 입력해 봐라. 다음과 같이 인사가 응답되는 것을 볼 수 있을 것이다.

그림 11-5 파이썬으로 만든 웹 서버에 접속해 본 화면

그림 11-5 파이썬으로 만든 웹 서버에 접속해 본 화면

코드 11-88의 웹 서버는 실제 서비스를 제공하는 웹 서버에 비하면 매우 단순하다. 하지만 모든 웹 서버는 이런 뼈대에 살을 붙인 것일 뿐이다. 예를 들어, 그림 파일을 제공하는 웹 서버는 요청된 파일의 경로를 URL에서 확인하여 서버에서 읽어들여 그 내용을 응답한다. 위의 웹 서버 프로그램에 그림 파일을 읽어들여 내보내는 기능을 추가하면 그런 기능을 만들 수 있다. 또, 게시판을 서비스하는 웹 서버는 사용자가 업로드한 정보를 데이터베이스에 저장해 두었다가 사용자가 요청했을 때 데이터베이스의 내용을 읽어 응답한다. 웹 서버에 데이터베이스 또는 파일에 정보를 읽고 쓰는 기능을 연동하면 게시판 서비스도 만들 수 있다.

이상으로 웹에서 정보를 요청하는 방법과 정보를 제공하는 방법 양 쪽을 모두 간단히 알아보았다.

  1. 웹 프로그래밍에 관련된 지식을 조금 더 얻고 싶은 독자들을 위해 부록에 HTTP에 관한 설명을 조금 더 실어 두었다. 하지만 반드시 읽지는 않아도 이 절의 내용을 학습하는 데 큰 문제는 없다. 

  2. 불가능한 것은 아니다. 웹에서 HTML 문서를 해석·자동 처리하는 방법을 자세히 알고 싶다면, 『파이썬으로 웹 크롤러 만들기』(라이언 미첼 저, 한선용 역, 한빛미디어)를 읽어보기를 추천한다.