Ama ne tür bir süreç? Nerede koşuyor? Nasıl gidiyor? Ve en önemlisi: nasıl inşa ederim?

Bu makalede, basit bir web sunucusunun temel yazılım bileşenlerini inceleyeceğim - bu kavramlar dilden bağımsızdır ve işletim sistemlerimiz tarafından sağlanan temel altyapının bir kısmını kullanır. Özellikle python'un kitaplıklarını kullanacağım, ancak çoğu programlama dilinin soketlere erişmenin bir yolu vardır.

Başlamadan önce, kendi terminalinizi takip etmenizi rica ediyorum. Web'in temel bileşenlerini kendi python ortamınızda oluşturarak çok daha fazlasını keşfedeceksiniz. Bunu gerçekten kolaylaştıracağım: sadece bir terminal açın ve bir python kabuğu başlatın. (Python 2'yi kullanabilirsiniz, ancak ben python 3'ü tercih ederim)

$ python3

Web sunucularına ulaşmadan önce, muhtemelen duymuş olduğunuz, ancak asla tam olarak anlamadığınız bir şeyle başlamalıyız:

Soket

Web iletişiminin ve genel olarak bilgisayar iletişiminin merkezinde mütevazı soket bulunur . Soket, bir iletişim uç noktasını temsil etmek ve kontrol etmek için tasarlanmış bir yazılım soyutlamasıdır. İletişimi dinleyebilir, başka bir sokete bağlanabilir, yanıtlar gönderebilir, bu bağlantıları kapatabilir vb.

Bu çok soyut görünüyorsa, şimdilik teorik bir posta kutusu hayal edebilirsiniz. Python'da oldukça basit bir şekilde yeni bir soket oluşturabilirim.

>>> import socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

Bu, adres olmadan bir soket örneği oluşturur . Pek kullanışlı değil. Boş uzayda yüzen bir posta kutusu gibi, kimse onu bulamayacak. Bir adres vermek için soketimi bir adrese bağlarım (örneğin: 127.0.0.1:8888). Bazılarınız bu adresi tanıyabilir, yerel web sunucuları, özellikle düğüm sunucuları için kullanılan ortak bir bağlantı noktasıdır. Şimdi soketimizi bu adrese bağlayalım.

>>> sock.bind(('', 8888))

bind ana bilgisayar adı ve bağlantı noktasının bir demetini kabul eder. '' tüm arabirimlere bağlanmak anlamına gelir.

Artık posta kutum yerel makinemde 8888 numaralı bağlantı noktasında bulunuyor. Sorun şu ki, kapalı - posta kabul etmiyor. Mesaj gönderen herkes bağlantı kuramadığını görecektir. Kontrol edin, yeni bir kabuk penceresi açın:

$ curl localhost:8888

Bir süre sonra isteğiniz zaman aşımına uğrar: "localhost bağlantı noktası 8888'e bağlanılamadı". Bunun nedeni soketinizin kapalı olmasıdır.

Gelen bağlantılara izin vermek için soketinizi bir dinleme soketine dönüştürmeniz gerekir :

>>> REQUEST_QUEUE_SIZE = 5
>>> sock.listen(REQUEST_QUEUE_SIZE)

Artık posta kutunuz açık ve posta kabul ediyor. Soketiniz gelen istekleri dinliyor. Ama yine de bir sorun var: kimse kontrol etmiyor. Soketinize curl ile tekrar bağlanmayı deneyin:

$ curl localhost:8888

Sonsuza kadar asılı kalmalı. Bağlanamadığınıza dikkat edin - aslında, sokete başarıyla bir istek gönderdiniz! Sorun şu ki, yuva herhangi bir yanıt göndermiyor, sadece eski kız arkadaşların gelen kutun gibi dinliyor. Ancak unutmayın, soketimiz aynı anda sadece 5 mesajı sıraya koyabilir, bu yüzden bu mesajları hızlı bir şekilde işlemeye başlamamız gerekir!

Bunun socket.accept için. Accept, soketinize sıradaki bir sonraki postayı beklemesini (veya zaten oradaysa kapmasını) ve istemcinin adresini içeren yeni bir soket açmasını söyler . Bu biraz kafa karıştırıcı, ama ne demek istediğimi anlayacaksınız. Python kabuğunuzda kendiniz deneyin:

>>> client_connection, client_address = sock.accept()
>>> client_address
('127.0.0.1', 52095)  # your address won't be quite the same

