본문 바로가기
Web/Django

[Django로 배우는 쉽고 빠른 웹개발(기초편)] 02 파이썬 웹 표준 라이브

by merona99 2022. 8. 8.
반응형

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 서버 실행]

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 통신에서 클라이언트의 요청을 받고 이를 처리하여 그 결과를 되돌려주는 것

 

[웹 서버를 만드는 가장 기본적인 방법]

  1. http.server 모듈을 임폴트
  2. BaseHTTPRequestHandler상속받아 원하는 로직으로 핸들러 클래스를 정의
  3. 서버의 IP, PORT 및 핸들러 클래스를 인자로 하여 HTTPServer 객체를 생성
  4. 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 서버 만들 때 중요 사항

  1. wsgiref.simple_server 모듈은 WSGI 규격을 준수하여 WSGI 서버를 작성할 수 있도록 make_server() 및 serve_forever() 메소드 API를 제공함
  2. my_app()과 같은 애플리케이션 로직을 호출 가능한 함수나 메소드로 정의하여 make_server 인자로 넘겨주어 WSGI 웹 서버를 만듬. 이는 애플리케이션 프로그램과 웹 서버 프로그램을 독립적으로 작성할 수 있게 해주는 WSGI의 중요한 원칙임
  3. my_app() 함수에서 응답을 위한 헤더 및 바디를 구성해서 반환해줌

앞의 http.server 모듈로 서버를 만드는 방식과 동일

다른 점은 애플리케이션 로직을 작성하는 my_app() 함수가 추가되었다는 점임

 

wsgiref.simple_server 모듈에 정의된 WSGIServer 클래스와 핸들러

 

// 그냥 장고만을 사용해서 서버를 만들어왔기에 이런 내용은 몰랐다. 제대로 서버를 공부하려면 어려울 것 같다.

 

반응형

댓글