[Python] 웹 서버를 기동하는 방법(http.server)


Development note/Python  2020. 1. 20. 00:34

안녕하세요. 명월입니다.


이 글은 Python에서 웹 서버를 기동하는 방법(http.server)에 대한 설명입니다.


저의 경우는 Python을 로컬 스크립트로 활용을 많이 하는 편이기 때문에 Python으로 웹 서버를 구축하는 경우는 거의 없습니다. 웹 프레임워크 django가 있기 때문에 http.server로 웹서버를 만들지 않을 것입니다.

개인적인 생각입니다만 보통 많은 개발자들이 Python만 다루는 사람은 많이 없을 것입니다. 자기가 메인으로 다루는 언어 java나 C# 혹은 PHP를 다루면서 부족한 부분(?)이나 개발의 편의성을 위해 python을 다루는 사람이 대부분이기 때문에 python으로 웹 서버를 만들 일은 거의 없을 것입니다.

java나 C#의 경우에 python과 같이 사용해서 complex 언어 타입으로도 사용할 수 있지만 아마 거의 없을 것이고, PHP의 경우는 PHP의 스크립트 언어로 한계가 있기 때문에 python과 같이 사용하는 경우가 많이 있을 것입니다.

그러나 그 경우에도 python을 독립적으로 http.server로 서버를 구축하기 보다는 apache의 cgi로 사용하는 것이 session관리나 cookie관리면에서 편하기 때문에 http.server를 사용하지는 않을 것입니다.


그럼 사용하지도 않는 라이브러리를 왜 소개하는 것에 대해 그래도 전혀 사용하지 않는 건 아니기 때문에, 또 알아두면 여러가지 유용하기 때문에 설명하겠습니다.

저의 경우에 이 http.server를 사용하는 경우는 업무를 하다보면 여러가지 스크립트를 만들어야 하는 경우가 많습니다. 간단하게 log 파일 정리부터 시작해서, db 백업, 이행이던가 jenkins등에 사용할 스크립트 파일등으로 많이 활용합니다.

근데 이 스크립트가 점점 많아지면 이 스크립트를 관리하고 싶은데 예를 들면 콘솔로 들어가서 「py script.py」이런 식으로 치는 것보다 하나의 화면을 만들어서 버튼 클릭으로 스크립트를 실행하게 하던가 파라미터가 필요하면 정해진 파라미터를 미리 설정하던가 등등입니다.

화면을 따로 만들기는 귀찮고 간단하게 관리하는 툴로써 사용하는 경우에 http.server를 사용합니다.


먼저 아주 간단하게 정적 웹 서버를 만들어 보겠습니다. 이 정적 웹 서버는 파이썬 파일(.py)를 만들 필요도 없습니다.

python -m http.server 80
<!DOCTYPE html>
<html>
  <head><title>python</title></head>
  <body>hello world</body>
</html>

여기까지 정적 웹 서버입니다. 정적 웹서버는 웹 페이지가 동적으로 변하지 않고 html파일이 그대로 전송이 되는 것을 이야기합니다. 여기에 websocket 코드를 넣고 websocket으로 활용해도 괜찮습니다.

링크 - [Python] Websocket을 사용하는 방법


그러나 좀 더 동적으로 웹 서버를 만들 경우도 있습니다. 그럴 때는 py파일로 작성을 해야합니다.

from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse as urlparse;
import io;

class myHandler(BaseHTTPRequestHandler):
  # GET형식의 파라미터를 파싱한다.(url의 물음표(?) 이후의 값을 딕셔너리로 파싱한다.
  def __get_Parameter(self, key):
    # 해당 클래스에 __param변수가 선언되었는지 확인한다.
    if hasattr(self,"_myHandler__param") == False:
      if "?" in self.path:
        # url의 ?이후의 값을 파싱한다.
        self.__param = dict(urlparse.parse_qsl(self.path.split("?")[1], True));
      else :
        # url의 ?가 없으면 빈 딕셔너리를 넣는다.
        self.__param = {};
    if key in self.__param:
      return self.__param[key];
    return None;
  # POST형식의 form 데이터를 파싱한다.
  def __get_Post_Parameter(self, key):
    # 해당 클래스에 __post_param변수가 선언되었는지 확인한다.
    if hasattr(self,"_myHandler__post_param") == False:
      # 해더로 부터 formdata를 가져온다.
      data = self.rfile.read(int(self.headers['Content-Length']));
      if data is not None:
        self.__post_param = dict(urlparse.parse_qs(data.decode()));
      else :
        self.__post_param = {};
    if key in self.__post_param:
      return self.__post_param[key][0];
    return None;

  # http 프로토콜의 header 내용을 넣는다.
  def __set_Header(self, code):
    # 응답 코드를 파라미터로 받아 응답한다.
    self.send_response(code);
    self.send_header('Content-type','text/html');
    self.end_headers();

  # http 프로토콜의 body내용을 넣는다.
  def __set_Body(self, data):
    # body 응답은 byte형식으로 넣어야 한다.(필요에 의해 주석 해제)
    #response = io.BytesIO()
    #response.write(b"<html><body><form method='post'><input type='text' name='test' value='hello'><input type='submit'></form></body></html>");
    #self.wfile.write(response.getvalue());
    # data(string)를 byte형식으로 변환해서 응답한다.
    self.wfile.write(data.encode());

  # POST 형식의 request일 때 호출된다.
  def do_GET(self):
    # GET 방식의 파라미터의 data 값을 취득한다.
    data = self.__get_Parameter('data');
    # response(응답)할 body내용이다.
    body = f"""
<!DOCTYPE html>
<html>
  <head><title>python</title></head>
  <body>
    <form method='post'>
      <input type='text' name='test' value='{data}'>
      <input type='submit'>
    </form>
  </body>
</html>
""";
    # response header code는 200(정상)으로 응답한다.
    self.__set_Header(200);
    # response body는 위 html 코드 내용을 넣는다.
    self.__set_Body(body);
  # POST 형식의 request일 때 호출된다.
  def do_POST(self):
    # response header code는 200(정상)으로 응답한다.
    self.__set_Header(200);
    # response body는 form으로 받은 test의 값으로 응답한다.
    self.__set_Body(self.__get_Post_Parameter('test'));

# http server를 생성한다.
httpd = HTTPServer(('', 80), myHandler);
# 서버 중지(Ctrl + Break)가 나올때까지 message 루프를 돌린다.
httpd.serve_forever();

get 방식으로는 form에 test의 name을 가진 input 오브젝트를 넣고 그 값을 url로 부터 data키값으로 넣습니다. 그리고 submit을 하여 post 요청을 일으킵니다. post요청이 오면 test의 name에 작성한 데이터를 화면에 표시합니다.

저는 예제를 작성하기 위해 간단하게 작성했습니다만, 웹에서 python을 컨트럴 하기 위해서 do_GET함수나 do_POST함수에 로직을 넣으면 처리가 됩니다.

예를 들면 input text대신에 select를 넣어서 그 파라미터 값에 따라 다른 python 스크립트를 실행시키던가 로컬 로직을 운용되게끔 만든다면 로컬에서 python을 조작할 수 있는 프로그램을 만들 수 있겠네요.


참조 - https://docs.python.org/3.3/library/http.server.html

참조 - https://docs.python.org/3/library/http.server.html

참조 - https://docs.python.org/3/library/io.html#io.BufferedIOBase

참조 - https://wiki.python.org/moin/BaseHttpServer

참조 - https://gist.github.com/pavgup/11220737


여기까지 Python에서 웹 서버를 기동하는 방법(http.server)에 대한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.