Gördüğünüz gibi , aslında tamamen yeni bir soket olansocket.accept() bir client_connection ve bu istemcinin adresini döndürür . Bu durumda, istemcinin 52095 numaralı bağlantı noktasındaki localhost'tan (127.0.0.1) geldiğini görüyoruz. Bu geçici bir bağlantı noktasıdır - bilgisayar onu curl isteğini yapmak için havadan oluşturmuştur. Unutmayın, bu yeni soket, orijinal soket bağlantı noktanız ile yeni istemci adresiniz arasındaki bir bağlantıdır . Bunu dene:

>>> client_connection.getsockname()
('127.0.0.1', 8888)
>>> client_connection.getpeername()
('127.0.0.1', 52095  # your address won't be quite the same

Bu soketin hala 8888 numaralı bağlantı noktasında bulunduğuna dikkat edin. Bu tip sokete bağlantı soketi denir Orijinal dinleme soketiniz hala ortalıkta - görevi daha fazla istek için dinlemeye devam etmektir (örneğin, başka bir terminal açabilir, yeni bir curl isteği yapabilir, ardından başka bir çağrı yapabilirsiniz socket.acceptve başka bir bağlantınız olur)

Soket aracılığıyla curl istemcimize bir mesaj gönderelim:

>>> client_connection.recv(1024)  # read the request
>>> response = b"""HTTP/1.1 200 OKnnHello World!"""
>>> client_connection.sendall(response)
>>> client_connection.close()

"B", bunun bir bayt dizesi olduğunu belirtir. Curl terminalinize geri bir mesaj almalısınız! Yaşasın!!

Soketler üzerinden HTTP gönderme fikri, sunucuların ve web uygulamalarının başladığı yerdir ve size her şeyi anlatmak için sabırsızlanıyorum - ama önce hızlı bir özet yapalım:

  • Soket, bir adrese bağlanan bir iletişim uç noktasıdır
  • Bir dinleme soketi , belirli bir adresteki iletişimleri dinler
  • Bir bağlantı soketi , bir isteği kabul ettiğinde dinleme soketi tarafından oluşturulur ve bağlantının yönetilmesinden, talebin tamamlanmasından ve bir yanıt gönderilmesinden sorumludur.
  • Diğer şeylerin yanı sıra HTTP isteklerini kabul edebilir ve soketler üzerinden HTTP yanıtları gönderebilirsiniz. Unutmayın, prizler evrensel bir iletişim kanalıdır. İstediğiniz bayt veriyi gönderebilirsiniz.

Basit Bir Web Sunucusu

Bunu beklemeye başlamış olabilirsiniz, ancak bir web sunucusu aslında sonsuz bir döngüde istekleri dinleyen ve kabul eden açık bir sokettir, örneğin:

server_socket = ...
<instantiate_socket_here>  # pseudocode
while true:  # do forever
    client, _ = server_socket.accept()
    request = client.recv(1024)  # read 1024 bytes from the request
    
    # run the request through your web framework of choice
    response = run_web_application(request)
    # send the response back to the client
    client.sendall(response)
    client.close()

Sunucu bir bağlantıyı kabul eder, isteği işler, bir yanıt gönderir, ardından bağlantıyı kapatır ve kuyruktaki bir sonraki isteği kabul eder.

İstemci bağlantısının aslında sunucu_soketinden ayrı bir soket olduğunu söylediğimizi hatırlayın - bu gerçekten yararlıdır, çünkü dinleme soketinin (sunucu_soketi) iletişimi idare etmesi gerekmediği anlamına gelir. Hemen bir sonraki mesaja geçmek ücretsizdir ve bu nedenle basit mimarimiz istekleri eşzamanlı olarak işleme potansiyeline sahiptir. Eşzamanlılığın gerçekleşmesi için biraz sihre ihtiyacımız var, ancak kendimizi aşıyoruz.

Dikkate değer başka bir şey: Bir bağlantı açıldıktan sonra sunucunun kapatması gerekmez . "Web soketleri" duyduğunuzda düşündüğünüz şey bu olabilir - doğrudan sunucuyla açık bir iletişim kanalına sahip olabilirsiniz. Sohbet uygulamaları size gerçek zamanlı güncellenmiş mesajlar verir. Herkesin sohbet sunucusuyla açık bir bağlantısı vardır (her kişinin sunucuya kendi soketi vardır). Sohbeti kapattığınızda bağlantınız kapanır.

Bir sunucuyla açık bir bağlantı kurmanın nasıl bir şey olabileceğini görmek için bu örneği takip edin:

  • senin emin olmak sockEmin, yeni bir piton kabuk açıp aşağıdaki komutları çalıştırın değilseniz önceki örneklerden port 8888 tarihinde dinleme:
  • >>> import socket
    >>> SERVER_ADDRESS = (HOST, PORT) = '', 8888
    >>> REQUEST_QUEUE_SIZE = 5
    >>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    >>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    >>> sock.bind(SERVER_ADDRESS)
    >>> sock.listen(REQUEST_QUEUE_SIZE)

    Şimdi 8888 numaralı bağlantı noktasında açık bir dinleme soketine sahip olmalısınız

    2. Yeni bir soket ile dinleme soketinize bağlayın. Bunu yapmak için yeni bir terminalde ikinci bir python kabuğu açın:

    >>> import socket
    >>> connect_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    >>> connect_sock.connect(('localhost', 8888))

    3. Orijinal soket üzerindeki bağlantıyı kabul edin ve mesajları dinlemeye başlayın

    >>> connection, _ = sock.accept()
    >>> while True:
    ...    connection.recv(1024) # constantly read from the socket

    4. Bağlı soketten mesaj göndermeye başlayın

    >>> connect_sock.sendall(b'Hello World, this is my first live msg!')
    >>> connect_sock.sendall(b'Second msg, this is still fun!')

    5. Bunların orijinal soket bağlantınızda görünmesini izleyin. Wheeeee!

    Bu, Javascript'in WebSocket API'sinin ve popüler web çerçevelerinin ilgili WebSocket uygulamalarının merkezinde bulunan işletim sistemi düzeyinde yazılımdır . Ve onu Python'da sıfırdan oluşturdunuz!

    Bir HTTP Sunucusu

    Sadece örnekleri tamamlamak için size python'un soket API'sini kullanan basit bir HTTP sunucusunun tam bir uygulamasını vereceğim:

    webserver.py

    import socket
    import time
    SERVER_ADDRESS = (HOST, PORT) = '', 8888
    REQUEST_QUEUE_SIZE = 5
    # Handle a request to the socket
    def handle_request(client_connection):
        request = client_connection.recv(1024) #read from request stream
        print(request.decode())
        http_response=b"""HTTP/1.1 200 OKnnHello World!"""
        client_connection.sendall(http_response)
    def serve_forever():
        listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listen_sock.bind(SERVER_ADDRESS)
        listen_sock.listen(REQUEST_QUEUE_SIZE)
        print('Serving HTTP on port {port}'.format(port=PORT))
        
        while True:
            # wait for the next connection and process it
            client_conn, _ = listen_sock.accept()
            handle_request(client_conn)
            client_conn.close()
    if __name__ == '__main__':
        serve_forever()

    Bu temel web sunucusunu çalıştırmak için yapmanız gereken tek şey çalıştırmaktır:

    >>> python webserver.py
    Serving HTTP on port 8888

    Curl ile test edebilirsiniz:

    curl localhost:8888
    Hello World!

    Tebrikler, soketleri kullanarak bir web sunucusu yaptınız. Elbette, yaptığı tek şey merhaba dünyasını yazdırmak, ancak temel mimari orada - sadece seçtiğiniz python web çerçevesini en üste yerleştirin ve çok daha fazlasını yapacaktır.

    Gunicorn gibi gerçek açık kaynaklı python sunucuları, temelde oluşturduğunuz sunucu gibi yapılandırılır, ancak (Python) Web Sunucusu Ağ Geçidi Arabirimi (WSGI) olarak bilinen standart bir arabirim uygularlar . Gunicorn bu nedenle bir "WSGI" sunucusudur. WSGI gibi bir şeyin yararı, tüm popüler web çerçevelerinin (Django, Flask, Pyramid) WSGI uyumlu olmasıdır - bu, herhangi bir WSGI sunucusunun istekleri sorunsuz bir şekilde işlemek için bu çerçeveleri kullanabileceği anlamına gelir.

    Sunucumuzu geçerli bir WSGI sunucusuna dönüştürmek aslında o kadar da zor değil; Daha fazlasını okumak isterseniz, bana python sunucuları hakkında çok şey öğreten bu blog gönderisini tavsiye ederim .

    İşte öğrendiklerimizin bir özeti

    • Soketler, birçok amaç için kullanılan iletişim soyutlamalarıdır
    • Soketler işletim sistemi düzeyinde soyutlamalardır, ancak bunlara erişmek için python'un soket kitaplığını kullanabiliriz
    • Bir dinleme soketi , bağlantı isteklerini depolar ve yönetir
    • socket.accept()bu isteklerden birini alır ve yeni bir bağlı soket açar
    • Bir bağlı soket veri istemci ve sunucu arasında ileri geri gönderilir ve mümkün kılar
    • HTTP sunucusu, gelen istekleri hızla kabul eden ve bu istekleri işlemek için yeni soketler açan bir dinleme soketidir. Bağlantı soketi bir HTTP yanıtı döndürür ve bağlantıyı kapatır.