构建持久 HTTP Web 服务器

问题描述 投票:0回答:1

我的包含

msg = client_socket.recv(1024).decode()
的内部 while 循环无法在循环内没有
client_socket, addr = server_socket.accept()
的情况下处理多个客户端请求。我不知道如何才能继续为所有请求重复使用相同的连接。

from socket import *
import sys

def run_server(port):
    # Create server socket
    server_socket = socket(AF_INET, SOCK_STREAM)
    # Bind the socket to a specific address
    server_socket.bind(('localhost', port))

    # Argument 1 represents the maximum number of queued connections
    server_socket.listen(1)
    print(f"Serving on port {port}")

    while 1: 
        # Accept incoming connection from client 
        client_socket, addr = server_socket.accept()
        print(f"Connection established from address {addr}")

        while 1:    
            # Receive data from client
            msg = client_socket.recv(1024).decode()
            # Extract file name
            file_name = msg.split('\n')[0].split()[1].replace('/', '')
        
            # Process client requests
            try:
                with open(file_name, 'rb') as file:
                    status_line = "HTTP/1.1 200 OK\r\n"

                    file_extension = file_name.split('.')[-1].lower()

                    if file_extension == 'html':
                        content_type_header = "Content-Type: text/html\r\n\r\n"
                    elif file_extension == 'png':
                        content_type_header = "Content-Type: image/png\r\n\r\n"

                    content = file.read()

                    # Combine status line, headers and content
                    http_response = (status_line + "Connection: keep-alive\r\n" + content_type_header).encode() + content
            except OSError as _:
                # File not found, generate a 404 response
                status_line = "HTTP/1.1 404 Not Found\r\n"
                content_type_header = "Content-Type: text/html\r\n\r\n"
                content = "<h1>404 Not Found</h1><p>The requested file was not found on this server.</p>"
                
                http_response = (status_line + content_type_header + content).encode()

            # Send HTTP response back to client
            client_socket.sendall(http_response)
            # Close the connection after handling multiple requests
        client_socket.close()
        print("Connection closed")

    server_socket.close()

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python WebServer.py <port>")
        sys.exit(1)

    # Get port number from command line parameter 
    port = int(sys.argv[1])
    run_server(port)
python http networking websocket tcp
1个回答
0
投票

我假设您这样做是为了学习,因为实现 HTTP 协议很复杂。

TCP是字节流协议,需要遵循HTTP协议来决定如何读取流以及读取多少。由于 HTTP 请求包含以

\r\n
结尾的请求和标头行以及指示标头结束的空行,因此通过
socket.makefile
将 TCP 套接字包装在类似文件的对象中可以更轻松地处理请求。

要实现保持活动状态,标头需要一个

Content-Length
标头。

下面我做了一些最小的更改来支持您的实施。它绝不是完整的。在与下面的脚本相同的目录中创建一个

root
目录,并在其中填充一些 HTML 和 PNG 文件,例如
index.html
icon.png
。将浏览器指向
http://localhost:8080/index.html
http://localhost:8080/icon.png
,文件应显示在浏览器中。至少他们在 Chrome 上为我做了 😊.

import socket
import sys
import os
from pprint import pprint

def run_server(port):
    with socket.socket() as server_socket:
        server_socket.bind(('localhost', port))
        server_socket.listen()
        print(f'Serving on port {port}')

        while True:
            client_socket, addr = server_socket.accept()
            # wrap client socket in a file-like object.  Gives access to .readline().
            with client_socket, client_socket.makefile('rb') as infile:
                print(f'{addr}: connected')

                while True:
                    # Receive HTTP request
                    line = infile.readline()
                    if not line: break
                    request = line.decode('ascii')
                    print(request)

                    # Read headers line-by-line and add to dictionary
                    headers = {}
                    while line := infile.readline():
                        if line == b'\r\n': break
                        key, _, value = line.decode('ascii').partition(':')
                        headers[key.strip()] = value.strip()
                    pprint(headers)

                    op, path, protocol = request.split()
                    print(op, path, protocol)
                    # hacky way to look under a directory called root below the webserver script.
                    file_name = os.path.abspath(os.path.join('root/.' + path))
                    print(file_name)

                    # minimally handles a GET for html and png files.
                    try:
                        with open(file_name, 'rb') as file:
                            http_response = b'HTTP/1.1 200 OK\r\n'
                            file_extension = file_name.split('.')[-1].lower()
                            if file_extension == 'html':
                                http_response += b'Content-Type: text/html\r\n'
                            elif file_extension == 'png':
                                http_response += b'Content-Type: image/png\r\n'
                            content = file.read()
                    except FileNotFoundError as e:
                        # File not found, generate a 404 response
                        http_response = b'HTTP/1.1 404 Not Found\r\n'
                        http_response += b'Content-Type: text/html\r\n'
                        content = b'<h1>404 Not Found</h1><p>The requested file was not found on this server.</p>'

                    # Send keepalive and the length of the content.
                    http_response += b'Connection: keep-alive\r\n'
                    http_response += f'Content-Length: {len(content)}\r\n'.encode('ascii')
                    # Send blank line (end of headers) and content.
                    http_response += b'\r\n' + content
                    client_socket.sendall(http_response)
                print(f'{addr}: disconnected')

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('Usage: python WebServer.py <port>')
        sys.exit(1)

    # Get port number from command line parameter
    port = int(sys.argv[1])
    run_server(port)
© www.soinside.com 2019 - 2024. All rights reserved.