Django로 배우는 쉽고 빠른 웹개발(기초편)
Chapter 02 - 파이썬 웹 표준 라이브러리
이번 챕터에서 알아보는 것파이썬 웹 라이브러리의 전체적인 구성 및 버전 2.x와 3.x를 비교하며 달라진 점
웹 라이브러리의 각각 중요한 모듈
상용 웹 서버와의 연동에 필요한 WSGI 서버
2.1 웹 라이브러리 구성
파이썬 3.x 버전에서는 다음과 같이 관련된 모듈들을 모아서 패키지를 만들었고, 모듈명을 통해 서버쪽 라이브러리와 클라이언트쪽 라이브러리를 좀 더 확실히 구분짓고 있다.
urllib 패키지
- 웹 클라이언트를 작성하는데 가장 빈번하게 사용되는 모듈
- http 서버 뿐만 아니라 ftp 서버 및 로컬 파일 등을 처리하는데, 클라이언트에서 공통적으로 필요한 함수와 클래스를 제공
http 패키지
- 크게 서버용과 클라이언트용 라이브러리로 나누어 모듈을 담고 있음
- 쿠키 관련 라이브러리도 http 패키지 내에서 서버용과 클라이언트용으로 모듈이 구분되어 있음
2.2 웹 클라이언트 라이브러리
- 우리가 많이 사용하는 웹 브라우저는 다양한 웹 클라이언트 중 하나일 뿐임
- 웹 브라우저 이외에도 웹 서버에 요청을 보내는 애플리케이션은 모두 웹 클라이언트라고 할 수 있음
웹 클라이언트를 위한 파이썬 표준 라이브러리가 있지만 실제 프로젝트에서는 외부 라이브러리인 requests, beautifulsoup4 등을 더 많이 사용하는 편임
1) urllib.parse 모듈
- URL의 분해, 조립, 변경 및 URL 문자 인코딩, 디코딩 등을 처리하는 함수를 제공
urlparse() 함수 : URL을 파싱한 결과로 ParseResult 인스턴스를 반환
ParseResult 클래스 속성의 의미
- scheme : URL에 사용된 프로토콜
- netloc : 네트워크 위치, user:password@host:port 형식으로 표현되며, HTTP 프로토콜인 경우 host:port 형식
- path : 파일이나 애플리케이션 경로를 의미
- params : 애플리케이션에 전달될 매개변수, 현재는 사용 x
- query : 질의 문자열 또는 매개변수로, &로 구분된 이름=값 쌍 형식 표시
- fragment : 문서 내의 앵커 등 조각을 지정
2) urllib.request 모듈
- 주어진 URL에서 데이터를 가져오는 기본 기능을 제공
- 가장 기본이 되는 urlopen() 함수만 잘 다뤄도 웹 클라이언트 대부분 작성할 수 있음
- url 인자로 지정한 URL로 연결하고, 유사 파일 객체를 반환, url 인자는 문자열이거나 Request 클래스의 인스턴스가 올 수 있음
- url에 file 스킴을 지정하면 로컬 파일을 열 수 있음
- 디폴트 요청 방식은 GET이고, 웹 서버에 전달할 파라미터가 있으면 질의 문자열을 url 인자에 포함해서 보냄
- 요청 방식을 POST로 보내고 싶으면 data 인자에 질의 문자열을 지정해주면 됨
- 옵션인 timeout은 응답을 기다리는 타임아웃 시간을 초로 표시함
urlopen() 함수 사용방법
사용 케이스 | 사용 방법 |
URL로 GET/POST 방식의 간단한 요청 처리 | urlopen() 함수만으로 가능 |
PUT, HEAD 메소드 등, 헤더 조작이 필요한 경우 | Request 클래스를 같이 사용 |
인증, 쿠키, 프록시 등 복잡한 요청 처리 | 인증/쿠키/프록시 해당 핸들러 클래스를 같이 사용 |
1. urlopen()함수 - GET 방식 요청
- HTTP GET 방식을 디폴트로 사용하여 웹 서버에 요청을 보냄
[POST 방식을 위한 test 서버 실행]
※ www.example.com 서버는 GET처리만 가능하므로 POST 방식의 웹 요청을 처리하는 서버가 필요함
2. urlopen()함수 - POST 방식 요청
- urlopen() 함수 호출 시 data 인자를 지정해주면 함수는 자동으로 POST 방식으로 요청을 보냄
- data 인자는 URL에 허용된 문자열로 인코딩되어야 하고, 유니코드(str) 타입이 아니라 바이트 스트링(bytes) 타입이어야 함
왼쪽 화면은 client이고 오른쪽 화면은 server임
응답 메시지도 잘 왔고 서버에서도 200이 뜬 것으로 보아 응답도 잘 되었음
3. urlopen()함수 - Request 클래스로 요청 헤더 지정
- 요청 헤더를 지정해서 보내고 싶은 경우 URL을 지정하는 방식을 변경하면 됨
- url 인자에 문자열 대신에 Request 객체를 지정함
- Request 객체를 생성하고 add_header()로 헤더를 추가해서 웹 서버로 요청을 보내면 됨
4. urlopen()함수 - HTTPBasicAuthHandler 클래스로 인증 요청
- HTTP의 고급 기능을 포함하여 요청을 보낼 수도 있음
- 각 기능에 맞는 핸들러 객체를 정의하고, 그 핸들러를 bulid_opener() 함수를 사용해 오프너에 등록 후 오프너의 open() 함수를 호출하면 서버로 요청이 전송됨
- 아래 코드는 HTTPBasicAuthHandler 클래스를 이용해 인증 데이터를 같이 보내는 프로그램임
- 인증 데이터인 realm, user, passwd는 모두 서버에서 지정한 것으로 채워서 보냄
- 특히 realm은 서버로부터 받는 401응답에서 알 수 있음
realm은 인증되는 영역을 설명하거나 인증의 범위(요청 URI의 보호공간을 식별하기 위한 문자열)를 알리는데 사용됨
이는 어떤 공간에 사용자가 접근하려고 시도하는지를 알리기 위하여, "중간 단계의 사이트에 대한 접근"과 같거나 또는 비슷한 메시지가 될 수 있음
5. urlopen()함수 - HTTPCookieProcessor 클래스로 쿠키 데이터를 포함하여 요청
- 아래는 HTTPCookieProcessor 클래스를 사용하여 쿠키 데이터를 처리하는 프로그램임
- 첫 번째 요청에서 쿠키를 담기 위한 준비를 하고 서버로 요청을 보냄
- 두 번째 요청에서 첫 번째 응답에서 받은 쿠키를 헤더에 담아서 요청을 보냄. 만약 두 번째 요청에 쿠키 데이터가 없다면 서버에서 에러로 응답
6. urlopen()함수 - ProxyHandler 및 ProxyBasicAuthHandler 클래스로 프록시 처리
- ProxyHandler 및 ProxyBasicAuthHandler 클래스를 사용해 프록시 서버를 통과해서 웹 서버로 요청을 보내는 프로그램
- install_opener() 함수를 사용해 디폴트 오프너를 지정할 수도 있음
※ 위 예제는 프록시 서버가 필요하므로 주석으로 이해하고 넘어가기(실습생략)
urllib.request 모듈
- 특정 웹 사이트에서 이미지만을 검색하여 그 리스트를 보여주는 코드 작성
- urlopen() + HTMLParser()
- HTMLParser : HTML 파싱에 사용되는 클래스
[예제]
from html.parser import HTMLParser
from urllib.request import urlopen
class ImageParser(HTMLParser):
# img 태크를 찾기 위해 handle_starttag 함수 오버라이드
def handle_starttag(self, tag, attrs):
if tag != 'img':
return
if not hasattr(self, 'result'):
self.result = []
# img src 속성을 찾으면 속성 값을 self.result 리스트에 추가
for name, value in attrs:
if name == 'src':
self.result.append(value)
# HTML이 주어지면 HTMLParse를 이용해 이미지를 찾고, 그 리스트를 출력
def parse_image(data):
parser = ImageParser()
# HTML 문장을 feed() 함수에 주면 파싱하고 그 결과를 parser.result 리스트에 추가
parser.feed(data)
# 파싱 결과를 set 타입의 dataSet으로 모아준다. -> set이므로 중복 제거
dataSet = set(x for x in parser.result)
return dataSet
def main():
url = "http://www.google.co.kr"
# urlopen을 사용하여 구글 사이트에 접속한 후 첫 페이지 내용을 가져온다.
with urlopen(url) as f:
# 사이트의 데이터는 인코딩된 데이터이므로, 인코딩 방식(charset)을 알아내 그 방식으로 디코딩 해준다.
charset = f.info().get_param('charset')
data = f.read().decode(charset)
dataSet = parse_image(data)
print("\n>>>>>>>> Fetch Images from", url)
# 찾은 이미지들을 정렬하여 라인별로 출력
print("\n".join(sorted(dataSet)))
if __name__ == '__main__':
main()
크롤링에 대한 부분은 알고 있으므로 눈으로 보고 넘어가자.
http.client 모듈
- urllib.request 모듈로는 처리할 수 없는 경우나 HTTP 프로토콜 요청에 대한 저수준의 세밀한 기능이 필요할 때 http.client 모듈을 사용
- urllib.request 모듈로 작성한 로직은 http.client 모듈을 사용해도 동일하게 작성할 수 있음
순번 | 코딩 순서 | 코딩예시 |
1 | 연결 객체 생성 | conn = http.client.HTTPConnection('www.python.org') |
2 | 요쳥을 보냄 | conn.request('GET', '/index.html') |
3 | 응답 객체 생성 | response = conn.getresponse() |
4 | 응답 데이터를 읽음 | data = response.read() |
5 | 연결을 닫음 | conn.close() |
[예제]
from html.parser import HTMLParser
from http.client import HTTPConnection
from os import makedirs
from os.path import exists, join, basename
from urllib.parse import urljoin, urlunparse
from urllib.request import urlretrieve
class ImageParser(HTMLParser):
def handle_starttag(self, tag, attrs):
if tag != 'img':
return
if not hasattr(self, 'result'):
self.result = []
for name, value in attrs:
if name == 'src':
self.result.append(value)
# HTML 문장이 주어지면 ImageParser를 이용해 이미지를 찾고,
# 그 이미지들을 DOWNLOAD 디렉토리에 다운로드하는 함수
def download_image(url, data):
if not exists('DOWNLOAD'):
makedirs('DOWNLOAD')
parser = ImageParser()
parser.feed(data)
dataSet = set(x for x in parser.result)
# dataSet으로 모은 파싱 결과를 정렬한 후 하나씩 처리한다.
for x in sorted(dataSet):
# 다운로드하기 위해 소스 URL과 타깃 파일명을 지정한다.
# urljoin() 함수로 소스 URL을 지정한다.
# urljoin()은 baseURL과 파일명을 합쳐서 완전한 URL을 리턴하는 함수이다.
imageUrl = urljoin(url, x)
basename_ = basename(imageUrl)
targetFile = join('DOWNLOAD', basename_)
print("Downloading...", imageUrl)
# urlretrieve() 함수는 src로부터 파일을 가져와서 targetFile로 생성해준다.
urlretrieve(imageUrl, targetFile)
def main():
host = "www.google.co.kr"
conn = HTTPConnection(host)
conn.request("GET", '')
resp = conn.getresponse()
charset = resp.msg.get_param('charset')
data = resp.read().decode(charset)
conn.close()
print("\n>>>>>>>>>> Download Images from", host)
url = urlunparse(('http', host, '', '', '', ''))
download_image(url, data)
if __name__ == '__main__':
main()
[결과]
2.3 웹 서버 라이브러리
웹 서버 프로그램을 작성할 때는 직접 파이썬 라이브러리를 사용해서 개발하기 보다는 웹 프레임워크를 사용해서 개발하는 경우가 많음
프레임워크는 개발자가 웹 서버 프로그램을 개발하기 쉽도록 저수준의 기능을 이미 만들어 놓은 기반 프로그램으로 웹 서버 프로그램 개발자는 프레임워크를 활용하여 응용 로직만 개발하면 되기 때문에 훨씬 효율적임
웹 서버의 역할 : http 통신에서 클라이언트의 요청을 받고 이를 처리하여 그 결과를 되돌려주는 것
[웹 서버를 만드는 가장 기본적인 방법]
- http.server 모듈을 임폴트
- BaseHTTPRequestHandler를 상속받아 원하는 로직으로 핸들러 클래스를 정의
- 서버의 IP, PORT 및 핸들러 클래스를 인자로 하여 HTTPServer 객체를 생성
- HTTPServer 객체의 serve_forever() 메소드를 호출
from http.server import BaseHTTPRequestHandler, HTTPServer
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response_only(200, 'OK')
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b"Hello World")
if __name__ == '__main__'
server = HTTPServer(('', 8888), MyHandler)
print("Started WebServer on port 8888...")
print("Press ^C to quit WebServer.")
server.serve_forever()
이를 기본으로 해서 HTTPServer 클래스나 적절한 핸들러 클래스를 상속받아 그 기능을 확장해 나가면 됨
클래스명 | 주요 기능 |
HTTPServer | 웹 서버를 만들기 위한 클래스, 서버 IP와 PORT를 바인딩 |
HTTPServer 객체 생성 시, 핸들러 클래스가 반드시 필요 | |
BaseHTTPRequestHandler | 핸들러를 만들기 위한 기반 클래스로, HTTP 프로토콜 처리 로직이 들어있음 |
이 클래스를 상속받아, 자신의 로직 처리를 담당하는 핸들러 클래스를 만듬 | |
SimpleHTTPRequestHandler | BaseHTTPRequestHandler 클래스를 상속받아 만든 클래스 |
GET과 HEAD 메소드 처리가 가능한 핸들러 클래스 | |
CGIHTTPRequestHandler | SimpleHTTPRequestHandler 클래스를 상속받아 만든 클래스 |
추가적으로 POST 메소드와 CGI 처리가 가능한 핸들러 클래스 |
1) HTTPServer 및 BaseHTTPRequestHandler 클래스
- 이 두 기반 클래스에는 HTTP 프로토콜을 처리해주는 기능이 있어서 기반 클래스를 상속받으면 따로 HTTP 프로토콜 관련 로직을 코딩하지 않아도 됨
- 아까의 코드를 실행시키면 8888 포트로 요청을 기다리는 메시지가 나타난다. 이처럼 웹 서버가 정상적으로 실행되었다면 브라우저를 열고 http://127.0.0.1:8888 접속
2) SimpleHTTPRequestHandler
- 앞에서는 MyHandler라는 핸들러를 코딩했는데 SimpleHTTPRequestHandler을 사용하면 별도의 코딩 없이도 즉시 웹 서버를 실행할 수 있음
- 핸들러에는 do_GET() 및 do_HEAD() 메소드가 정의 되어 있어서 GET 및 HEAD 방식을 처리할 수 있음
3) CGIHTTPRequestHandler
- do_POST() 메소드가 정의되어 있어서 POST 방식을 처리할 수 있음
- CGI 웹 서버가 CGI 스크립트를 정상적으로 처리하는 지 확인하기 위해선 2가지 준비 작업이 필요함
1. 서버에서 실행되는 스크립트가 필요함
2. POST 방식으로 요청보낼 웹 클라이언트가 필요함. 웹 브라우저로는 POST 요청을 보낼 수 없기 때문임
2.4 CGI/WSGI 라이브러리
파이썬에서는 WSGI(Web Server Gateway Interface) 규격이 정의되어 있음
WSGI는 웹 서버와 웹 어플리케이션을 연결해주는 규격으로, 장고와 같은 파이썬 웹 프레임워크를 개발하거나, 이런 웹 프레임워크를 아파치와 같은 웹 서버와 연동할 때 사용함
[WSGI]
- CGI 방식은 요청이 들어올 때마다 처리를 위한 프로세스가 생성되는 방식이라 짧은 시간에 많은 요청을 받으면 서버의 부하가 높아져서 프로세스가 멈추거나 다운될 수 있음
- 이러한 CGI 방식의 단점을 해결하고 파이썬 언어로 웹 서버와 웹 애플리케이션 간의 연동 규격을 정의한 것이 WSGI(Web Server Gateway Interface) 규격임
- WSGI 규격만 맞추면 장고로 웹 애플리케이션을 작성하면 이 애플리케이션은 Apache 웹 서버에서도 실행할 수 있고, Nginx 웹 서버에서도 실행할 수 있다.
Apahce나 Nginx는 일반 범용 웹 서버로 WSGI 처리 기능이 없어서 이 중간에서 WSGI 통신 규격을 처리해주는 것이 mod_wsgi, uWSGI, Gunicorn과 같은 WSGI 서버임
[WSGI 서버의 애플리케이션 처리 과정]
파이썬 웹 프레임워크는 WSGI 서버를 제공하며 개발자는 WSGI 서버에 대한 API 규격만 맞추면 웹 서버와는 독립적으로 애플리케이션을 작성할 수 있어 생산성이 높아짐
※ WSGI 규격에 맞춰 개발 시 중요 사항
1. 개발이 필요한 애플리케이션을 함수 또는 클래스의 메소드로 정의하고 애플리케이션 함수의 인자는 다음과 같이 정의함
- def application_name(environ, start_response) :
- environ : 웹 프레임워크에 이미 정의되어 있으며 HTTP 환경변수를 포함
- start_response : 애플리케이션 내에서 응답을 시작하기 위해 반드시 호출해야 하는 함수
2. start_response 함수의 인자는 다음과 같이 정의함
- start_response(status, headers)
- status : 응답 코드 및 응답 메세지를 지정(200 OK, 404 Not Found 등)
- headers : 응답 헤더를 지정
3. 애플리케이션 함수의 리턴값은 응답 바디에 해당하는 내용으로 리스트나 제너레이터와 같은 iterable 타입이어야 함
[wsgiref.simple_server 모듈]
WSGI 스펙을 준수하는 웹 서버에 대한 참조 서버, 즉 개발자에게 참고가 될 수 있도록 미리 만들어 놓은 WSGIServer 클래스와 WSGIRequestHandler 클래스를 정의하고 있음
장고의 runserver도 이들 클래스를 사용하여 만든 테스트용 웹 서버
from wsgiref.simple_server import make_server
def my_app(environ, start_response):
status = "200 OK"
headers = [('Content-Type', 'text/plain')]
start_response(status, headers)
response = [b"This is a sample WSGI Application."]
return response
if __name__ == '__main__':
print("Started WSGI Server on port 8888...")
server = make_server('', 8888, my_app)
server.serve_forever()
※ 위의 WSGI 서버 만들 때 중요 사항
- wsgiref.simple_server 모듈은 WSGI 규격을 준수하여 WSGI 서버를 작성할 수 있도록 make_server() 및 serve_forever() 메소드 API를 제공함
- my_app()과 같은 애플리케이션 로직을 호출 가능한 함수나 메소드로 정의하여 make_server 인자로 넘겨주어 WSGI 웹 서버를 만듬. 이는 애플리케이션 프로그램과 웹 서버 프로그램을 독립적으로 작성할 수 있게 해주는 WSGI의 중요한 원칙임
- my_app() 함수에서 응답을 위한 헤더 및 바디를 구성해서 반환해줌
앞의 http.server 모듈로 서버를 만드는 방식과 동일함
다른 점은 애플리케이션 로직을 작성하는 my_app() 함수가 추가되었다는 점임
// 그냥 장고만을 사용해서 서버를 만들어왔기에 이런 내용은 몰랐다. 제대로 서버를 공부하려면 어려울 것 같다.
'Web > Django' 카테고리의 다른 글
[Django로 배우는 쉽고 빠른 웹개발(기초편)] 04 Django의 핵심기능 (0) | 2022.11.02 |
---|---|
[Django로 배우는 쉽고 빠른 웹개발(기초편)] 03 Django 웹 프레임워크 (0) | 2022.08.10 |
[Django로 배우는 쉽고 빠른 웹개발(기초편)] 01 웹 프로그래밍의 이해 (0) | 2022.08.08 |
[Django] views.py에서 사용자계정 찾기 (0) | 2020.11.28 |
[Django] checkbox value 다중 넘기기 (0) | 2020.11.27 |
댓글