웹서버란 무엇인가

What is a web server?

웹서버는 많은 웹개발자들에게는 블랙박스나 마법의 상자 같은 물건인데, 실상 그것은 하나의 프로그램이다. 이 프로그램은 80번 포트에 소켓을 열고 이 포트를 통해 들어오는 HTTP 요청을 받아, 다시 응답을 보내주는 프로그램이다. 요즘의 웹서버들은 이런 기본적인 기능에 덧붙여 여러가지 부가기능들을 제공한다. 하지만 이러한 기능들은 모두 부가적인 것이며, 웹 서버의 본질은 HTTP 응답을 내보배는 것이다. 만약 에코서버를 작성해본 경험이 있다면 웹서버 역시 이러한 에코 서버와 크게 다르지 않다. 단지 들어오는 입력에 대해 좀 더 많은 손질을 해서 내보내는 것일 뿐이다.

간단 웹서버

에코서버는 들어온 입력을 그대로 내보내는 서버인데, 웹 서버는 조금 다른 동작을 한다.

import socket

listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.bind(('', 80)) # 80번 포트는 웹의 일반적인 포트임
listen_socket.listen(1)
connection, address = listen_socket.accept()
while True:
    connection.recv(1024)
    connection.sendall("""HTTP/1.1 200 OK
    Content-type: text/html


    <html>
        <body>
            <h1>hello, World!</h1>
        </body>
    </html>""")
    connection.close()

위 코드를 실행시키면 분명 제대로된 응답을 보여준다. HTTP 요청이나 응답은 모두 간단한 텍스트메시지일 뿐이다.

HTTP 프로토콜

HTTP 프로토콜은 웹 페이지를 요청하고 전송하는 프로토콜이다. 이 프로토콜은 HTTP 요청HTTP 응답으로 구성된다. HTTP요청은 웹 문서(HTML 문서)를 요청하고, 애플리케이션에게 데이터를 전달하기 위해 사용한다. (데이터전달이란 폼 전송과 같은 것들이 포함된다) 서버는 이 요청 메시지를 처리해서 올바른 응답을 구성하고, 이를 HTTP 응답으로 구성하여 클라이언트에게 돌려보낸다.

HTTP요청은 여러가지 형태로 만들어질 수 있지만 모두 같은 구조를 공유하고 있다. 요청은 각 행으로 구분되며 첫번째 행, 스타트라인은 좀 특별한 의미를 갖는다. 이 첫번째 행은 세 가지 정보를 담고 있다.

  • 요청 방식(method)
  • URI
  • HTTP 버전

method란 HTTP메시지가 웹서버에 보내는 요청의 종류를 의미한다. 일반적으로 페이지를 요청하는 것은 GET 방식이며, 폼 데이터 전송은 POST가 사용된다. 그 외에도 서버 상의 리소스를 삭제하기 위한 DELETE 요청이나 PATCH, PUT 등의 요청 방식이 있으나 이들은 흔히 쓰이는 것들은 아니다.

URIUniform Resource Identifier의 약어로 요청하는 리소스가 정확히 무엇인지 서버에게 알려주는 역할을 한다. URI는 웹 주소에서 도메인이름을 제외한 부분으로 서버가 사용하는 홈디렉토리로부터 어떤 위치에 요청하는 문서가 있는지를 명시한다.

/는 서버의 홈 디렉토리(루트)를 의미한다. 단, 이것은 웹 서버가 인지하는 홈 디렉토리이며 실제 서버 머신에서의 하드디스크의 루트 디렉토리와는 구분된다. 이를 테면 /srv/www/pubilc_html/이라는 디렉토리를 웹서버가 홈 디렉토리로 사용하고 있고, 요청된 URI가 /iamges/my.jpg라면 이 파일은 서버의 디스크에는 /srv/www/pubilc_html/images/my.jpg라는 경로상에 있을 것이다. 그러면 웹 서버는 해당 파일을 응답으로 전송해주게 된다.

스타트 라인을 제외하면 HTTP요청은 헤더와 몸체로 다시 나뉠 수 있다. 헤더는 요청에 대한 메타 정보를 담고 있다.

예를 들어 gzip 압축전송을 지원하는 웹서버라면 헤더에 Accetp: x-application/gzip과 같은 내용을 넣을 수 있다.

요청 몸체는 GET 방식의 요청에 경우에는 내용이 없다. POST 방식의 요청이라면 폼 필드의 내용이 연결된 문자열이 그 내용을 채우게 될 것이다.

좀 더 기능적인 웹 서버

앞서 작성한 간단한 서버는 오직 고정된 메시지만을 내보내는데, 이를 좀 수정해서 HTML 파일을 전송할 수 있는 서버를 작성해보자.

import socket
import os.path
import sys

DOCUMENT_ROOT = "d:/temp"
RESPONSE_TEMPLATE = """HTTP/1.1 200 OK
Content-length: {}


{}"""

def main():
    listen_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    listen_socket.bind(('', 80))
    listen_socket.listen(1)

    while True:
        connection, address = listen_socket.accept()
        request = connection.recv(1024)
        start_line = request.split('\n')[0]
        method, uri, version = start_line.split()
        path = os.path.join(DOCUMENT_ROOT, uri[1])
        if not os.path.exists(path):
            connection.sendall('HTTP/1.1 404 Not Found\n')
        else:
            with open(path) as file_handle:
                file_content = file_handle.read()
                response = RESPONSE_TEMPLATE.format(len(file_content), file_content)
                connection.sendall(response)
        connection.close()

if __name__ == "__main__":
    sys.exit(main())

이 버전에서는 HTTP요청의 첫 줄을 파싱해서 URI를 인식한다음, 여느 웹서버처럼 해당 위치의 파일을 찾고 없는 경우 404 오류를 리턴하고 그렇지 않은 경우 해당 파일을 읽어서 내용을 내려보내주게 된다.