Рубрики
Без рубрики

Создание базового HTTP-сервера с нуля в Python

Создайте веб-сервер с нуля с помощью розетки Python.

Автор оригинала: João Ventura.

По его сути, современная сеть – это просто текст, идущий взад-вперед между клиентами и серверами. Как разработчики, мы часто используем веб-каркасы, чтобы помочь нам построить строки для отправки клиентам. Веб-каркасы Аннотация США от основной «текстовой реальности» путем разбора входящих HTTP-запросов (что является просто строкой), вызовите соответствующую функцию и создайте реакцию строки (в основном с помощью шаблонов). Наконец, клиенты разбирают эти строки и делают все, что они хотят от этого.

Этот пост блога показывает, как создать HTTP-сервер Barebones с нуля, и он основан на упражнении, которое я дал своим ученикам MSC. Единственный предварительный реквизит – это базовое понимание Python 3. Если вы хотите реализовать это, как мы идем, вы можете схватить начальное приложение Из этой ссылки Отказ Конечный исходный код можно найти в этот гид Отказ

Http только текст

Http это Протокол Этот браузеры используют для извлечения и нажимания информации на серверы. По его сути HTTP – это просто текст, который следует на определенной картине: в первой строке, которую вы указываете, какой ресурс вы хотите, то он следует за заголовками, а затем у вас есть пустая строка, которая разделяет заголовки из тела сообщения (если есть ). Вот как вы могли бы получить о странице веб-сайта:

GET /about.html HTTP/1.0
User-Agent: Mozilla/5.0

И вот как вы можете отправить некоторые данные в веб-сервере, используя метод Post:

POST /form.php HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

name=John&surname=Doe

Чтобы доказать, что это только текст, вы можете просто скопировать – вставить текст выше и использовать то, что позволяет вам отправлять текст по сети. Давайте использовать Telnet, чтобы получить страницу Google:

$> telnet google.com 80
Trying 84.91.171.170...
Connected to google.com.
Escape character is '^]'.

(1)
GET /about/ HTTP/1.0

(2)
HTTP/1.0 200 OK
Vary: Accept-Encoding
Content-Type: text/html
Date: Thu, 09 Feb 2017 16:41:37 GMT
Expires: Thu, 09 Feb 2017 16:41:37 GMT
Cache-Control: private, max-age=0
Last-Modified: Thu, 08 Dec 2016 01:00:57 GMT
X-Content-Type-Options: nosniff
Server: sffe
X-XSS-Protection: 1; mode=block
Accept-Ranges: none



    
        
        ...


Connection closed by foreign host.

и отправить сообщение для http://httpbin.org/post :

$> telnet httpbin.org 80

Trying 54.175.219.8...
Connected to httpbin.org.
Escape character is '^]'.

(1)
POST /post HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

name=John&surname=Doe

(2)
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 09 Feb 2017 16:38:26 GMT
Content-Type: application/json
Content-Length: 328
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "form": {
    "name": "John",
    "surname": "Doe"
  },
  "headers": {
    "Content-Length": "21",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org"
  },
  "url": "http://httpbin.org/post"
}
Connection closed by foreign host.

Вы можете увидеть HTTP-запросы (1) сопровождаемый ответами HTTP-сервера в (2) Отказ Та же рисунок по запросам и ответам, а текст везде! Больше информации о http на отлично Высокопроизводительные браузерные сети книга.

Отправка откликов HTTP с помощью розеток

Если вы планируете реализовать сетевые приложения с нуля, вам, вероятно, надо работать с сетевые розетки Отказ Разъем – это абстракция, предоставляемая вашей операционной системой, которая позволяет отправлять и получать байты через сеть. Вот базовая реализация HTTP-сервера (вы можете получить его из этой ссылки ):

"""
 Implements a simple HTTP/1.0 Server

"""

import socket


# Define socket host and port
SERVER_HOST = '0.0.0.0'
SERVER_PORT = 8000

# Create socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((SERVER_HOST, SERVER_PORT))
server_socket.listen(1)
print('Listening on port %s ...' % SERVER_PORT)

while True:    
    # Wait for client connections
    client_connection, client_address = server_socket.accept()

    # Get the client request
    request = client_connection.recv(1024).decode()
    print(request)

    # Send HTTP response
    response = 'HTTP/1.0 200 OK\n\nHello World'
    client_connection.sendall(response.encode())
    client_connection.close()

# Close socket
server_socket.close()

Начнем с определения хоста и порта сокета. Тогда мы создаем Server_socket переменная и установить его на Af_inet (IPv4 адрес семьи) и Sock_stream (TCP, в основном). Остальная часть кода есть для настройки сокета для прослушивания запросов на данный (хост, порт). Проверьте Python Docs на розетках для получения дополнительной информации.

Остальная часть кода является самоснабжением: ждать клиентских подключений, прочитайте строку запроса, отправьте строку HTTP-форматы с помощью Здравствуйте, мир на тело ответа и закройте клиентское соединение. Мы делаем это навсегда (или пока кто-то не нажимает Ctrl + C). Откройте свой браузер на http://localhost: 8000/ И вы должны увидеть ответ сервера:

python-webserver-1.png

Как упражнение, измените Здравствуйте, мир к

Hello World

и посмотри, что происходит. И вы видели Печать (запрос) В исходном коде сервера? Вот что он выводит в консоли:

Listening on port 8000 ...

GET / HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: pt-PT,pt;q=0.8,en;q=0.5,en-US;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

Да, это браузер, запрашивающий корневую страницу («/») сервера.

Index.html.

По умолчанию, когда браузер запрашивает корню сервера (используя HTTP-запрос, такой как Get/http/1.0 ), мы должны вернуть index.html страница. Давайте изменим код внутри то время, чтобы всегда вернуть содержимое htdocss/index.html файл.

while True:
    # Wait for client connections
    (...)
    # Get the client request
    (...)

    # Get the content of htdocs/index.html
    fin = open('htdocs/index.html')
    content = fin.read()
    fin.close()

    # Send HTTP response
    response = 'HTTP/1.0 200 OK\n\n' + content
    client_connection.sendall(response.encode())
    (...)

По сути, мы читаем содержимое файла и добавить его в ответ строка в качестве тела сообщения, а не предыдущий Здравствуйте, мир Отказ index.html Файл – это просто текстовый файл (внутри каталога htdocs ) с содержимым HTML:



    Hello World


    

Hello World!

Welcome to the index.html web page..

Here's a link to Ipsum

Вот как это должно выглядеть в браузере:

Python-WebServer-2.png

Вы можете нажать на ссылку столько раз, сколько вы хотите, но вы серверу будут всегда вернуть содержимое index.html Отказ Это было запрограммировано вести себя так!

Вернуть другие страницы

До сих пор наш сервер возвращает index.html Страница, но мы должны позволить ему также вернуть другие страницы. Технически, это означает, что мы должны анализировать первую строку запроса HTTP (что это что-то вроде Получить/ipsum.html http/1.0 ), откройте предполагаемый файл и возвращает его содержимое. Вот изменения:

while True:
    # Wait for client connections
    (...)
    # Get the client request
    (...)

    # Parse HTTP headers
    headers = request.split('\n')
    filename = headers[0].split()[1]

    # Get the content of the file
    if filename == '/':
        filename = '/index.html'

    fin = open('htdocs' + filename)
    content = fin.read()
    fin.close()

    # Send HTTP response
    response = 'HTTP/1.0 200 OK\n\n' + content
    client_connection.sendall(response.encode())
    (...)

По сути, мы извлекаем имя файла из строки запроса, откройте файл (предположим, что все HTML-файлы находятся внутри HTDOCS папка) и вернуть его содержимое. Вы также можете проверить, что мы правильно вернем index.html Файл Когда клиенты просят корневой ресурс (‘/’).

Вот содержание htdocs/ipsum.html :



    Ipsum


    

Ipsum!

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque tincidunt libero diam, nec imperdiet libero sodales quis. Nulla in pulvinar sem. Vivamus placerat ullamcorper sagittis. Proin varius, erat sed egestas semper, enim lectus viverra diam, id placerat est augue et turpis.

Попробуйте его по собственному коду и посмотрите, можете ли вы открыть index.html и ipsum.html файлы.

404 Не Найдено

Мы еще не закончены! Это то, что произойдет, если мы попытаемся запросить файл, который не существует, например http://localhost: 8000/hello.html :

GET /hello.html HTTP/1.1

Traceback (most recent call last):
  File "httpserver.py", line 36, in 
    fin = open('htdocs' + filename)
FileNotFoundError: [Errno 2] No such file or directory: 'htdocs/hello.html'

Сервер вылетает и выходит!

Нам нужно поймать исключение и вернуть ответ 404:

while True:
    # Wait for client connections
    (...)
    # Get the client request
    (...)

    # Parse HTTP headers
    headers = request.split('\n')
    filename = headers[0].split()[1]

    # Get the content of the file
    if filename == '/':
        filename = '/index.html'

    try:
        fin = open('htdocs' + filename)
        content = fin.read()
        fin.close()

        response = 'HTTP/1.0 200 OK\n\n' + content
    except FileNotFoundError:

        response = 'HTTP/1.0 404 NOT FOUND\n\nFile Not Found'

    # Send HTTP response
    client_connection.sendall(response.encode())
    client_connection.close()

Если вы хотите, вы можете изменить тело ответа HTTP 404, чтобы иметь персонализированные сообщения об ошибках.

python-webserver-3.png

Весь исходный код для этого примера можно найти в этот гид Отказ