웹서버란 무엇인가
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
등의 요청 방식이 있으나 이들은 흔히 쓰이는 것들은 아니다.
URI
는 Uniform 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 오류를 리턴하고 그렇지 않은 경우 해당 파일을 읽어서 내용을 내려보내주게 된다.