웹서버란 무엇인가

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 오류를 리턴하고 그렇지 않은 경우 해당 파일을 읽어서 내용을 내려보내주게 된다.

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop